Change ZRTP SAS UI

This commit is contained in:
Benoit Martins 2024-06-27 11:50:43 +02:00
parent 8875e2ba54
commit 4b46322264
13 changed files with 900 additions and 191 deletions

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "lock-key.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M128,112a28,28,0,0,0-8,54.83V184a8,8,0,0,0,16,0V166.83A28,28,0,0,0,128,112Zm0,40a12,12,0,1,1,12-12A12,12,0,0,1,128,152Zm80-72H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Z"></path></svg>

After

Width:  |  Height:  |  Size: 417 B

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "security.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="S&#195;&#169;curit&#195;&#169;">
<path id="Vector" d="M23 2.52647V9.2355C23 19.4652 14.3123 22.8591 12.5729 23.4356C12.2014 23.5614 11.7986 23.5614 11.4271 23.4356C9.68542 22.8591 1 19.4652 1 9.2355V2.52647C1 2.04205 1.19315 1.57747 1.53697 1.23493C1.88079 0.892387 2.3471 0.699951 2.83333 0.699951H21.1667C21.6529 0.699951 22.1192 0.892387 22.463 1.23493C22.8068 1.57747 23 2.04205 23 2.52647Z" fill="#364860"/>
<path id="Vector_2" d="M20.3333 1.59998H3.66667C3.22464 1.59998 2.80072 1.77557 2.48816 2.08813C2.17559 2.40069 2 2.82461 2 3.26664V9.38852C2 18.7229 9.89792 21.8198 11.4792 22.3458C11.8169 22.4607 12.1831 22.4607 12.5208 22.3458C14.1042 21.8198 22 18.7229 22 9.38852V3.26664C22 2.82461 21.8244 2.40069 21.5118 2.08813C21.1993 1.77557 20.7754 1.59998 20.3333 1.59998ZM16.7563 8.85623L10.9229 14.6896C10.8455 14.767 10.7536 14.8285 10.6525 14.8704C10.5513 14.9124 10.4428 14.934 10.3333 14.934C10.2238 14.934 10.1154 14.9124 10.0142 14.8704C9.91305 14.8285 9.82114 14.767 9.74375 14.6896L7.24375 12.1896C7.08738 12.0332 6.99954 11.8211 6.99954 11.6C6.99954 11.3788 7.08738 11.1668 7.24375 11.0104C7.40012 10.854 7.6122 10.7662 7.83333 10.7662C8.05447 10.7662 8.26655 10.854 8.42292 11.0104L10.3333 12.9208L15.5771 7.67706C15.6545 7.59963 15.7464 7.53822 15.8476 7.49631C15.9487 7.45441 16.0572 7.43285 16.1667 7.43285C16.2762 7.43285 16.3846 7.45441 16.4857 7.49631C16.5869 7.53822 16.6788 7.59963 16.7563 7.67706C16.8337 7.75448 16.8951 7.8464 16.937 7.94756C16.9789 8.04872 17.0005 8.15715 17.0005 8.26664C17.0005 8.37614 16.9789 8.48456 16.937 8.58572C16.8951 8.68688 16.8337 8.7788 16.7563 8.85623Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "shield-warning.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M120,136V96a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm8,48a12,12,0,1,0-12-12A12,12,0,0,0,128,184ZM224,56v56c0,52.72-25.52,84.67-46.93,102.19-23.06,18.86-46,25.27-47,25.53a8,8,0,0,1-4.2,0c-1-.26-23.91-6.67-47-25.53C57.52,196.67,32,164.72,32,112V56A16,16,0,0,1,48,40H208A16,16,0,0,1,224,56Zm-16,0L48,56l0,56c0,37.3,13.82,67.51,41.07,89.81A128.25,128.25,0,0,0,128,223.62a129.3,129.3,0,0,0,39.41-22.2C194.34,179.16,208,149.07,208,112Z"></path></svg>

After

Width:  |  Height:  |  Size: 546 B

View file

@ -204,9 +204,6 @@
}, },
"Appel" : { "Appel" : {
},
"Appel chiffré de bout en bout" : {
}, },
"assistant_account_create" : { "assistant_account_create" : {
"localizations" : { "localizations" : {
@ -421,6 +418,278 @@
}, },
"Call transfer failed!" : { "Call transfer failed!" : {
},
"call_action_hang_up" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hang up"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Raccrocher"
}
}
}
},
"call_dialog_zrtp_security_alert_message" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "This call confidentiality may be compromise!"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "La confidentialité de votre appel peut être compromise !"
}
}
}
},
"call_dialog_zrtp_security_alert_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Security alert"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Alerte de sécurité"
}
}
}
},
"call_dialog_zrtp_security_alert_try_again" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Try again"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Réessayer"
}
}
}
},
"call_dialog_zrtp_validate_trust_letters_do_not_match" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nothing matches"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aucune correspondance"
}
}
}
},
"call_dialog_zrtp_validate_trust_local_code_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your code:"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Votre code :"
}
}
}
},
"call_dialog_zrtp_validate_trust_message" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "For your safety, we need to authenticate your correspondent device.Please exchange your codes:"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pour garantir le chiffrement, nous avons besoin dauthentifier lappareil de votre correspondant.Veuillez échanger vos codes :"
}
}
}
},
"call_dialog_zrtp_validate_trust_remote_code_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Correspondent code:"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Code correspondant :"
}
}
}
},
"call_dialog_zrtp_validate_trust_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Validate the device"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vérification de sécurité"
}
}
}
},
"call_dialog_zrtp_validate_trust_warning_message" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "For your safety, we need to re-authenticate your correspondent device.Please re-exchange your codes:"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pour garantir le chiffrement, nous avons besoin de réauthentifier lappareil de votre correspondant.Veuillez ré-échanger vos codes :"
}
}
}
},
"call_not_encrypted" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Call is not encrypted"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Appel non chiffré"
}
}
}
},
"call_srtp_point_to_point_encrypted" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Point-to-point encrypted by SRTP"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Appel chiffré de point à point"
}
}
}
},
"call_waiting_for_encryption_info" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Waiting for encryption…"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "En attente du chiffrement…"
}
}
}
},
"call_zrtp_end_to_end_encrypted" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "End-to-end encrypted by ZRTP"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Appel chiffré de bout en bout"
}
}
}
},
"call_zrtp_sas_validation_required" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Validation required"
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Vérification nécessaire"
}
}
}
},
"call_zrtp_sas_validation_skip" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Skip"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Passer"
}
}
}
}, },
"Calls" : { "Calls" : {
@ -889,21 +1158,9 @@
}, },
"Joining..." : { "Joining..." : {
},
"Key" : {
"extractionState" : "manual"
},
"Key 1" : {
"extractionState" : "manual"
},
"Key 2" : {
"extractionState" : "manual"
}, },
"Last name" : { "Last name" : {
},
"Letters don't match!" : {
}, },
"Linphone" : { "Linphone" : {
@ -1101,9 +1358,6 @@
}, },
"Resuming" : { "Resuming" : {
},
"Say %@ and click on the letters given by your correspondent:" : {
}, },
"Say something..." : { "Say something..." : {
@ -1283,9 +1537,6 @@
}, },
"Username error" : { "Username error" : {
},
"Validate the device" : {
}, },
"Vidéo" : { "Vidéo" : {

View file

@ -225,7 +225,12 @@ extension ProviderDelegate: CXProviderDelegate {
DispatchQueue.main.async { DispatchQueue.main.async {
if UIApplication.shared.applicationState != .active { if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCall = call TelecomManager.shared.backgroundContextCall = call
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true if call?.callLog != nil {
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
} else {
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true
}
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
if call?.cameraEnabled == true { if call?.cameraEnabled == true {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported

View file

@ -44,6 +44,7 @@ class TelecomManager: ObservableObject {
@Published var callInProgress: Bool = false @Published var callInProgress: Bool = false
@Published var callDisplayed: Bool = true @Published var callDisplayed: Bool = true
@Published var callStarted: Bool = false @Published var callStarted: Bool = false
@Published var isNotVerifiedCounter: Int = 0
@Published var outgoingCallStarted: Bool = false @Published var outgoingCallStarted: Bool = false
@Published var remoteConfVideo: Bool = false @Published var remoteConfVideo: Bool = false
@Published var isRecordingByRemote: Bool = false @Published var isRecordingByRemote: Bool = false
@ -269,6 +270,7 @@ class TelecomManager: ObservableObject {
DispatchQueue.main.async { DispatchQueue.main.async {
self.outgoingCallStarted = true self.outgoingCallStarted = true
self.callStarted = true self.callStarted = true
self.isNotVerifiedCounter = 0
if self.callInProgress == false { if self.callInProgress == false {
withAnimation { withAnimation {
self.callInProgress = true self.callInProgress = true
@ -316,6 +318,7 @@ class TelecomManager: ObservableObject {
DispatchQueue.main.async { DispatchQueue.main.async {
self.callStarted = true self.callStarted = true
self.isNotVerifiedCounter = 0
} }
} catch { } catch {
Log.error("accept call failed \(error)") Log.error("accept call failed \(error)")

View file

@ -177,11 +177,19 @@ struct CallView: View {
} }
if callViewModel.zrtpPopupDisplayed == true { if callViewModel.zrtpPopupDisplayed == true {
ZRTPPopup(callViewModel: callViewModel) if idiom != .pad
.background(.black.opacity(0.65)) && (orientation == .landscapeLeft
.onTapGesture { || orientation == .landscapeRight
callViewModel.zrtpPopupDisplayed = false || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)
} && buttonSize != 45 {
ZRTPPopup(callViewModel: callViewModel, resizeView: 1.5)
.background(.black.opacity(0.65))
.frame(maxHeight: geo.size.height)
} else {
ZRTPPopup(callViewModel: callViewModel, resizeView: buttonSize == 45 ? 1.5 : 1)
.background(.black.opacity(0.65))
.frame(maxHeight: geo.size.height)
}
} }
if telecomManager.remainingCall { if telecomManager.remainingCall {
@ -226,7 +234,7 @@ struct CallView: View {
} }
Text(callViewModel.displayName) Text(callViewModel.displayName)
.default_text_style_white_800(styleSize: 16) .default_text_style_white_800(styleSize: 16)
if !telecomManager.outgoingCallStarted && telecomManager.callInProgress { if !telecomManager.outgoingCallStarted && telecomManager.callInProgress {
Text("|") Text("|")
@ -281,26 +289,114 @@ struct CallView: View {
.frame(height: 40) .frame(height: 40)
.zIndex(1) .zIndex(1)
if callViewModel.isMediaEncrypted { if !telecomManager.outgoingCallStarted && telecomManager.callInProgress {
HStack { if callViewModel.isMediaEncrypted && callViewModel.isRemoteDeviceTrusted && callViewModel.isZrtp {
Image("lock_simple") HStack {
.resizable() Image("lock-key")
.frame(width: 15, height: 15, alignment: .leading) .renderingMode(.template)
.padding(.leading, 50) .resizable()
.padding(.top, 35) .foregroundStyle(Color.blueInfo500)
.frame(width: 15, height: 15, alignment: .leading)
Text("Appel chiffré de bout en bout") .padding(.leading, 50)
.foregroundStyle(Color.blueInfo500) .padding(.top, 35)
.default_text_style_white(styleSize: 12)
.padding(.top, 35) Text("call_zrtp_end_to_end_encrypted")
.foregroundStyle(Color.blueInfo500)
Spacer() .default_text_style_white(styleSize: 12)
.padding(.top, 35)
Spacer()
}
.onTapGesture {
mediaEncryptedSheet = true
}
.frame(height: 40)
.zIndex(1)
} else if callViewModel.isMediaEncrypted && !callViewModel.isZrtp {
HStack {
Image("lock_simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.blueInfo500)
.frame(width: 15, height: 15, alignment: .leading)
.padding(.leading, 50)
.padding(.top, 35)
Text("call_srtp_point_to_point_encrypted")
.foregroundStyle(Color.blueInfo500)
.default_text_style_white(styleSize: 12)
.padding(.top, 35)
Spacer()
}
.onTapGesture {
mediaEncryptedSheet = true
}
.frame(height: 40)
.zIndex(1)
} else if callViewModel.isMediaEncrypted && (!callViewModel.isRemoteDeviceTrusted && callViewModel.isZrtp) || callViewModel.cacheMismatch {
HStack {
Image("warning-circle")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeWarning600)
.frame(width: 15, height: 15, alignment: .leading)
.padding(.leading, 50)
.padding(.top, 35)
Text("call_zrtp_sas_validation_required")
.foregroundStyle(Color.orangeWarning600)
.default_text_style_white(styleSize: 12)
.padding(.top, 35)
Spacer()
}
.onTapGesture {
mediaEncryptedSheet = true
}
.frame(height: 40)
.zIndex(1)
} else if callViewModel.isNotEncrypted {
HStack {
Image("lock_simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 15, height: 15, alignment: .leading)
.padding(.leading, 50)
.padding(.top, 35)
Text("call_not_encrypted")
.foregroundStyle(.white)
.default_text_style_white(styleSize: 12)
.padding(.top, 35)
Spacer()
}
.onTapGesture {
mediaEncryptedSheet = true
}
.frame(height: 40)
.zIndex(1)
} else {
HStack {
ProgressView()
.controlSize(.mini)
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.frame(width: 15, height: 15, alignment: .leading)
.padding(.leading, 50)
.padding(.top, 35)
Text("call_waiting_for_encryption_info")
.foregroundStyle(.white)
.default_text_style_white(styleSize: 12)
.padding(.top, 35)
Spacer()
}
.frame(height: 40)
.zIndex(1)
} }
.onTapGesture {
mediaEncryptedSheet = true
}
.frame(height: 40)
.zIndex(1)
} }
} }
} }

View file

@ -27,123 +27,367 @@ struct ZRTPPopup: View {
@ObservedObject var callViewModel: CallViewModel @ObservedObject var callViewModel: CallViewModel
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@State private var orientation = UIDevice.current.orientation
var resizeView: CGFloat
var body: some View { var body: some View {
if callViewModel.isNotVerified {
alertZRTP
} else {
popupZRTP
}
}
var popupZRTP: some View {
GeometryReader { geometry in GeometryReader { geometry in
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Validate the device") ZStack(alignment: .top, content: {
.default_text_style_600(styleSize: 20) HStack {
Spacer()
Text("Say \(callViewModel.upperCaseAuthTokenToRead) and click on the letters given by your correspondent:")
.default_text_style(styleSize: 15) VStack {
.padding(.bottom, 20) Image("security")
.resizable()
HStack(spacing: 25) { .frame(width: 20, height: 20, alignment: .leading)
Spacer()
Text("call_dialog_zrtp_validate_trust_title")
HStack(alignment: .center) { .default_text_style_white_700(styleSize: 16 / resizeView)
Text(callViewModel.letters1) }
.default_text_style(styleSize: 30)
.frame(width: 60, height: 60)
}
.padding(10)
.background(Color.grayMain2c200)
.cornerRadius(40)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters2)
.default_text_style(styleSize: 30)
.frame(width: 60, height: 60)
}
.padding(10)
.background(Color.grayMain2c200)
.cornerRadius(40)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
callViewModel.zrtpPopupDisplayed = false
}
Spacer()
}
.padding(.bottom, 20)
HStack(spacing: 25) {
Spacer()
HStack(alignment: .center) {
Text(callViewModel.letters3)
.default_text_style(styleSize: 30)
.frame(width: 60, height: 60)
}
.padding(10)
.background(Color.grayMain2c200)
.cornerRadius(40)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters4)
.default_text_style(styleSize: 30)
.frame(width: 60, height: 60)
}
.padding(10)
.background(Color.grayMain2c200)
.cornerRadius(40)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
callViewModel.zrtpPopupDisplayed = false
}
Spacer()
}
.padding(.bottom, 20)
HStack {
Text("Skip")
.underline()
.tint(Color.grayMain2c600)
.default_text_style_600(styleSize: 15)
.foregroundStyle(Color.grayMain2c500)
}
.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!")
.default_text_style_orange_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
Spacer()
}
.padding(.top, 15)
.padding(.bottom, 2)
HStack {
Spacer()
HStack {
Text("call_zrtp_sas_validation_skip")
.underline()
.tint(.white)
.default_text_style_white_600(styleSize: 16 / resizeView)
.foregroundStyle(.white)
}
.onTapGesture {
callViewModel.skipZrtpAuthentication()
callViewModel.zrtpPopupDisplayed = false
}
}
.padding(.top, 10 / resizeView)
.padding(.trailing, 15 / resizeView)
}) })
.padding(.horizontal, 20)
.padding(.vertical, 10) VStack(alignment: .center) {
.cornerRadius(60) VStack {
.overlay( if idiom != .pad && (orientation == .landscapeLeft
RoundedRectangle(cornerRadius: 60) || orientation == .landscapeRight
.inset(by: 0.5) || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
.stroke(Color.orangeMain500, lineWidth: 1) HStack {
) Text("call_dialog_zrtp_validate_trust_message")
.padding(.bottom) .default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
.padding(.bottom, 10 / resizeView)
VStack {
Text("call_dialog_zrtp_validate_trust_local_code_label")
.default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
Text(!callViewModel.upperCaseAuthTokenToRead.isEmpty ? callViewModel.upperCaseAuthTokenToRead : "ZZ")
.default_text_style_700(styleSize: 22 / resizeView)
.padding(.bottom, 20 / resizeView)
}
}
} else {
Text(callViewModel.cacheMismatch ? "call_dialog_zrtp_validate_trust_warning_message" : "call_dialog_zrtp_validate_trust_message")
.default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
.padding(.bottom, 10 / resizeView)
Text("call_dialog_zrtp_validate_trust_local_code_label")
.default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
Text(!callViewModel.upperCaseAuthTokenToRead.isEmpty ? callViewModel.upperCaseAuthTokenToRead : "ZZ")
.default_text_style_800(styleSize: 22 / resizeView)
}
}
.padding(.bottom, 5)
VStack {
Text("call_dialog_zrtp_validate_trust_remote_code_label")
.default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
.padding(.top, 15 / resizeView)
.padding(.bottom, 10 / resizeView)
if idiom != .pad && (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
HStack(spacing: 30) {
HStack(alignment: .center) {
Text(callViewModel.letters1)
.default_text_style(styleSize: 24 / resizeView)
.frame(width: 45 / resizeView, height: 45 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters2)
.default_text_style(styleSize: 24 / resizeView)
.frame(width: 45 / resizeView, height: 45 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters3)
.default_text_style(styleSize: 24 / resizeView)
.frame(width: 45 / resizeView, height: 45 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters4)
.default_text_style(styleSize: 24 / resizeView)
.frame(width: 45 / resizeView, height: 45 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
callViewModel.zrtpPopupDisplayed = false
}
}
.padding(.horizontal, 40 / resizeView)
.padding(.bottom, 20 / resizeView)
} else {
HStack(spacing: 30) {
HStack(alignment: .center) {
Text(callViewModel.letters1)
.default_text_style(styleSize: 34 / resizeView)
.frame(width: 60 / resizeView, height: 60 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters1)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters2)
.default_text_style(styleSize: 34 / resizeView)
.frame(width: 60 / resizeView, height: 60 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters2)
callViewModel.zrtpPopupDisplayed = false
}
}
.padding(.horizontal, 40 / resizeView)
.padding(.bottom, 20 / resizeView)
HStack(spacing: 30) {
HStack(alignment: .center) {
Text(callViewModel.letters3)
.default_text_style(styleSize: 34 / resizeView)
.frame(width: 60 / resizeView, height: 60 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters3)
callViewModel.zrtpPopupDisplayed = false
}
HStack(alignment: .center) {
Text(callViewModel.letters4)
.default_text_style(styleSize: 34 / resizeView)
.frame(width: 60 / resizeView, height: 60 / resizeView)
}
.padding(10 / resizeView)
.background(.white)
.clipShape(Circle())
.shadow(color: .gray.opacity(0.4), radius: 4)
.onTapGesture {
callViewModel.updateZrtpSas(authTokenClicked: callViewModel.letters4)
callViewModel.zrtpPopupDisplayed = false
}
}
.padding(.horizontal, 40 / resizeView)
.padding(.bottom, 20 / resizeView)
}
}
.padding(.horizontal, 10 / resizeView)
.padding(.bottom, 10 / resizeView)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.inset(by: 0.5)
.stroke(Color.grayMain2c200, lineWidth: 1)
)
.padding(.bottom, 10 / resizeView)
Button(action: {
callViewModel.updateZrtpSas(authTokenClicked: "")
callViewModel.zrtpPopupDisplayed = false
}, label: {
Text("call_dialog_zrtp_validate_trust_letters_do_not_match")
.foregroundStyle(Color.redDanger500)
.default_text_style_orange_600(styleSize: 20 / resizeView)
.frame(height: 35 / resizeView)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20 / resizeView)
.padding(.vertical, 10 / resizeView)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.redDanger500, lineWidth: 1)
)
.padding(.bottom)
}
.padding(.top, 20 / resizeView)
.padding(.horizontal, 20 / resizeView)
.background(.white)
.cornerRadius(20)
} }
.padding(.horizontal, 20) .background(callViewModel.cacheMismatch ? Color.orangeWarning600 : Color.blueInfo500)
.padding(.vertical, 20)
.background(.white)
.cornerRadius(20) .cornerRadius(20)
.padding(.horizontal) .padding(.horizontal, 2)
.frame(maxHeight: .infinity) .frame(maxHeight: .infinity)
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2) .shadow(color: callViewModel.cacheMismatch ? Color.orangeWarning600 : Color.blueInfo500, radius: 0, x: 0, y: 2)
.frame(maxWidth: sharedMainViewModel.maxWidth) .frame(maxWidth: sharedMainViewModel.maxWidth * 1.2)
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
.onAppear {
callViewModel.remoteAuthenticationTokens()
}
}
}
var alertZRTP: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
ZStack(alignment: .top, content: {
HStack {
Spacer()
VStack {
Image("shield-warning")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 25, height: 25, alignment: .leading)
Text("call_dialog_zrtp_security_alert_title")
.default_text_style_white_700(styleSize: 16 / resizeView)
}
.frame(maxWidth: .infinity)
Spacer()
}
.padding(.top, 15)
.padding(.bottom, 2)
})
VStack(alignment: .center) {
VStack {
Text("call_dialog_zrtp_security_alert_message")
.default_text_style(styleSize: 16 / resizeView)
.multilineTextAlignment(.center)
.padding(.bottom, 10 / resizeView)
}
.padding(.bottom, 5)
if telecomManager.isNotVerifiedCounter <= 1 {
Button(action: {
callViewModel.isNotVerified = false
}, label: {
Text("call_dialog_zrtp_security_alert_try_again")
.foregroundStyle(Color.redDanger500)
.default_text_style_orange_600(styleSize: 20 / resizeView)
.frame(height: 35 / resizeView)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20 / resizeView)
.padding(.vertical, 10 / resizeView)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.redDanger500, lineWidth: 1)
)
.padding(.bottom)
}
Button(action: {
callViewModel.terminateCall()
}, label: {
HStack {
Image("phone-disconnect")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 20, height: 20)
Text("call_action_hang_up")
.default_text_style_white_600(styleSize: 20)
.frame(height: 35)
}
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20 / resizeView)
.padding(.vertical, 10 / resizeView)
.background(Color.redDanger500)
.cornerRadius(60)
.padding(.bottom)
}
.padding(.top, 20 / resizeView)
.padding(.horizontal, 20 / resizeView)
.background(.white)
.cornerRadius(20)
}
.background(Color.redDanger500)
.cornerRadius(20)
.padding(.horizontal, 2)
.frame(maxHeight: .infinity)
.shadow(color: Color.redDanger500, radius: 0, x: 0, y: 2)
.frame(maxWidth: sharedMainViewModel.maxWidth * 1.2)
.position(x: geometry.size.width / 2, y: geometry.size.height / 2) .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
.onAppear { .onAppear {
callViewModel.remoteAuthenticationTokens() callViewModel.remoteAuthenticationTokens()
@ -153,5 +397,5 @@ struct ZRTPPopup: View {
} }
#Preview { #Preview {
ZRTPPopup(callViewModel: CallViewModel()) ZRTPPopup(callViewModel: CallViewModel(), resizeView: 1)
} }

View file

@ -42,8 +42,11 @@ class CallViewModel: ObservableObject {
@Published var upperCaseAuthTokenToRead = "" @Published var upperCaseAuthTokenToRead = ""
@Published var upperCaseAuthTokenToListen = "" @Published var upperCaseAuthTokenToListen = ""
@Published var isMediaEncrypted: Bool = false @Published var isMediaEncrypted: Bool = false
@Published var isZrtpPq: Bool = false @Published var isNotEncrypted: Bool = false
@Published var isZrtp: Bool = false
@Published var isRemoteDeviceTrusted: Bool = false @Published var isRemoteDeviceTrusted: Bool = false
@Published var cacheMismatch: Bool = false
@Published var isNotVerified: Bool = false
@Published var selectedCall: Call? @Published var selectedCall: Call?
@Published var isTransferInsteadCall: Bool = false @Published var isTransferInsteadCall: Bool = false
@Published var isOneOneCall: Bool = false @Published var isOneOneCall: Bool = false
@ -125,14 +128,14 @@ class CallViewModel: ObservableObject {
} }
var isMediaEncryptedTmp = false var isMediaEncryptedTmp = false
var isZrtpPqTmp = false var isZrtpTmp = false
if self.currentCall != nil && self.currentCall!.currentParams != nil { if self.currentCall != nil && self.currentCall!.currentParams != nil {
if self.currentCall!.currentParams!.mediaEncryption == .ZRTP || if self.currentCall!.currentParams!.mediaEncryption == .ZRTP ||
self.currentCall!.currentParams!.mediaEncryption == .SRTP || self.currentCall!.currentParams!.mediaEncryption == .SRTP ||
self.currentCall!.currentParams!.mediaEncryption == .DTLS { self.currentCall!.currentParams!.mediaEncryption == .DTLS {
isMediaEncryptedTmp = true isMediaEncryptedTmp = true
isZrtpPqTmp = self.currentCall!.currentParams!.mediaEncryption == .ZRTP isZrtpTmp = self.currentCall!.currentParams!.mediaEncryption == .ZRTP
} }
} }
@ -200,6 +203,9 @@ class CallViewModel: ObservableObject {
self.zrtpPopupDisplayed = false self.zrtpPopupDisplayed = false
self.upperCaseAuthTokenToRead = "" self.upperCaseAuthTokenToRead = ""
self.upperCaseAuthTokenToListen = "" self.upperCaseAuthTokenToListen = ""
self.isNotVerified = false
self.updateEncryption()
self.isConference = false self.isConference = false
self.participantList = [] self.participantList = []
self.activeSpeakerParticipant = nil self.activeSpeakerParticipant = nil
@ -209,7 +215,9 @@ class CallViewModel: ObservableObject {
self.videoDisplayed = videoDisplayedTmp self.videoDisplayed = videoDisplayedTmp
self.isOneOneCall = isOneOneCallTmp self.isOneOneCall = isOneOneCallTmp
self.isMediaEncrypted = isMediaEncryptedTmp self.isMediaEncrypted = isMediaEncryptedTmp
self.isZrtpPq = isZrtpPqTmp self.isNotEncrypted = false
self.isZrtp = isZrtpTmp
self.cacheMismatch = cacheMismatchFlag
self.getCallsList() self.getCallsList()
@ -228,21 +236,35 @@ class CallViewModel: ObservableObject {
}) })
self.callSuscriptions.insert(self.currentCall!.publisher?.onStatsUpdated?.postOnCoreQueue {(cbVal: (call: Call, stats: CallStats)) in self.callSuscriptions.insert(self.currentCall!.publisher?.onStatsUpdated?.postOnCoreQueue {(cbVal: (call: Call, stats: CallStats)) in
if self.currentCall != nil { DispatchQueue.main.async {
DispatchQueue.main.async { if self.currentCall != nil {
self.callStatsModel.update(call: self.currentCall!, stats: cbVal.stats) self.callStatsModel.update(call: self.currentCall!, stats: cbVal.stats)
} }
} }
}) })
self.callSuscriptions.insert( self.callSuscriptions.insert(
self.currentCall!.publisher?.onAuthenticationTokenVerified?.postOnCoreQueue {(call: Call, verified: Bool) in self.currentCall!.publisher?.onAuthenticationTokenVerified?.postOnCoreQueue {(call: Call, verified: Bool) in
Log.warn("[CallViewModel][ZRTPPopup] Notified that authentication token is \(verified ? "verified" : "not verified!")") Log.warn("[CallViewModel][ZRTPPopup] Notified that authentication token is \(verified ? "verified" : "not verified!")")
if verified {
self.updateEncryption() self.updateEncryption()
if self.currentCall != nil { if self.currentCall != nil {
self.callMediaEncryptionModel.update(call: self.currentCall!) self.callMediaEncryptionModel.update(call: self.currentCall!)
}
} else {
if self.telecomManager.isNotVerifiedCounter == 0 {
DispatchQueue.main.async {
self.isNotVerified = true
self.telecomManager.isNotVerifiedCounter += 1
}
self.showZrtpSasDialogIfPossible()
} else {
DispatchQueue.main.async {
self.isNotVerified = true
self.telecomManager.isNotVerifiedCounter += 1
self.zrtpPopupDisplayed = true
}
}
} }
} }
) )
@ -641,6 +663,7 @@ class CallViewModel: ObservableObject {
telecomManager.callInProgress = true telecomManager.callInProgress = true
telecomManager.callDisplayed = true telecomManager.callDisplayed = true
telecomManager.callStarted = true telecomManager.callStarted = true
telecomManager.isNotVerifiedCounter = 0
} }
coreContext.doOnCoreQueue { core in coreContext.doOnCoreQueue { core in
@ -881,18 +904,20 @@ class CallViewModel: ObservableObject {
coreContext.doOnCoreQueue { core in coreContext.doOnCoreQueue { core in
if core.currentCall != nil { if core.currentCall != nil {
let tokens = core.currentCall!.remoteAuthenticationTokens let tokens = core.currentCall!.remoteAuthenticationTokens
DispatchQueue.main.async { if !tokens.isEmpty {
self.letters1 = tokens[0] DispatchQueue.main.async {
self.letters2 = tokens[1] self.letters1 = tokens[0]
self.letters3 = tokens[2] self.letters2 = tokens[1]
self.letters4 = tokens[3] self.letters3 = tokens[2]
self.letters4 = tokens[3]
}
} }
} }
} }
} }
private func updateEncryption() { private func updateEncryption() {
coreContext.doOnCoreQueue { core in coreContext.doOnCoreQueue { _ in
if self.currentCall != nil && self.currentCall!.currentParams != nil { if self.currentCall != nil && self.currentCall!.currentParams != nil {
switch self.currentCall!.currentParams!.mediaEncryption { switch self.currentCall!.currentParams!.mediaEncryption {
case MediaEncryption.ZRTP: case MediaEncryption.ZRTP:
@ -918,12 +943,14 @@ class CallViewModel: ObservableObject {
*/ */
// When Post Quantum is available, ZRTP is Post Quantum // When Post Quantum is available, ZRTP is Post Quantum
let isZrtpPqTmp = Core.getPostQuantumAvailable let isZrtpPQTmp = Core.getPostQuantumAvailable
DispatchQueue.main.async { DispatchQueue.main.async {
self.isRemoteDeviceTrusted = isRemoteDeviceTrustedTmp self.isRemoteDeviceTrusted = isRemoteDeviceTrustedTmp
self.isMediaEncrypted = true self.isMediaEncrypted = true
self.isZrtpPq = isZrtpPqTmp self.isZrtp = true
self.cacheMismatch = cacheMismatchFlag
self.isNotEncrypted = false
if isDeviceTrusted { if isDeviceTrusted {
ToastViewModel.shared.toastMessage = "Info_call_securised" ToastViewModel.shared.toastMessage = "Info_call_securised"
@ -938,12 +965,18 @@ class CallViewModel: ObservableObject {
case MediaEncryption.SRTP, MediaEncryption.DTLS: case MediaEncryption.SRTP, MediaEncryption.DTLS:
DispatchQueue.main.async { DispatchQueue.main.async {
self.isMediaEncrypted = true self.isMediaEncrypted = true
self.isZrtpPq = false self.isZrtp = false
self.isNotEncrypted = false
} }
default: case MediaEncryption.None:
DispatchQueue.main.async { DispatchQueue.main.async {
self.isMediaEncrypted = false self.isMediaEncrypted = false
self.isZrtpPq = false self.isZrtp = false
if self.currentCall!.state == .StreamsRunning {
self.isNotEncrypted = true
} else {
self.isNotEncrypted = false
}
} }
} }
} }
@ -971,26 +1004,28 @@ class CallViewModel: ObservableObject {
let mySubstringSuffix = upperCaseAuthToken.suffix(2) let mySubstringSuffix = upperCaseAuthToken.suffix(2)
switch self.currentCall!.dir { DispatchQueue.main.async {
case Call.Dir.Incoming: switch self.currentCall!.dir {
self.upperCaseAuthTokenToRead = String(mySubstringPrefix) case Call.Dir.Incoming:
self.upperCaseAuthTokenToListen = String(mySubstringSuffix) self.upperCaseAuthTokenToRead = String(mySubstringPrefix)
default: self.upperCaseAuthTokenToListen = String(mySubstringSuffix)
self.upperCaseAuthTokenToRead = String(mySubstringSuffix) default:
self.upperCaseAuthTokenToListen = String(mySubstringPrefix) self.upperCaseAuthTokenToRead = String(mySubstringSuffix)
self.upperCaseAuthTokenToListen = String(mySubstringPrefix)
}
self.zrtpPopupDisplayed = true
} }
self.zrtpPopupDisplayed = true
} }
} }
func transferClicked() { func transferClicked() {
coreContext.doOnCoreQueue { core in coreContext.doOnCoreQueue { core in
var callToTransferTo = core.calls.last { call in let callToTransferTo = core.calls.last { call in
call.state == Call.State.Paused && call.callLog?.callId != self.currentCall?.callLog?.callId call.state == Call.State.Paused && call.callLog?.callId != self.currentCall?.callLog?.callId
} }
if (callToTransferTo == nil) { if callToTransferTo == nil {
Log.error( Log.error(
"[CallViewModel] Couldn't find a call in Paused state to transfer current call to" "[CallViewModel] Couldn't find a call in Paused state to transfer current call to"
) )

View file

@ -35,6 +35,10 @@ extension Account {
} }
static func == (lhs: Account, rhs: Account) -> Bool { static func == (lhs: Account, rhs: Account) -> Bool {
return lhs.params?.identityAddress?.asString() == rhs.params?.identityAddress?.asString() if lhs.params != nil && lhs.params?.identityAddress != nil && rhs.params != nil && rhs.params?.identityAddress != nil {
return lhs.params?.identityAddress?.asString() == rhs.params?.identityAddress?.asString()
} else {
return false
}
} }
} }