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:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:replace="android:allowBackup"
|
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:allowBackup="false"
|
android:fullBackupOnly="false"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:backupAgent=".absbackup.SignalBackupAgent"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:largeHeap="true">
|
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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.core.util.StringStringSerializer;
|
||||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
|
@ -13,6 +14,8 @@ import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public final class KbsValues extends SignalStoreValues {
|
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";
|
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
|
||||||
public static final String OPTED_OUT = "kbs.opted_out";
|
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 PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
|
||||||
|
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
|
||||||
|
|
||||||
KbsValues(KeyValueStore store) {
|
KbsValues(KeyValueStore store) {
|
||||||
super(store);
|
super(store);
|
||||||
|
@ -165,6 +169,30 @@ public final class KbsValues extends SignalStoreValues {
|
||||||
putBoolean(PIN_FORGOTTEN_OR_SKIPPED, value);
|
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}. */
|
/** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */
|
||||||
public synchronized void optOut() {
|
public synchronized void optOut() {
|
||||||
getStore().beginWrite()
|
getStore().beginWrite()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.pin;
|
package org.thoughtcrime.securesms.pin;
|
||||||
|
|
||||||
|
import android.app.backup.BackupManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ import org.signal.core.util.logging.Log;
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
import org.thoughtcrime.securesms.KbsEnclave;
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||||
|
@ -69,6 +72,7 @@ public class KbsRepository {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authorization = authorization == null ? kbs.getAuthorization() : authorization;
|
authorization = authorization == null ? kbs.getAuthorization() : authorization;
|
||||||
|
backupAuthToken(authorization);
|
||||||
token = kbs.getToken(authorization);
|
token = kbs.getToken(authorization);
|
||||||
} catch (NonSuccessfulResponseCodeException e) {
|
} catch (NonSuccessfulResponseCodeException e) {
|
||||||
if (e.getCode() == 404) {
|
if (e.getCode() == 404) {
|
||||||
|
@ -95,6 +99,13 @@ public class KbsRepository {
|
||||||
return Objects.requireNonNull(firstKnownTokenData);
|
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
|
* Invoked during registration to restore the master key based on the server response during
|
||||||
* verification.
|
* verification.
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
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 LongSerializer<T> : Serializer<T, Long>
|
||||||
|
|
||||||
interface ByteSerializer<T> : Serializer<T, ByteArray>
|
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"));
|
return Hex.toStringCondensed(derive("Registration Lock"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String deriveRegistrationRecoveryToken() {
|
||||||
|
return Hex.toStringCondensed(derive("Registration Recovery"));
|
||||||
|
}
|
||||||
|
|
||||||
public StorageKey deriveStorageServiceKey() {
|
public StorageKey deriveStorageServiceKey() {
|
||||||
return new StorageKey(derive("Storage Service Encryption"));
|
return new StorageKey(derive("Storage Service Encryption"));
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue