mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Update ZRTP related code to use newly added APIs
This commit is contained in:
parent
fb75ea344c
commit
274ed49f16
7 changed files with 103 additions and 94 deletions
|
|
@ -231,24 +231,17 @@ class ActiveCallFragment : GenericCallFragment() {
|
|||
val model = ZrtpSasConfirmationDialogModel(pair.first, pair.second)
|
||||
val dialog = DialogUtils.getZrtpSasConfirmationDialog(requireActivity(), model)
|
||||
|
||||
model.dismissEvent.observe(viewLifecycleOwner) { event ->
|
||||
model.skipEvent.observe(viewLifecycleOwner) { event ->
|
||||
event.consume {
|
||||
callViewModel.skipZrtpSas()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
model.trustVerified.observe(viewLifecycleOwner) { event ->
|
||||
event.consume { verified ->
|
||||
callViewModel.updateZrtpSas(verified)
|
||||
model.authTokenClickedEvent.observe(viewLifecycleOwner) { event ->
|
||||
event.consume { authToken ->
|
||||
callViewModel.updateZrtpSas(authToken)
|
||||
dialog.dismiss()
|
||||
|
||||
if (verified) {
|
||||
(requireActivity() as GenericActivity).showBlueToast(
|
||||
getString(R.string.call_can_be_trusted_toast),
|
||||
R.drawable.trusted,
|
||||
doNotTint = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,6 +250,23 @@ class ActiveCallFragment : GenericCallFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
callViewModel.zrtpAuthTokenVerifiedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { verified ->
|
||||
if (verified) {
|
||||
(requireActivity() as GenericActivity).showBlueToast(
|
||||
getString(R.string.call_can_be_trusted_toast),
|
||||
R.drawable.trusted,
|
||||
doNotTint = true
|
||||
)
|
||||
} else {
|
||||
(requireActivity() as GenericActivity).showRedToast(
|
||||
getString(R.string.call_can_not_be_trusted_alert_toast),
|
||||
R.drawable.warning_circle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.callDuration.observe(viewLifecycleOwner) { duration ->
|
||||
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
||||
binding.chronometer.start()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ package org.linphone.ui.call.model
|
|||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.util.Random
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.GenericViewModel
|
||||
|
|
@ -30,11 +29,10 @@ import org.linphone.utils.Event
|
|||
|
||||
class ZrtpSasConfirmationDialogModel @UiThread constructor(
|
||||
authTokenToRead: String,
|
||||
private val authTokenToListen: String
|
||||
authTokensToListen: List<String>
|
||||
) : GenericViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[ZRTP SAS Confirmation Dialog]"
|
||||
private const val ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
}
|
||||
|
||||
val message = MutableLiveData<String>()
|
||||
|
|
@ -43,57 +41,36 @@ class ZrtpSasConfirmationDialogModel @UiThread constructor(
|
|||
val letters3 = MutableLiveData<String>()
|
||||
val letters4 = MutableLiveData<String>()
|
||||
|
||||
val trustVerified = MutableLiveData<Event<Boolean>>()
|
||||
val authTokenClickedEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
val dismissEvent = MutableLiveData<Event<Boolean>>()
|
||||
val skipEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
init {
|
||||
message.value = AppUtils.getFormattedString(
|
||||
R.string.call_dialog_zrtp_validate_trust_subtitle,
|
||||
authTokenToRead
|
||||
)
|
||||
|
||||
// TODO FIXME: use SDK API when it will be available
|
||||
val rnd = Random()
|
||||
val randomLetters1 = "${ALPHABET[rnd.nextInt(ALPHABET.length)]}${ALPHABET[
|
||||
rnd.nextInt(
|
||||
ALPHABET.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters2 = "${ALPHABET[rnd.nextInt(ALPHABET.length)]}${ALPHABET[
|
||||
rnd.nextInt(
|
||||
ALPHABET.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters3 = "${ALPHABET[rnd.nextInt(ALPHABET.length)]}${ALPHABET[
|
||||
rnd.nextInt(
|
||||
ALPHABET.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters4 = "${ALPHABET[rnd.nextInt(ALPHABET.length)]}${ALPHABET[
|
||||
rnd.nextInt(
|
||||
ALPHABET.length
|
||||
)
|
||||
]}"
|
||||
|
||||
val correctLetters = rnd.nextInt(4)
|
||||
letters1.value = if (correctLetters == 0) authTokenToListen else randomLetters1
|
||||
letters2.value = if (correctLetters == 1) authTokenToListen else randomLetters2
|
||||
letters3.value = if (correctLetters == 2) authTokenToListen else randomLetters3
|
||||
letters4.value = if (correctLetters == 3) authTokenToListen else randomLetters4
|
||||
letters1.value = authTokensToListen[0]
|
||||
letters2.value = authTokensToListen[1]
|
||||
letters3.value = authTokensToListen[2]
|
||||
letters4.value = authTokensToListen[3]
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun dismiss() {
|
||||
dismissEvent.value = Event(true)
|
||||
fun skip() {
|
||||
skipEvent.value = Event(true)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun notFound() {
|
||||
Log.e("$TAG User clicked on 'Not Found' button!")
|
||||
authTokenClickedEvent.value = Event("")
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun lettersClicked(letters: MutableLiveData<String>) {
|
||||
val verified = letters.value == authTokenToListen
|
||||
Log.i(
|
||||
"$TAG User clicked on ${if (verified) "right" else "wrong"} letters"
|
||||
)
|
||||
trustVerified.value = Event(verified)
|
||||
val token = letters.value.orEmpty()
|
||||
Log.i("$TAG User clicked on [$token] letters")
|
||||
authTokenClickedEvent.value = Event(token)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import androidx.annotation.WorkerThread
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -165,8 +164,12 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
// ZRTP related
|
||||
|
||||
val showZrtpSasDialogEvent: MutableLiveData<Event<Pair<String, String>>> by lazy {
|
||||
MutableLiveData<Event<Pair<String, String>>>()
|
||||
val showZrtpSasDialogEvent: MutableLiveData<Event<Pair<String, List<String>>>> by lazy {
|
||||
MutableLiveData<Event<Pair<String, List<String>>>>()
|
||||
}
|
||||
|
||||
val zrtpAuthTokenVerifiedEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
// Chat
|
||||
|
|
@ -226,10 +229,18 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
|
|||
private val callListener = object : CallListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onEncryptionChanged(call: Call, on: Boolean, authenticationToken: String?) {
|
||||
Log.i("$TAG Call encryption changed, updating...")
|
||||
updateEncryption()
|
||||
callMediaEncryptionModel.update(call)
|
||||
}
|
||||
|
||||
override fun onAuthenticationTokenVerified(call: Call, verified: Boolean) {
|
||||
Log.w(
|
||||
"$TAG Notified that authentication token is [${if (verified) "verified" else "not verified!"}]"
|
||||
)
|
||||
zrtpAuthTokenVerifiedEvent.postValue(Event(verified))
|
||||
}
|
||||
|
||||
override fun onRemoteRecording(call: Call, recording: Boolean) {
|
||||
Log.i("$TAG Remote recording changed: $recording")
|
||||
isRemoteRecordingEvent.postValue(Event(Pair(recording, displayedName.value.orEmpty())))
|
||||
|
|
@ -523,14 +534,29 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun updateZrtpSas(verified: Boolean) {
|
||||
fun skipZrtpSas() {
|
||||
coreContext.postOnCoreThread {
|
||||
if (::currentCall.isInitialized) {
|
||||
if (!verified && !currentCall.authenticationTokenVerified) {
|
||||
Log.w("$TAG ZRTP SAS validation failed")
|
||||
Log.w("$TAG User skipped SAS validation in ZRTP call")
|
||||
currentCall.skipZrtpAuthentication()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun updateZrtpSas(authTokenClicked: String) {
|
||||
coreContext.postOnCoreThread {
|
||||
if (::currentCall.isInitialized) {
|
||||
if (authTokenClicked.isEmpty()) {
|
||||
Log.e(
|
||||
"$TAG Doing a fake ZRTP SAS check with empty token because user clicked on 'Not Found' button!"
|
||||
)
|
||||
} else {
|
||||
currentCall.authenticationTokenVerified = verified
|
||||
Log.i(
|
||||
"$TAG Checking if ZRTP SAS auth token [$authTokenClicked] is the right one"
|
||||
)
|
||||
}
|
||||
currentCall.checkAuthenticationTokenSelected(authTokenClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -921,45 +947,33 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
|
|||
fun showZrtpSasDialogIfPossible() {
|
||||
coreContext.postOnCoreThread {
|
||||
if (currentCall.currentParams.mediaEncryption == MediaEncryption.ZRTP) {
|
||||
val authToken = currentCall.authenticationToken
|
||||
val isDeviceTrusted = currentCall.authenticationTokenVerified && authToken != null
|
||||
val isDeviceTrusted = currentCall.authenticationTokenVerified
|
||||
Log.i(
|
||||
"$TAG Current call media encryption is ZRTP, auth token is ${if (isDeviceTrusted) "trusted" else "not trusted yet"}"
|
||||
"$TAG Current call media encryption is ZRTP, auth token is [${if (isDeviceTrusted) "trusted" else "not trusted yet"}]"
|
||||
)
|
||||
if (!authToken.isNullOrEmpty()) {
|
||||
showZrtpSasDialog(authToken)
|
||||
val tokenToRead = currentCall.localAuthenticationToken
|
||||
val tokensToDisplay = currentCall.remoteAuthenticationTokens.toList()
|
||||
if (!tokenToRead.isNullOrEmpty() && tokensToDisplay.size == 4) {
|
||||
showZrtpSasDialogEvent.postValue(Event(Pair(tokenToRead, tokensToDisplay)))
|
||||
} else {
|
||||
Log.w(
|
||||
"$TAG Either local auth token is null/empty or remote tokens list doesn't contains 4 elements!"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun showZrtpSasDialog(authToken: String) {
|
||||
val upperCaseAuthToken = authToken.uppercase(Locale.getDefault())
|
||||
val toRead: String
|
||||
val toListen: String
|
||||
when (currentCall.dir) {
|
||||
Call.Dir.Incoming -> {
|
||||
toRead = upperCaseAuthToken.substring(0, 2)
|
||||
toListen = upperCaseAuthToken.substring(2)
|
||||
}
|
||||
else -> {
|
||||
toRead = upperCaseAuthToken.substring(2)
|
||||
toListen = upperCaseAuthToken.substring(0, 2)
|
||||
}
|
||||
}
|
||||
showZrtpSasDialogEvent.postValue(Event(Pair(toRead, toListen)))
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun updateEncryption(): Boolean {
|
||||
when (val mediaEncryption = currentCall.currentParams.mediaEncryption) {
|
||||
MediaEncryption.ZRTP -> {
|
||||
val authToken = currentCall.authenticationToken
|
||||
val isDeviceTrusted = currentCall.authenticationTokenVerified && authToken != null
|
||||
val isDeviceTrusted = currentCall.authenticationTokenVerified
|
||||
val cacheMismatch = currentCall.zrtpCacheMismatchFlag
|
||||
Log.i(
|
||||
"$TAG Current call media encryption is ZRTP, auth token is ${if (isDeviceTrusted) "trusted" else "not trusted yet"}"
|
||||
"$TAG Current call media encryption is ZRTP, auth token is [${if (isDeviceTrusted) "trusted" else "not trusted yet"}], cache mismatch is [$cacheMismatch]"
|
||||
)
|
||||
|
||||
val securityLevel = if (isDeviceTrusted) SecurityLevel.EndToEndEncryptedAndVerified else SecurityLevel.EndToEndEncrypted
|
||||
val avatarModel = contact.value
|
||||
if (avatarModel != null && currentCall.conference == null) { // Don't do it for conferences
|
||||
|
|
@ -984,9 +998,17 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
|
|||
coreContext.core.postQuantumAvailable && stats?.isZrtpKeyAgreementAlgoPostQuantum == true
|
||||
)
|
||||
|
||||
if (!isDeviceTrusted && !authToken.isNullOrEmpty()) {
|
||||
if (cacheMismatch || !isDeviceTrusted) {
|
||||
Log.i("$TAG Showing ZRTP SAS confirmation dialog")
|
||||
showZrtpSasDialog(authToken)
|
||||
val tokenToRead = currentCall.localAuthenticationToken
|
||||
val tokensToDisplay = currentCall.remoteAuthenticationTokens.toList()
|
||||
if (!tokenToRead.isNullOrEmpty() && tokensToDisplay.size == 4) {
|
||||
showZrtpSasDialogEvent.postValue(Event(Pair(tokenToRead, tokensToDisplay)))
|
||||
} else {
|
||||
Log.w(
|
||||
"$TAG Either local auth token is null/empty or remote tokens list doesn't contains 4 elements!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return isDeviceTrusted
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
|
@ -135,7 +134,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="@id/letters_1"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:onClick="@{() -> viewModel.skip()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/skip"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -153,7 +152,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@id/nothing_matches"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:onClick="@{() -> viewModel.notFound()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/nothing_matches"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
|
@ -136,7 +135,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="@id/letters_3"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:onClick="@{() -> viewModel.skip()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/skip"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -154,7 +153,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@id/nothing_matches"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:onClick="@{() -> viewModel.notFound()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/nothing_matches"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
|||
|
|
@ -611,6 +611,7 @@
|
|||
|
||||
<string name="call_history_deleted_toast">L\'historique des appels a été supprimé</string>
|
||||
<string name="call_can_be_trusted_toast">Cet appel est complètement sécurisé</string>
|
||||
<string name="call_can_not_be_trusted_alert_toast">Problème de sécurité !</string>
|
||||
<string name="call_transfer_in_progress_toast">Appel en cours de transfert</string>
|
||||
<string name="call_transfer_successful_toast">L\'appel a été transferré</string>
|
||||
<string name="call_transfer_failed_toast">Le transfert a échoué !</string>
|
||||
|
|
|
|||
|
|
@ -648,6 +648,7 @@
|
|||
|
||||
<string name="call_history_deleted_toast">History has been deleted</string>
|
||||
<string name="call_can_be_trusted_toast">This call can be trusted</string>
|
||||
<string name="call_can_not_be_trusted_alert_toast">Security alert !</string>
|
||||
<string name="call_transfer_in_progress_toast">Call is being transferred</string>
|
||||
<string name="call_transfer_successful_toast">Call has been successfully transferred</string>
|
||||
<string name="call_transfer_failed_toast">Call transfer failed!</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue