mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			105 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			105 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Swift
		
	
| import Foundation
 | |
| 
 | |
| @objc(SNSessionCipher)
 | |
| public final class LokiSessionCipher : SessionCipher {
 | |
|     private let sessionResetImplementation: SessionRestorationProtocol?
 | |
|     private let sessionStore: SessionStore
 | |
|     private let preKeyStore: PreKeyStore
 | |
|     private let recipientID: String
 | |
|     private let deviceID: Int32
 | |
|     
 | |
|     @objc public static let newSessionAdoptedNotification = "LKNewSessionAdoptedNotification"
 | |
|     @objc public static let contactKey = "LKContactKey"
 | |
|     
 | |
|     @objc public init(sessionResetImplementation: SessionRestorationProtocol, sessionStore: SessionStore, preKeyStore: PreKeyStore, signedPreKeyStore: SignedPreKeyStore, identityKeyStore: IdentityKeyStore, recipientID: String, deviceID: Int32) {
 | |
|         self.sessionResetImplementation = sessionResetImplementation
 | |
|         self.sessionStore = sessionStore
 | |
|         self.preKeyStore = preKeyStore
 | |
|         self.recipientID = recipientID
 | |
|         self.deviceID = deviceID
 | |
|         super.init(sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityKeyStore: identityKeyStore, recipientId: recipientID, deviceId: deviceID)
 | |
|     }
 | |
|     
 | |
|     @available(*, unavailable)
 | |
|     override convenience private init(axolotlStore sessionStore: AxolotlStore, recipientId: String, deviceId: Int32) {
 | |
|         self.init(sessionStore: sessionStore, preKeyStore: sessionStore, signedPreKeyStore: sessionStore, identityKeyStore: sessionStore, recipientId: recipientId, deviceId: deviceId)
 | |
|     }
 | |
|     
 | |
|     override private init(sessionStore: SessionStore, preKeyStore: PreKeyStore, signedPreKeyStore: SignedPreKeyStore, identityKeyStore: IdentityKeyStore, recipientId: String, deviceId: Int32) {
 | |
|         self.sessionResetImplementation = nil
 | |
|         self.sessionStore = sessionStore
 | |
|         self.preKeyStore = preKeyStore
 | |
|         self.recipientID = recipientId
 | |
|         self.deviceID = deviceId
 | |
|         super.init(sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityKeyStore: identityKeyStore, recipientId: recipientId, deviceId: deviceId)
 | |
|     }
 | |
|     
 | |
|     override public func decrypt(_ whisperMessage: CipherMessage, protocolContext: Any?) throws -> Data {
 | |
|         // Note that while decrypting our state may change internally
 | |
|         let currentState = getCurrentState(protocolContext: protocolContext)
 | |
|         if (currentState == nil && whisperMessage.cipherMessageType == .prekey) {
 | |
|             try sessionResetImplementation?.validatePreKeyWhisperMessage(for: recipientID, preKeyWhisperMessage: whisperMessage as! PreKeyWhisperMessage, using: protocolContext!)
 | |
|         }
 | |
|         let plainText = try super.decrypt(whisperMessage, protocolContext: protocolContext)
 | |
|         handleSessionReset(for: whisperMessage, previousState: currentState, protocolContext: protocolContext!)
 | |
|         return plainText
 | |
|     }
 | |
|     
 | |
|     private func getCurrentState(protocolContext: Any?) -> SessionState? {
 | |
|         let record = sessionStore.loadSession(recipientID, deviceId: deviceID, protocolContext: protocolContext)
 | |
|         return record.isFresh() ? nil : record.sessionState()
 | |
|     }
 | |
|     
 | |
|     private func handleSessionReset(for whisperMessage: CipherMessage, previousState: SessionState?, protocolContext: Any) {
 | |
|         // Don't bother doing anything if we didn't have a session before
 | |
|         guard let previousState = previousState else { return }
 | |
|         let sessionResetStatus = sessionResetImplementation?.getSessionRestorationStatus(for: recipientID) ?? SessionRestorationStatus.none
 | |
|         // Bail early if no session reset is in progress
 | |
|         guard sessionResetStatus != .none else { return }
 | |
|         let currentState = getCurrentState(protocolContext: protocolContext)
 | |
|         // Check if our previous state and our current state differ
 | |
|         if (currentState == nil || currentState!.aliceBaseKey != previousState.aliceBaseKey) {
 | |
|             if sessionResetStatus == .requestReceived {
 | |
|                 // The other user used an old session to contact us. Wait for them to use a new one
 | |
|                 restoreSession(previousState, protocolContext: protocolContext)
 | |
|             } else {
 | |
|                 // Our session reset went through successfully.
 | |
|                 // We initiated a session reset and got a different session back from the user.
 | |
|                 deleteAllSessions(except: currentState, protocolContext: protocolContext)
 | |
|                 notifySessionAdopted(protocolContext)
 | |
|             }
 | |
|         } else if sessionResetStatus == .requestReceived {
 | |
|             // Our session reset went through successfully.
 | |
|             // We got a message with the same session from the other user.
 | |
|             deleteAllSessions(except: previousState, protocolContext: protocolContext)
 | |
|             notifySessionAdopted(protocolContext)
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private func notifySessionAdopted(_ protocolContext: Any) {
 | |
|         self.sessionResetImplementation?.handleNewSessionAdopted(for: recipientID, using: protocolContext)
 | |
|         NotificationCenter.default.post(name: NSNotification.Name(rawValue: LokiSessionCipher.newSessionAdoptedNotification), object: nil, userInfo: [ LokiSessionCipher.contactKey : recipientID ])
 | |
|     }
 | |
|     
 | |
|     private func deleteAllSessions(except state: SessionState?, protocolContext: Any?) {
 | |
|         let record = sessionStore.loadSession(recipientID, deviceId: deviceID, protocolContext: protocolContext)
 | |
|         record.removePreviousSessionStates()
 | |
|         let newState = state ?? SessionState()
 | |
|         record.setState(newState)
 | |
|         sessionStore.storeSession(recipientID, deviceId: deviceID, session: record, protocolContext: protocolContext)
 | |
|     }
 | |
|     
 | |
|     private func restoreSession(_ state: SessionState, protocolContext: Any?) {
 | |
|         let record = sessionStore.loadSession(recipientID, deviceId: deviceID, protocolContext: protocolContext)
 | |
|         // Remove the state from previous session states
 | |
|         record.previousSessionStates()?.enumerateObjects(options: .reverse) { obj, index, stop in
 | |
|             guard let obj = obj as? SessionState, state.aliceBaseKey == obj.aliceBaseKey else { return }
 | |
|             record.previousSessionStates()?.removeObject(at: index)
 | |
|             stop.pointee = true
 | |
|         }
 | |
|         // Promote so the previous state gets archived
 | |
|         record.promoteState(state)
 | |
|         sessionStore.storeSession(recipientID, deviceId: deviceID, session: record, protocolContext: protocolContext)
 | |
|     }
 | |
| }
 |