//// Loki: Refer to Docs/SessionReset.md for explanations // //#import "SessionCipher+Loki.h" //#import "NSNotificationCenter+OWS.h" //#import "PreKeyWhisperMessage.h" //#import "OWSPrimaryStorage+Loki.h" //#import "TSContactThread.h" //#import // //NSString *const kNSNotificationName_SessionAdopted = @"kNSNotificationName_SessionAdopted"; //NSString *const kNSNotificationKey_ContactPubKey = @"kNSNotificationKey_ContactPubKey"; // //@interface SessionCipher () // //@property (nonatomic, readonly) NSString *recipientId; //@property (nonatomic, readonly) int deviceId; // //@property (nonatomic, readonly) id sessionStore; //@property (nonatomic, readonly) id prekeyStore; // //@end // //@implementation SessionCipher (Loki) // //- (NSData *)throws_lokiDecrypt:(id)whisperMessage protocolContext:(nullable id)protocolContext //{ // // Our state before we decrypt the message // SessionState *_Nullable state = [self getCurrentState:protocolContext]; // // // Verify incoming friend request messages // if (!state) { // [self throws_validatePreKeysForFriendRequestAcceptance:whisperMessage protocolContext:protocolContext]; // } // // // While decrypting our state may change internally // NSData *plainText = [self throws_decrypt:whisperMessage protocolContext:protocolContext]; // // // // Loki: Handle any session resets // [self handleSessionReset:whisperMessage previousState:state protocolContext:protocolContext]; // // return plainText; //} // ///// Get the current session state //- (SessionState *_Nullable)getCurrentState:(nullable id)protocolContext { // SessionRecord *record = [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; // SessionState *state = record.sessionState; // // // Check if session is initialized // if (!state.hasSenderChain) { return nil; } // // return state; //} // ///// Handle any Loki session reset stuff //- (void)handleSessionReset:(id)whisperMessage previousState:(SessionState *_Nullable)previousState protocolContext:(nullable id)protocolContext //{ // // Don't bother doing anything if we didn't have a session before // if (!previousState) { return; } // // OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); // YapDatabaseReadWriteTransaction *transaction = protocolContext; // // // Get the thread // TSContactThread *thread = [TSContactThread getThreadWithContactId:self.recipientId transaction:transaction]; // if (!thread) { return; } // // // Bail early if no session reset is in progress // if (thread.sessionResetState == TSContactThreadSessionResetStateNone) { return; } // // BOOL sessionResetReceived = thread.sessionResetState == TSContactThreadSessionResetStateRequestReceived; // SessionState *_Nullable currentState = [self getCurrentState:protocolContext]; // // // Check if our previous state and our current state differ // if (!currentState || ![currentState.aliceBaseKey isEqualToData:previousState.aliceBaseKey]) { // // if (sessionResetReceived) { // // The other user used an old session to contact us. // // Wait for them to use a new one // [self restoreSession:previousState protocolContext:protocolContext]; // } else { // // Our session reset went through successfully // // We had initiated a session reset and got a different session back from the user // [self deleteAllSessionsExcept:currentState protocolContext:protocolContext]; // [self notifySessionAdopted]; // } // // } else if (sessionResetReceived) { // // Our session reset went through successfully // // We got a message with the same session from the other user // [self deleteAllSessionsExcept:previousState protocolContext:protocolContext]; // [self notifySessionAdopted]; // } //} // ///// Send a notification about a new session being adopted //- (void)notifySessionAdopted //{ // NSDictionary *userInfo = @{ kNSNotificationKey_ContactPubKey : self.recipientId }; // [NSNotificationCenter.defaultCenter postNotificationNameAsync:kNSNotificationName_SessionAdopted object:nil userInfo:userInfo]; //} // ///// Delete all other sessions except the given one //- (void)deleteAllSessionsExcept:(SessionState *)state protocolContext:(nullable id)protocolContext //{ // SessionRecord *record = [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; // [record removePreviousSessionStates]; // // SessionState *newState = state == nil ? [SessionState new] : state; // [record setState:newState]; // // [self.sessionStore storeSession:self.recipientId deviceId:self.deviceId session:record protocolContext:protocolContext]; //} // ///// Set the given session as the active one while archiving the old one //- (void)restoreSession:(SessionState *)state protocolContext:(nullable id)protocolContext //{ // SessionRecord *record = [self.sessionStore loadSession:self.recipientId deviceId:self.deviceId protocolContext:protocolContext]; // // // Remove the state from previous session states // [record.previousSessionStates enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(SessionState *obj, NSUInteger idx, BOOL *stop) { // if ([state.aliceBaseKey isEqualToData:obj.aliceBaseKey]) { // [record.previousSessionStates removeObjectAtIndex:idx]; // *stop = YES; // } // }]; // // // Promote it so the previous state gets archived // [record promoteState:state]; // // [self.sessionStore storeSession:self.recipientId deviceId:self.deviceId session:record protocolContext:protocolContext]; //} // ///// Check that we have matching pre keys in the case of a `PreKeyWhisperMessage`. ///// This is so that we don't trigger a false friend request accept on unknown contacts. //- (void)throws_validatePreKeysForFriendRequestAcceptance:(id)whisperMessage protocolContext:(nullable id)protocolContext { // OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadTransaction class]]); // YapDatabaseReadTransaction *transaction = protocolContext; // // // Ignore anything that isn't a `PreKeyWhisperMessage` // if (![whisperMessage isKindOfClass:[PreKeyWhisperMessage class]]) { return; } // // // Check the pre key store // if (![self.prekeyStore isKindOfClass:[OWSPrimaryStorage class]]) { return; } // // PreKeyWhisperMessage *preKeyMessage = whisperMessage; // OWSPrimaryStorage *primaryStorage = self.prekeyStore; // // PreKeyRecord *_Nullable storedPreKey = [primaryStorage getPreKeyForContact:self.recipientId transaction:transaction]; // if (!storedPreKey) { // OWSRaiseException(@"Loki", @"Received a friend request from a public key for which no pre key bundle was created."); // } // // if (storedPreKey.Id != preKeyMessage.prekeyID) { // OWSRaiseException(@"Loki", @"Received a PreKeyWhisperMessage (friend request accept) from an unknown source."); // } //} // //@end