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

563 lines
21 KiB
Objective-C
Executable File

//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "Cryptography.h"
#import "NSData+Base64.h"
#import "NSData+OWSConstantTimeCompare.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
#import <openssl/evp.h>
#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
// Returned by many OpenSSL functions - indicating success
const int kOpenSSLSuccess = 1;
// length of initialization nonce
static const NSUInteger kAESGCM256_IVLength = 12;
// length of authentication tag for AES256-GCM
static const NSUInteger kAESGCM256_TagLength = 16;
const NSUInteger kAES256_KeyByteLength = 32;
@implementation OWSAES256Key
+ (nullable instancetype)keyWithData:(NSData *)data
{
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
{
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"];
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
@implementation Cryptography
#pragma mark random bytes methods
+ (NSMutableData *)generateRandomBytes:(NSUInteger)numberBytes {
/* used to generate db master key, and to generate signaling key, both at install */
NSMutableData *randomBytes = [NSMutableData dataWithLength:numberBytes];
int err = SecRandomCopyBytes(kSecRandomDefault, numberBytes, [randomBytes mutableBytes]);
if (err != noErr) {
DDLogError(@"Error in generateRandomBytes: %d", err);
@throw
[NSException exceptionWithName:@"random problem" reason:@"problem generating random bytes." userInfo:nil];
}
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
{
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
*/
+ (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
{
// Verify hmac of: version? || iv || encrypted data
NSMutableData *dataToAuth = [NSMutableData data];
if (version != nil) {
[dataToAuth appendData:version];
}
[dataToAuth appendData:iv];
[dataToAuth appendData:dataToDecrypt];
NSData *ourHmacData;
if (hmacType == TSHMACSHA1Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA1HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256AttachementType) {
ourHmacData =
[Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
}
if (hmac == nil || ![ourHmacData ows_constantTimeIsEqualToData:hmac]) {
DDLogError(@"%@ %s Bad HMAC on decrypting payload. Their MAC: %@, our MAC: %@", self.tag, __PRETTY_FUNCTION__, hmac, ourHmacData);
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;
}
}
// decrypt
size_t bufferSize = [dataToDecrypt length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (buffer == NULL) {
DDLogError(@"%@ Failed to allocate memory.", self.tag);
return nil;
}
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];
} else {
DDLogError(@"%@ Failed CBC decryption", self.tag);
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];
}
+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt withKey:(NSData *)key digest:(nullable NSData *)digest
{
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);
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];
}
+ (NSData *)encryptAttachmentData:(NSData *)attachmentData
outKey:(NSData *_Nonnull *_Nullable)outKey
outDigest:(NSData *_Nonnull *_Nullable)outDigest
{
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];
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);
return [encryptedAttachmentData copy];
}
+ (nullable NSData *)encryptAESGCMWithData:(NSData *)plaintext key:(OWSAES256Key *)key
{
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;
}
// 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;
}
// 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;
}
// 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];
[encryptedData appendData:ciphertext];
[encryptedData appendData:authTag];
return [encryptedData copy];
}
+ (nullable NSData *)decryptAESGCMWithData:(NSData *)encryptedData key:(OWSAES256Key *)key
{
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)];
NSData *authTag =
[encryptedData subdataWithRange:NSMakeRange(kAESGCM256_IVLength + cipherTextLength, kAESGCM256_TagLength)];
return
[self decryptAESGCMWithInitializationVector:initializationVector ciphertext:ciphertext authTag:authTag key:key];
}
+ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector
ciphertext:(NSData *)ciphertext
authTag:(NSData *)authTagFromEncrypt
key:(OWSAES256Key *)key
{
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;
}
// 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);
return nil;
}
if (decryptedBytes != ciphertext.length) {
OWSFail(@"%@ Failed to decrypt entire ciphertext", self.tag);
return nil;
}
// 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;
}
// 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;
}
@end
NS_ASSUME_NONNULL_END