From 7fb63c19ddf39fd1a4ff6e10ef830e40a7873217 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Mon, 10 Jun 2024 14:55:29 +0200 Subject: [PATCH] ZRTP Changes --- Linphone/UI/Call/CallView.swift | 12 -- Linphone/UI/Call/Fragments/ZRTPPopup.swift | 45 ++--- .../UI/Call/ViewModel/CallViewModel.swift | 177 +++++++++++------- 3 files changed, 124 insertions(+), 110 deletions(-) diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 82cdad0fe..2d74a788b 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -210,18 +210,6 @@ struct CallView: View { ZStack { VStack { if !fullscreenVideo || (fullscreenVideo && telecomManager.isPausedByRemote) { - if #available(iOS 16.0, *) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - } else if idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 1) - } ZStack { HStack { Button { diff --git a/Linphone/UI/Call/Fragments/ZRTPPopup.swift b/Linphone/UI/Call/Fragments/ZRTPPopup.swift index 8e1a1b448..332803d22 100644 --- a/Linphone/UI/Call/Fragments/ZRTPPopup.swift +++ b/Linphone/UI/Call/Fragments/ZRTPPopup.swift @@ -27,11 +27,6 @@ struct ZRTPPopup: View { @ObservedObject var callViewModel: CallViewModel - @State private var letters1: String = "AA" - @State private var letters2: String = "BB" - @State private var letters3: String = "CC" - @State private var letters4: String = "DD" - var body: some View { GeometryReader { geometry in VStack(alignment: .leading) { @@ -46,7 +41,7 @@ struct ZRTPPopup: View { Spacer() HStack(alignment: .center) { - Text(letters1) + Text(callViewModel.letters1) .default_text_style(styleSize: 30) .frame(width: 60, height: 60) } @@ -54,12 +49,12 @@ struct ZRTPPopup: View { .background(Color.grayMain2c200) .cornerRadius(40) .onTapGesture { - callViewModel.lettersClicked(letters: letters1) + callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1) callViewModel.zrtpPopupDisplayed = false } HStack(alignment: .center) { - Text(letters2) + Text(callViewModel.letters2) .default_text_style(styleSize: 30) .frame(width: 60, height: 60) } @@ -67,7 +62,7 @@ struct ZRTPPopup: View { .background(Color.grayMain2c200) .cornerRadius(40) .onTapGesture { - callViewModel.lettersClicked(letters: letters2) + callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2) callViewModel.zrtpPopupDisplayed = false } @@ -79,7 +74,7 @@ struct ZRTPPopup: View { Spacer() HStack(alignment: .center) { - Text(letters3) + Text(callViewModel.letters3) .default_text_style(styleSize: 30) .frame(width: 60, height: 60) } @@ -87,12 +82,12 @@ struct ZRTPPopup: View { .background(Color.grayMain2c200) .cornerRadius(40) .onTapGesture { - callViewModel.lettersClicked(letters: letters3) + callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3) callViewModel.zrtpPopupDisplayed = false } HStack(alignment: .center) { - Text(letters4) + Text(callViewModel.letters4) .default_text_style(styleSize: 30) .frame(width: 60, height: 60) } @@ -100,7 +95,7 @@ struct ZRTPPopup: View { .background(Color.grayMain2c200) .cornerRadius(40) .onTapGesture { - callViewModel.lettersClicked(letters: letters4) + callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4) callViewModel.zrtpPopupDisplayed = false } @@ -118,10 +113,12 @@ struct ZRTPPopup: View { .frame(maxWidth: .infinity) .padding(.bottom, 30) .onTapGesture { + callViewModel.skipZrtpAuthentication() callViewModel.zrtpPopupDisplayed = false } Button(action: { + callViewModel.updateZrtpSas(authTokenClicked: "") callViewModel.zrtpPopupDisplayed = false }, label: { Text("Letters don't match!") @@ -149,30 +146,10 @@ struct ZRTPPopup: View { .frame(maxWidth: sharedMainViewModel.maxWidth) .position(x: geometry.size.width / 2, y: geometry.size.height / 2) .onAppear { - - var random = SystemRandomNumberGenerator() - let correctLetters = Int(random.next(upperBound: UInt32(4))) - - letters1 = (correctLetters == 0) ? callViewModel.upperCaseAuthTokenToListen : self.randomAlphanumericString(2) - letters2 = (correctLetters == 1) ? callViewModel.upperCaseAuthTokenToListen : self.randomAlphanumericString(2) - letters3 = (correctLetters == 2) ? callViewModel.upperCaseAuthTokenToListen : self.randomAlphanumericString(2) - letters4 = (correctLetters == 3) ? callViewModel.upperCaseAuthTokenToListen : self.randomAlphanumericString(2) + callViewModel.remoteAuthenticationTokens() } } } - - func randomAlphanumericString(_ length: Int) -> String { - let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - let len = UInt32(letters.count) - var random = SystemRandomNumberGenerator() - var randomString = "" - for _ in 0..() + @Published var letters1: String = "AA" + @Published var letters2: String = "BB" + @Published var letters3: String = "CC" + @Published var letters4: String = "DD" + init() { do { try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: .allowBluetooth) @@ -164,8 +169,9 @@ class CallViewModel: ObservableObject { let isPausedTmp = self.isCallPaused() let timeElapsedTmp = self.currentCall?.duration ?? 0 - let authToken = self.currentCall!.authenticationToken - let isDeviceTrusted = self.currentCall!.authenticationTokenVerified && authToken != nil + let authToken = self.currentCall!.localAuthenticationToken + let cacheMismatchFlag = self.currentCall!.zrtpCacheMismatchFlag + let isDeviceTrusted = !cacheMismatchFlag && self.currentCall!.authenticationTokenVerified && authToken != nil let isRemoteDeviceTrustedTmp = self.telecomManager.callInProgress ? isDeviceTrusted : false if self.currentCall != nil { @@ -215,11 +221,9 @@ class CallViewModel: ObservableObject { } self.callSuscriptions.insert(self.currentCall!.publisher?.onEncryptionChanged?.postOnCoreQueue {(cbVal: (call: Call, on: Bool, authenticationToken: String?)) in - DispatchQueue.main.async { - _ = self.updateEncryption() - if self.currentCall != nil { - self.callMediaEncryptionModel.update(call: self.currentCall!) - } + self.updateEncryption() + if self.currentCall != nil { + self.callMediaEncryptionModel.update(call: self.currentCall!) } }) @@ -231,6 +235,18 @@ class CallViewModel: ObservableObject { } }) + + self.callSuscriptions.insert( + self.currentCall!.publisher?.onAuthenticationTokenVerified?.postOnCoreQueue {(call: Call, verified: Bool) in + Log.warn("[CallViewModel][ZRTPPopup] Notified that authentication token is \(verified ? "verified" : "not verified!")") + + self.updateEncryption() + if self.currentCall != nil { + self.callMediaEncryptionModel.update(call: self.currentCall!) + } + } + ) + self.updateCallQualityIcon() } } @@ -832,81 +848,114 @@ class CallViewModel: ObservableObject { } } - func lettersClicked(letters: String) { - let verified = letters == self.upperCaseAuthTokenToListen + func skipZrtpAuthentication() { Log.info( - "[ZRTPPopup] User clicked on \(verified ? "right" : "wrong") letters" + "[ZRTPPopup] User skipped SAS validation in ZRTP call" ) - if verified { - coreContext.doOnCoreQueue { core in - if core.currentCall != nil { - core.currentCall!.authenticationTokenVerified = verified - } + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + core.currentCall!.skipZrtpAuthentication() } } } - private func updateEncryption() -> Bool { - if currentCall != nil && currentCall!.currentParams != nil { - switch currentCall!.currentParams!.mediaEncryption { - case MediaEncryption.ZRTP: - let authToken = currentCall!.authenticationToken - let isDeviceTrusted = currentCall!.authenticationTokenVerified && authToken != nil - - Log.info( - "[CallViewModel] Current call media encryption is ZRTP, auth token is \(isDeviceTrusted ? "trusted" : "not trusted yet")" - ) - - isRemoteDeviceTrusted = isDeviceTrusted - - if isDeviceTrusted { - ToastViewModel.shared.toastMessage = "Info_call_securised" - ToastViewModel.shared.displayToast = true + func updateZrtpSas(authTokenClicked: String) { + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + if authTokenClicked.isEmpty { + Log.error( + "[ZRTPPopup] Doing a fake ZRTP SAS check with empty token because user clicked on 'Not Found' button!" + ) + } else { + Log.info( + "[ZRTPPopup] Checking if ZRTP SAS auth token \(authTokenClicked) is the right one" + ) + } + core.currentCall!.checkAuthenticationTokenSelected(selectedValue: authTokenClicked) + } + } + } + + func remoteAuthenticationTokens() { + coreContext.doOnCoreQueue { core in + if core.currentCall != nil { + let tokens = core.currentCall!.remoteAuthenticationTokens + self.letters1 = tokens[0] + self.letters2 = tokens[1] + self.letters3 = tokens[2] + self.letters4 = tokens[3] + } + } + } + + private func updateEncryption() { + coreContext.doOnCoreQueue { core in + if self.currentCall != nil && self.currentCall!.currentParams != nil { + switch self.currentCall!.currentParams!.mediaEncryption { + case MediaEncryption.ZRTP: + let authToken = self.currentCall!.localAuthenticationToken + let isDeviceTrusted = self.currentCall!.authenticationTokenVerified && authToken != nil + + Log.info( + "[CallViewModel] Current call media encryption is ZRTP, auth token is \(isDeviceTrusted ? "trusted" : "not trusted yet")" + ) + + let cacheMismatchFlag = self.currentCall!.zrtpCacheMismatchFlag + let isRemoteDeviceTrustedTmp = !cacheMismatchFlag && isDeviceTrusted + + /* + let securityLevel = isDeviceTrusted ? SecurityLevel.Safe : SecurityLevel.Encrypted + let avatarModel = contact + if (avatarModel != nil) { + avatarModel.trust.postValue(securityLevel) + contact.postValue(avatarModel!!) + } else { + Log.error("$TAG No avatar model found!") + } + */ + + // When Post Quantum is available, ZRTP is Post Quantum + let isZrtpPqTmp = Core.getPostQuantumAvailable + + DispatchQueue.main.async { + self.isRemoteDeviceTrusted = isRemoteDeviceTrustedTmp + self.isMediaEncrypted = true + self.isZrtpPq = isZrtpPqTmp + + if isDeviceTrusted { + ToastViewModel.shared.toastMessage = "Info_call_securised" + ToastViewModel.shared.displayToast = true + } + } + + if !isDeviceTrusted && authToken != nil && !authToken!.isEmpty { + Log.info("[CallViewModel] Showing ZRTP SAS confirmation dialog") + self.showZrtpSasDialog(authToken: authToken!) + } + case MediaEncryption.SRTP, MediaEncryption.DTLS: + DispatchQueue.main.async { + self.isMediaEncrypted = true + self.isZrtpPq = false + } + default: + DispatchQueue.main.async { + self.isMediaEncrypted = false + self.isZrtpPq = false + } } - - /* - let securityLevel = isDeviceTrusted ? SecurityLevel.Safe : SecurityLevel.Encrypted - let avatarModel = contact - if (avatarModel != nil) { - avatarModel.trust.postValue(securityLevel) - contact.postValue(avatarModel!!) - } else { - Log.error("$TAG No avatar model found!") - } - */ - - isMediaEncrypted = true - // When Post Quantum is available, ZRTP is Post Quantum - isZrtpPq = Core.getPostQuantumAvailable - - if !isDeviceTrusted && authToken != nil && !authToken!.isEmpty { - Log.info("[CallViewModel] Showing ZRTP SAS confirmation dialog") - showZrtpSasDialog(authToken: authToken!) - } - - return isDeviceTrusted - case MediaEncryption.SRTP, MediaEncryption.DTLS: - isMediaEncrypted = true - isZrtpPq = false - return false - default: - isMediaEncrypted = false - isZrtpPq = false - return false } } - return false } func showZrtpSasDialogIfPossible() { if currentCall != nil && currentCall!.currentParams != nil && currentCall!.currentParams!.mediaEncryption == MediaEncryption.ZRTP { - let authToken = currentCall!.authenticationToken + let authToken = currentCall!.localAuthenticationToken let isDeviceTrusted = currentCall!.authenticationTokenVerified && authToken != nil Log.info( "[CallViewModel] Current call media encryption is ZRTP, auth token is \(isDeviceTrusted ? "trusted" : "not trusted yet")" ) - if (authToken != nil && !authToken!.isEmpty) { + if authToken != nil && !authToken!.isEmpty { showZrtpSasDialog(authToken: authToken!) } }