diff --git a/Example/TSKitiOSTestApp/Podfile.lock b/Example/TSKitiOSTestApp/Podfile.lock index b7c3ffe72..c1dbfc0f1 100644 --- a/Example/TSKitiOSTestApp/Podfile.lock +++ b/Example/TSKitiOSTestApp/Podfile.lock @@ -144,4 +144,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1a7633963dbcaa43f298949d83c42c1cd1dce940 -COCOAPODS: 1.1.1 +COCOAPODS: 1.2.0 diff --git a/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj b/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj index 46206806b..e668183de 100644 --- a/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj +++ b/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj @@ -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 = ""; }; 454092F91DB7AFDE00579DE1 /* OWSFakeNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSFakeNetworkManager.m; path = ../../../tests/TestSupport/Fakes/OWSFakeNetworkManager.m; sourceTree = ""; }; 45458B6A1CC342B600A02153 /* SignedPreKeyDeletionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignedPreKeyDeletionTests.m; sourceTree = ""; }; - 45458B6C1CC342B600A02153 /* TSAttachmentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAttachmentsTest.m; sourceTree = ""; }; 45458B6E1CC342B600A02153 /* TSMessageStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSMessageStorageTests.m; sourceTree = ""; }; 45458B6F1CC342B600A02153 /* TSStorageIdentityKeyStoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSStorageIdentityKeyStoreTests.m; sourceTree = ""; }; 45458B701CC342B600A02153 /* TSStoragePreKeyStoreTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSStoragePreKeyStoreTests.m; sourceTree = ""; }; @@ -178,15 +176,6 @@ path = ../../../tests/Account; sourceTree = ""; }; - 45458B6B1CC342B600A02153 /* Attachments */ = { - isa = PBXGroup; - children = ( - 45458B6C1CC342B600A02153 /* TSAttachmentsTest.m */, - ); - name = Attachments; - path = ../../../tests/Attachments; - sourceTree = ""; - }; 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 */, diff --git a/protobuf/OWSSignalServiceProtos.proto b/protobuf/OWSSignalServiceProtos.proto index 2ffbaa441..61b4105bc 100644 --- a/protobuf/OWSSignalServiceProtos.proto +++ b/protobuf/OWSSignalServiceProtos.proto @@ -133,6 +133,7 @@ message AttachmentPointer { optional bytes key = 3; optional uint32 size = 4; optional bytes thumbnail = 5; + optional bytes digest = 6; } message GroupContext { diff --git a/src/Messages/Attachments/OWSAttachmentsProcessor.m b/src/Messages/Attachments/OWSAttachmentsProcessor.m index 7487fcfd7..b0e75b914 100644 --- a/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -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", @"")); diff --git a/src/Messages/Attachments/TSAttachmentPointer.h b/src/Messages/Attachments/TSAttachmentPointer.h index fba17e869..a96aef9fb 100644 --- a/src/Messages/Attachments/TSAttachmentPointer.h +++ b/src/Messages/Attachments/TSAttachmentPointer.h @@ -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 diff --git a/src/Messages/Attachments/TSAttachmentPointer.m b/src/Messages/Attachments/TSAttachmentPointer.m index c41934f3b..426878483 100644 --- a/src/Messages/Attachments/TSAttachmentPointer.m +++ b/src/Messages/Attachments/TSAttachmentPointer.m @@ -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; diff --git a/src/Messages/Attachments/TSAttachmentStream.h b/src/Messages/Attachments/TSAttachmentStream.h index 9bf50613b..9a3f9b329 100644 --- a/src/Messages/Attachments/TSAttachmentStream.h +++ b/src/Messages/Attachments/TSAttachmentStream.h @@ -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 diff --git a/src/Messages/Interactions/TSOutgoingMessage.m b/src/Messages/Interactions/TSOutgoingMessage.m index de6ff2cc8..b53014ccb 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.m +++ b/src/Messages/Interactions/TSOutgoingMessage.m @@ -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]; } diff --git a/src/Messages/OWSSignalServiceProtos.pb.h b/src/Messages/OWSSignalServiceProtos.pb.h index c8facbbe5..ef02dfb63 100644 --- a/src/Messages/OWSSignalServiceProtos.pb.h +++ b/src/Messages/OWSSignalServiceProtos.pb.h @@ -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 { @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" diff --git a/src/Messages/OWSSignalServiceProtos.pb.m b/src/Messages/OWSSignalServiceProtos.pb.m index d6b9e22ab..1cd7cc552 100644 --- a/src/Messages/OWSSignalServiceProtos.pb.m +++ b/src/Messages/OWSSignalServiceProtos.pb.m @@ -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 () diff --git a/src/Network/API/OWSUploadingService.m b/src/Network/API/OWSUploadingService.m index 39db71e2d..a73f53f68 100644 --- a/src/Network/API/OWSUploadingService.m +++ b/src/Network/API/OWSUploadingService.m @@ -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 diff --git a/src/Util/Cryptography.h b/src/Util/Cryptography.h index 4eb2f26b7..7f9abd021 100755 --- a/src/Util/Cryptography.h +++ b/src/Util/Cryptography.h @@ -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 diff --git a/src/Util/Cryptography.m b/src/Util/Cryptography.m index 22db2b09d..00821fa33 100755 --- a/src/Util/Cryptography.m +++ b/src/Util/Cryptography.m @@ -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 @@ -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 diff --git a/src/Util/NSData+OWSConstantTimeCompare.h b/src/Util/NSData+OWSConstantTimeCompare.h new file mode 100644 index 000000000..75260d798 --- /dev/null +++ b/src/Util/NSData+OWSConstantTimeCompare.h @@ -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 diff --git a/src/Util/NSData+OWSConstantTimeCompare.m b/src/Util/NSData+OWSConstantTimeCompare.m new file mode 100644 index 000000000..08e710146 --- /dev/null +++ b/src/Util/NSData+OWSConstantTimeCompare.m @@ -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 diff --git a/tests/Attachments/TSAttachmentsTest.m b/tests/Attachments/TSAttachmentsTest.m deleted file mode 100644 index 125dd2d66..000000000 --- a/tests/Attachments/TSAttachmentsTest.m +++ /dev/null @@ -1,30 +0,0 @@ -// Created by Frederic Jacobs on 21/12/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. - -#import - -#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 diff --git a/tests/Util/CryptographyTests.m b/tests/Util/CryptographyTests.m index a37d31df4..24b568cfa 100644 --- a/tests/Util/CryptographyTests.m +++ b/tests/Util/CryptographyTests.m @@ -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 #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