kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix bug with stale linked devices when changing number.
rodzic
24b7593178
commit
ca0e52e141
|
@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.testing.success
|
||||||
import org.thoughtcrime.securesms.testing.timeout
|
import org.thoughtcrime.securesms.testing.timeout
|
||||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.internal.push.MismatchedDevices
|
||||||
import org.whispersystems.signalservice.internal.push.PreKeyState
|
import org.whispersystems.signalservice.internal.push.PreKeyState
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ -249,6 +250,109 @@ class ChangeNumberViewModelTest {
|
||||||
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
|
||||||
|
// GIVEN
|
||||||
|
val aci = Recipient.self().requireServiceId()
|
||||||
|
val newPni = ServiceId.from(UUID.randomUUID())
|
||||||
|
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||||
|
lateinit var setPreKeysRequest: PreKeyState
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
|
||||||
|
Put("/v1/accounts/number") { r ->
|
||||||
|
changeNumberRequest = r.parsedRequestBody()
|
||||||
|
if (changeNumberRequest.deviceMessages.isEmpty()) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/2") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
|
||||||
|
},
|
||||||
|
Put("/v2/keys") { r ->
|
||||||
|
setPreKeysRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
|
||||||
|
// GIVEN
|
||||||
|
val aci = Recipient.self().requireServiceId()
|
||||||
|
val newPni = ServiceId.from(UUID.randomUUID())
|
||||||
|
|
||||||
|
lateinit var changeNumberRequest: ChangePhoneNumberRequest
|
||||||
|
lateinit var setPreKeysRequest: PreKeyState
|
||||||
|
|
||||||
|
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
|
||||||
|
|
||||||
|
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
|
||||||
|
Put("/v1/accounts/number") { r ->
|
||||||
|
changeNumberRequest = r.parsedRequestBody()
|
||||||
|
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
|
||||||
|
MockResponse().failure(423, MockProvider.lockedFailure)
|
||||||
|
} else if (changeNumberRequest.deviceMessages.isEmpty()) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (changeNumberRequest.deviceMessages.size == 1) {
|
||||||
|
MockResponse().failure(
|
||||||
|
409,
|
||||||
|
MismatchedDevices().apply {
|
||||||
|
missingDevices = listOf(2, 3)
|
||||||
|
extraDevices = emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/2") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
|
||||||
|
},
|
||||||
|
Get("/v2/keys/$aci/3") {
|
||||||
|
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
|
||||||
|
},
|
||||||
|
Put("/v2/keys") { r ->
|
||||||
|
setPreKeysRequest = r.parsedRequestBody()
|
||||||
|
MockResponse().success()
|
||||||
|
},
|
||||||
|
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
|
||||||
|
processor.registrationLock() assertIs true
|
||||||
|
Recipient.self().requirePni() assertIsNot newPni
|
||||||
|
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
|
||||||
|
}
|
||||||
|
|
||||||
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
|
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
|
||||||
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
||||||
val pniMetadataStore = SignalStore.account().pniPreKeys
|
val pniMetadataStore = SignalStore.account().pniPreKeys
|
||||||
|
|
|
@ -410,7 +410,7 @@ class RecipientDatabaseTest_processPnpTuple {
|
||||||
fun process(e164: String?, pni: PNI?, aci: ACI?) {
|
fun process(e164: String?, pni: PNI?, aci: ACI?) {
|
||||||
SignalDatabase.rawDatabase.beginTransaction()
|
SignalDatabase.rawDatabase.beginTransaction()
|
||||||
try {
|
try {
|
||||||
generatedIds += recipientDatabase.processPnpTuple(e164, pni, aci, pniVerified = false, pnpEnabled = true).finalId
|
generatedIds += recipientDatabase.processPnpTuple(e164, pni, aci, pniVerified = false).finalId
|
||||||
SignalDatabase.rawDatabase.setTransactionSuccessful()
|
SignalDatabase.rawDatabase.setTransactionSuccessful()
|
||||||
} finally {
|
} finally {
|
||||||
SignalDatabase.rawDatabase.endTransaction()
|
SignalDatabase.rawDatabase.endTransaction()
|
||||||
|
|
|
@ -109,7 +109,7 @@ class MyStoryMigrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runMigration() {
|
private fun runMigration() {
|
||||||
MyStoryMigration.migrate(
|
V151_MyStoryMigration.migrate(
|
||||||
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
|
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
|
||||||
SignalDatabase.rawDatabase,
|
SignalDatabase.rawDatabase,
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -77,6 +77,7 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
|
||||||
serviceNetworkAccessMock = mock {
|
serviceNetworkAccessMock = mock {
|
||||||
on { getConfiguration() } doReturn uncensoredConfiguration
|
on { getConfiguration() } doReturn uncensoredConfiguration
|
||||||
on { getConfiguration(any()) } doReturn uncensoredConfiguration
|
on { getConfiguration(any()) } doReturn uncensoredConfiguration
|
||||||
|
on { uncensoredConfiguration } doReturn uncensoredConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBackupService = mock()
|
keyBackupService = mock()
|
||||||
|
|
|
@ -6,7 +6,14 @@ import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.stub
|
import org.mockito.kotlin.stub
|
||||||
import org.signal.core.util.Hex
|
import org.signal.core.util.Hex
|
||||||
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
|
import org.signal.libsignal.protocol.state.PreKeyRecord
|
||||||
|
import org.signal.libsignal.protocol.util.KeyHelper
|
||||||
|
import org.signal.libsignal.protocol.util.Medium
|
||||||
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||||
import org.thoughtcrime.securesms.pin.TokenData
|
import org.thoughtcrime.securesms.pin.TokenData
|
||||||
import org.thoughtcrime.securesms.test.BuildConfig
|
import org.thoughtcrime.securesms.test.BuildConfig
|
||||||
|
@ -16,10 +23,14 @@ import org.whispersystems.signalservice.api.kbs.HashedPin
|
||||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||||
import org.whispersystems.signalservice.internal.push.DeviceInfoList
|
import org.whispersystems.signalservice.internal.push.DeviceInfoList
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyEntity
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyResponse
|
||||||
|
import org.whispersystems.signalservice.internal.push.PreKeyResponseItem
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||||
import org.whispersystems.signalservice.internal.push.SenderCertificate
|
import org.whispersystems.signalservice.internal.push.SenderCertificate
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||||
|
@ -83,4 +94,21 @@ object MockProvider {
|
||||||
on { newRegistrationSession(any(), any()) } doReturn session
|
on { newRegistrationSession(any(), any()) } doReturn session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
|
||||||
|
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
|
||||||
|
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())
|
||||||
|
|
||||||
|
val device = PreKeyResponseItem().apply {
|
||||||
|
this.deviceId = deviceId
|
||||||
|
registrationId = KeyHelper.generateRegistrationId(false)
|
||||||
|
signedPreKey = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
|
||||||
|
preKey = PreKeyEntity(oneTimePreKey.id, oneTimePreKey.keyPair.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PreKeyResponse().apply {
|
||||||
|
identityKey = identity.publicKey
|
||||||
|
devices = listOf(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import io.reactivex.rxjava3.core.Single
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
|
import org.signal.libsignal.protocol.SignalProtocolAddress
|
||||||
import org.signal.libsignal.protocol.state.SignalProtocolStore
|
import org.signal.libsignal.protocol.state.SignalProtocolStore
|
||||||
import org.signal.libsignal.protocol.util.KeyHelper
|
import org.signal.libsignal.protocol.util.KeyHelper
|
||||||
import org.signal.libsignal.protocol.util.Medium
|
import org.signal.libsignal.protocol.util.Medium
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
||||||
|
@ -30,7 +30,6 @@ import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender
|
||||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
|
|
||||||
import org.whispersystems.signalservice.api.push.PNI
|
import org.whispersystems.signalservice.api.push.PNI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
import org.whispersystems.signalservice.api.push.ServiceIdType
|
import org.whispersystems.signalservice.api.push.ServiceIdType
|
||||||
|
@ -41,13 +40,17 @@ import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
|
||||||
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
|
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
|
||||||
|
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
private val TAG: String = Log.tag(ChangeNumberRepository::class.java)
|
private val TAG: String = Log.tag(ChangeNumberRepository::class.java)
|
||||||
|
|
||||||
class ChangeNumberRepository(private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager()) {
|
class ChangeNumberRepository(
|
||||||
|
private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager(),
|
||||||
|
private val messageSender: SignalServiceMessageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
||||||
|
) {
|
||||||
|
|
||||||
fun ensureDecryptionsDrained(): Completable {
|
fun ensureDecryptionsDrained(): Completable {
|
||||||
return Completable.create { emitter ->
|
return Completable.create { emitter ->
|
||||||
|
@ -61,9 +64,26 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||||
|
|
||||||
fun changeNumber(code: String, newE164: String): Single<ServiceResponse<VerifyAccountResponse>> {
|
fun changeNumber(code: String, newE164: String): Single<ServiceResponse<VerifyAccountResponse>> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
|
var completed = false
|
||||||
|
var attempts = 0
|
||||||
|
lateinit var changeNumberResponse: ServiceResponse<VerifyAccountResponse>
|
||||||
|
|
||||||
|
while (!completed && attempts < 5) {
|
||||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, null)
|
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, null)
|
||||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||||
accountManager.changeNumber(request)
|
|
||||||
|
changeNumberResponse = accountManager.changeNumber(request)
|
||||||
|
|
||||||
|
val possibleError: Throwable? = changeNumberResponse.applicationError.orElse(null)
|
||||||
|
if (possibleError is MismatchedDevicesException) {
|
||||||
|
messageSender.handleChangeNumberMismatchDevices(possibleError.mismatchedDevices)
|
||||||
|
attempts++
|
||||||
|
} else {
|
||||||
|
completed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeNumberResponse
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
||||||
}
|
}
|
||||||
|
@ -75,22 +95,40 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||||
tokenData: TokenData
|
tokenData: TokenData
|
||||||
): Single<ServiceResponse<VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse>> {
|
): Single<ServiceResponse<VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse>> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
try {
|
val kbsData: KbsPinData
|
||||||
val kbsData: KbsPinData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
|
val registrationLock: String
|
||||||
val registrationLock: String = kbsData.masterKey.deriveRegistrationLock()
|
|
||||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, registrationLock)
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
kbsData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
|
||||||
|
registrationLock = kbsData.masterKey.deriveRegistrationLock()
|
||||||
|
} catch (e: KeyBackupSystemWrongPinException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
} catch (e: KeyBackupSystemNoDataException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
return@fromCallable ServiceResponse.forExecutionError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed = false
|
||||||
|
var attempts = 0
|
||||||
|
lateinit var changeNumberResponse: ServiceResponse<VerifyAccountResponse>
|
||||||
|
|
||||||
|
while (!completed && attempts < 5) {
|
||||||
|
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, registrationLock)
|
||||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||||
|
|
||||||
val response: ServiceResponse<VerifyAccountResponse> = accountManager.changeNumber(request)
|
changeNumberResponse = accountManager.changeNumber(request)
|
||||||
VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(response, kbsData)
|
|
||||||
} catch (e: KeyBackupSystemWrongPinException) {
|
val possibleError: Throwable? = changeNumberResponse.applicationError.orElse(null)
|
||||||
ServiceResponse.forExecutionError(e)
|
if (possibleError is MismatchedDevicesException) {
|
||||||
} catch (e: KeyBackupSystemNoDataException) {
|
messageSender.handleChangeNumberMismatchDevices(possibleError.mismatchedDevices)
|
||||||
ServiceResponse.forExecutionError(e)
|
attempts++
|
||||||
} catch (e: IOException) {
|
} else {
|
||||||
ServiceResponse.forExecutionError(e)
|
completed = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(changeNumberResponse, kbsData)
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
|
||||||
}
|
}
|
||||||
|
@ -199,24 +237,23 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||||
newE164: String,
|
newE164: String,
|
||||||
registrationLock: String?
|
registrationLock: String?
|
||||||
): ChangeNumberRequestData {
|
): ChangeNumberRequestData {
|
||||||
val messageSender: SignalServiceMessageSender = ApplicationDependencies.getSignalServiceMessageSender()
|
val selfIdentifier: String = SignalStore.account().requireAci().toString()
|
||||||
val pniProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().pni()
|
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
|
||||||
val pniMetadataStore: PreKeyMetadataStore = SignalStore.account().pniPreKeys
|
|
||||||
|
|
||||||
val devices: List<DeviceInfo> = accountManager.getDevices()
|
|
||||||
|
|
||||||
val pniIdentity: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
val pniIdentity: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
val deviceMessages = mutableListOf<OutgoingPushMessage>()
|
||||||
val devicePniSignedPreKeys = mutableMapOf<String, SignedPreKeyEntity>()
|
val devicePniSignedPreKeys = mutableMapOf<Int, SignedPreKeyEntity>()
|
||||||
val pniRegistrationIds = mutableMapOf<String, Int>()
|
val pniRegistrationIds = mutableMapOf<Int, Int>()
|
||||||
val primaryDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID.toString()
|
val primaryDeviceId: Int = SignalServiceAddress.DEFAULT_DEVICE_ID
|
||||||
|
|
||||||
for (device in devices) {
|
val devices: List<Int> = listOf(primaryDeviceId) + aciProtocolStore.getSubDeviceSessions(selfIdentifier)
|
||||||
val deviceId = device.id.toString()
|
|
||||||
|
|
||||||
|
devices
|
||||||
|
.filter { it == primaryDeviceId || aciProtocolStore.containsSession(SignalProtocolAddress(selfIdentifier, it)) }
|
||||||
|
.forEach { deviceId ->
|
||||||
// Signed Prekeys
|
// Signed Prekeys
|
||||||
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
|
||||||
PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey)
|
PreKeyUtil.generateAndStoreSignedPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey)
|
||||||
} else {
|
} else {
|
||||||
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
|
||||||
}
|
}
|
||||||
|
@ -237,11 +274,20 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan
|
||||||
.setRegistrationId(pniRegistrationId)
|
.setRegistrationId(pniRegistrationId)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(device.id, pniChangeNumber)
|
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(deviceId, pniChangeNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = ChangePhoneNumberRequest(newE164, code, registrationLock, pniIdentity.publicKey, deviceMessages, devicePniSignedPreKeys, pniRegistrationIds)
|
val request = ChangePhoneNumberRequest(
|
||||||
|
newE164,
|
||||||
|
code,
|
||||||
|
registrationLock,
|
||||||
|
pniIdentity.publicKey,
|
||||||
|
deviceMessages,
|
||||||
|
devicePniSignedPreKeys.mapKeys { it.key.toString() },
|
||||||
|
pniRegistrationIds.mapKeys { it.key.toString() }
|
||||||
|
)
|
||||||
|
|
||||||
val metadata = PendingChangeNumberMetadata.newBuilder()
|
val metadata = PendingChangeNumberMetadata.newBuilder()
|
||||||
.setPreviousPni(SignalStore.account().pni!!.toByteString())
|
.setPreviousPni(SignalStore.account().pni!!.toByteString())
|
||||||
.setPniIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
.setPniIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
|
||||||
|
|
|
@ -211,7 +211,7 @@ open class SignalServiceNetworkAccess(context: Context) {
|
||||||
COUNTRY_CODE_UZBEKISTAN,
|
COUNTRY_CODE_UZBEKISTAN,
|
||||||
)
|
)
|
||||||
|
|
||||||
val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
|
open val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
|
||||||
arrayOf(SignalServiceUrl(BuildConfig.SIGNAL_URL, serviceTrustStore)),
|
arrayOf(SignalServiceUrl(BuildConfig.SIGNAL_URL, serviceTrustStore)),
|
||||||
mapOf(
|
mapOf(
|
||||||
0 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, serviceTrustStore)),
|
0 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, serviceTrustStore)),
|
||||||
|
|
|
@ -2235,6 +2235,12 @@ public class SignalServiceMessageSender {
|
||||||
archiveSessions(recipient, staleDevices.getStaleDevices());
|
archiveSessions(recipient, staleDevices.getStaleDevices());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleChangeNumberMismatchDevices(@Nonnull MismatchedDevices mismatchedDevices)
|
||||||
|
throws IOException, UntrustedIdentityException
|
||||||
|
{
|
||||||
|
handleMismatchedDevices(socket, localAddress, mismatchedDevices);
|
||||||
|
}
|
||||||
|
|
||||||
private void archiveSessions(SignalServiceAddress recipient, List<Integer> devices) {
|
private void archiveSessions(SignalServiceAddress recipient, List<Integer> devices) {
|
||||||
List<SignalProtocolAddress> addressesToClear = convertToProtocolAddresses(recipient, devices);
|
List<SignalProtocolAddress> addressesToClear = convertToProtocolAddresses(recipient, devices);
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,10 @@ import java.util.List;
|
||||||
|
|
||||||
public class MismatchedDevices {
|
public class MismatchedDevices {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private List<Integer> missingDevices;
|
public List<Integer> missingDevices;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private List<Integer> extraDevices;
|
public List<Integer> extraDevices;
|
||||||
|
|
||||||
public List<Integer> getMissingDevices() {
|
public List<Integer> getMissingDevices() {
|
||||||
return missingDevices;
|
return missingDevices;
|
||||||
|
|
|
@ -20,6 +20,8 @@ public class OutgoingPushMessage {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
public OutgoingPushMessage() {}
|
||||||
|
|
||||||
public OutgoingPushMessage(int type,
|
public OutgoingPushMessage(int type,
|
||||||
int destinationDeviceId,
|
int destinationDeviceId,
|
||||||
int destinationRegistrationId,
|
int destinationRegistrationId,
|
||||||
|
|
|
@ -20,10 +20,10 @@ public class PreKeyResponse {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
|
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
|
||||||
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
|
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
|
||||||
private IdentityKey identityKey;
|
public IdentityKey identityKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private List<PreKeyResponseItem> devices;
|
public List<PreKeyResponseItem> devices;
|
||||||
|
|
||||||
public IdentityKey getIdentityKey() {
|
public IdentityKey getIdentityKey() {
|
||||||
return identityKey;
|
return identityKey;
|
||||||
|
|
|
@ -13,16 +13,16 @@ import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
|
||||||
public class PreKeyResponseItem {
|
public class PreKeyResponseItem {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int deviceId;
|
public int deviceId;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private int registrationId;
|
public int registrationId;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private SignedPreKeyEntity signedPreKey;
|
public SignedPreKeyEntity signedPreKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private PreKeyEntity preKey;
|
public PreKeyEntity preKey;
|
||||||
|
|
||||||
public int getDeviceId() {
|
public int getDeviceId() {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
|
|
Ładowanie…
Reference in New Issue