Introduce AEP and SSRE2.

This commit is contained in:
Greyson Parrelli
2024-11-18 13:12:58 -05:00
parent 1401256ffd
commit 1b2c0db693
60 changed files with 1162 additions and 511 deletions

View File

@@ -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))

View File

@@ -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.

View File

@@ -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()
}
}
}
}

View File

@@ -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)
}