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)
|
|
}
|
|
}
|