Store additional data that will allow us to reduce the number of verification SMSs.

main
Nicholas 2023-01-17 16:41:02 -05:00 zatwierdzone przez Cody Henthorne
rodzic dcf8a82c37
commit 70c6e9e60f
10 zmienionych plików z 191 dodań i 2 usunięć

Wyświetl plik

@ -98,9 +98,10 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:replace="android:allowBackup"
android:resizeableActivity="true"
android:allowBackup="false"
android:fullBackupOnly="false"
android:allowBackup="true"
android:backupAgent=".absbackup.SignalBackupAgent"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">

Wyświetl plik

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.absbackup
/**
* Abstracts away the implementation of pieces of data we want to hand off to various backup services.
* Here we can control precisely which data gets backed up and more importantly, what does not.
*/
interface AndroidBackupItem {
fun getKey(): String
fun getDataForBackup(): ByteArray
fun restoreData(data: ByteArray)
}

Wyświetl plik

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.absbackup
import android.app.backup.BackupAgent
import android.app.backup.BackupDataInput
import android.app.backup.BackupDataOutput
import android.os.ParcelFileDescriptor
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
/**
* Uses the [Android Backup Service](https://developer.android.com/guide/topics/data/keyvaluebackup) and backs up everything in [items]
*/
class SignalBackupAgent : BackupAgent() {
private val items: List<AndroidBackupItem> = listOf(
KbsAuthTokens,
)
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {
val contentsHash = cumulativeHashCode()
if (oldState == null) {
performBackup(data)
} else {
val hash = try {
DataInputStream(FileInputStream(oldState.fileDescriptor)).use { it.readInt() }
} catch (e: IOException) {
Log.w(TAG, "No old state, may be first backup request or bug with not writing to newState at end.", e)
}
if (hash != contentsHash) {
performBackup(data)
}
}
DataOutputStream(FileOutputStream(newState.fileDescriptor)).use { it.writeInt(contentsHash) }
}
private fun performBackup(data: BackupDataOutput) {
items.forEach {
val backupData = it.getDataForBackup()
data.writeEntityHeader(it.getKey(), backupData.size)
data.writeEntityData(backupData, backupData.size)
}
}
override fun onRestore(dataInput: BackupDataInput, appVersionCode: Int, newState: ParcelFileDescriptor) {
while (dataInput.readNextHeader()) {
val buffer = ByteArray(dataInput.dataSize)
dataInput.readEntityData(buffer, 0, dataInput.dataSize)
items.find { dataInput.key == it.getKey() }?.restoreData(buffer)
}
DataOutputStream(FileOutputStream(newState.fileDescriptor)).use { it.writeInt(cumulativeHashCode()) }
}
private fun cumulativeHashCode(): Int {
return items.fold("") { acc: String, androidBackupItem: AndroidBackupItem -> acc + androidBackupItem.getDataForBackup().decodeToString() }.hashCode()
}
companion object {
private const val TAG = "SignalBackupAgent"
}
}

Wyświetl plik

@ -0,0 +1,40 @@
package org.thoughtcrime.securesms.absbackup.backupables
import com.google.protobuf.InvalidProtocolBufferException
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.AndroidBackupItem
import org.thoughtcrime.securesms.absbackup.ExternalBackupProtos
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
*/
object KbsAuthTokens : AndroidBackupItem {
private const val TAG = "KbsAuthTokens"
override fun getKey(): String {
return TAG
}
override fun getDataForBackup(): ByteArray {
val registrationRecoveryTokenList = SignalStore.kbsValues().kbsAuthTokenList
val proto = ExternalBackupProtos.KbsAuthToken.newBuilder()
.addAllToken(registrationRecoveryTokenList)
.build()
return proto.toByteArray()
}
override fun restoreData(data: ByteArray) {
if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) {
return
}
try {
val proto = ExternalBackupProtos.KbsAuthToken.parseFrom(data)
SignalStore.kbsValues().putAuthTokenList(proto.tokenList)
} catch (e: InvalidProtocolBufferException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}
}
}

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.StringStringSerializer;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.signalservice.api.KbsPinData;
@ -13,6 +14,8 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class KbsValues extends SignalStoreValues {
@ -24,6 +27,7 @@ public final class KbsValues extends SignalStoreValues {
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
public static final String OPTED_OUT = "kbs.opted_out";
private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
KbsValues(KeyValueStore store) {
super(store);
@ -165,6 +169,30 @@ public final class KbsValues extends SignalStoreValues {
putBoolean(PIN_FORGOTTEN_OR_SKIPPED, value);
}
public synchronized void putAuthTokenList(List<String> tokens) {
putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
}
public synchronized List<String> getKbsAuthTokenList() {
return getList(KBS_AUTH_TOKENS, StringStringSerializer.INSTANCE);
}
/**
* Keeps the 10 most recent KBS auth tokens.
* @param token
* @return whether the token was added (new) or ignored (already existed)
*/
public synchronized boolean appendAuthTokenToList(String token) {
List<String> tokens = getKbsAuthTokenList();
if (tokens.contains(token)) {
return false;
} else {
final List<String> result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList());
putAuthTokenList(result);
return true;
}
}
/** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */
public synchronized void optOut() {
getStore().beginWrite()

Wyświetl plik

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.pin;
import android.app.backup.BackupManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -8,6 +10,7 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.KbsEnclave;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupService;
@ -69,6 +72,7 @@ public class KbsRepository {
try {
authorization = authorization == null ? kbs.getAuthorization() : authorization;
backupAuthToken(authorization);
token = kbs.getToken(authorization);
} catch (NonSuccessfulResponseCodeException e) {
if (e.getCode() == 404) {
@ -95,6 +99,13 @@ public class KbsRepository {
return Objects.requireNonNull(firstKnownTokenData);
}
private static void backupAuthToken(String token) {
final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token);
if (tokenIsNew) {
new BackupManager(ApplicationDependencies.getApplication()).dataChanged();
}
}
/**
* Invoked during registration to restore the master key based on the server response during
* verification.

Wyświetl plik

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

Wyświetl plik

@ -0,0 +1,16 @@
/**
* Copyright (C) 2023 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto3";
package signal;
option java_package = "org.thoughtcrime.securesms.absbackup";
option java_outer_classname = "ExternalBackupProtos";
message KbsAuthToken {
repeated string token = 1;
}

Wyświetl plik

@ -15,3 +15,15 @@ interface IntSerializer<T> : Serializer<T, Int>
interface LongSerializer<T> : Serializer<T, Long>
interface ByteSerializer<T> : Serializer<T, ByteArray>
object StringStringSerializer : StringSerializer<String?> {
override fun serialize(data: String?): String {
return data ?: ""
}
override fun deserialize(data: String): String {
return data
}
}

Wyświetl plik

@ -31,6 +31,10 @@ public final class MasterKey {
return Hex.toStringCondensed(derive("Registration Lock"));
}
public String deriveRegistrationRecoveryToken() {
return Hex.toStringCondensed(derive("Registration Recovery"));
}
public StorageKey deriveStorageServiceKey() {
return new StorageKey(derive("Storage Service Encryption"));
}