mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-27 14:40:22 +00:00
Use separate PNI key distribution endpoint instead of change number.
This commit is contained in:
@@ -113,7 +113,7 @@ class ChangeNumberRepository(
|
||||
.timeout(15, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
fun changeNumber(sessionId: String? = null, recoveryPassword: String? = null, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyResponse>> {
|
||||
fun changeNumber(sessionId: String? = null, recoveryPassword: String? = null, newE164: String): Single<ServiceResponse<VerifyResponse>> {
|
||||
check((sessionId != null && recoveryPassword == null) || (sessionId == null && recoveryPassword != null))
|
||||
|
||||
return Single.fromCallable {
|
||||
@@ -125,8 +125,7 @@ class ChangeNumberRepository(
|
||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
|
||||
sessionId = sessionId,
|
||||
recoveryPassword = recoveryPassword,
|
||||
newE164 = newE164,
|
||||
pniUpdateMode = pniUpdateMode
|
||||
newE164 = newE164
|
||||
)
|
||||
|
||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||
@@ -308,19 +307,17 @@ class ChangeNumberRepository(
|
||||
}.subscribeOn(Schedulers.single())
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
@WorkerThread
|
||||
private fun createChangeNumberRequest(
|
||||
sessionId: String? = null,
|
||||
recoveryPassword: String? = null,
|
||||
newE164: String,
|
||||
registrationLock: String? = null,
|
||||
pniUpdateMode: Boolean = false
|
||||
registrationLock: String? = null
|
||||
): ChangeNumberRequestData {
|
||||
val selfIdentifier: String = SignalStore.account().requireAci().toString()
|
||||
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
|
||||
|
||||
val pniIdentity: IdentityKeyPair = if (pniUpdateMode) SignalStore.account().pniIdentityKey else IdentityKeyUtil.generateIdentityKeyPair()
|
||||
val pniIdentity: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
||||
val devicePniSignedPreKeys = mutableMapOf<Int, SignedPreKeyEntity>()
|
||||
val devicePniLastResortKyberPreKeys = mutableMapOf<Int, KyberPreKeyEntity>()
|
||||
@@ -334,11 +331,7 @@ class ChangeNumberRepository(
|
||||
.forEach { deviceId ->
|
||||
// Signed Prekeys
|
||||
val signedPreKeyRecord: SignedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||
if (pniUpdateMode) {
|
||||
ApplicationDependencies.getProtocolStore().pni().loadSignedPreKey(SignalStore.account().pniPreKeys.activeSignedPreKeyId)
|
||||
} else {
|
||||
PreKeyUtil.generateAndStoreSignedPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||
}
|
||||
PreKeyUtil.generateAndStoreSignedPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||
} else {
|
||||
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||
}
|
||||
@@ -346,22 +339,14 @@ class ChangeNumberRepository(
|
||||
|
||||
// Last-resort kyber prekeys
|
||||
val lastResortKyberPreKeyRecord: KyberPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||
if (pniUpdateMode) {
|
||||
ApplicationDependencies.getProtocolStore().pni().loadKyberPreKey(SignalStore.account().pniPreKeys.lastResortKyberPreKeyId)
|
||||
} else {
|
||||
PreKeyUtil.generateAndStoreLastResortKyberPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||
}
|
||||
PreKeyUtil.generateAndStoreLastResortKyberPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||
} else {
|
||||
PreKeyUtil.generateKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||
}
|
||||
devicePniLastResortKyberPreKeys[deviceId] = KyberPreKeyEntity(lastResortKyberPreKeyRecord.id, lastResortKyberPreKeyRecord.keyPair.publicKey, lastResortKyberPreKeyRecord.signature)
|
||||
|
||||
// Registration Ids
|
||||
var pniRegistrationId = if (deviceId == primaryDeviceId && pniUpdateMode) {
|
||||
SignalStore.account().pniRegistrationId
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
var pniRegistrationId = -1
|
||||
|
||||
while (pniRegistrationId < 0 || pniRegistrationIds.values.contains(pniRegistrationId)) {
|
||||
pniRegistrationId = KeyHelper.generateRegistrationId(false)
|
||||
@@ -378,7 +363,7 @@ class ChangeNumberRepository(
|
||||
.setNewE164(newE164)
|
||||
.build()
|
||||
|
||||
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(deviceId, pniChangeNumber)
|
||||
deviceMessages += messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId, pniChangeNumber)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -568,8 +568,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
sectionHeaderPref(DSLSettingsText.from("PNP"))
|
||||
|
||||
clickPref(
|
||||
title = DSLSettingsText.from("Trigger No-Op Change Number"),
|
||||
summary = DSLSettingsText.from("Mimics the 'Hello world' event"),
|
||||
title = DSLSettingsText.from("Trigger 'Hello World' event"),
|
||||
isEnabled = true,
|
||||
onClick = {
|
||||
SimpleTask.run(viewLifecycleOwner.lifecycle, {
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.safeBlockingGet
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SignalProtocolStore
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.signal.libsignal.protocol.util.KeyHelper
|
||||
import org.signal.libsignal.protocol.util.Medium
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberRepository
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponse
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
|
||||
/**
|
||||
* To be run when all clients support PNP and we need to initialize all linked devices with appropriate PNP data.
|
||||
@@ -23,7 +47,6 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
|
||||
companion object {
|
||||
const val KEY = "PnpInitializeDevicesJob"
|
||||
private val TAG = Log.tag(PnpInitializeDevicesJob::class.java)
|
||||
private const val PLACEHOLDER_SESSION_ID = "123456789"
|
||||
|
||||
@JvmStatic
|
||||
fun enqueueIfNecessary() {
|
||||
@@ -81,13 +104,11 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
|
||||
return
|
||||
}
|
||||
|
||||
val changeNumberRepository = ChangeNumberRepository()
|
||||
val e164 = SignalStore.account().requireE164()
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Calling change number with our current number to distribute PNI messages")
|
||||
changeNumberRepository
|
||||
.changeNumber(sessionId = PLACEHOLDER_SESSION_ID, newE164 = e164, pniUpdateMode = true)
|
||||
Log.i(TAG, "Initializing PNI for linked devices")
|
||||
initializeDevices(e164)
|
||||
.map(::VerifyResponseWithoutKbs)
|
||||
.safeBlockingGet()
|
||||
.resultOrThrow
|
||||
@@ -104,6 +125,109 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeDevices(newE164: String): Single<ServiceResponse<VerifyResponse>> {
|
||||
val accountManager = ApplicationDependencies.getSignalServiceAccountManager()
|
||||
val messageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
||||
|
||||
return Single.fromCallable {
|
||||
var completed = false
|
||||
var attempts = 0
|
||||
lateinit var distributionResponse: ServiceResponse<VerifyAccountResponse>
|
||||
|
||||
while (!completed && attempts < 5) {
|
||||
val request = createInitializeDevicesRequest(
|
||||
newE164 = newE164
|
||||
)
|
||||
|
||||
distributionResponse = accountManager.distributePniKeys(request)
|
||||
|
||||
val possibleError: Throwable? = distributionResponse.applicationError.orNull()
|
||||
if (possibleError is MismatchedDevicesException) {
|
||||
messageSender.handleChangeNumberMismatchDevices(possibleError.mismatchedDevices)
|
||||
attempts++
|
||||
} else {
|
||||
completed = true
|
||||
}
|
||||
}
|
||||
|
||||
VerifyResponse.from(distributionResponse, null, null)
|
||||
}.subscribeOn(Schedulers.single())
|
||||
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createInitializeDevicesRequest(
|
||||
newE164: String
|
||||
): PniKeyDistributionRequest {
|
||||
val selfIdentifier: String = SignalStore.account().requireAci().toString()
|
||||
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
|
||||
val pniProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
||||
val messageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
||||
|
||||
val pniIdentity: IdentityKeyPair = SignalStore.account().pniIdentityKey
|
||||
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
||||
val devicePniSignedPreKeys = mutableMapOf<Int, SignedPreKeyEntity>()
|
||||
val devicePniLastResortKyberPreKeys = mutableMapOf<Int, KyberPreKeyEntity>()
|
||||
val pniRegistrationIds = mutableMapOf<Int, Int>()
|
||||
val primaryDeviceId: Int = SignalServiceAddress.DEFAULT_DEVICE_ID
|
||||
|
||||
val devices: List<Int> = listOf(primaryDeviceId) + aciProtocolStore.getSubDeviceSessions(selfIdentifier)
|
||||
|
||||
devices
|
||||
.filter { it == primaryDeviceId || aciProtocolStore.containsSession(SignalProtocolAddress(selfIdentifier, it)) }
|
||||
.forEach { deviceId ->
|
||||
// Signed Prekeys
|
||||
val signedPreKeyRecord: SignedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||
pniProtocolStore.loadSignedPreKey(SignalStore.account().pniPreKeys.activeSignedPreKeyId)
|
||||
} else {
|
||||
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||
}
|
||||
devicePniSignedPreKeys[deviceId] = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
|
||||
|
||||
// Last-resort kyber prekeys
|
||||
val lastResortKyberPreKeyRecord: KyberPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||
pniProtocolStore.loadKyberPreKey(SignalStore.account().pniPreKeys.lastResortKyberPreKeyId)
|
||||
} else {
|
||||
PreKeyUtil.generateKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||
}
|
||||
devicePniLastResortKyberPreKeys[deviceId] = KyberPreKeyEntity(lastResortKyberPreKeyRecord.id, lastResortKyberPreKeyRecord.keyPair.publicKey, lastResortKyberPreKeyRecord.signature)
|
||||
|
||||
// Registration Ids
|
||||
var pniRegistrationId = if (deviceId == primaryDeviceId) {
|
||||
SignalStore.account().pniRegistrationId
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
|
||||
while (pniRegistrationId < 0 || pniRegistrationIds.values.contains(pniRegistrationId)) {
|
||||
pniRegistrationId = KeyHelper.generateRegistrationId(false)
|
||||
}
|
||||
pniRegistrationIds[deviceId] = pniRegistrationId
|
||||
|
||||
// Device Messages
|
||||
if (deviceId != primaryDeviceId) {
|
||||
val pniChangeNumber = SignalServiceProtos.SyncMessage.PniChangeNumber.newBuilder()
|
||||
.setIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
||||
.setSignedPreKey(signedPreKeyRecord.serialize().toProtoByteString())
|
||||
.setLastResortKyberPreKey(lastResortKyberPreKeyRecord.serialize().toProtoByteString())
|
||||
.setRegistrationId(pniRegistrationId)
|
||||
.setNewE164(newE164)
|
||||
.build()
|
||||
|
||||
deviceMessages += messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId, pniChangeNumber)
|
||||
}
|
||||
}
|
||||
|
||||
return PniKeyDistributionRequest(
|
||||
pniIdentity.publicKey,
|
||||
deviceMessages,
|
||||
devicePniSignedPreKeys.mapKeys { it.key.toString() },
|
||||
devicePniLastResortKyberPreKeys.mapKeys { it.key.toString() },
|
||||
pniRegistrationIds.mapKeys { it.key.toString() },
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
return e is IOException
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user