mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Add key transparency backend support.
This commit is contained in:
committed by
Greyson Parrelli
parent
26739491a5
commit
423b8c942c
@@ -84,6 +84,13 @@ class VerifyDisplayFragment : Fragment() {
|
||||
binding.caption.setLinkColor(ContextCompat.getColor(requireContext(), CoreUiR.color.signal_colorPrimary))
|
||||
|
||||
viewModel.getAutomaticVerification().observe(viewLifecycleOwner) { status ->
|
||||
if (status == AutomaticVerificationStatus.NONE) {
|
||||
binding.autoVerifyContainer.setOnClickListener {
|
||||
viewModel.verifyAutomatically()
|
||||
}
|
||||
} else {
|
||||
binding.autoVerifyContainer.setOnClickListener(null)
|
||||
}
|
||||
updateStatus(status)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.thoughtcrime.securesms.verify
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.net.KeyTransparency
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.model.KeyTransparencyStore
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Repository for safety numbers, namely to support key transparency / automatic verification
|
||||
*/
|
||||
object VerifySafetyNumberRepository {
|
||||
|
||||
private val TAG = Log.tag(VerifySafetyNumberRepository::class.java)
|
||||
|
||||
/**
|
||||
* Given a recipient will try to verify via search (first time) or monitor (subsequent).
|
||||
*/
|
||||
suspend fun verifyAutomatically(recipient: Recipient): VerifyResult {
|
||||
if (recipient.aci.isEmpty || recipient.e164.isEmpty) {
|
||||
return VerifyResult.UnretryableFailure
|
||||
}
|
||||
|
||||
val identityRecord = AppDependencies.protocolStore.aci().identities().getIdentityRecord(recipient.id)
|
||||
val aciIdentityKey = identityRecord.get().identityKey
|
||||
val aci = recipient.requireAci().libSignalAci
|
||||
val e164 = recipient.requireE164()
|
||||
val unidentifiedAccessKey = ProfileKeyUtil.profileKeyOrNull(recipient.profileKey).let { UnidentifiedAccess.deriveAccessKeyFrom(it) }
|
||||
val monitorMode = if (recipient.isSelf) KeyTransparency.MonitorMode.SELF else KeyTransparency.MonitorMode.OTHER
|
||||
val firstSearch = recipient.keyTransparencyData == null
|
||||
|
||||
val result = if (firstSearch) {
|
||||
Log.i(TAG, "First search in key transparency")
|
||||
SignalNetwork.keyTransparency.search(aci, aciIdentityKey, e164, unidentifiedAccessKey, KeyTransparencyStore)
|
||||
} else {
|
||||
Log.i(TAG, "Monitoring search in key transparency")
|
||||
SignalNetwork.keyTransparency.monitor(monitorMode, aci, aciIdentityKey, e164, unidentifiedAccessKey, KeyTransparencyStore)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Key transparency complete, result: $result")
|
||||
return when (result) {
|
||||
is RequestResult.Success -> {
|
||||
VerifyResult.Success
|
||||
}
|
||||
is RequestResult.NonSuccess -> {
|
||||
if (result.error.exception is IllegalArgumentException) {
|
||||
VerifyResult.CorruptedFailure
|
||||
} else {
|
||||
VerifyResult.UnretryableFailure
|
||||
}
|
||||
}
|
||||
is RequestResult.RetryableNetworkError -> {
|
||||
if (result.retryAfter != null) {
|
||||
VerifyResult.RetryableFailure(result.retryAfter!!)
|
||||
} else {
|
||||
VerifyResult.UnretryableFailure
|
||||
}
|
||||
}
|
||||
is RequestResult.ApplicationError -> VerifyResult.UnretryableFailure
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface VerifyResult {
|
||||
/** Successful verification */
|
||||
data object Success : VerifyResult
|
||||
|
||||
/** Retryable failure **/
|
||||
data class RetryableFailure(val duration: Duration) : VerifyResult
|
||||
|
||||
/** Failure when either the head or the data is corrupted. Retryable if both are reset. */
|
||||
data object CorruptedFailure : VerifyResult
|
||||
|
||||
/** Failures that should not be retried. */
|
||||
data object UnretryableFailure : VerifyResult
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.util.concurrent.SignalDispatchers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
@@ -17,6 +22,7 @@ import org.signal.libsignal.protocol.fingerprint.Fingerprint
|
||||
import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -43,6 +49,52 @@ class VerifySafetyNumberViewModel(
|
||||
|
||||
init {
|
||||
initializeFingerprints()
|
||||
checkAutomaticVerificationEligibility()
|
||||
}
|
||||
|
||||
private fun checkAutomaticVerificationEligibility() {
|
||||
if (recipient.get().e164.isEmpty || recipient.get().aci.isEmpty) {
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.UNAVAILABLE_PERMANENT)
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyAutomatically(canRetry: Boolean = true) {
|
||||
viewModelScope.launch(SignalDispatchers.IO) {
|
||||
if (automaticVerificationLiveData.value == AutomaticVerificationStatus.UNAVAILABLE_PERMANENT || !isActive) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.VERIFYING)
|
||||
|
||||
when (val result = VerifySafetyNumberRepository.verifyAutomatically(recipient.get())) {
|
||||
VerifySafetyNumberRepository.VerifyResult.Success -> {
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.VERIFIED)
|
||||
}
|
||||
is VerifySafetyNumberRepository.VerifyResult.RetryableFailure -> {
|
||||
if (canRetry) {
|
||||
delay(result.duration.toMillis())
|
||||
verifyAutomatically(canRetry = false)
|
||||
} else {
|
||||
Log.i(TAG, "Got a retryable exception, but we already retried once. Ignoring.")
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.UNAVAILABLE_TEMPORARY)
|
||||
}
|
||||
}
|
||||
VerifySafetyNumberRepository.VerifyResult.CorruptedFailure -> {
|
||||
Log.w(TAG, "KT store was corrupted. Clearing everything and starting again.")
|
||||
SignalStore.account.distinguishedHead = null
|
||||
SignalDatabase.recipients.setKeyTransparencyData(recipient.get().requireAci(), null)
|
||||
if (canRetry) {
|
||||
verifyAutomatically(canRetry = false)
|
||||
} else {
|
||||
Log.i(TAG, "Store was corrupted and we can retry, but we already retried once. Ignoring.")
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.UNAVAILABLE_TEMPORARY)
|
||||
}
|
||||
}
|
||||
VerifySafetyNumberRepository.VerifyResult.UnretryableFailure -> {
|
||||
automaticVerificationLiveData.postValue(AutomaticVerificationStatus.UNAVAILABLE_TEMPORARY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFingerprints() {
|
||||
|
||||
Reference in New Issue
Block a user