Include digest in attachments

- constant time compare
- free buffer passed to NSData

// FREEBIE
pull/1/head
Michael Kirk 8 years ago
parent 32ac0fb7c9
commit 452110b687

@ -144,4 +144,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 1a7633963dbcaa43f298949d83c42c1cd1dce940
COCOAPODS: 1.1.1
COCOAPODS: 1.2.0

@ -20,7 +20,6 @@
454021ED1D960ABF00F2126D /* OWSDisappearingMessageFinderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 454021EC1D960ABF00F2126D /* OWSDisappearingMessageFinderTest.m */; };
454092FA1DB7AFDE00579DE1 /* OWSFakeNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 454092F91DB7AFDE00579DE1 /* OWSFakeNetworkManager.m */; };
45458B751CC342B600A02153 /* SignedPreKeyDeletionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45458B6A1CC342B600A02153 /* SignedPreKeyDeletionTests.m */; };
45458B761CC342B600A02153 /* TSAttachmentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45458B6C1CC342B600A02153 /* TSAttachmentsTest.m */; };
45458B771CC342B600A02153 /* TSMessageStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45458B6E1CC342B600A02153 /* TSMessageStorageTests.m */; };
45458B781CC342B600A02153 /* TSStorageIdentityKeyStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45458B6F1CC342B600A02153 /* TSStorageIdentityKeyStoreTests.m */; };
45458B791CC342B600A02153 /* TSStoragePreKeyStoreTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45458B701CC342B600A02153 /* TSStoragePreKeyStoreTests.m */; };
@ -79,7 +78,6 @@
454092F81DB7AFDE00579DE1 /* OWSFakeNetworkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSFakeNetworkManager.h; path = ../../../tests/TestSupport/Fakes/OWSFakeNetworkManager.h; sourceTree = "<group>"; };
454092F91DB7AFDE00579DE1 /* OWSFakeNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSFakeNetworkManager.m; path = ../../../tests/TestSupport/Fakes/OWSFakeNetworkManager.m; sourceTree = "<group>"; };
45458B6A1CC342B600A02153 /* SignedPreKeyDeletionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignedPreKeyDeletionTests.m; sourceTree = "<group>"; };
45458B6C1CC342B600A02153 /* TSAttachmentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAttachmentsTest.m; sourceTree = "<group>"; };
45458B6E1CC342B600A02153 /* TSMessageStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageStorageTests.m; sourceTree = "<group>"; };
45458B6F1CC342B600A02153 /* TSStorageIdentityKeyStoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSStorageIdentityKeyStoreTests.m; sourceTree = "<group>"; };
45458B701CC342B600A02153 /* TSStoragePreKeyStoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSStoragePreKeyStoreTests.m; sourceTree = "<group>"; };
@ -178,15 +176,6 @@
path = ../../../tests/Account;
sourceTree = "<group>";
};
45458B6B1CC342B600A02153 /* Attachments */ = {
isa = PBXGroup;
children = (
45458B6C1CC342B600A02153 /* TSAttachmentsTest.m */,
);
name = Attachments;
path = ../../../tests/Attachments;
sourceTree = "<group>";
};
45458B6D1CC342B600A02153 /* Storage */ = {
isa = PBXGroup;
children = (
@ -333,7 +322,6 @@
children = (
453E1FD41DA83DD500DDD7B7 /* TestSupport */,
45458B691CC342B600A02153 /* Account */,
45458B6B1CC342B600A02153 /* Attachments */,
459850BF1D22C6C4006FFEDB /* Contacts */,
45D7243D1D67894100E0CA54 /* Devices */,
45C6A0971D2F0254007D8AC0 /* Messages */,
@ -568,7 +556,6 @@
452EE6D51D4AC43300E934BA /* OWSOrphanedDataCleanerTest.m in Sources */,
450E3C9A1D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m in Sources */,
452EE6CF1D4A754C00E934BA /* TSThreadTest.m in Sources */,
45458B761CC342B600A02153 /* TSAttachmentsTest.m in Sources */,
45C6A09A1D2F029B007D8AC0 /* TSMessageTest.m in Sources */,
453E1FCF1DA8313100DDD7B7 /* OWSMessageSenderTest.m in Sources */,
45AE484C1E072871004D96C2 /* OWSFakeCallMessageHandler.m in Sources */,

@ -133,6 +133,7 @@ message AttachmentPointer {
optional bytes key = 3;
optional uint32 size = 4;
optional bytes thumbnail = 5;
optional bytes digest = 6;
}
message GroupContext {

@ -65,6 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
for (OWSSignalServiceProtosAttachmentPointer *attachmentProto in attachmentProtos) {
TSAttachmentPointer *pointer = [[TSAttachmentPointer alloc] initWithServerId:attachmentProto.id
key:attachmentProto.key
digest:attachmentProto.digest
contentType:attachmentProto.contentType
relay:relay];
@ -159,7 +160,8 @@ NS_ASSUME_NONNULL_BEGIN
success:(void (^)(TSAttachmentStream *attachmentStream))successHandler
failure:(void (^)(NSError *error))failureHandler
{
NSData *plaintext = [Cryptography decryptAttachment:cipherText withKey:attachment.encryptionKey];
NSData *plaintext =
[Cryptography decryptAttachment:cipherText withKey:attachment.encryptionKey digest:attachment.digest];
if (!plaintext) {
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 17/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSAttachment.h"
@ -12,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithServerId:(UInt64)serverId
key:(NSData *)key
digest:(NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay NS_DESIGNATED_INITIALIZER;
@ -19,6 +21,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (atomic, readwrite, getter=isDownloading) BOOL downloading;
@property (atomic, readwrite, getter=hasFailed) BOOL failed;
// Though now required, `digest` may be null for pre-existing records or from
// messages received from other clients
@property (nullable, nonatomic, readonly) NSData *digest;
@end
NS_ASSUME_NONNULL_END

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 17/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSAttachmentPointer.h"
@ -9,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithServerId:(UInt64)serverId
key:(NSData *)key
digest:(NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay
{
@ -17,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
OWSAssert(digest != nil);
_digest = digest;
_failed = NO;
_downloading = NO;
_relay = relay;

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 17/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSAttachment.h"
#if TARGET_OS_IPHONE
@ -18,6 +19,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (atomic, readwrite) BOOL isDownloaded;
// Though now required, `digest` may be null for pre-existing records or from
// messages received from other clients
@property (nullable, nonatomic) NSData *digest;
#if TARGET_OS_IPHONE
- (nullable UIImage *)image;
#endif

@ -203,6 +203,7 @@ NS_ASSUME_NONNULL_BEGIN
[builder setId:attachmentStream.serverId];
[builder setContentType:attachmentStream.contentType];
[builder setKey:attachmentStream.encryptionKey];
[builder setDigest:attachmentStream.digest];
return [builder build];
}

@ -1277,17 +1277,20 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
#define AttachmentPointer_key @"key"
#define AttachmentPointer_size @"size"
#define AttachmentPointer_thumbnail @"thumbnail"
#define AttachmentPointer_digest @"digest"
@interface OWSSignalServiceProtosAttachmentPointer : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasId_:1;
BOOL hasContentType_:1;
BOOL hasKey_:1;
BOOL hasThumbnail_:1;
BOOL hasDigest_:1;
BOOL hasSize_:1;
UInt64 id;
NSString* contentType;
NSData* key;
NSData* thumbnail;
NSData* digest;
UInt32 size;
}
- (BOOL) hasId;
@ -1295,11 +1298,13 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (BOOL) hasKey;
- (BOOL) hasSize;
- (BOOL) hasThumbnail;
- (BOOL) hasDigest;
@property (readonly) UInt64 id;
@property (readonly, strong) NSString* contentType;
@property (readonly, strong) NSData* key;
@property (readonly) UInt32 size;
@property (readonly, strong) NSData* thumbnail;
@property (readonly, strong) NSData* digest;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
@ -1360,6 +1365,11 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (NSData*) thumbnail;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) setThumbnail:(NSData*) value;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) clearThumbnail;
- (BOOL) hasDigest;
- (NSData*) digest;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) setDigest:(NSData*) value;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) clearDigest;
@end
#define GroupContext_id @"id"

@ -5319,6 +5319,7 @@ static OWSSignalServiceProtosSyncMessageRead* defaultOWSSignalServiceProtosSyncM
@property (strong) NSData* key;
@property UInt32 size;
@property (strong) NSData* thumbnail;
@property (strong) NSData* digest;
@end
@implementation OWSSignalServiceProtosAttachmentPointer
@ -5358,6 +5359,13 @@ static OWSSignalServiceProtosSyncMessageRead* defaultOWSSignalServiceProtosSyncM
hasThumbnail_ = !!_value_;
}
@synthesize thumbnail;
- (BOOL) hasDigest {
return !!hasDigest_;
}
- (void) setHasDigest:(BOOL) _value_ {
hasDigest_ = !!_value_;
}
@synthesize digest;
- (instancetype) init {
if ((self = [super init])) {
self.id = 0L;
@ -5365,6 +5373,7 @@ static OWSSignalServiceProtosSyncMessageRead* defaultOWSSignalServiceProtosSyncM
self.key = [NSData data];
self.size = 0;
self.thumbnail = [NSData data];
self.digest = [NSData data];
}
return self;
}
@ -5399,6 +5408,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (self.hasThumbnail) {
[output writeData:5 value:self.thumbnail];
}
if (self.hasDigest) {
[output writeData:6 value:self.digest];
}
[self.unknownFields writeToCodedOutputStream:output];
}
- (SInt32) serializedSize {
@ -5423,6 +5435,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (self.hasThumbnail) {
size_ += computeDataSize(5, self.thumbnail);
}
if (self.hasDigest) {
size_ += computeDataSize(6, self.digest);
}
size_ += self.unknownFields.serializedSize;
memoizedSerializedSize = size_;
return size_;
@ -5473,6 +5488,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (self.hasThumbnail) {
[output appendFormat:@"%@%@: %@\n", indent, @"thumbnail", self.thumbnail];
}
if (self.hasDigest) {
[output appendFormat:@"%@%@: %@\n", indent, @"digest", self.digest];
}
[self.unknownFields writeDescriptionTo:output withIndent:indent];
}
- (void) storeInDictionary:(NSMutableDictionary *)dictionary {
@ -5491,6 +5509,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (self.hasThumbnail) {
[dictionary setObject: self.thumbnail forKey: @"thumbnail"];
}
if (self.hasDigest) {
[dictionary setObject: self.digest forKey: @"digest"];
}
[self.unknownFields storeInDictionary:dictionary];
}
- (BOOL) isEqual:(id)other {
@ -5512,6 +5533,8 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
(!self.hasSize || self.size == otherMessage.size) &&
self.hasThumbnail == otherMessage.hasThumbnail &&
(!self.hasThumbnail || [self.thumbnail isEqual:otherMessage.thumbnail]) &&
self.hasDigest == otherMessage.hasDigest &&
(!self.hasDigest || [self.digest isEqual:otherMessage.digest]) &&
(self.unknownFields == otherMessage.unknownFields || (self.unknownFields != nil && [self.unknownFields isEqual:otherMessage.unknownFields]));
}
- (NSUInteger) hash {
@ -5531,6 +5554,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (self.hasThumbnail) {
hashCode = hashCode * 31 + [self.thumbnail hash];
}
if (self.hasDigest) {
hashCode = hashCode * 31 + [self.digest hash];
}
hashCode = hashCode * 31 + [self.unknownFields hash];
return hashCode;
}
@ -5589,6 +5615,9 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
if (other.hasThumbnail) {
[self setThumbnail:other.thumbnail];
}
if (other.hasDigest) {
[self setDigest:other.digest];
}
[self mergeUnknownFields:other.unknownFields];
return self;
}
@ -5630,6 +5659,10 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
[self setThumbnail:[input readData]];
break;
}
case 50: {
[self setDigest:[input readData]];
break;
}
}
}
}
@ -5713,6 +5746,22 @@ static OWSSignalServiceProtosAttachmentPointer* defaultOWSSignalServiceProtosAtt
resultAttachmentPointer.thumbnail = [NSData data];
return self;
}
- (BOOL) hasDigest {
return resultAttachmentPointer.hasDigest;
}
- (NSData*) digest {
return resultAttachmentPointer.digest;
}
- (OWSSignalServiceProtosAttachmentPointerBuilder*) setDigest:(NSData*) value {
resultAttachmentPointer.hasDigest = YES;
resultAttachmentPointer.digest = value;
return self;
}
- (OWSSignalServiceProtosAttachmentPointerBuilder*) clearDigest {
resultAttachmentPointer.hasDigest = NO;
resultAttachmentPointer.digest = [NSData data];
return self;
}
@end
@interface OWSSignalServiceProtosGroupContext ()

@ -68,10 +68,12 @@ NS_ASSUME_NONNULL_BEGIN
}
NSData *encryptionKey;
NSData *digest;
NSData *encryptedAttachmentData =
[Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey];
[Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest];
attachmentStream.encryptionKey = encryptionKey;
attachmentStream.digest = digest;
[self uploadDataWithProgress:encryptedAttachmentData
location:location

@ -1,5 +1,8 @@
// Created by Christine Corbett Moran on 3/26/13.
// Copyright (c) 2013 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface Cryptography : NSObject
@ -13,7 +16,12 @@ typedef NS_ENUM(NSInteger, TSMACType) {
#pragma mark SHA and HMAC methods
+ (NSData *)computeSHA256:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes;
// Full length SHA256 digest for `data`
+ (NSData *)computeSHA256Digest:(NSData *)data;
// Truncated SHA256 digest for `data`
+ (NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes;
+ (NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string;
+ (NSString *)computeSHA1DigestForString:(NSString *)input;
@ -24,8 +32,12 @@ typedef NS_ENUM(NSInteger, TSMACType) {
+ (NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString;
#pragma mark encrypt and decrypt attachment data
+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt withKey:(NSData *)key;
+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt withKey:(NSData *)key digest:(nullable NSData *)digest;
+ (NSData *)encryptAttachmentData:(NSData *)attachmentData outKey:(NSData **)outKey;
+ (NSData *)encryptAttachmentData:(NSData *)attachmentData
outKey:(NSData *_Nonnull *_Nullable)outKey
outDigest:(NSData *_Nonnull *_Nullable)outDigest;
@end
NS_ASSUME_NONNULL_END

@ -1,9 +1,5 @@
//
// Cryptography.m
// TextSecureiOS
//
// Created by Christine Corbett Moran on 3/26/13.
// Copyright (c) 2013 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <CommonCrypto/CommonCryptor.h>
@ -11,12 +7,15 @@
#import "Cryptography.h"
#import "NSData+Base64.h"
#import "NSData+OWSConstantTimeCompare.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
@implementation Cryptography
@ -65,8 +64,14 @@
return output;
}
#pragma mark SHA256
+ (NSData *)computeSHA256:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes {
#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
@ -98,104 +103,65 @@
#pragma mark AES CBC Mode
+ (NSData *)encryptCBCMode:(NSData *)dataToEncrypt
withKey:(NSData *)key
withIV:(NSData *)iv
withVersion:(NSData *)version
withHMACKey:(NSData *)hmacKey
withHMACType:(TSMACType)hmacType
computedHMAC:(NSData **)hmac {
/* AES256 CBC encrypt then mac
Returns nil if encryption fails
*/
size_t bufferSize = [dataToEncrypt length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (buffer == NULL) {
DDLogError(@"Failed to allocate memory.");
return nil;
}
size_t bytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
[key bytes],
[key length],
[iv bytes],
[dataToEncrypt bytes],
[dataToEncrypt length],
buffer,
bufferSize,
&bytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *encryptedData = [NSData dataWithBytesNoCopy:buffer length:bytesEncrypted];
// compute hmac of version||encrypted data||iv
NSMutableData *dataToHmac = [NSMutableData data];
if (version != nil) {
[dataToHmac appendData:version];
}
[dataToHmac appendData:iv];
[dataToHmac appendData:encryptedData];
if (hmacType == TSHMACSHA1Truncated10Bytes) {
*hmac = [Cryptography truncatedSHA1HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
*hmac = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256AttachementType) {
*hmac = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
}
return encryptedData;
}
free(buffer);
return nil;
}
/**
* 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:(NSData *)version
version:(nullable NSData *)version
HMACKey:(NSData *)hmacKey
HMACType:(TSMACType)hmacType
matchingHMAC:(NSData *)hmac {
/* AES256 CBC encrypt then mac
Returns nil if hmac invalid or decryption fails
*/
// verify hmac of version||encrypted data||iv
NSMutableData *dataToHmac = [NSMutableData data];
matchingHMAC:(NSData *)hmac
digest:(nullable NSData *)digest
{
// Verify hmac of: version? || iv || encrypted data
NSMutableData *dataToAuth = [NSMutableData data];
if (version != nil) {
[dataToHmac appendData:version];
[dataToAuth appendData:version];
}
[dataToHmac appendData:iv];
[dataToHmac appendData:dataToDecrypt];
[dataToAuth appendData:iv];
[dataToAuth appendData:dataToDecrypt];
NSData *ourHmacData;
if (hmacType == TSHMACSHA1Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA1HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
ourHmacData = [Cryptography truncatedSHA1HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
} else if (hmacType == TSHMACSHA256AttachementType) {
ourHmacData =
[Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
[Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
}
if (hmac == nil || ![ourHmacData isEqualToData:hmac]) {
DDLogError(@"Bad HMAC on decrypting payload");
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;
}
} else {
DDLogVerbose(@"%@ %s no digest to verify", self.tag, __PRETTY_FUNCTION__);
}
// decrypt
size_t bufferSize = [dataToDecrypt length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (buffer == NULL) {
DDLogError(@"Failed to allocate memory.");
DDLogError(@"%@ Failed to allocate memory.", self.tag);
return nil;
}
@ -212,9 +178,9 @@
bufferSize,
&bytesDecrypted);
if (cryptStatus == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer length:bytesDecrypted];
return [NSData dataWithBytesNoCopy:buffer length:bytesDecrypted freeWhenDone:YES];
} else {
DDLogError(@"Failed CBC decryption");
DDLogError(@"%@ Failed CBC decryption", self.tag);
free(buffer);
}
@ -246,13 +212,15 @@
version:[NSData dataWithBytes:version length:1]
HMACKey:signalingKeyHMACKeyMaterial
HMACType:TSHMACSHA256Truncated10Bytes
matchingHMAC:[NSData dataWithBytes:mac length:10]];
matchingHMAC:[NSData dataWithBytes:mac length:10]
digest:nil];
}
+ (NSData *)decryptAttachment:(NSData *)dataToDecrypt withKey:(NSData *)key {
+ (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!");
DDLogError(@"%@ Message shorter than crypto overhead!", self.tag);
return nil;
}
@ -274,36 +242,84 @@
version:nil
HMACKey:hmacKey
HMACType:TSHMACSHA256AttachementType
matchingHMAC:hmac];
matchingHMAC:hmac
digest:digest];
}
+ (NSData *)encryptAttachmentData:(NSData *)attachmentData outKey:(NSData **)outKey
+ (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 *key = [NSMutableData data];
[key appendData:encryptionKey];
[key appendData:hmacKey];
*outKey = [key copy];
NSData *computedHMAC;
NSData *ciphertext = [Cryptography encryptCBCMode:attachmentData
withKey:encryptionKey
withIV:iv
withVersion:nil
withHMACKey:hmacKey
withHMACType:TSHMACSHA256AttachementType
computedHMAC:&computedHMAC];
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];
[encryptedAttachmentData appendData:computedHMAC];
[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;
return [encryptedAttachmentData copy];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSData (OWSConstantTimeCompare)
- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,30 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSData+OWSConstantTimeCompare.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSData (OWSConstantTimeCompare)
- (BOOL)ows_constantTimeIsEqualToData:(NSData *)other
{
BOOL isEqual = YES;
if (self.length != other.length) {
return NO;
}
UInt8 *leftBytes = (UInt8 *)self.bytes;
UInt8 *rightBytes = (UInt8 *)other.bytes;
for (int i = 0; i < self.length; i++) {
isEqual = isEqual && (leftBytes[i] == rightBytes[i]);
}
return isEqual;
}
@end
NS_ASSUME_NONNULL_END

@ -1,30 +0,0 @@
// Created by Frederic Jacobs on 21/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
#import <XCTest/XCTest.h>
#import "TSAttachmentStream.h"
#import "Cryptography.h"
NS_ASSUME_NONNULL_BEGIN
@interface TSAttachmentsTest : XCTestCase
@end
@implementation TSAttachmentsTest
- (void)testAttachmentEncryptionDecryption
{
NSData *plaintext = [Cryptography generateRandomBytes:100];
NSData *encryptionKey;
NSData *encryptedData = [Cryptography encryptAttachmentData:plaintext outKey:&encryptionKey];
NSData *plaintextBis = [Cryptography decryptAttachment:encryptedData withKey:encryptionKey];
XCTAssert([plaintext isEqualToData:plaintextBis], @"Attachments encryption failed");
}
@end
NS_ASSUME_NONNULL_END

@ -1,20 +1,17 @@
//
// CryptographyTests.m
// TextSecureiOS
//
// Created by Christine Corbett Moran on 12/19/13.
// Copyright (c) 2013 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "Cryptography.h"
#import "NSData+Base64.h"
NS_ASSUME_NONNULL_BEGIN
@interface CryptographyTests : XCTestCase
@end
@interface Cryptography (Test)
+ (NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(int)bytes;
+ (NSData *)encryptCBCMode:(NSData *)dataToEncrypt
@ -36,55 +33,94 @@
@implementation CryptographyTests
- (void)testEncryptAttachmentData
{
NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
NSData *plainTextData = [NSData dataFromBase64String:plainText];
// Sanity
XCTAssertNotNil(plainTextData);
NSData *generatedKey;
NSData *generatedDigest;
NSData *cipherText =
[Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];
NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:generatedKey digest:generatedDigest];
XCTAssertEqualObjects(plainTextData, decryptedData);
}
- (void)testDecryptAttachmentWithBadKey
{
NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
NSData *plainTextData = [NSData dataFromBase64String:plainText];
// Sanity
XCTAssertNotNil(plainTextData);
NSData *generatedKey;
NSData *generatedDigest;
NSData *cipherText =
[Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];
- (void)testLocalDecryption {
NSString *originalMessage = @"Hawaii is awesome";
NSString *signalingKeyString = @"VJuRzZcwuY/6VjGw+QSPy5ROzHo8xE36mKwHNvkfyZ+mSPaDlSDcenUqavIX1Vwn\nRRIdrg==";
NSData *signalingKey = [NSData dataFromBase64String:signalingKeyString];
XCTAssertTrue([signalingKey length] == 52,
@"signaling key is not 52 bytes but %llu",
(unsigned long long)[signalingKey length]);
NSData *signalingKeyAESKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(0, 32)];
NSData *signalingKeyHMACKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(32, 20)];
NSData *iv = [Cryptography generateRandomBytes:16];
NSData *version = [Cryptography generateRandomBytes:1];
NSData *mac;
NSData *encryption = [Cryptography encryptCBCMode:[originalMessage dataUsingEncoding:NSUTF8StringEncoding]
withKey:signalingKeyAESKeyMaterial
withIV:iv
withVersion:version
withHMACKey:signalingKeyHMACKeyMaterial
withHMACType:TSHMACSHA1Truncated10Bytes
computedHMAC:&mac]; // Encrypt
NSMutableData *dataToHmac = [NSMutableData data];
[dataToHmac appendData:version];
[dataToHmac appendData:iv];
[dataToHmac appendData:encryption];
NSData *expectedHmac =
[Cryptography truncatedSHA1HMAC:dataToHmac withHMACKey:signalingKeyHMACKeyMaterial truncation:10];
XCTAssertTrue([mac isEqualToData:expectedHmac],
@"Hmac of encrypted data %@, not equal to expected hmac %@",
[mac base64EncodedString],
[expectedHmac base64EncodedString]);
NSData *decryption = [Cryptography decryptCBCMode:encryption
key:signalingKeyAESKeyMaterial
IV:iv
version:version
HMACKey:signalingKeyHMACKeyMaterial
HMACType:TSHMACSHA1Truncated10Bytes
matchingHMAC:mac];
NSString *decryptedMessage = [[NSString alloc] initWithData:decryption encoding:NSUTF8StringEncoding];
XCTAssertTrue([decryptedMessage isEqualToString:originalMessage],
@"Decrypted message: %@ is not equal to original: %@",
decryptedMessage,
originalMessage);
NSData *badKey = [Cryptography generateRandomBytes:64];
NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:badKey digest:generatedDigest];
XCTAssertNil(decryptedData);
}
- (void)testDecryptAttachmentWithBadDigest
{
NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
NSData *plainTextData = [NSData dataFromBase64String:plainText];
// Sanity
XCTAssertNotNil(plainTextData);
NSData *generatedKey;
NSData *generatedDigest;
NSData *cipherText =
[Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];
NSData *badDigest = [Cryptography generateRandomBytes:32];
NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:generatedKey digest:badDigest];
XCTAssertNil(decryptedData);
}
- (void)testComputeSHA256Digest
{
NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
NSData *plainTextData = [NSData dataFromBase64String:plainText];
NSData *digest = [Cryptography computeSHA256Digest:plainTextData];
const uint8_t expectedBytes[] = {
0xba, 0x5f, 0xf1, 0x26,
0x82, 0xbb, 0xb2, 0x51,
0x8b, 0xe6, 0x06, 0x48,
0xc5, 0x53, 0xd0, 0xa2,
0xbf, 0x71, 0xf1, 0xec,
0xb4, 0xdb, 0x02, 0x12,
0x5f, 0x80, 0xea, 0x34,
0xc9, 0x8d, 0xee, 0x1f
};
NSData *expectedDigest = [NSData dataWithBytes:expectedBytes length:32];
XCTAssertEqualObjects(expectedDigest, digest);
NSData *expectedTruncatedDigest = [NSData dataWithBytes:expectedBytes length:10];
NSData *truncatedDigest = [Cryptography computeSHA256Digest:plainTextData truncatedToBytes:10];
XCTAssertEqualObjects(expectedTruncatedDigest, truncatedDigest);
}
@end
NS_ASSUME_NONNULL_END

Loading…
Cancel
Save