Convert SvrValues to kotlin.

This commit is contained in:
Greyson Parrelli
2024-11-06 11:35:10 -05:00
parent 35b80be8c8
commit 16e36c94de
16 changed files with 276 additions and 302 deletions

View File

@@ -93,7 +93,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
* Key used to backup messages.
*/
val messageBackupKey: MessageBackupKey
get() = SignalStore.svr.getOrCreateMasterKey().derivateMessageBackupKey()
get() = SignalStore.svr.masterKey.derivateMessageBackupKey()
/**
* Key used to backup media. Purely random and separate from the message backup key.
@@ -108,14 +108,14 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
Log.i(TAG, "Generating MediaRootBackupKey...", Throwable())
val bytes = Util.getSecretBytes(32)
putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, bytes)
store.beginWrite().putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, bytes).commit()
return MediaRootBackupKey(bytes)
}
}
set(value) {
lock.withLock {
Log.i(TAG, "Setting MediaRootBackupKey", Throwable())
putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, value.value)
store.beginWrite().putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, value.value).commit()
mediaCredentials.clearAll()
cachedMediaCdnPath = null
}

View File

@@ -33,7 +33,7 @@ public class StorageServiceValues extends SignalStoreValues {
if (getStore().containsKey(SYNC_STORAGE_KEY)) {
return new StorageKey(getBlob(SYNC_STORAGE_KEY, null));
}
return SignalStore.svr().getOrCreateMasterKey().deriveStorageServiceKey();
return SignalStore.svr().getMasterKey().deriveStorageServiceKey();
}
public long getLastSyncTime() {

View File

@@ -1,278 +0,0 @@
package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.StringStringSerializer;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class SvrValues extends SignalStoreValues {
public static final String REGISTRATION_LOCK_ENABLED = "kbs.v2_lock_enabled";
private static final String MASTER_KEY = "kbs.registration_lock_master_key";
private static final String TOKEN_RESPONSE = "kbs.token_response";
private static final String PIN = "kbs.pin";
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
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 SVR2_AUTH_TOKENS = "kbs.kbs_auth_tokens";
private static final String SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp";
private static final String SVR3_AUTH_TOKENS = "kbs.svr3_auth_tokens";
SvrValues(KeyValueStore store) {
super(store);
}
@Override
void onFirstEverAppLaunch() {
}
@Override
@NonNull
List<String> getKeysToIncludeInBackup() {
return List.of(
SVR2_AUTH_TOKENS,
SVR3_AUTH_TOKENS
);
}
/**
* Deliberately does not clear the {@link #MASTER_KEY}.
*/
public void clearRegistrationLockAndPin() {
getStore().beginWrite()
.remove(REGISTRATION_LOCK_ENABLED)
.remove(TOKEN_RESPONSE)
.remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.remove(LAST_CREATE_FAILED_TIMESTAMP)
.remove(OPTED_OUT)
.remove(SVR2_AUTH_TOKENS)
.remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP)
.commit();
}
public synchronized void setMasterKey(@NonNull MasterKey masterKey, @NonNull String pin) {
getStore().beginWrite()
.putBlob(MASTER_KEY, masterKey.serialize())
.putString(LOCK_LOCAL_PIN_HASH, PinHashUtil.localPinHash(pin))
.putString(PIN, pin)
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
.putBoolean(OPTED_OUT, false)
.commit();
}
synchronized void setPinIfNotPresent(@NonNull String pin) {
if (getStore().getString(PIN, null) == null) {
getStore().beginWrite().putString(PIN, pin).commit();
}
}
public synchronized void setRegistrationLockEnabled(boolean enabled) {
putBoolean(REGISTRATION_LOCK_ENABLED, enabled);
}
/**
* Whether or not registration lock V2 is enabled.
*/
public synchronized boolean isRegistrationLockEnabled() {
return getBoolean(REGISTRATION_LOCK_ENABLED, false);
}
public synchronized void onPinCreateFailure() {
putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis());
}
/**
* Whether or not the last time the user attempted to create a PIN, it failed.
*/
public synchronized boolean lastPinCreateFailed() {
return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0;
}
/**
* Finds or creates the master key. Therefore this will always return a master key whether backed
* up or not.
* <p>
* If you only want a key when it's backed up, use {@link #getPinBackedMasterKey()}.
*/
public synchronized @NonNull MasterKey getOrCreateMasterKey() {
byte[] blob = getStore().getBlob(MASTER_KEY, null);
if (blob == null) {
getStore().beginWrite()
.putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
.commit();
blob = getBlob(MASTER_KEY, null);
}
return new MasterKey(blob);
}
/**
* Returns null if master key is not backed up by a pin.
*/
public synchronized @Nullable MasterKey getPinBackedMasterKey() {
if (!isRegistrationLockEnabled()) return null;
return getMasterKey();
}
private synchronized @Nullable MasterKey getMasterKey() {
byte[] blob = getBlob(MASTER_KEY, null);
return blob != null ? new MasterKey(blob) : null;
}
public @Nullable String getRegistrationLockToken() {
MasterKey masterKey = getPinBackedMasterKey();
if (masterKey == null) {
return null;
} else {
return masterKey.deriveRegistrationLock();
}
}
public synchronized @Nullable String getRecoveryPassword() {
MasterKey masterKey = getMasterKey();
if (masterKey != null && hasPin()) {
return masterKey.deriveRegistrationRecoveryPassword();
} else {
return null;
}
}
public synchronized @Nullable String getPin() {
return getString(PIN, null);
}
public synchronized @Nullable String getLocalPinHash() {
return getString(LOCK_LOCAL_PIN_HASH, null);
}
public synchronized boolean hasPin() {
return getLocalPinHash() != null;
}
public synchronized boolean isPinForgottenOrSkipped() {
return getBoolean(PIN_FORGOTTEN_OR_SKIPPED, false);
}
public synchronized void setPinForgottenOrSkipped(boolean value) {
putBoolean(PIN_FORGOTTEN_OR_SKIPPED, value);
}
public synchronized void putSvr2AuthTokens(List<String> tokens) {
putList(SVR2_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
setLastRefreshAuthTimestamp(System.currentTimeMillis());
}
public synchronized void putSvr3AuthTokens(List<String> tokens) {
putList(SVR3_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
setLastRefreshAuthTimestamp(System.currentTimeMillis());
}
public synchronized List<String> getSvr2AuthTokens() {
return getList(SVR2_AUTH_TOKENS, StringStringSerializer.INSTANCE);
}
public synchronized List<String> getSvr3AuthTokens() {
return getList(SVR3_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 appendSvr2AuthTokenToList(String token) {
List<String> tokens = getSvr2AuthTokens();
if (tokens.contains(token)) {
return false;
} else {
final List<String> result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList());
putSvr2AuthTokens(result);
return true;
}
}
/**
* Keeps the 10 most recent SVR3 auth tokens.
* @param token
* @return whether the token was added (new) or ignored (already existed)
*/
public synchronized boolean appendSvr3AuthTokenToList(String token) {
List<String> tokens = getSvr3AuthTokens();
if (tokens.contains(token)) {
return false;
} else {
final List<String> result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList());
putSvr3AuthTokens(result);
return true;
}
}
public boolean removeSvr2AuthTokens(@NonNull List<String> invalid) {
List<String> tokens = new ArrayList<>(getSvr2AuthTokens());
if (tokens.removeAll(invalid)) {
putSvr2AuthTokens(tokens);
return true;
}
return false;
}
public boolean removeSvr3AuthTokens(@NonNull List<String> invalid) {
List<String> tokens = new ArrayList<>(getSvr3AuthTokens());
if (tokens.removeAll(invalid)) {
putSvr3AuthTokens(tokens);
return true;
}
return false;
}
public synchronized void optOut() {
getStore().beginWrite()
.putBoolean(OPTED_OUT, true)
.remove(TOKEN_RESPONSE)
.putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
.remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
.commit();
}
public synchronized boolean hasOptedOut() {
return getBoolean(OPTED_OUT, false);
}
public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() {
String token = getStore().getString(TOKEN_RESPONSE, null);
if (token == null) return null;
try {
return JsonUtils.fromJson(token, TokenResponse.class);
} catch (IOException e) {
throw new AssertionError(e);
}
}
private void setLastRefreshAuthTimestamp(long timestamp) {
putLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, timestamp);
}
public long getLastRefreshAuthTimestamp() {
return getLong(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L);
}
}

View File

@@ -0,0 +1,254 @@
package org.thoughtcrime.securesms.keyvalue
import org.signal.core.util.StringStringSerializer
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.util.JsonUtils
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.kbs.PinHashUtil.localPinHash
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import java.io.IOException
import java.security.SecureRandom
class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
companion object {
private val TAG = Log.tag(SvrValues::class)
const val REGISTRATION_LOCK_ENABLED: String = "kbs.v2_lock_enabled"
const val OPTED_OUT: String = "kbs.opted_out"
private const val MASTER_KEY = "kbs.registration_lock_master_key"
private const val TOKEN_RESPONSE = "kbs.token_response"
private const val PIN = "kbs.pin"
private const val LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash"
private const val LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp"
private const val PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped"
private const val SVR2_AUTH_TOKENS = "kbs.kbs_auth_tokens"
private const val SVR_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp"
private const val SVR3_AUTH_TOKENS = "kbs.svr3_auth_tokens"
}
public override fun onFirstEverAppLaunch() = Unit
public override fun getKeysToIncludeInBackup(): List<String> {
return listOf(
SVR2_AUTH_TOKENS,
SVR3_AUTH_TOKENS
)
}
/** Deliberately does not clear the [MASTER_KEY]. */
@Synchronized
fun clearRegistrationLockAndPin() {
store.beginWrite()
.remove(REGISTRATION_LOCK_ENABLED)
.remove(TOKEN_RESPONSE)
.remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.remove(LAST_CREATE_FAILED_TIMESTAMP)
.remove(OPTED_OUT)
.remove(SVR2_AUTH_TOKENS)
.remove(SVR_LAST_AUTH_REFRESH_TIMESTAMP)
.commit()
}
@Synchronized
fun setMasterKey(masterKey: MasterKey, pin: String) {
store.beginWrite()
.putBlob(MASTER_KEY, masterKey.serialize())
.putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin))
.putString(PIN, pin)
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
.putBoolean(OPTED_OUT, false)
.commit()
}
@Synchronized
fun setPinIfNotPresent(pin: String) {
if (store.getString(PIN, null) == null) {
store.beginWrite().putString(PIN, pin).commit()
}
}
/** Whether or not registration lock V2 is enabled. */
@get:Synchronized
@set:Synchronized
var isRegistrationLockEnabled: Boolean by booleanValue(REGISTRATION_LOCK_ENABLED, false)
@Synchronized
fun onPinCreateFailure() {
putLong(LAST_CREATE_FAILED_TIMESTAMP, System.currentTimeMillis())
}
/** Whether or not the last time the user attempted to create a PIN, it failed. */
@Synchronized
fun lastPinCreateFailed(): Boolean {
return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0
}
@get:Synchronized
val masterKey: MasterKey
/** Returns the Master Key, lazily creating one if needed. */
get() {
val blob = store.getBlob(MASTER_KEY, null)
if (blob != null) {
return MasterKey(blob)
}
Log.i(TAG, "Generating Master Key...", Throwable())
val masterKey = MasterKey.createNew(SecureRandom())
store.beginWrite().putBlob(MASTER_KEY, masterKey.serialize()).commit()
return masterKey
}
@get:Synchronized
val pinBackedMasterKey: MasterKey?
/** Returns null if master key is not backed up by a pin. */
get() {
if (!isRegistrationLockEnabled) return null
return rawMasterKey
}
@get:Synchronized
private val rawMasterKey: MasterKey?
get() = getBlob(MASTER_KEY, null)?.let { MasterKey(it) }
@get:Synchronized
val registrationLockToken: String?
get() {
val masterKey = pinBackedMasterKey
return masterKey?.deriveRegistrationLock()
}
@get:Synchronized
val recoveryPassword: String?
get() {
val masterKey = rawMasterKey
return if (masterKey != null && hasPin()) {
masterKey.deriveRegistrationRecoveryPassword()
} else {
null
}
}
@get:Synchronized
val pin: String? by stringValue(PIN, null)
@get:Synchronized
val localPinHash: String? by stringValue(LOCK_LOCAL_PIN_HASH, null)
@Synchronized
fun hasPin(): Boolean {
return localPinHash != null
}
@get:Synchronized
@set:Synchronized
var isPinForgottenOrSkipped: Boolean by booleanValue(PIN_FORGOTTEN_OR_SKIPPED, false)
@Synchronized
fun putSvr2AuthTokens(tokens: List<String>) {
putList(SVR2_AUTH_TOKENS, tokens, StringStringSerializer)
lastRefreshAuthTimestamp = System.currentTimeMillis()
}
@Synchronized
fun putSvr3AuthTokens(tokens: List<String>) {
putList(SVR3_AUTH_TOKENS, tokens, StringStringSerializer)
lastRefreshAuthTimestamp = System.currentTimeMillis()
}
@get:Synchronized
val svr2AuthTokens: List<String>
get() = getList(SVR2_AUTH_TOKENS, StringStringSerializer).requireNoNulls()
@get:Synchronized
val svr3AuthTokens: List<String>
get() = getList(SVR3_AUTH_TOKENS, StringStringSerializer).requireNoNulls()
/**
* Keeps the 10 most recent KBS auth tokens.
* @param token
* @return whether the token was added (new) or ignored (already existed)
*/
@Synchronized
fun appendSvr2AuthTokenToList(token: String): Boolean {
val tokens = svr2AuthTokens
if (tokens.contains(token)) {
return false
} else {
val result = (listOf(token) + tokens).take(10)
putSvr2AuthTokens(result)
return true
}
}
/**
* Keeps the 10 most recent SVR3 auth tokens.
* @param token
* @return whether the token was added (new) or ignored (already existed)
*/
@Synchronized
fun appendSvr3AuthTokenToList(token: String): Boolean {
val tokens = svr3AuthTokens
if (tokens.contains(token)) {
return false
} else {
val result = (listOf(token) + tokens).take(10)
putSvr3AuthTokens(result)
return true
}
}
@Synchronized
fun removeSvr2AuthTokens(invalid: List<String>): Boolean {
val tokens: MutableList<String> = ArrayList(svr2AuthTokens)
if (tokens.removeAll(invalid)) {
putSvr2AuthTokens(tokens)
return true
}
return false
}
@Synchronized
fun removeSvr3AuthTokens(invalid: List<String>): Boolean {
val tokens: MutableList<String> = ArrayList(svr3AuthTokens)
if (tokens.removeAll(invalid)) {
putSvr3AuthTokens(tokens)
return true
}
return false
}
@Synchronized
fun optOut() {
store.beginWrite()
.putBoolean(OPTED_OUT, true)
.remove(TOKEN_RESPONSE)
.putBlob(MASTER_KEY, MasterKey.createNew(SecureRandom()).serialize())
.remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
.commit()
}
@Synchronized
fun hasOptedOut(): Boolean {
return getBoolean(OPTED_OUT, false)
}
@get:Synchronized
val registrationLockTokenResponse: TokenResponse?
get() {
val token = store.getString(TOKEN_RESPONSE, null) ?: return null
try {
return JsonUtils.fromJson(token, TokenResponse::class.java)
} catch (e: IOException) {
throw AssertionError(e)
}
}
var lastRefreshAuthTimestamp: Long by longValue(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L)
}