mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 07:47:54 +00:00
core, ui: confirm before connecting a call answered without e2e encryption (#7647)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
b79223fe5c
commit
86671a1699
@@ -2914,23 +2914,19 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
m.callInvitations[invitation.contact.id] = invitation
|
||||
}
|
||||
activateCall(invitation)
|
||||
case let .callOffer(_, contact, callType, offer, sharedKey, _):
|
||||
case let .callOffer(_, contact, callType, offer, sharedKey, askConfirmation):
|
||||
await withCall(contact) { call in
|
||||
await MainActor.run {
|
||||
call.callState = .offerReceived
|
||||
call.sharedKey = sharedKey
|
||||
}
|
||||
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
|
||||
let iceServers = getIceServers()
|
||||
logger.debug(".callOffer useRelay \(useRelay)")
|
||||
logger.debug(".callOffer iceServers \(String(describing: iceServers))")
|
||||
await m.callCommand.processCommand(.offer(
|
||||
offer: offer.rtcSession,
|
||||
iceCandidates: offer.rtcIceCandidates,
|
||||
media: callType.media, aesKey: sharedKey,
|
||||
iceServers: iceServers,
|
||||
relay: useRelay
|
||||
))
|
||||
if askConfirmation {
|
||||
showUnencryptedCallAlert(call) {
|
||||
Task { await processCallOffer(callType, offer, sharedKey) }
|
||||
}
|
||||
} else {
|
||||
await processCallOffer(callType, offer, sharedKey)
|
||||
}
|
||||
}
|
||||
case let .callAnswer(_, contact, answer):
|
||||
await withCall(contact) { call in
|
||||
@@ -3067,6 +3063,43 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
logger.debug("processReceivedMsg: ignoring \(res.responseType), not in call with the contact \(contact.id)")
|
||||
}
|
||||
}
|
||||
|
||||
func processCallOffer(_ callType: CallType, _ offer: WebRTCSession, _ sharedKey: String?) async {
|
||||
let useRelay = UserDefaults.standard.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY)
|
||||
let iceServers = getIceServers()
|
||||
logger.debug(".callOffer useRelay \(useRelay)")
|
||||
logger.debug(".callOffer iceServers \(String(describing: iceServers))")
|
||||
await m.callCommand.processCommand(.offer(
|
||||
offer: offer.rtcSession,
|
||||
iceCandidates: offer.rtcIceCandidates,
|
||||
media: callType.media, aesKey: sharedKey,
|
||||
iceServers: iceServers,
|
||||
relay: useRelay
|
||||
))
|
||||
}
|
||||
|
||||
func showUnencryptedCallAlert(_ call: Call, onContinue: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
showAlert(
|
||||
NSLocalizedString("Call is not encrypted", comment: "alert title"),
|
||||
message: String.localizedStringWithFormat(NSLocalizedString("%@ accepted the call without end-to-end encryption.", comment: "alert message"), call.contact.displayName),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("End call", comment: "alert action"), style: .destructive) { _ in
|
||||
// the call may have ended, or a new one started with the same contact, while the alert was shown
|
||||
guard m.activeCall === call else { return }
|
||||
if let uuid = call.callUUID {
|
||||
CallController.shared.endCall(callUUID: uuid)
|
||||
} else {
|
||||
CallController.shared.endCall(call: call) {}
|
||||
}
|
||||
},
|
||||
UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in
|
||||
if m.activeCall === call { onContinue() }
|
||||
}
|
||||
]}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchToLocalSession() {
|
||||
|
||||
+38
-12
@@ -3371,20 +3371,13 @@ object ChatController {
|
||||
chatModel.callManager.reportNewIncomingCall(r.callInvitation.copy(remoteHostId = rhId))
|
||||
}
|
||||
is CR.CallOffer -> {
|
||||
// TODO askConfirmation?
|
||||
// TODO check encryption is compatible
|
||||
withCall(r, r.contact) { call ->
|
||||
chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, hasSharedKey = r.sharedKey != null)
|
||||
val useRelay = appPrefs.webrtcPolicyRelay.get()
|
||||
val iceServers = getIceServers()
|
||||
chatModel.callCommand.add(WCallCommand.Offer(
|
||||
offer = r.offer.rtcSession,
|
||||
iceCandidates = r.offer.rtcIceCandidates,
|
||||
media = r.callType.media,
|
||||
aesKey = r.sharedKey,
|
||||
iceServers = iceServers,
|
||||
relay = useRelay
|
||||
))
|
||||
if (r.askConfirmation) {
|
||||
showUnencryptedCallAlert(call) { processCallOffer(r) }
|
||||
} else {
|
||||
processCallOffer(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.CallAnswer -> {
|
||||
@@ -3640,6 +3633,39 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
private fun processCallOffer(r: CR.CallOffer) {
|
||||
val useRelay = appPrefs.webrtcPolicyRelay.get()
|
||||
val iceServers = getIceServers()
|
||||
chatModel.callCommand.add(WCallCommand.Offer(
|
||||
offer = r.offer.rtcSession,
|
||||
iceCandidates = r.offer.rtcIceCandidates,
|
||||
media = r.callType.media,
|
||||
aesKey = r.sharedKey,
|
||||
iceServers = iceServers,
|
||||
relay = useRelay
|
||||
))
|
||||
}
|
||||
|
||||
private fun showUnencryptedCallAlert(call: Call, onContinue: () -> Unit) {
|
||||
// the call may have ended, or a new one started with the same contact, while the alert was shown
|
||||
fun offerPending(): Boolean {
|
||||
val c = chatModel.activeCall.value
|
||||
return c != null && c.remoteHostId == call.remoteHostId && c.contact.id == call.contact.id && c.callState == CallState.OfferReceived
|
||||
}
|
||||
val endCall = { if (offerPending()) withBGApi { chatModel.callManager.endCall(call) } }
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.call_not_encrypted_title),
|
||||
text = generalGetString(MR.strings.call_not_encrypted_desc).format(call.contact.displayName),
|
||||
confirmText = generalGetString(MR.strings.call_service_notification_end_call),
|
||||
onConfirm = { endCall() },
|
||||
dismissText = generalGetString(MR.strings.continue_to_next_step),
|
||||
onDismiss = { if (offerPending()) onContinue() },
|
||||
onDismissRequest = { endCall() },
|
||||
destructive = true,
|
||||
parseHtml = false
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun leaveGroup(rh: Long?, groupId: Long) {
|
||||
val groupInfo = apiLeaveGroup(rh, groupId)
|
||||
if (groupInfo != null) {
|
||||
|
||||
@@ -1415,6 +1415,8 @@
|
||||
<string name="status_no_e2e_encryption">no e2e encryption</string>
|
||||
<string name="status_contact_has_e2e_encryption">contact has e2e encryption</string>
|
||||
<string name="status_contact_has_no_e2e_encryption">contact has no e2e encryption</string>
|
||||
<string name="call_not_encrypted_title">Call is not encrypted</string>
|
||||
<string name="call_not_encrypted_desc">%s accepted the call without end-to-end encryption.</string>
|
||||
<string name="call_connection_peer_to_peer">peer-to-peer</string>
|
||||
<string name="call_connection_via_relay">via relay</string>
|
||||
<string name="icon_descr_hang_up">Hang up</string>
|
||||
|
||||
@@ -3053,7 +3053,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
|
||||
| callVersion `isCompatible` callVR -> do
|
||||
let sharedKey = callMediaKey callVersion callId <$> callDhPubKey <*> localDhPrivKey
|
||||
callState' = CallOfferReceived {localCallType, peerCallType = callType, peerCallSession = rtcSession, sharedKey}
|
||||
askConfirmation = encryptedCall localCallType && not (encryptedCall callType)
|
||||
askConfirmation = encryptedCall localCallType && isNothing sharedKey
|
||||
toView CEvtCallOffer {user, contact = ct, callType, offer = rtcSession, sharedKey, askConfirmation}
|
||||
pure (Just call {callState = callState'}, Just . ACIContent SMDSnd $ CISndCall CISCallAccepted 0)
|
||||
| otherwise -> do
|
||||
|
||||
Reference in New Issue
Block a user