kopia lustrzana https://github.com/ryukoposting/Signal-Android
Store additional data that will allow us to reduce the number of verification SMSs.
rodzic
dcf8a82c37
commit
70c6e9e60f
|
@ -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">
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue