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

563 lines
21 KiB
Mathematica
Raw Normal View History

2015-12-07 03:31:43 +01:00
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
2015-12-07 03:31:43 +01:00
//
#import "Cryptography.h"
#import "NSData+Base64.h"
#import "NSData+OWSConstantTimeCompare.h"
2017-08-21 17:21:23 +02:00
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
#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
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;
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) {
OWSFail(@"Invalid key length for AES128: %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) {
OWSFail(@"Invalid key length: %lu", (unsigned long)keyData.length);
return nil;
}
_keyData = keyData;
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_keyData forKey:@"keyData"];
}
@end
2015-12-07 03:31:43 +01:00
@implementation Cryptography
#pragma mark random bytes methods
2017-06-17 19:34:26 +02:00
2015-12-07 03:31:43 +01:00
+ (NSMutableData *)generateRandomBytes:(NSUInteger)numberBytes {
/* used to generate db master key, and to generate signaling key, both at install */
NSMutableData *randomBytes = [NSMutableData dataWithLength:numberBytes];
2017-06-17 19:34:26 +02:00
int err = SecRandomCopyBytes(kSecRandomDefault, numberBytes, [randomBytes mutableBytes]);
2015-12-07 03:31:43 +01:00
if (err != noErr) {
2017-06-17 19:34:26 +02:00
DDLogError(@"Error in generateRandomBytes: %d", err);
@throw
[NSException exceptionWithName:@"random problem" reason:@"problem generating random bytes." userInfo:nil];
2015-12-07 03:31:43 +01:00
}
return randomBytes;
}
#pragma mark SHA1
+ (NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string {
/* used by TSContactManager to send hashed/truncated contact list to server */
NSMutableData *hashData = [NSMutableData dataWithLength:20];
CC_SHA1([string dataUsingEncoding:NSUTF8StringEncoding].bytes,
(unsigned int)[string dataUsingEncoding:NSUTF8StringEncoding].length,
hashData.mutableBytes);
NSData *truncatedData = [hashData subdataWithRange:NSMakeRange(0, 10)];
return [[truncatedData base64EncodedString] stringByReplacingOccurrencesOfString:@"=" withString:@""];
}
+ (NSString *)computeSHA1DigestForString:(NSString *)input {
// Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA1
// encryption method.
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (unsigned int)data.length, digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
#pragma mark SHA256 Digest
+ (NSData *)computeSHA256Digest:(NSData *)data
{
return [self computeSHA256Digest:(NSData *)data truncatedToBytes:CC_SHA256_DIGEST_LENGTH];
}
+ (NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes
{
2015-12-07 03:31:43 +01:00
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (unsigned int)data.length, digest);
return
[[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH] subdataWithRange:NSMakeRange(0, truncatedBytes)];
}
#pragma mark HMAC/SHA256
+ (NSData *)computeSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey {
uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0};
CCHmac(kCCHmacAlgSHA256, [HMACKey bytes], [HMACKey length], [dataToHMAC bytes], [dataToHMAC length], ourHmac);
return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH];
}
+ (NSData *)computeSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey {
uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0};
CCHmac(kCCHmacAlgSHA1, [HMACKey bytes], [HMACKey length], [dataToHMAC bytes], [dataToHMAC length], ourHmac);
return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH];
}
+ (NSData *)truncatedSHA1HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes {
return [[Cryptography computeSHA1HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)];
}
+ (NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(NSUInteger)bytes {
return [[Cryptography computeSHA256HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)];
}
#pragma mark AES CBC Mode
/**
* AES256 CBC encrypt then mac. Used to decrypt both signal messages and attachment blobs
*
* @return decrypted data or nil if hmac invalid/decryption fails
*/
2015-12-07 03:31:43 +01:00
+ (NSData *)decryptCBCMode:(NSData *)dataToDecrypt
key:(NSData *)key
IV:(NSData *)iv
version:(nullable NSData *)version
2015-12-07 03:31:43 +01:00
HMACKey:(NSData *)hmacKey
HMACType:(TSMACType)hmacType
matchingHMAC:(NSData *)hmac
digest:(nullable NSData *)digest
{
// Verify hmac of: version? || iv || encrypted data
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
NSData *ourHmacData;
if (hmacType == TSHMACSHA1Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA1HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
2015-12-07 03:31:43 +01:00
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
2015-12-07 03:31:43 +01:00
} else if (hmacType == TSHMACSHA256AttachementType) {
ourHmacData =
[Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
2015-12-07 03:31:43 +01:00
}
if (hmac == nil || ![ourHmacData ows_constantTimeIsEqualToData:hmac]) {
DDLogError(@"%@ %s Bad HMAC on decrypting payload. Their MAC: %@, our MAC: %@", self.tag, __PRETTY_FUNCTION__, hmac, ourHmacData);
2015-12-07 03:31:43 +01:00
return nil;
}
// Optionally verify digest of: version? || iv || encrypted data || hmac
if (digest) {
DDLogDebug(@"%@ %s verifying their digest: %@", self.tag, __PRETTY_FUNCTION__, digest);
[dataToAuth appendData:ourHmacData];
NSData *ourDigest = [Cryptography computeSHA256Digest:dataToAuth];
if (!ourDigest || ![ourDigest ows_constantTimeIsEqualToData:digest]) {
DDLogWarn(@"%@ Bad digest on decrypting payload. Their digest: %@, our digest: %@", self.tag, digest, ourDigest);
return nil;
}
}
2015-12-07 03:31:43 +01:00
// decrypt
size_t bufferSize = [dataToDecrypt length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (buffer == NULL) {
DDLogError(@"%@ Failed to allocate memory.", self.tag);
return nil;
}
2015-12-07 03:31:43 +01:00
size_t bytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
[key bytes],
[key length],
[iv bytes],
[dataToDecrypt bytes],
[dataToDecrypt length],
buffer,
bufferSize,
&bytesDecrypted);
if (cryptStatus == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer length:bytesDecrypted freeWhenDone:YES];
2015-12-07 03:31:43 +01:00
} else {
DDLogError(@"%@ Failed CBC decryption", self.tag);
2015-12-07 03:31:43 +01:00
free(buffer);
}
return nil;
}
#pragma mark methods which use AES CBC
+ (NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString {
assert(payload);
assert(signalingKeyString);
unsigned char version[1];
unsigned char iv[16];
NSUInteger ciphertext_length = ([payload length] - 10 - 17) * sizeof(char);
unsigned char *ciphertext = (unsigned char *)malloc(ciphertext_length);
unsigned char mac[10];
[payload getBytes:version range:NSMakeRange(0, 1)];
[payload getBytes:iv range:NSMakeRange(1, 16)];
[payload getBytes:ciphertext range:NSMakeRange(17, [payload length] - 10 - 17)];
[payload getBytes:mac range:NSMakeRange([payload length] - 10, 10)];
NSData *signalingKey = [NSData dataFromBase64String:signalingKeyString];
NSData *signalingKeyAESKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(0, 32)];
NSData *signalingKeyHMACKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(32, 20)];
return
[Cryptography decryptCBCMode:[NSData dataWithBytesNoCopy:ciphertext length:ciphertext_length freeWhenDone:YES]
key:signalingKeyAESKeyMaterial
IV:[NSData dataWithBytes:iv length:16]
version:[NSData dataWithBytes:version length:1]
HMACKey:signalingKeyHMACKeyMaterial
HMACType:TSHMACSHA256Truncated10Bytes
matchingHMAC:[NSData dataWithBytes:mac length:10]
digest:nil];
2015-12-07 03:31:43 +01:00
}
+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt withKey:(NSData *)key digest:(nullable NSData *)digest
{
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)) {
DDLogError(@"%@ Message shorter than crypto overhead!", self.tag);
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)];
NSData *encryptedAttachment = [dataToDecrypt
subdataWithRange:NSMakeRange(AES_CBC_IV_LENGTH,
[dataToDecrypt length] - AES_CBC_IV_LENGTH - HMAC256_OUTPUT_LENGTH)];
NSData *hmac = [dataToDecrypt
subdataWithRange:NSMakeRange([dataToDecrypt length] - HMAC256_OUTPUT_LENGTH, HMAC256_OUTPUT_LENGTH)];
return [Cryptography decryptCBCMode:encryptedAttachment
key:encryptionKey
IV:iv
version:nil
HMACKey:hmacKey
HMACType:TSHMACSHA256AttachementType
matchingHMAC:hmac
digest:digest];
2015-12-07 03:31:43 +01:00
}
+ (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
{
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];
// Encrypt
size_t bufferSize = [attachmentData length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (buffer == NULL) {
DDLogError(@"%@ Failed to allocate memory.", self.tag);
return nil;
}
size_t bytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
[encryptionKey bytes],
[encryptionKey length],
[iv bytes],
[attachmentData bytes],
[attachmentData length],
buffer,
bufferSize,
&bytesEncrypted);
if (cryptStatus != kCCSuccess) {
DDLogError(@"%@ %s CCCrypt failed with status: %d", self.tag, __PRETTY_FUNCTION__, (int32_t)cryptStatus);
free(buffer);
return nil;
}
NSData *cipherText = [NSData dataWithBytesNoCopy:buffer length:bytesEncrypted freeWhenDone:YES];
2015-12-07 03:31:43 +01:00
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
NSMutableData *encryptedAttachmentData = [NSMutableData data];
[encryptedAttachmentData appendData:iv];
[encryptedAttachmentData appendData:cipherText];
// compute hmac of: iv || encrypted data
NSData *hmac =
[Cryptography truncatedSHA256HMAC:encryptedAttachmentData withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
DDLogVerbose(@"%@ computed hmac: %@", self.tag, hmac);
[encryptedAttachmentData appendData:hmac];
// compute digest of: iv || encrypted data || hmac
*outDigest = [self computeSHA256Digest:encryptedAttachmentData];
DDLogVerbose(@"%@ computed digest: %@", self.tag, *outDigest);
2015-12-07 03:31:43 +01:00
return [encryptedAttachmentData copy];
}
2017-08-21 17:21:23 +02:00
+ (nullable NSData *)encryptAESGCMWithData:(NSData *)plaintext 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) {
OWSFail(@"%@ failed to build context while encrypting", self.tag);
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) {
OWSFail(@"%@ failed to init encryption", self.tag);
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) {
OWSFail(@"%@ failed to set IV length", self.tag);
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) {
OWSFail(@"%@ failed to set key and iv while encrypting", self.tag);
return nil;
}
int bytesEncrypted = 0;
// Provide the message to be encrypted, and obtain the encrypted output.
// EVP_EncryptUpdate can be called multiple times if necessary
if (EVP_EncryptUpdate(ctx, ciphertext.mutableBytes, &bytesEncrypted, plaintext.bytes, (int)plaintext.length)
!= kOpenSSLSuccess) {
OWSFail(@"%@ encryptUpdate failed", self.tag);
return nil;
}
if (bytesEncrypted != plaintext.length) {
OWSFail(@"%@ bytesEncrypted != plainTextData.length", self.tag);
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) {
OWSFail(@"%@ failed to finalize encryption", self.tag);
return nil;
}
if (finalizedBytes != 0) {
OWSFail(@"%@ Unexpected finalized bytes written", self.tag);
return nil;
}
/* Get the tag */
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, kAESGCM256_TagLength, authTag.mutableBytes) != kOpenSSLSuccess) {
OWSFail(@"%@ failed to write tag", self.tag);
return nil;
}
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
// build up return value: initializationVector || ciphertext || authTag
NSMutableData *encryptedData = [initializationVector mutableCopy];
2017-08-21 17:21:23 +02:00
[encryptedData appendData:ciphertext];
[encryptedData appendData:authTag];
return [encryptedData copy];
}
2017-08-21 17:21:23 +02:00
+ (nullable NSData *)decryptAESGCMWithData:(NSData *)encryptedData key:(OWSAES256Key *)key
{
2017-08-21 17:21:23 +02:00
OWSAssert(encryptedData.length > kAESGCM256_IVLength + kAESGCM256_TagLength);
NSUInteger cipherTextLength = encryptedData.length - kAESGCM256_IVLength - kAESGCM256_TagLength;
// encryptedData layout: initializationVector || ciphertext || authTag
NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, kAESGCM256_IVLength)];
NSData *ciphertext = [encryptedData subdataWithRange:NSMakeRange(kAESGCM256_IVLength, cipherTextLength)];
2017-08-15 16:19:47 +02:00
NSData *authTag =
2017-08-21 17:21:23 +02:00
[encryptedData subdataWithRange:NSMakeRange(kAESGCM256_IVLength + cipherTextLength, kAESGCM256_TagLength)];
2017-08-15 16:19:47 +02:00
return
2017-08-21 17:21:23 +02:00
[self decryptAESGCMWithInitializationVector:initializationVector ciphertext:ciphertext authTag:authTag key:key];
2017-08-15 16:19:47 +02:00
}
+ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector
2017-08-21 17:21:23 +02:00
ciphertext:(NSData *)ciphertext
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
{
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) {
OWSFail(@"%@ failed to build context while decrypting", self.tag);
return nil;
}
// Initialise the decryption operation.
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != kOpenSSLSuccess) {
OWSFail(@"%@ failed to init decryption", self.tag);
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) {
OWSFail(@"%@ failed to set key and iv while decrypting", self.tag);
return nil;
}
// Initialise key and IV
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) {
OWSFail(@"%@ failed to init decryption", self.tag);
return nil;
}
2017-08-15 16:19:47 +02:00
2017-08-21 17:21:23 +02:00
// Provide the message to be decrypted, and obtain the plaintext output.
// EVP_DecryptUpdate can be called multiple times if necessary
int decryptedBytes = 0;
if (EVP_DecryptUpdate(ctx, plaintext.mutableBytes, &decryptedBytes, ciphertext.bytes, (int)ciphertext.length)
!= kOpenSSLSuccess) {
OWSFail(@"%@ decryptUpdate failed", self.tag);
2017-08-15 16:19:47 +02:00
return nil;
}
2017-08-21 17:21:23 +02:00
if (decryptedBytes != ciphertext.length) {
OWSFail(@"%@ Failed to decrypt entire ciphertext", self.tag);
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
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)authTagFromEncrypt.length, authTagFromEncrypt.bytes)
!= kOpenSSLSuccess) {
OWSFail(@"%@ Failed to set auth tag in decrypt.", self.tag);
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, plaintext.bytes + decryptedBytes, &finalBytes);
// AESGCM doesn't write any final bytes
OWSAssert(finalBytes == 0);
// Clean up
EVP_CIPHER_CTX_free(ctx);
if (decryptStatus > 0) {
return [plaintext copy];
} else {
OWSFail(@"%@ Decrypt verificaiton failed", self.tag);
return nil;
}
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
2015-12-07 03:31:43 +01:00
}
@end
NS_ASSUME_NONNULL_END