mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
Introduce AEP and SSRE2.
This commit is contained in:
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
@@ -30,6 +31,9 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.api.util.toByteArray
|
||||
import java.security.SecureRandom
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import org.signal.libsignal.messagebackup.AccountEntropyPool as LibSignalAccountEntropyPool
|
||||
|
||||
class AccountValues internal constructor(store: KeyValueStore, context: Context) : SignalStoreValues(store) {
|
||||
|
||||
@@ -79,6 +83,10 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
||||
private const val KEY_IS_REGISTERED = "account.is_registered"
|
||||
|
||||
private const val KEY_HAS_LINKED_DEVICES = "account.has_linked_devices"
|
||||
|
||||
private const val KEY_ACCOUNT_ENTROPY_POOL = "account.account_entropy_pool"
|
||||
|
||||
private val AEP_LOCK = ReentrantLock()
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -111,10 +119,37 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
||||
KEY_PNI_IDENTITY_PRIVATE_KEY,
|
||||
KEY_USERNAME,
|
||||
KEY_USERNAME_LINK_ENTROPY,
|
||||
KEY_USERNAME_LINK_SERVER_ID
|
||||
KEY_USERNAME_LINK_SERVER_ID,
|
||||
KEY_ACCOUNT_ENTROPY_POOL
|
||||
)
|
||||
}
|
||||
|
||||
val accountEntropyPool: AccountEntropyPool
|
||||
get() {
|
||||
AEP_LOCK.withLock {
|
||||
getString(KEY_ACCOUNT_ENTROPY_POOL, null)?.let {
|
||||
return AccountEntropyPool(it)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Generating Account Entropy Pool (AEP)...")
|
||||
val newAep = LibSignalAccountEntropyPool.generate()
|
||||
putString(KEY_ACCOUNT_ENTROPY_POOL, newAep)
|
||||
return AccountEntropyPool(newAep)
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreAccountEntropyPool(aep: AccountEntropyPool) {
|
||||
AEP_LOCK.withLock {
|
||||
store.beginWrite().putString(KEY_ACCOUNT_ENTROPY_POOL, aep.value).commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun resetAccountEntropyPool() {
|
||||
AEP_LOCK.withLock {
|
||||
store.beginWrite().putString(KEY_ACCOUNT_ENTROPY_POOL, null).commit()
|
||||
}
|
||||
}
|
||||
|
||||
/** The local user's [ACI]. */
|
||||
val aci: ACI?
|
||||
get() = ACI.parseOrNull(getString(KEY_ACI, null))
|
||||
|
||||
@@ -101,7 +101,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
* Key used to backup messages.
|
||||
*/
|
||||
val messageBackupKey: MessageBackupKey
|
||||
get() = SignalStore.svr.masterKey.derivateMessageBackupKey()
|
||||
get() = SignalStore.account.accountEntropyPool.deriveMessageBackupKey()
|
||||
|
||||
/**
|
||||
* Key used to backup media. Purely random and separate from the message backup key.
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
|
||||
class StorageServiceValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
companion object {
|
||||
private val TAG = Log.tag(StorageServiceValues::class)
|
||||
|
||||
private const val LAST_SYNC_TIME = "storage.last_sync_time"
|
||||
private const val NEEDS_ACCOUNT_RESTORE = "storage.needs_account_restore"
|
||||
private const val MANIFEST = "storage.manifest"
|
||||
|
||||
// TODO [linked-device] No need to track this separately -- we'd get the AEP from the primary
|
||||
private const val SYNC_STORAGE_KEY = "storage.syncStorageKey"
|
||||
private const val INITIAL_RESTORE_STORAGE_KEY = "storage.initialRestoreStorageKey"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
|
||||
public override fun getKeysToIncludeInBackup(): List<String> = emptyList()
|
||||
|
||||
@get:Synchronized
|
||||
val storageKey: StorageKey
|
||||
get() {
|
||||
if (store.containsKey(SYNC_STORAGE_KEY)) {
|
||||
@@ -54,4 +59,30 @@ class StorageServiceValues internal constructor(store: KeyValueStore) : SignalSt
|
||||
set(manifest) {
|
||||
putBlob(MANIFEST, manifest.serialize())
|
||||
}
|
||||
|
||||
/**
|
||||
* The [StorageKey] that should be used for our initial storage service data restore.
|
||||
* The presence of this value indicates that it hasn't been used yet.
|
||||
* Once there has been *any* write to storage service, this value needs to be cleared.
|
||||
*/
|
||||
@get:Synchronized
|
||||
@set:Synchronized
|
||||
var storageKeyForInitialDataRestore: StorageKey?
|
||||
get() {
|
||||
return getBlob(INITIAL_RESTORE_STORAGE_KEY, null)?.let { StorageKey(it) }
|
||||
}
|
||||
set(value) {
|
||||
if (value != storageKeyForInitialDataRestore) {
|
||||
if (value == storageKey) {
|
||||
Log.w(TAG, "The key already matches the one derived from the AEP! All good, no need to store it.")
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, null).commit()
|
||||
} else if (value != null) {
|
||||
Log.w(TAG, "Setting initial restore key!", Throwable())
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, value.serialize()).commit()
|
||||
} else {
|
||||
Log.w(TAG, "Clearing initial restore key!", Throwable())
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, null).commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,8 @@ 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 {
|
||||
@@ -16,8 +12,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
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"
|
||||
@@ -42,7 +36,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
fun clearRegistrationLockAndPin() {
|
||||
store.beginWrite()
|
||||
.remove(REGISTRATION_LOCK_ENABLED)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.remove(PIN)
|
||||
.remove(LAST_CREATE_FAILED_TIMESTAMP)
|
||||
@@ -52,10 +45,11 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
.commit()
|
||||
}
|
||||
|
||||
@Deprecated("Switch to restoring AEP instead")
|
||||
@Synchronized
|
||||
fun setMasterKey(masterKey: MasterKey, pin: String?) {
|
||||
store.beginWrite().apply {
|
||||
putBlob(MASTER_KEY, masterKey.serialize())
|
||||
// putBlob(MASTER_KEY, masterKey.serialize())
|
||||
putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
|
||||
putBoolean(OPTED_OUT, false)
|
||||
|
||||
@@ -71,10 +65,21 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
}.commit()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun setPin(pin: String) {
|
||||
store.beginWrite()
|
||||
.putString(PIN, pin)
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin))
|
||||
.commit()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun setPinIfNotPresent(pin: String) {
|
||||
if (store.getString(PIN, null) == null) {
|
||||
store.beginWrite().putString(PIN, pin).commit()
|
||||
store.beginWrite()
|
||||
.putString(PIN, pin)
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin))
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,33 +99,18 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0
|
||||
}
|
||||
|
||||
/** Returns the Master Key, lazily creating one if needed. */
|
||||
@get:Synchronized
|
||||
/** Returns the Master Key */
|
||||
val masterKey: MasterKey
|
||||
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() = SignalStore.account.accountEntropyPool.deriveMasterKey()
|
||||
|
||||
@get:Synchronized
|
||||
val pinBackedMasterKey: MasterKey?
|
||||
/** Returns null if master key is not backed up by a pin. */
|
||||
get() {
|
||||
if (!isRegistrationLockEnabled) return null
|
||||
return rawMasterKey
|
||||
return masterKey
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
private val rawMasterKey: MasterKey?
|
||||
get() = getBlob(MASTER_KEY, null)?.let { MasterKey(it) }
|
||||
|
||||
@get:Synchronized
|
||||
val registrationLockToken: String?
|
||||
get() {
|
||||
@@ -131,8 +121,7 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
@get:Synchronized
|
||||
val recoveryPassword: String?
|
||||
get() {
|
||||
val masterKey = rawMasterKey
|
||||
return if (masterKey != null && hasOptedInWithAccess()) {
|
||||
return if (hasOptedInWithAccess()) {
|
||||
masterKey.deriveRegistrationRecoveryPassword()
|
||||
} else {
|
||||
null
|
||||
@@ -242,8 +231,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
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)
|
||||
.remove(RESTORED_VIA_ACCOUNT_ENTROPY_KEY)
|
||||
@@ -256,17 +243,5 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user