diff --git a/protobuf/OWSSignalServiceProtos.proto b/protobuf/OWSSignalServiceProtos.proto index 9a52b058d..497d1ecfd 100644 --- a/protobuf/OWSSignalServiceProtos.proto +++ b/protobuf/OWSSignalServiceProtos.proto @@ -134,7 +134,6 @@ message SyncMessage { optional State state = 1; // The e164 phone number of the user. optional string destination = 2; - // identityKey should be set IFF state is VERIFIED. optional bytes identityKey = 3; } diff --git a/src/Devices/OWSVerificationStateSyncMessage.h b/src/Devices/OWSVerificationStateSyncMessage.h index c140fe046..232b89770 100644 --- a/src/Devices/OWSVerificationStateSyncMessage.h +++ b/src/Devices/OWSVerificationStateSyncMessage.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN // identityKey should be set IFF verificationState == OWSVerificationStateVerified; - (void)addVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData * _Nullable)identityKey + identityKey:(NSData *)identityKey recipientId:(NSString *)recipientId; // Returns the list of recipient ids referenced in this message. diff --git a/src/Devices/OWSVerificationStateSyncMessage.m b/src/Devices/OWSVerificationStateSyncMessage.m index 1d9488180..f55894e27 100644 --- a/src/Devices/OWSVerificationStateSyncMessage.m +++ b/src/Devices/OWSVerificationStateSyncMessage.m @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSVerificationStateTuple : NSObject @property (nonatomic) OWSVerificationState verificationState; -@property (nonatomic, nullable) NSData *identityKey; +@property (nonatomic) NSData *identityKey; @property (nonatomic) NSString *recipientId; @end @@ -46,14 +46,12 @@ NS_ASSUME_NONNULL_BEGIN } - (void)addVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData * _Nullable)identityKey + identityKey:(NSData *)identityKey recipientId:(NSString *)recipientId { + OWSAssert(identityKey.length > 0); OWSAssert(recipientId.length > 0); OWSAssert(self.tuples); - - OWSAssert((identityKey.length > 0) == - verificationState == OWSVerificationStateVerified); OWSVerificationStateTuple *tuple = [OWSVerificationStateTuple new]; tuple.verificationState = verificationState; @@ -70,18 +68,15 @@ NS_ASSUME_NONNULL_BEGIN for (OWSVerificationStateTuple *tuple in self.tuples) { OWSSignalServiceProtosSyncMessageVerificationBuilder *verificationBuilder = [OWSSignalServiceProtosSyncMessageVerificationBuilder new]; [verificationBuilder setDestination:tuple.recipientId]; + [verificationBuilder setIdentityKey:tuple.identityKey]; switch (tuple.verificationState) { case OWSVerificationStateDefault: - OWSAssert(!tuple.identityKey); [verificationBuilder setState:OWSSignalServiceProtosSyncMessageVerificationStateDefault]; break; case OWSVerificationStateVerified: - OWSAssert(tuple.identityKey.length > 0); [verificationBuilder setState:OWSSignalServiceProtosSyncMessageVerificationStateVerified]; - [verificationBuilder setIdentityKey:tuple.identityKey]; break; case OWSVerificationStateNoLongerVerified: - OWSAssert(!tuple.identityKey); [verificationBuilder setState:OWSSignalServiceProtosSyncMessageVerificationStateNoLongerVerified]; break; } diff --git a/src/Messages/OWSIdentityManager.h b/src/Messages/OWSIdentityManager.h index 373907383..5ea71becb 100644 --- a/src/Messages/OWSIdentityManager.h +++ b/src/Messages/OWSIdentityManager.h @@ -14,6 +14,7 @@ extern NSString *const TSStorageManagerTrustedKeysCollection; extern NSString *const kNSNotificationName_IdentityStateDidChange; @class OWSRecipientIdentity; +@class OWSSignalServiceProtosSyncMessageVerification; // This class can be safely accessed and used from any thread. @interface OWSIdentityManager : NSObject @@ -43,6 +44,8 @@ extern NSString *const kNSNotificationName_IdentityStateDidChange; // Will try to send a sync message with all verification states. - (void)syncAllVerificationStates; +- (void)processIncomingSyncMessage:(NSArray *)verifications; + @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSIdentityManager.m b/src/Messages/OWSIdentityManager.m index 212a6f94f..df8c0329f 100644 --- a/src/Messages/OWSIdentityManager.m +++ b/src/Messages/OWSIdentityManager.m @@ -214,24 +214,24 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa // it for the first time. [self saveRemoteIdentity:identityKey recipientId:recipientId]; - OWSRecipientIdentity *identity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + OWSRecipientIdentity *recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; - if (identity == nil) { + if (recipientIdentity == nil) { OWSFail(@"Missing expected identity: %@", recipientId); return; } - if (identity.verificationState == verificationState) { + if (recipientIdentity.verificationState == verificationState) { return; } DDLogInfo(@"%@ setVerificationState: %@ (%@ -> %@)", self.tag, recipientId, - OWSVerificationStateToString(identity.verificationState), + OWSVerificationStateToString(recipientIdentity.verificationState), OWSVerificationStateToString(verificationState)); - [identity updateWithVerificationState:verificationState]; + [recipientIdentity updateWithVerificationState:verificationState]; if (sendSyncMessage) { [self enqueueSyncMessageForVerificationState:verificationState @@ -440,6 +440,10 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa OWSFail(@"Could not load recipient identity for recipientId: %@", recipientId); continue; } + if (recipientIdentity.recipientId.length < 1 || recipientIdentity.identityKey.length < 1) { + OWSFail(@"Invalid recipient identity for recipientId: %@", recipientId); + continue; + } if (recipientIdentity.verificationState == OWSVerificationStateNoLongerVerified) { // We don't want to sync "no longer verified" state. Other clients can // figure this out from the /profile/ endpoint, and this can cause data @@ -448,9 +452,7 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa continue; } [message addVerificationState:recipientIdentity.verificationState - identityKey:(recipientIdentity.verificationState == OWSVerificationStateVerified - ? recipientIdentity.identityKey - : nil) + identityKey:recipientIdentity.identityKey recipientId:recipientId]; } if (message.recipientIds.count > 0) { @@ -476,10 +478,12 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa // Don't bother syncing default state. return; } + if (recipientIdentity.recipientId.length < 1 || recipientIdentity.identityKey.length < 1) { + OWSFail(@"Invalid recipient identity for recipientId: %@", recipientIdentity.recipientId); + return; + } [message addVerificationState:recipientIdentity.verificationState - identityKey:(recipientIdentity.verificationState == OWSVerificationStateVerified - ? recipientIdentity.identityKey - : nil) + identityKey:recipientIdentity.identityKey recipientId:recipientIdentity.recipientId]; }]; if (message.recipientIds.count > 0) { @@ -528,6 +532,147 @@ NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationNa }); } +- (void)processIncomingSyncMessage:(NSArray *)verifications +{ + for (OWSSignalServiceProtosSyncMessageVerification *verification in verifications) { + NSString *recipientId = [verification destination]; + if (recipientId.length < 1) { + OWSFail(@"Verification state sync message missing recipientId."); + continue; + } + NSData *identityKey = [verification identityKey]; + if (identityKey.length < 1) { + OWSFail(@"Verification state sync message missing identityKey."); + continue; + } + switch (verification.state) { + case OWSSignalServiceProtosSyncMessageVerificationStateDefault: + [self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateDefault + recipientId:recipientId + identityKey:identityKey + overwriteOnConflict:NO]; + break; + case OWSSignalServiceProtosSyncMessageVerificationStateVerified: + [self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateVerified + recipientId:recipientId + identityKey:identityKey + overwriteOnConflict:YES]; + break; + case OWSSignalServiceProtosSyncMessageVerificationStateNoLongerVerified: + OWSFail(@"Verification state sync message for recipientId: %@ has unexpected value: %@.", + recipientId, + OWSVerificationStateToString(OWSVerificationStateNoLongerVerified)); + continue; + } + } +} + +- (void)tryToApplyVerificationStateFromSyncMessage:(OWSVerificationState)verificationState + recipientId:(NSString *)recipientId + identityKey:(NSData *)identityKey + overwriteOnConflict:(BOOL)overwriteOnConflict +{ + if (recipientId.length < 1) { + OWSFail(@"Verification state sync message missing recipientId."); + return; + } + if (identityKey.length < 1) { + OWSFail(@"Verification state sync message missing identityKey."); + return; + } + + @synchronized(self) + { + OWSRecipientIdentity *_Nullable recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + if (!recipientIdentity) { + // There's no existing recipient identity for this recipient. + // We should probably create one. + + if (verificationState == OWSVerificationStateDefault) { + // There's no point in creating a new recipient identity just to + // set its verification state to default. + return; + } + + // Ensure a remote identity exists for this key. We may be learning about + // it for the first time. + [self saveRemoteIdentity:identityKey recipientId:recipientId]; + + recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + + if (recipientIdentity == nil) { + OWSFail(@"Missing expected identity: %@", recipientId); + return; + } + + if (![recipientIdentity.recipientId isEqualToString:recipientId]) { + OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId); + return; + } + + if (![recipientIdentity.identityKey isEqualToData:identityKey]) { + OWSFail(@"recipientIdentity has unexpected identityKey"); + return; + } + + if (recipientIdentity.verificationState == verificationState) { + return; + } + + DDLogInfo(@"%@ setVerificationState: %@ (%@ -> %@)", + self.tag, + recipientId, + OWSVerificationStateToString(recipientIdentity.verificationState), + OWSVerificationStateToString(verificationState)); + + [recipientIdentity updateWithVerificationState:verificationState]; + } else { + // There's an existing recipient identity for this recipient. + // We should update it. + if (![recipientIdentity.recipientId isEqualToString:recipientId]) { + OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId); + return; + } + + if (![recipientIdentity.identityKey isEqualToData:identityKey]) { + // The conflict case where we receive a verification sync message + // whose identity key disagrees with the local identity key for + // this recipient. + if (!overwriteOnConflict) { + DDLogWarn(@"recipientIdentity has non-matching identityKey"); + return; + } + + DDLogWarn(@"recipientIdentity has non-matching identityKey; overwriting."); + [self saveRemoteIdentity:identityKey recipientId:recipientId]; + + recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; + + if (recipientIdentity == nil) { + OWSFail(@"Missing expected identity: %@", recipientId); + return; + } + + if (![recipientIdentity.recipientId isEqualToString:recipientId]) { + OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId); + return; + } + + if (![recipientIdentity.identityKey isEqualToData:identityKey]) { + OWSFail(@"recipientIdentity has unexpected identityKey"); + return; + } + } + + if (recipientIdentity.verificationState == verificationState) { + return; + } + + [recipientIdentity updateWithVerificationState:OWSVerificationStateDefault]; + } + } +} + #pragma mark - Notifications - (void)applicationDidBecomeActive:(NSNotification *)notification diff --git a/src/Messages/TSMessagesManager.m b/src/Messages/TSMessagesManager.m index 2cc8dbeed..52afb1bda 100644 --- a/src/Messages/TSMessagesManager.m +++ b/src/Messages/TSMessagesManager.m @@ -695,6 +695,10 @@ NS_ASSUME_NONNULL_BEGIN [[OWSReadReceiptsProcessor alloc] initWithReadReceiptProtos:syncMessage.read storageManager:self.storageManager]; [readReceiptsProcessor process]; + } else if (syncMessage.verification.count > 0) { + DDLogInfo(@"%@ Received %ld verification state(s)", self.tag, (u_long)syncMessage.verification.count); + + [[OWSIdentityManager sharedManager] processIncomingSyncMessage:syncMessage.verification]; } else { DDLogWarn(@"%@ Ignoring unsupported sync message.", self.tag); }