diff --git a/Podfile.lock b/Podfile.lock index 3955cd792..56e8c0f0d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -83,7 +83,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: JSQMessagesViewController: - :commit: bc976a04d906ab3e5316148ee66de172a6e370b2 + :commit: 8703ce020c86054ca32fbfdede0b4bded4428994 :git: https://github.com/dtsbourg/JSQMessagesViewController SocketRocket: :commit: d0585af165 diff --git a/Pods b/Pods index 6641c0c61..9601c2be9 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 6641c0c61b183015950fb020a53a43a993bbb28f +Subproject commit 9601c2be9292f740e40d530ec35ea7182d0a8441 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 8de1ab05f..22dd48990 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -396,6 +396,7 @@ B6C6AE561A305ED1006BAF8F /* textsecure.cer in Resources */ = {isa = PBXBuildFile; fileRef = B6C6AE541A305ED1006BAF8F /* textsecure.cer */; }; B6C93C4E199567AD00EDF894 /* DebugLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = B6C93C4D199567AD00EDF894 /* DebugLogger.m */; }; B6CBF53F1A254BD1000D4184 /* ContactDetailCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B6CBF53E1A254BD1000D4184 /* ContactDetailCell.m */; }; + B6E314C91A38FAAF00A41AFB /* TSFingerprintGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B90418E7183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B96A3100187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */; }; @@ -1041,6 +1042,8 @@ B6C93C4D199567AD00EDF894 /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = ""; }; B6CBF53D1A254BD1000D4184 /* ContactDetailCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactDetailCell.h; path = "Signal/src/view controllers/ContactDetailCell.h"; sourceTree = SOURCE_ROOT; }; B6CBF53E1A254BD1000D4184 /* ContactDetailCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactDetailCell.m; path = "Signal/src/view controllers/ContactDetailCell.m"; sourceTree = SOURCE_ROOT; }; + B6E314C71A38FAAF00A41AFB /* TSFingerprintGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSFingerprintGenerator.h; sourceTree = ""; }; + B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSFingerprintGenerator.m; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Bd.otf"; sourceTree = ""; }; @@ -2331,6 +2334,8 @@ children = ( B6B0963C1A1D25ED008BFAA6 /* SecurityUtils.h */, B6B0963D1A1D25ED008BFAA6 /* SecurityUtils.m */, + B6E314C71A38FAAF00A41AFB /* TSFingerprintGenerator.h */, + B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */, ); path = Security; sourceTree = ""; @@ -3194,6 +3199,7 @@ E16E5BEF18AAC40200B7C403 /* EC25KeyAgreementProtocol.m in Sources */, B6B096901A1D25ED008BFAA6 /* Cryptography.m in Sources */, 76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */, + B6E314C91A38FAAF00A41AFB /* TSFingerprintGenerator.m in Sources */, B6B096721A1D25ED008BFAA6 /* TSMessage.m in Sources */, 76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */, 76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */, diff --git a/Signal/src/textsecure/Messages/TSErrorMessage.h b/Signal/src/textsecure/Messages/TSErrorMessage.h index d77d8e67e..a6bb72bb0 100644 --- a/Signal/src/textsecure/Messages/TSErrorMessage.h +++ b/Signal/src/textsecure/Messages/TSErrorMessage.h @@ -19,21 +19,23 @@ typedef NS_ENUM(int32_t, TSErrorMessageType){ TSErrorMessageMissingKeyId, TSErrorMessageInvalidMessage, TSErrorMessageDuplicateMessage, - TSErrorMessageInvalidVersion + TSErrorMessageInvalidVersion, }; ++ (instancetype)corruptedMessageWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; + (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; + (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; + (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; -+ (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; + (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; -+ (instancetype)userNotRegisteredErrorMessageInThread:(TSThread*)thread; +/** + * Methods on TSErrorMessageWrongTrustedIdentityKey error types + */ -- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread failedMessageType:(TSErrorMessageType)errorMessageType; ++ (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction; -- (NSData*)retryBody; -- (BOOL)supportsRetry; +- (void)acceptNewIdentityKey; +- (NSString*)newIdentityKey; @property (nonatomic, readonly) TSErrorMessageType errorType; diff --git a/Signal/src/textsecure/Messages/TSErrorMessage.m b/Signal/src/textsecure/Messages/TSErrorMessage.m index 1a46f1b3c..5962d8990 100644 --- a/Signal/src/textsecure/Messages/TSErrorMessage.m +++ b/Signal/src/textsecure/Messages/TSErrorMessage.m @@ -8,9 +8,28 @@ #import "TSErrorMessage.h" #import "NSDate+millisecondTimeStamp.h" +#import "TSStorageManager+IdentityKeyStore.h" +#import +#import +#import "TSMessagesManager.h" +#import "TSFingerprintGenerator.h" + +@interface TSErrorMessage() +@property NSData *pushSignal; +@end @implementation TSErrorMessage +- (instancetype)initForUnknownIdentityKeyWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread incomingPushSignal:(NSData*)signal{ + self = [self initWithTimestamp:timestamp inThread:thread failedMessageType:TSErrorMessageWrongTrustedIdentityKey]; + + if (self) { + _pushSignal = signal; + } + + return self; +} + - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread failedMessageType:(TSErrorMessageType)errorMessageType{ self = [super initWithTimestamp:timestamp inThread:thread messageBody:nil attachements:nil]; @@ -21,6 +40,11 @@ return self; } +- (instancetype)initWithSignal:(IncomingPushMessageSignal*)signal transaction:(YapDatabaseReadWriteTransaction*)transaction failedMessageType:(TSErrorMessageType)errorMessageType{ + TSContactThread *contactThread = [TSContactThread threadWithContactId:signal.source transaction:transaction]; + return [self initWithTimestamp:signal.timestamp inThread:contactThread failedMessageType:errorMessageType]; +} + - (NSString*)description{ switch (_errorType) { case TSErrorMessageNoSession: @@ -36,45 +60,65 @@ case TSErrorMessageInvalidKeyException: return @"The recipient's key is not valid."; case TSErrorMessageWrongTrustedIdentityKey: - return @"Your contact's identity key changed. Tap to verify and accept new key"; + return @"Identity key changed. Tap to verify new key."; default: return @"An unknown error occured"; break; } } -+ (instancetype)userNotRegisteredErrorMessageInThread:(TSThread*)thread{ - return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread]; ++ (instancetype)corruptedMessageWithSignal:(IncomingPushMessageSignal *)signal withTransaction:(YapDatabaseReadWriteTransaction *)transaction{ + return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidMessage]; } -+ (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ - TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction]; - TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageInvalidVersion]; - return errorMessage; ++ (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ + return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidVersion]; } -+ (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ - TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction]; - TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageMissingKeyId]; - return errorMessage; ++ (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ + return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageMissingKeyId]; } -+ (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ - TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction]; - TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageInvalidKeyException]; - return errorMessage; ++ (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ + return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidKeyException]; } + (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction]; - TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageWrongTrustedIdentityKey]; + TSErrorMessage *errorMessage = [[self alloc] initForUnknownIdentityKeyWithTimestamp:preKeyMessage.timestamp inThread:contactThread incomingPushSignal:preKeyMessage.data]; return errorMessage; } -+ (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ - TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction]; - TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageNoSession]; - return errorMessage; ++ (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{ + return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageNoSession]; +} + +- (void)acceptNewIdentityKey{ + if (_errorType != TSErrorMessageWrongTrustedIdentityKey || !_pushSignal) { + return; + } + + TSStorageManager *storage = [TSStorageManager sharedManager]; + IncomingPushMessageSignal *signal = [IncomingPushMessageSignal parseFromData:_pushSignal]; + PreKeyWhisperMessage *message = [[PreKeyWhisperMessage alloc] initWithData:signal.message]; + NSData *newKey = [message.identityKey removeKeyType]; + + [storage saveRemoteIdentity:newKey recipientId:signal.source]; + + [[TSMessagesManager sharedManager] handleMessageSignal:signal]; + //TODO: Decrypt any other messages encrypted with that new identity key automatically. +} + +- (NSString *)newIdentityKey{ + if (_errorType != TSErrorMessageWrongTrustedIdentityKey || !_pushSignal) { + return @""; + } + + IncomingPushMessageSignal *signal = [IncomingPushMessageSignal parseFromData:_pushSignal]; + PreKeyWhisperMessage *message = [[PreKeyWhisperMessage alloc] initWithData:signal.message]; + NSData *identityKey = [message.identityKey removeKeyType]; + + return [TSFingerprintGenerator getFingerprintForDisplay:identityKey]; } @end diff --git a/Signal/src/textsecure/Messages/TSInfoMessage.h b/Signal/src/textsecure/Messages/TSInfoMessage.h index 40509597e..aaa930193 100644 --- a/Signal/src/textsecure/Messages/TSInfoMessage.h +++ b/Signal/src/textsecure/Messages/TSInfoMessage.h @@ -12,9 +12,12 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType){ TSInfoMessageTypeSessionDidEnd, + TSInfoMessageUserNotRegistered, TSInfoMessageTypeUnsupportedMessage }; ++ (instancetype)userNotRegisteredMessageInThread:(TSThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction; + @property TSInfoMessageType messageType; - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage; diff --git a/Signal/src/textsecure/Messages/TSInfoMessage.m b/Signal/src/textsecure/Messages/TSInfoMessage.m index 4baed8b1d..1dd176fca 100644 --- a/Signal/src/textsecure/Messages/TSInfoMessage.m +++ b/Signal/src/textsecure/Messages/TSInfoMessage.m @@ -7,11 +7,12 @@ // #import "TSInfoMessage.h" +#import "NSDate+millisecondTimeStamp.h" @implementation TSInfoMessage - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage{ - self = [super initWithTimestamp:timestamp inThread:contact messageBody:@"Placeholder for info message." attachements:nil]; + self = [super initWithTimestamp:timestamp inThread:contact messageBody:nil attachements:nil]; if (self) { _messageType = infoMessage; @@ -20,12 +21,19 @@ return self; } ++ (instancetype)userNotRegisteredMessageInThread:(TSContactThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction{ + return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageType:TSInfoMessageUserNotRegistered]; + +} + - (NSString *)description{ switch (_messageType) { case TSInfoMessageTypeSessionDidEnd: return @"Secure session ended."; case TSInfoMessageTypeUnsupportedMessage: return @"Media messages are currently not supported."; + case TSInfoMessageUserNotRegistered: + return @"The user is not registered."; default: break; } diff --git a/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m b/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m index 4b849eb12..bde3d99ef 100644 --- a/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m +++ b/Signal/src/textsecure/Messages/TSMessagesManager+sendMessages.m @@ -29,6 +29,7 @@ #import "TSRecipientPrekeyRequest.h" #import "TSErrorMessage.h" +#import "TSInfoMessage.h" #import "TSContactThread.h" #import "TSGroupThread.h" @@ -99,7 +100,7 @@ dispatch_queue_t sendingQueue() { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [recipient removeWithTransaction:transaction]; [message setMessageState:TSOutgoingMessageStateUnsent]; - [[TSErrorMessage userNotRegisteredErrorMessageInThread:thread] saveWithTransaction:transaction]; + [[TSInfoMessage userNotRegisteredMessageInThread:thread transaction:transaction] saveWithTransaction:transaction]; }]; break; } diff --git a/Signal/src/textsecure/Messages/TSMessagesManager.m b/Signal/src/textsecure/Messages/TSMessagesManager.m index 4df55e5cf..ed35a6c91 100644 --- a/Signal/src/textsecure/Messages/TSMessagesManager.m +++ b/Signal/src/textsecure/Messages/TSMessagesManager.m @@ -8,6 +8,7 @@ #import "TSMessagesManager.h" +#import #import #import "Cryptography.h" @@ -224,10 +225,12 @@ TSThread *thread; if (groupId) { TSGroupThread *gThread = [TSGroupThread threadWithGroupId:groupId]; + [gThread saveWithTransaction:transaction]; incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:gThread authorId:message.source messageBody:body attachements:nil]; thread = gThread; } else{ TSContactThread *cThread = [TSContactThread threadWithContactId:message.source transaction:transaction]; + [cThread saveWithTransaction:transaction]; incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:cThread messageBody:body attachements:nil]; thread = cThread; } @@ -238,9 +241,27 @@ } - (void)processException:(NSException*)exception pushSignal:(IncomingPushMessageSignal*)signal{ - DDLogError(@"Got exception: %@", exception.description); + DDLogError(@"Got exception: %@ of type: %@", exception.description, exception.name); [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [[TSErrorMessage alloc] initWithTimestamp:signal.timestamp inThread:[TSContactThread threadWithContactId:signal.source transaction:transaction] failedMessageType:TSErrorMessageNoSession]; + TSErrorMessage *errorMessage; + + if ([exception.name isEqualToString:NoSessionException]) { + errorMessage = [TSErrorMessage missingSessionWithSignal:signal withTransaction:transaction]; + } else if ([exception.name isEqualToString:InvalidKeyException]){ + errorMessage = [TSErrorMessage invalidKeyExceptionWithSignal:signal withTransaction:transaction]; + } else if ([exception.name isEqualToString:InvalidKeyIdException]){ + errorMessage = [TSErrorMessage invalidKeyExceptionWithSignal:signal withTransaction:transaction]; + } else if ([exception.name isEqualToString:DuplicateMessageException]){ + // Duplicate messages are dismissed + return ; + } else if ([exception.name isEqualToString:InvalidVersionException]){ + errorMessage = [TSErrorMessage invalidVersionWithSignal:signal withTransaction:transaction]; + } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]){ + errorMessage = [TSErrorMessage untrustedKeyWithSignal:signal withTransaction:transaction]; + } else { + errorMessage = [TSErrorMessage corruptedMessageWithSignal:signal withTransaction:transaction]; + } + [errorMessage saveWithTransaction:transaction]; }]; diff --git a/Signal/src/textsecure/Security/TSFingerprintGenerator.h b/Signal/src/textsecure/Security/TSFingerprintGenerator.h new file mode 100644 index 000000000..714e457ed --- /dev/null +++ b/Signal/src/textsecure/Security/TSFingerprintGenerator.h @@ -0,0 +1,15 @@ +// +// TSFingerprintGenerator.h +// Signal +// +// Created by Frederic Jacobs on 10/12/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import + +@interface TSFingerprintGenerator : NSObject + ++ (NSString*)getFingerprintForDisplay:(NSData*)identityKey; + +@end diff --git a/Signal/src/textsecure/Security/TSFingerprintGenerator.m b/Signal/src/textsecure/Security/TSFingerprintGenerator.m new file mode 100644 index 000000000..9181e203e --- /dev/null +++ b/Signal/src/textsecure/Security/TSFingerprintGenerator.m @@ -0,0 +1,37 @@ +// +// TSFingerprintGenerator.m +// Signal +// +// Created by Frederic Jacobs on 10/12/14. +// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// + +#import "NSData+hexString.h" +#import + +#import "TSFingerprintGenerator.h" + +@implementation TSFingerprintGenerator + ++ (NSString*)getFingerprintForDisplay:(NSData*)identityKey { + // idea here is to insert a space every two characters. there is probably a cleverer/more native way to do this. + + identityKey = [identityKey prependKeyType]; + NSString *fingerprint = [identityKey hexadecimalString]; + __block NSString* formattedFingerprint = @""; + + + [fingerprint enumerateSubstringsInRange:NSMakeRange(0, [fingerprint length]) + options:NSStringEnumerationByComposedCharacterSequences + usingBlock: + ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + if (substringRange.location % 2 != 0 && substringRange.location != [fingerprint length]-1) { + substring = [substring stringByAppendingString:@" "]; + } + formattedFingerprint = [formattedFingerprint stringByAppendingString:substring]; + }]; + return formattedFingerprint; +} + + +@end diff --git a/Signal/src/view controllers/FingerprintViewController.m b/Signal/src/view controllers/FingerprintViewController.m index e7343ae28..8dd465f5d 100644 --- a/Signal/src/view controllers/FingerprintViewController.m +++ b/Signal/src/view controllers/FingerprintViewController.m @@ -16,6 +16,8 @@ #import "TSStorageManager.h" #import "TSStorageManager+IdentityKeyStore.h" +#import "TSFingerprintGenerator.h" + @interface FingerprintViewController () @property TSContactThread *thread; @end @@ -26,25 +28,6 @@ self.thread = (TSContactThread*)thread; } -- (NSString*)getFingerprintForDisplay:(NSData*)identityKey { - // idea here is to insert a space every two characters. there is probably a cleverer/more native way to do this. - - identityKey = [identityKey prependKeyType]; - NSString *fingerprint = [identityKey hexadecimalString]; - __block NSString* formattedFingerprint = @""; - - - [fingerprint enumerateSubstringsInRange:NSMakeRange(0, [fingerprint length]) - options:NSStringEnumerationByComposedCharacterSequences - usingBlock: - ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - if (substringRange.location % 2 != 0 && substringRange.location != [fingerprint length]-1) { - substring = [substring stringByAppendingString:@" "]; - } - formattedFingerprint = [formattedFingerprint stringByAppendingString:substring]; - }]; - return formattedFingerprint; -} - (void)viewDidLoad { [super viewDidLoad]; @@ -59,10 +42,10 @@ { self.contactFingerprintTitleLabel.text = self.thread.name; NSData *identityKey = [[TSStorageManager sharedManager] identityKeyForRecipientId:self.thread.contactIdentifier]; - self.contactFingerprintLabel.text = [self getFingerprintForDisplay:identityKey]; + self.contactFingerprintLabel.text = [TSFingerprintGenerator getFingerprintForDisplay:identityKey]; NSData *myPublicKey = [[TSStorageManager sharedManager] identityKeyPair].publicKey; - self.userFingerprintLabel.text = [self getFingerprintForDisplay:myPublicKey]; + self.userFingerprintLabel.text = [TSFingerprintGenerator getFingerprintForDisplay:myPublicKey]; [UIView animateWithDuration:0.6 delay:0. options:UIViewAnimationOptionCurveEaseInOut animations:^{ [self.view setAlpha:1]; diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index eaeb348e9..9ed4ad23d 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -31,9 +31,12 @@ #import "TSStorageManager.h" #import "TSDatabaseView.h" #import -#import "TSInteraction.h" + + #import "TSMessageAdapter.h" +#import "TSErrorMessage.h" #import "TSIncomingMessage.h" +#import "TSInteraction.h" #import "TSMessagesManager+sendMessages.h" #import "NSDate+millisecondTimeStamp.h" @@ -451,57 +454,88 @@ typedef enum : NSUInteger { - (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath { - TSMessageAdapter * messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; + TSMessageAdapter *messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath]; + TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; - BOOL isMessage = (messageItem.messageType == TSIncomingMessageAdapter) || (messageItem.messageType == TSOutgoingMessageAdapter); - - BOOL isMediaMessage = isMessage ? [messageItem isMediaMessage] : NO; - - if (isMediaMessage) { - id messageMedia = [messageItem media]; + switch (messageItem.messageType) { + case TSOutgoingMessageAdapter: + if (messageItem.messageState == TSOutgoingMessageStateUnsent) { + [self handleUnsentMessageTap:(TSOutgoingMessage*)interaction]; + } + case TSIncomingMessageAdapter:{ - if ([messageMedia isKindOfClass:JSQPhotoMediaItem.class]) { - //is a photo - tappedImage = ((JSQPhotoMediaItem*)messageMedia).image ; - [self performSegueWithIdentifier:@"fullImage" sender:self]; + BOOL isMediaMessage = [messageItem isMediaMessage]; - } else if ([messageMedia isKindOfClass:JSQVideoMediaItem.class]) { - //is a video - } + if (isMediaMessage) { + id messageMedia = [messageItem media]; + + if ([messageMedia isKindOfClass:JSQPhotoMediaItem.class]) { + //is a photo + tappedImage = ((JSQPhotoMediaItem*)messageMedia).image ; + [self performSegueWithIdentifier:@"fullImage" sender:self]; + + } else if ([messageMedia isKindOfClass:JSQVideoMediaItem.class]) { + //is a video + } + } + + break;} + case TSErrorMessageAdapter: + [self handleErrorMessageTap:(TSErrorMessage*)interaction]; + break; + case TSInfoMessageAdapter: + break; + + default: + break; } - - BOOL isUnsent = messageItem.messageState == TSOutgoingMessageStateAttemptingOut && [messageItem.senderId isEqualToString:self.senderId]; - - if (isMessage && isUnsent) - { - [DJWActionSheet showInView:self.tabBarController.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Send again"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) { +} + +#pragma mark Bubble User Actions + +- (void)handleUnsentMessageTap:(TSOutgoingMessage*)message{ + [DJWActionSheet showInView:self.tabBarController.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Send again"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) { + if (tappedButtonIndex == actionSheet.cancelButtonIndex) { + NSLog(@"User Cancelled"); + } else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) { + [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){ + [message removeWithTransaction:transaction]; + }]; + }else { + [[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread]; + [self finishSendingMessage]; + } + }]; +} + +- (void)handleErrorMessageTap:(TSErrorMessage*)message{ + if (message.errorType == TSErrorMessageWrongTrustedIdentityKey) { + NSString *newKeyFingerprint = [message newIdentityKey]; + NSString *messageString = [NSString stringWithFormat:@"Do you want to accept %@'s new identity key: %@", _thread.name, newKeyFingerprint]; + NSArray *actions = @[@"Accept new identity key", @"Copy new identity key to pasteboard"]; + + [DJWActionSheet showInView:self.tabBarController.view withTitle:messageString cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:actions tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) { if (tappedButtonIndex == actionSheet.cancelButtonIndex) { NSLog(@"User Cancelled"); } else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) { - - [self.uiDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){ - TSOutgoingMessage * message = (TSOutgoingMessage*)messageItem; + [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){ [message removeWithTransaction:transaction]; - [self finishSendingMessage]; }]; - - }else { + } else { switch (tappedButtonIndex) { case 0: - { - TSOutgoingMessage * message = (TSOutgoingMessage*)messageItem; - [[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread]; - [self finishSendingMessage]; + [message acceptNewIdentityKey]; + break; + + case 1: + [[UIPasteboard generalPasteboard] setString:newKeyFingerprint]; break; - } - default: break; } } }]; } - } #pragma mark - Navigation @@ -514,7 +548,6 @@ typedef enum : NSUInteger { } else if ([segue.identifier isEqualToString:@"fingerprintSegue"]){ FingerprintViewController *vc = [segue destinationViewController]; - TSContactThread *thread = (TSContactThread*) self.thread; [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [vc configWithThread:self.thread]; }]; @@ -697,8 +730,7 @@ typedef enum : NSUInteger { return numberOfMessages; } -- (TSMessageAdapter*)messageAtIndexPath:(NSIndexPath *)indexPath -{ +- (TSInteraction*)interactionAtIndexPath:(NSIndexPath*)indexPath { __block TSInteraction *message = nil; [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName]; @@ -714,7 +746,13 @@ typedef enum : NSUInteger { message = [viewTransaction objectAtRow:row inSection:section withMappings:self.messageMappings]; NSParameterAssert(message != nil); }]; - return [TSMessageAdapter messageViewDataWithInteraction:message inThread:self.thread]; + + return message; +} + +- (TSMessageAdapter*)messageAtIndexPath:(NSIndexPath *)indexPath { + TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; + return [TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread]; } #pragma mark Accessory View