Add some initial backupV2 network infrastructure.

This commit is contained in:
Greyson Parrelli
2023-12-07 16:11:57 -05:00
committed by Cody Henthorne
parent e17b07bb12
commit 6230a7553d
20 changed files with 609 additions and 11 deletions

View File

@@ -21,12 +21,16 @@ import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.io.ByteArrayOutputStream
import java.io.InputStream
import kotlin.time.Duration.Companion.milliseconds
object BackupRepository {
@@ -149,6 +153,54 @@ object BackupRepository {
Log.d(TAG, "import() ${eventTimer.stop().summary}")
}
/**
* A simple test method that just hits various network endpoints. Only useful for the playground.
*/
fun testNetworkInteractions() {
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
// Just running some sample requests
api
.triggerBackupIdReservation(backupKey)
.then { getAuthCredential() }
.then { credential ->
api.setPublicKey(backupKey, credential)
.also { Log.i(TAG, "PublicKeyResult: $it") }
.map { credential }
}
.then { credential ->
api.getBackupInfo(backupKey, credential)
.also { Log.i(TAG, "BackupInfoResult: $it") }
.map { credential }
}
.then { credential ->
api.getMessageBackupUploadForm(backupKey, credential)
.also { Log.i(TAG, "UploadFormResult: $it") }
}.also { Log.i(TAG, "OverallResponse: $it") }
}
/**
* Retrieves an auth credential, preferring a cached value if available.
*/
private fun getAuthCredential(): NetworkResult<ArchiveServiceCredential> {
val currentTime = System.currentTimeMillis()
val credential = SignalStore.backup().credentialsByDay.getForCurrentTime(currentTime.milliseconds)
if (credential != null) {
return NetworkResult.Success(credential)
}
Log.w(TAG, "No credentials found for today, need to fetch new ones! This shouldn't happen under normal circumstances. We should ensure the routine fetch is running properly.")
return ApplicationDependencies.getSignalServiceAccountManager().archiveApi.getServiceCredentials(currentTime).map { result ->
SignalStore.backup().addCredentials(result.credentials.toList())
SignalStore.backup().clearCredentialsOlderThan(currentTime)
SignalStore.backup().credentialsByDay.getForCurrentTime(currentTime.milliseconds)!!
}
}
data class SelfData(
val aci: ACI,
val pni: PNI,

View File

@@ -97,7 +97,8 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
}
exportFileLauncher.launch(intent)
}
},
onTestNetworkClicked = { viewModel.testNetworkInteractions() }
)
}
@@ -113,7 +114,8 @@ fun Screen(
onImportMemoryClicked: () -> Unit = {},
onImportFileClicked: () -> Unit = {},
onPlaintextClicked: () -> Unit = {},
onSaveToDiskClicked: () -> Unit = {}
onSaveToDiskClicked: () -> Unit = {},
onTestNetworkClicked: () -> Unit = {}
) {
Surface {
Column(
@@ -142,6 +144,12 @@ fun Screen(
) {
Text("Export")
}
Buttons.LargePrimary(
onClick = onTestNetworkClicked,
enabled = state.backupState == BackupState.EXPORT_DONE
) {
Text("Test network")
}
Buttons.LargeTonal(
onClick = onImportMemoryClicked,
enabled = state.backupState == BackupState.EXPORT_DONE

View File

@@ -80,6 +80,13 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
_state.value = _state.value.copy(plaintext = !_state.value.plaintext)
}
fun testNetworkInteractions() {
disposables += Single
.fromCallable { BackupRepository.testNetworkInteractions() }
.subscribeOn(Schedulers.io())
.subscribe()
}
override fun onCleared() {
disposables.clear()
}

View File

@@ -0,0 +1,76 @@
package org.thoughtcrime.securesms.keyvalue
import com.fasterxml.jackson.annotation.JsonProperty
import org.signal.core.util.logging.Log
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
import org.whispersystems.signalservice.internal.util.JsonUtil
import java.io.IOException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
internal class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
companion object {
val TAG = Log.tag(BackupValues::class.java)
val KEY_CREDENTIALS = "backup.credentials"
}
override fun onFirstEverAppLaunch() = Unit
override fun getKeysToIncludeInBackup(): List<String> = emptyList()
/**
* Retrieves the stored credentials, mapped by the day they're valid. The day is represented as
* the unix time (in seconds) of the start of the day. Wrapped in a [ArchiveServiceCredentials]
* type to make it easier to use. See [ArchiveServiceCredentials.getForCurrentTime].
*/
val credentialsByDay: ArchiveServiceCredentials
get() {
val serialized = store.getString(KEY_CREDENTIALS, null) ?: return ArchiveServiceCredentials()
return try {
val map = JsonUtil.fromJson(serialized, SerializedCredentials::class.java).credentialsByDay
ArchiveServiceCredentials(map)
} catch (e: IOException) {
Log.w(TAG, "Invalid JSON! Clearing.", e)
putString(KEY_CREDENTIALS, null)
ArchiveServiceCredentials()
}
}
/**
* Adds the given credentials to the existing list of stored credentials.
*/
fun addCredentials(credentials: List<ArchiveServiceCredential>) {
val current: MutableMap<Long, ArchiveServiceCredential> = credentialsByDay.toMutableMap()
current.putAll(credentials.associateBy { it.redemptionTime })
putString(KEY_CREDENTIALS, JsonUtil.toJson(SerializedCredentials(current)))
}
/**
* Trims out any credentials that are for days older than the given timestamp.
*/
fun clearCredentialsOlderThan(startOfDayInSeconds: Long) {
val current: MutableMap<Long, ArchiveServiceCredential> = credentialsByDay.toMutableMap()
val updated = current.filterKeys { it < startOfDayInSeconds }
putString(KEY_CREDENTIALS, JsonUtil.toJson(SerializedCredentials(updated)))
}
class SerializedCredentials(
@JsonProperty
val credentialsByDay: Map<Long, ArchiveServiceCredential>
)
/**
* A [Map] wrapper that makes it easier to get the credential for the current time.
*/
class ArchiveServiceCredentials(map: Map<Long, ArchiveServiceCredential>) : Map<Long, ArchiveServiceCredential> by map {
constructor() : this(mapOf())
/**
* Retrieves a credential that is valid for the current time, otherwise null.
*/
fun getForCurrentTime(currentTime: Duration): ArchiveServiceCredential? {
val startOfDayInSeconds: Long = currentTime.inWholeDays.days.inWholeSeconds
return this[startOfDayInSeconds]
}
}
}

View File

@@ -44,6 +44,7 @@ public final class SignalStore {
private final ReleaseChannelValues releaseChannelValues;
private final StoryValues storyValues;
private final ApkUpdateValues apkUpdate;
private final BackupValues backupValues;
private final PlainTextSharedPrefsDataStore plainTextValues;
@@ -89,6 +90,7 @@ public final class SignalStore {
this.releaseChannelValues = new ReleaseChannelValues(store);
this.storyValues = new StoryValues(store);
this.apkUpdate = new ApkUpdateValues(store);
this.backupValues = new BackupValues(store);
this.plainTextValues = new PlainTextSharedPrefsDataStore(ApplicationDependencies.getApplication());
}
@@ -270,6 +272,10 @@ public final class SignalStore {
return getInstance().apkUpdate;
}
public static @NonNull BackupValues backup() {
return getInstance().backupValues;
}
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AciAuthorizationCache() {
return GroupsV2AuthorizationSignalStoreCache.createAciCache(getStore());
}

View File

@@ -158,6 +158,12 @@ open class SignalServiceNetworkAccess(context: Context) {
throw AssertionError(e)
}
private val backupServerPublicParams: ByteArray = try {
Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
} catch (e: IOException) {
throw AssertionError(e)
}
private val baseGHostConfigs: List<HostConfig> = listOf(
HostConfig("https://www.google.com", G_HOST, GMAIL_CONNECTION_SPEC),
HostConfig("https://android.clients.google.com", G_HOST, PLAY_CONNECTION_SPEC),
@@ -182,7 +188,8 @@ open class SignalServiceNetworkAccess(context: Context) {
dns = Optional.of(DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = zkGroupServerPublicParams,
genericServerPublicParams = genericServerPublicParams
genericServerPublicParams = genericServerPublicParams,
backupServerPublicParams = backupServerPublicParams
)
private val censorshipConfiguration: Map<Int, SignalServiceConfiguration> = mapOf(
@@ -234,7 +241,8 @@ open class SignalServiceNetworkAccess(context: Context) {
dns = Optional.of(DNS),
signalProxy = if (SignalStore.proxy().isProxyEnabled) Optional.ofNullable(SignalStore.proxy().proxy) else Optional.empty(),
zkGroupServerPublicParams = zkGroupServerPublicParams,
genericServerPublicParams = genericServerPublicParams
genericServerPublicParams = genericServerPublicParams,
backupServerPublicParams = backupServerPublicParams
)
open fun getConfiguration(): SignalServiceConfiguration {
@@ -303,7 +311,8 @@ open class SignalServiceNetworkAccess(context: Context) {
dns = Optional.of(DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = zkGroupServerPublicParams,
genericServerPublicParams = genericServerPublicParams
genericServerPublicParams = genericServerPublicParams,
backupServerPublicParams = backupServerPublicParams
)
}