From f2f3acb897d24efa9de66fb5ff16268b5d0eff71 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 23 May 2017 07:28:50 -0700 Subject: [PATCH] IdentityKeyStore changes 1) Always accept keys from incoming messages 2) Block sending only if it's a recent change, or if always block is enabled // FREEBIE --- src/Account/TSPrivacyPreferences.h | 19 -- src/Account/TSPrivacyPreferences.m | 48 ---- ...SInvalidIdentityKeyReceivingErrorMessage.m | 5 +- .../TSInvalidIdentityKeySendingErrorMessage.m | 5 +- src/Messages/TSGroupModel.h | 9 +- src/Messages/TSGroupModel.m | 7 +- src/Protocols/TSPreferences.h | 13 + src/Security/OWSFingerprintBuilder.h | 7 +- src/Security/OWSFingerprintBuilder.m | 12 +- src/Security/OWSRecipientIdentity.h | 38 +++ src/Security/OWSRecipientIdentity.m | 96 ++++++++ .../TSStorageManager+IdentityKeyStore.h | 23 +- .../TSStorageManager+IdentityKeyStore.m | 225 +++++++++++++++--- src/Storage/TSStorageManager.h | 2 - src/Storage/TSStorageManager.m | 6 - src/Storage/TSYapDatabaseObject.h | 9 +- src/TextSecureKitEnv.h | 5 +- src/TextSecureKitEnv.m | 11 +- 18 files changed, 403 insertions(+), 137 deletions(-) delete mode 100644 src/Account/TSPrivacyPreferences.h delete mode 100644 src/Account/TSPrivacyPreferences.m create mode 100644 src/Protocols/TSPreferences.h create mode 100644 src/Security/OWSRecipientIdentity.h create mode 100644 src/Security/OWSRecipientIdentity.m diff --git a/src/Account/TSPrivacyPreferences.h b/src/Account/TSPrivacyPreferences.h deleted file mode 100644 index 47c9af719..000000000 --- a/src/Account/TSPrivacyPreferences.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSPrivacyPreferences : TSYapDatabaseObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedInstance; - -@property BOOL shouldBlockOnIdentityChange; - -@end - -NS_ASSUME_NONNULL_END diff --git a/src/Account/TSPrivacyPreferences.m b/src/Account/TSPrivacyPreferences.m deleted file mode 100644 index fe3aa7054..000000000 --- a/src/Account/TSPrivacyPreferences.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "TSPrivacyPreferences.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const TSPrivacyPreferencesSingletonKey = @"TSPrivacyPreferences"; - -@implementation TSPrivacyPreferences - -+ (instancetype)sharedInstance -{ - static TSPrivacyPreferences *sharedInstance; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [self fetchObjectWithUniqueID:TSPrivacyPreferencesSingletonKey]; - if (!sharedInstance) { - sharedInstance = [[self alloc] initDefault]; - } - }); - - return sharedInstance; -} - -- (instancetype)initDefault -{ - return [self initWithShouldBlockOnIdentityChange:YES]; -} - -- (instancetype)initWithShouldBlockOnIdentityChange:(BOOL)shouldBlockOnIdentityChange -{ - self = [super initWithUniqueId:TSPrivacyPreferencesSingletonKey]; - if (!self) { - return self; - } - - _shouldBlockOnIdentityChange = shouldBlockOnIdentityChange; - - OWSSingletonAssert(); - - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m index d7de9b34e..f11267334 100644 --- a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m +++ b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m @@ -81,7 +81,10 @@ NS_ASSUME_NONNULL_BEGIN // Saving a new identity mutates the session store so it must happen on the sessionStoreQueue dispatch_async([OWSDispatch sessionStoreQueue], ^{ - [[TSStorageManager sharedManager] saveRemoteIdentity:newKey recipientId:self.envelope.source]; + [[TSStorageManager sharedManager] saveRemoteIdentity:newKey + recipientId:self.envelope.source + approvedForBlockingUse:YES + approvedForNonBlockingUse:YES]; dispatch_async(dispatch_get_main_queue(), ^{ // Decrypt this and any old messages for the newly accepted key diff --git a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m index 363eca908..b39f340b1 100644 --- a/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m +++ b/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m @@ -59,7 +59,10 @@ NSString *TSInvalidRecipientKey = @"TSInvalidRecipientKey"; { // Saving a new identity mutates the session store so it must happen on the sessionStoreQueue dispatch_async([OWSDispatch sessionStoreQueue], ^{ - [[TSStorageManager sharedManager] saveRemoteIdentity:self.newIdentityKey recipientId:self.recipientId]; + [[TSStorageManager sharedManager] saveRemoteIdentity:self.newIdentityKey + recipientId:self.recipientId + approvedForBlockingUse:YES + approvedForNonBlockingUse:YES]; }); } diff --git a/src/Messages/TSGroupModel.h b/src/Messages/TSGroupModel.h index 0b21ffefb..e9653f447 100644 --- a/src/Messages/TSGroupModel.h +++ b/src/Messages/TSGroupModel.h @@ -1,12 +1,13 @@ -// Created by Frederic Jacobs. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "TSYapDatabaseObject.h" #import "ContactsManagerProtocol.h" @interface TSGroupModel : TSYapDatabaseObject -@property (nonatomic, strong) NSMutableArray *groupMemberIds; +@property (nonatomic, strong) NSMutableArray *groupMemberIds; @property (nonatomic, strong) NSString *groupName; @property (nonatomic, strong) NSData *groupId; @@ -14,7 +15,7 @@ @property (nonatomic, strong) UIImage *groupImage; - (instancetype)initWithTitle:(NSString *)title - memberIds:(NSMutableArray *)memberIds + memberIds:(NSMutableArray *)memberIds image:(UIImage *)image groupId:(NSData *)groupId; diff --git a/src/Messages/TSGroupModel.m b/src/Messages/TSGroupModel.m index 55a42e42b..5bf0fa0d3 100644 --- a/src/Messages/TSGroupModel.m +++ b/src/Messages/TSGroupModel.m @@ -1,5 +1,6 @@ -// Created by Frederic Jacobs on 13/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "TSGroupModel.h" #import "FunctionalUtil.h" @@ -8,7 +9,7 @@ #if TARGET_OS_IOS - (instancetype)initWithTitle:(NSString *)title - memberIds:(NSMutableArray *)memberIds + memberIds:(NSMutableArray *)memberIds image:(UIImage *)image groupId:(NSData *)groupId { diff --git a/src/Protocols/TSPreferences.h b/src/Protocols/TSPreferences.h new file mode 100644 index 000000000..c21ab211e --- /dev/null +++ b/src/Protocols/TSPreferences.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@protocol TSPreferences + +- (BOOL)isSendingIdentityApprovalRequired; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Security/OWSFingerprintBuilder.h b/src/Security/OWSFingerprintBuilder.h index 537713cad..5c7ae4360 100644 --- a/src/Security/OWSFingerprintBuilder.h +++ b/src/Security/OWSFingerprintBuilder.h @@ -1,5 +1,6 @@ -// Created by Michael Kirk on 9/22/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// NS_ASSUME_NONNULL_BEGIN @@ -16,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Builds a fingerprint combining your current credentials with their most recently accepted credentials. */ -- (OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId; +- (nullable OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId; /** * Builds a fingerprint combining your current credentials with the specified identity key. diff --git a/src/Security/OWSFingerprintBuilder.m b/src/Security/OWSFingerprintBuilder.m index 197f8e625..eb004a50b 100644 --- a/src/Security/OWSFingerprintBuilder.m +++ b/src/Security/OWSFingerprintBuilder.m @@ -1,5 +1,6 @@ -// Created by Michael Kirk on 9/22/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSFingerprintBuilder.h" #import "ContactsManagerProtocol.h" @@ -33,10 +34,15 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId +- (nullable OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId { NSData *_Nullable theirIdentityKey = [self.storageManager identityKeyForRecipientId:theirSignalId]; + if (theirIdentityKey == nil) { + OWSAssert(NO); + return nil; + } + return [self fingerprintWithTheirSignalId:theirSignalId theirIdentityKey:theirIdentityKey]; } diff --git a/src/Security/OWSRecipientIdentity.h b/src/Security/OWSRecipientIdentity.h new file mode 100644 index 000000000..b34f7e879 --- /dev/null +++ b/src/Security/OWSRecipientIdentity.h @@ -0,0 +1,38 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSRecipientIdentity : TSYapDatabaseObject + +@property (nonatomic, readonly) NSString *recipientId; +@property (nonatomic, readonly) NSData *identityKey; +@property (nonatomic, readonly) NSDate *createdAt; +@property (nonatomic, readonly) BOOL wasSeen; +@property (nonatomic, readonly) BOOL isFirstKnownKey; +@property (nonatomic) BOOL approvedForBlockingUse; +@property (nonatomic) BOOL approvedForNonBlockingUse; + +- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE; + +- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithRecipientId:(NSString *)recipientId + identityKey:(NSData *)identityKey + isFirstKnownKey:(BOOL)isFirstKnownKey + createdAt:(NSDate *)createdAt + approvedForBlockingUse:(BOOL)approvedForBlockingUse + approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse NS_DESIGNATED_INITIALIZER; + +- (void)markAsSeen; + +#pragma mark - debug + ++ (void)printAllIdentities; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Security/OWSRecipientIdentity.m b/src/Security/OWSRecipientIdentity.m new file mode 100644 index 000000000..ad7d1e580 --- /dev/null +++ b/src/Security/OWSRecipientIdentity.m @@ -0,0 +1,96 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSRecipientIdentity.h" +#import "TSStorageManager.h" + +NS_ASSUME_NONNULL_BEGIN +/** + * Record for a recipients identity key and some meta data around it used to make trust decisions. + * + * NOTE: Instances of this class MUST only be retrieved/persisted via it's internal `dbConnection`, + * which makes some special accomodations to enforce consistency. + */ +@implementation OWSRecipientIdentity + +- (instancetype)initWithRecipientId:(NSString *)recipientId + identityKey:(NSData *)identityKey + isFirstKnownKey:(BOOL)isFirstKnownKey + createdAt:(NSDate *)createdAt + approvedForBlockingUse:(BOOL)approvedForBlockingUse + approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse +{ + self = [super initWithUniqueId:recipientId]; + if (!self) { + return self; + } + + _recipientId = recipientId; + _identityKey = identityKey; + _isFirstKnownKey = isFirstKnownKey; + _createdAt = createdAt; + _approvedForBlockingUse = approvedForBlockingUse; + _approvedForNonBlockingUse = approvedForNonBlockingUse; + _wasSeen = NO; + + return self; +} + +- (void)markAsSeen +{ + _wasSeen = YES; +} + +/** + * Override to disable the object cache to better enforce transaction semantics on the store. + * Note that it's still technically possible to access this collection from a different collection, + * but that should be considered a bug. + */ ++ (YapDatabaseConnection *)dbConnection +{ + static dispatch_once_t onceToken; + static YapDatabaseConnection *sharedDBConnection; + dispatch_once(&onceToken, ^{ + sharedDBConnection = [TSStorageManager sharedManager].newDatabaseConnection; + sharedDBConnection.objectCacheEnabled = NO; + sharedDBConnection.permittedTransactions = YDB_AnySyncTransaction; + }); + + return sharedDBConnection; +} + +#pragma mark - debug + ++ (void)printAllIdentities +{ + DDLogInfo(@"%@ ### All Recipient Identities ###", self.tag); + __block int count = 0; + [self enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) { + count++; + if (![obj isKindOfClass:[self class]]) { + DDLogError(@"%@ unexpected object in collection: %@", self.tag, obj); + OWSAssert(NO); + return; + } + OWSRecipientIdentity *recipientIdentity = (OWSRecipientIdentity *)obj; + + DDLogInfo(@"%@ Identity %d: %@", self.tag, count, recipientIdentity.debugDescription); + }]; +} + +#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/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.h b/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.h index 05ba2a110..07f141dd6 100644 --- a/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.h +++ b/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.h @@ -1,18 +1,29 @@ // -// TSStorageManager+IdentityKeyStore.h -// TextSecureKit -// -// Created by Frederic Jacobs on 06/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import #import "TSStorageManager.h" +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const TSStorageManagerTrustedKeysCollection; + @interface TSStorageManager (IdentityKeyStore) +/** + * Explicitly mark an identity as approved for blocking/nonblocking use + * e.g. in response to a user confirmation action. + */ +- (BOOL)saveRemoteIdentity:(NSData *)identityKey + recipientId:(NSString *)recipientId + approvedForBlockingUse:(BOOL)approvedForBlockingUse + approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse; + - (void)generateNewIdentityKey; -- (NSData *)identityKeyForRecipientId:(NSString *)recipientId; +- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId; - (void)removeIdentityKeyForRecipient:(NSString *)receipientId; @end + +NS_ASSUME_NONNULL_END diff --git a/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.m b/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.m index aa3b52ddc..7f0dd3dcb 100644 --- a/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.m +++ b/src/Storage/AxolotlStore/TSStorageManager+IdentityKeyStore.m @@ -7,19 +7,37 @@ #import "TSContactThread.h" #import "TSErrorMessage.h" #import "TSGroupThread.h" -#import "TSPrivacyPreferences.h" +#import "TSPreferences.h" #import "TSStorageManager+IdentityKeyStore.h" #import "TSStorageManager+SessionStore.h" +#import "TextSecureKitEnv.h" +#import "OWSRecipientIdentity.h" #import <25519/Curve25519.h> -#define TSStorageManagerIdentityKeyStoreIdentityKey \ - @"TSStorageManagerIdentityKeyStoreIdentityKey" // Key for our identity key -#define TSStorageManagerIdentityKeyStoreCollection @"TSStorageManagerIdentityKeyStoreCollection" -#define TSStorageManagerTrustedKeysCollection @"TSStorageManagerTrustedKeysCollection" +NS_ASSUME_NONNULL_BEGIN +// Storing our own identity key +NSString *const TSStorageManagerIdentityKeyStoreIdentityKey = @"TSStorageManagerIdentityKeyStoreIdentityKey"; +NSString *const TSStorageManagerIdentityKeyStoreCollection = @"TSStorageManagerIdentityKeyStoreCollection"; + +// Storing recipients identity keys +NSString *const TSStorageManagerTrustedKeysCollection = @"TSStorageManagerTrustedKeysCollection"; + +// Don't trust an identity for sending to unless they've been around for at least this long +const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0; @implementation TSStorageManager (IdentityKeyStore) ++ (id)sharedIdentityKeyLock +{ + static id identityKeyLock; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + identityKeyLock = [NSObject new]; + }); + return identityKeyLock; +} + - (void)generateNewIdentityKey { [self setObject:[Curve25519 generateKeyPair] forKey:TSStorageManagerIdentityKeyStoreIdentityKey @@ -27,12 +45,17 @@ } -- (NSData *)identityKeyForRecipientId:(NSString *)recipientId { - return [self dataForKey:recipientId inCollection:TSStorageManagerTrustedKeysCollection]; +- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId +{ + @synchronized([[self class] sharedIdentityKeyLock]) + { + return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId].identityKey; + } } -- (ECKeyPair *)identityKeyPair { +- (nullable ECKeyPair *)identityKeyPair +{ return [self keyPairForKey:TSStorageManagerIdentityKeyStoreIdentityKey inCollection:TSStorageManagerIdentityKeyStoreCollection]; } @@ -41,50 +64,180 @@ return (int)[TSAccountManager getOrGenerateRegistrationId]; } -- (void)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId { - NSData *existingKey = [self identityKeyForRecipientId:recipientId]; - if ([existingKey isEqual:identityKey]) { - // Since we need to clear existing sessions when identity changes, we have to exit early - // when the identity key hasn't changed, lest we blow away valid sessions. - DDLogDebug(@"%s no-op since identity hasn't changed for recipient: %@", __PRETTY_FUNCTION__, recipientId); - return; +- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId +{ + NSParameterAssert(identityKey != nil); + NSParameterAssert(recipientId != nil); + + @synchronized([[self class] sharedIdentityKeyLock]) + { + // If send-blocking is disabled at the time the identity was saved, we want to consider the identity as + // approved for blocking. Otherwise the user will see inexplicable failures when trying to send to this + // identity, if they later enabled send-blocking. + BOOL approvedForBlockingUse = ![TextSecureKitEnv sharedEnv].preferences.isSendingIdentityApprovalRequired; + return [self saveRemoteIdentity:identityKey + recipientId:recipientId + approvedForBlockingUse:approvedForBlockingUse + approvedForNonBlockingUse:NO]; } - - DDLogInfo(@"%s invalidating any pre-existing sessions for recipientId: %@", __PRETTY_FUNCTION__, recipientId); - [self deleteAllSessionsForContact:recipientId]; - - DDLogInfo(@"%s saving new identity key for recipientId: %@", __PRETTY_FUNCTION__, recipientId); - [self setObject:identityKey forKey:recipientId inCollection:TSStorageManagerTrustedKeysCollection]; } -- (BOOL)isTrustedIdentityKey:(NSData *)identityKey recipientId:(NSString *)recipientId { - NSData *existingKey = [self identityKeyForRecipientId:recipientId]; +- (BOOL)saveRemoteIdentity:(NSData *)identityKey + recipientId:(NSString *)recipientId + approvedForBlockingUse:(BOOL)approvedForBlockingUse + approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse +{ + NSParameterAssert(identityKey != nil); + NSParameterAssert(recipientId != nil); + + NSString const *logTag = @"[IdentityKeyStore]"; + @synchronized ([[self class] sharedIdentityKeyLock]) { + OWSRecipientIdentity *existingIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + + if (existingIdentity == nil) { + DDLogInfo(@"%@ saving first use identity for recipient: %@", logTag, recipientId); + [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId + identityKey:identityKey + isFirstKnownKey:YES + createdAt:[NSDate new] + approvedForBlockingUse:approvedForBlockingUse + approvedForNonBlockingUse:approvedForNonBlockingUse] save]; + return NO; + } + + if (![existingIdentity.identityKey isEqual:identityKey]) { + DDLogInfo(@"%@ replacing identity for existing recipient: %@", logTag, recipientId); + [self createIdentityChangeInfoMessageForRecipientId:recipientId]; + [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId + identityKey:identityKey + isFirstKnownKey:NO + createdAt:[NSDate new] + approvedForBlockingUse:approvedForBlockingUse + approvedForNonBlockingUse:approvedForNonBlockingUse] save]; + + return YES; + } + + if ([self isBlockingApprovalRequiredForIdentity:existingIdentity] || [self isNonBlockingApprovalRequiredForIdentity:existingIdentity]) { + existingIdentity.approvedForBlockingUse = approvedForBlockingUse; + existingIdentity.approvedForNonBlockingUse = approvedForNonBlockingUse; + [existingIdentity save]; + return NO; + } + + DDLogDebug(@"%@ no changes for identity saved for recipient: %@", logTag, recipientId); + return NO; + } +} - if (!existingKey) { - return YES; +- (BOOL)isTrustedIdentityKey:(NSData *)identityKey + recipientId:(NSString *)recipientId + direction:(TSMessageDirection)direction +{ + NSParameterAssert(identityKey != nil); + NSParameterAssert(recipientId != nil); + NSParameterAssert(direction != TSMessageDirectionUnknown); + + @synchronized([[self class] sharedIdentityKeyLock]) + { + if ([[[self class] localNumber] isEqualToString:recipientId]) { + if ([[self identityKeyPair].publicKey isEqualToData:identityKey]) { + return YES; + } else { + DDLogError(@"%s Wrong identity: %@ for local key: %@", + __PRETTY_FUNCTION__, + identityKey, + [self identityKeyPair].publicKey); + OWSAssert(NO); + return NO; + } + } + + switch (direction) { + case TSMessageDirectionIncoming: { + return YES; + } + case TSMessageDirectionOutgoing: { + OWSRecipientIdentity *existingIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + return [self isTrustedKey:identityKey forSendingToIdentity:existingIdentity]; + } + default: { + DDLogError(@"%s unexpected message direction: %ld", __PRETTY_FUNCTION__, (long)direction); + OWSAssert(NO); + return NO; + } + } } +} - if ([existingKey isEqualToData:identityKey]) { +- (BOOL)isTrustedKey:(NSData *)identityKey forSendingToIdentity:(nullable OWSRecipientIdentity *)recipientIdentity +{ + NSParameterAssert(identityKey != nil); + + @synchronized([[self class] sharedIdentityKeyLock]) + { + if (recipientIdentity == nil) { + DDLogDebug(@"%s Trusting on first use for recipient: %@", __PRETTY_FUNCTION__, recipientIdentity.recipientId); + return YES; + } + + OWSAssert(recipientIdentity.identityKey != nil); + if (![recipientIdentity.identityKey isEqualToData:identityKey]) { + DDLogWarn(@"%s key mismatch for recipient: %@", __PRETTY_FUNCTION__, recipientIdentity.recipientId); + return NO; + } + + if ([self isBlockingApprovalRequiredForIdentity:recipientIdentity]) { + DDLogWarn(@"%s not trusting until blocking approval is granted. recipient: %@", + __PRETTY_FUNCTION__, + recipientIdentity.recipientId); + return NO; + } + + if ([self isNonBlockingApprovalRequiredForIdentity:recipientIdentity]) { + DDLogWarn(@"%s not trusting until non-blocking approval is granted. recipient: %@", + __PRETTY_FUNCTION__, + recipientIdentity.recipientId); + return NO; + } + return YES; } +} - if (self.privacyPreferences.shouldBlockOnIdentityChange) { - return NO; - } +- (BOOL)isBlockingApprovalRequiredForIdentity:(OWSRecipientIdentity *)recipientIdentity +{ + NSParameterAssert(recipientIdentity != nil); + OWSAssert([TextSecureKitEnv sharedEnv].preferences != nil); + + return !recipientIdentity.isFirstKnownKey && + [TextSecureKitEnv sharedEnv].preferences.isSendingIdentityApprovalRequired && + !recipientIdentity.approvedForBlockingUse; +} + +- (BOOL)isNonBlockingApprovalRequiredForIdentity:(OWSRecipientIdentity *)recipientIdentity +{ + NSParameterAssert(recipientIdentity != nil); - DDLogInfo(@"Updating identity key for recipient:%@", recipientId); - [self createIdentityChangeInfoMessageForRecipientId:recipientId]; - [self saveRemoteIdentity:identityKey recipientId:recipientId]; - return YES; + return !recipientIdentity.isFirstKnownKey && + [[NSDate new] timeIntervalSinceDate:recipientIdentity.createdAt] < kIdentityKeyStoreNonBlockingSecondsThreshold && + !recipientIdentity.approvedForNonBlockingUse; } -- (void)removeIdentityKeyForRecipient:(NSString *)receipientId { - [self removeObjectForKey:receipientId inCollection:TSStorageManagerTrustedKeysCollection]; +- (void)removeIdentityKeyForRecipient:(NSString *)recipientId +{ + NSParameterAssert(recipientId != nil); + + [[OWSRecipientIdentity fetchObjectWithUniqueID:recipientId] remove]; } - (void)createIdentityChangeInfoMessageForRecipientId:(NSString *)recipientId { + NSParameterAssert(recipientId != nil); + TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId]; + OWSAssert(contactThread != nil); + [[TSErrorMessage nonblockingIdentityChangeInThread:contactThread recipientId:recipientId] save]; for (TSGroupThread *groupThread in [TSGroupThread groupThreadsWithRecipientId:recipientId]) { @@ -93,3 +246,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/src/Storage/TSStorageManager.h b/src/Storage/TSStorageManager.h index 3616cd43b..1771eaeda 100644 --- a/src/Storage/TSStorageManager.h +++ b/src/Storage/TSStorageManager.h @@ -10,7 +10,6 @@ @class ECKeyPair; @class PreKeyRecord; @class SignedPreKeyRecord; -@class TSPrivacyPreferences; NS_ASSUME_NONNULL_BEGIN @@ -56,7 +55,6 @@ extern NSString *const TSUIDatabaseConnectionDidUpdateNotification; - (void)purgeCollection:(NSString *)collection; @property (nullable, nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) TSPrivacyPreferences *privacyPreferences; @end diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index 3393ae393..fd4ba4a66 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -15,7 +15,6 @@ #import "TSDatabaseSecondaryIndexes.h" #import "TSDatabaseView.h" #import "TSInteraction.h" -#import "TSPrivacyPreferences.h" #import "TSThread.h" #import <25519/Randomness.h> #import @@ -247,11 +246,6 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; return self.database.newConnection; } -- (TSPrivacyPreferences *)privacyPreferences -{ - return [TSPrivacyPreferences sharedInstance]; -} - - (BOOL)userSetPassword { return FALSE; } diff --git a/src/Storage/TSYapDatabaseObject.h b/src/Storage/TSYapDatabaseObject.h index c20cf3b51..f13e47ef7 100644 --- a/src/Storage/TSYapDatabaseObject.h +++ b/src/Storage/TSYapDatabaseObject.h @@ -1,5 +1,6 @@ -// Created by Frederic Jacobs on 16/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import @@ -73,8 +74,8 @@ * * @return Instance of the object or nil if non-existent */ -+ (instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID transaction:(YapDatabaseReadTransaction *)transaction; -+ (instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID; ++ (instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID transaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(fetch(uniqueId:transaction:)); ++ (instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID NS_SWIFT_NAME(fetch(uniqueId:)); /** * Saves the object with a new YapDatabaseConnection diff --git a/src/TextSecureKitEnv.h b/src/TextSecureKitEnv.h index 424f6daed..80ddefc1b 100644 --- a/src/TextSecureKitEnv.h +++ b/src/TextSecureKitEnv.h @@ -8,13 +8,15 @@ NS_ASSUME_NONNULL_BEGIN @class OWSMessageSender; @protocol NotificationsProtocol; @protocol OWSCallMessageHandler; +@protocol TSPreferences; @interface TextSecureKitEnv : NSObject - (instancetype)initWithCallMessageHandler:(id)callMessageHandler contactsManager:(id)contactsManager messageSender:(OWSMessageSender *)messageSender - notificationsManager:(id)notificationsManager NS_DESIGNATED_INITIALIZER; + notificationsManager:(id)notificationsManager + preferences:(id)preferences NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @@ -25,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) id contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) id notificationsManager; +@property (nonatomic, readonly) id preferences; @end diff --git a/src/TextSecureKitEnv.m b/src/TextSecureKitEnv.m index cf9c9e8b7..b62757eaa 100644 --- a/src/TextSecureKitEnv.m +++ b/src/TextSecureKitEnv.m @@ -13,12 +13,14 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; @synthesize callMessageHandler = _callMessageHandler, contactsManager = _contactsManager, messageSender = _messageSender, - notificationsManager = _notificationsManager; + notificationsManager = _notificationsManager, + preferences = _preferences; - (instancetype)initWithCallMessageHandler:(id)callMessageHandler contactsManager:(id)contactsManager messageSender:(OWSMessageSender *)messageSender notificationsManager:(id)notificationsManager + preferences:(nonnull id)preferences { self = [super init]; if (!self) { @@ -29,6 +31,7 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; _contactsManager = contactsManager; _messageSender = messageSender; _notificationsManager = notificationsManager; + _preferences = preferences; return self; } @@ -73,6 +76,12 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance; return _notificationsManager; } +- (id)preferences +{ + NSAssert(_preferences, @"Trying to access preferences before it's set."); + return _preferences; +} + @end NS_ASSUME_NONNULL_END