Add support for PNI registration ids and PNP change number.

fork-5.53.8
Cody Henthorne 2022-08-03 11:50:16 -04:00
rodzic 0d3ea22641
commit 83b97d274f
54 zmienionych plików z 1273 dodań i 188 usunięć

Wyświetl plik

@ -231,7 +231,7 @@ android {
} }
} }
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
} }
@ -544,6 +544,9 @@ dependencies {
androidTestImplementation testLibs.androidx.test.core androidTestImplementation testLibs.androidx.test.core
androidTestImplementation testLibs.androidx.test.core.ktx androidTestImplementation testLibs.androidx.test.core.ktx
androidTestImplementation testLibs.androidx.test.ext.junit.ktx androidTestImplementation testLibs.androidx.test.ext.junit.ktx
androidTestImplementation testLibs.mockito.android
androidTestImplementation testLibs.mockito.kotlin
androidTestImplementation testLibs.square.okhttp.mockserver
testImplementation testLibs.espresso.core testImplementation testLibs.espresso.core

Wyświetl plik

@ -0,0 +1,15 @@
package org.thoughtcrime.securesms
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
/**
* Application context for running instrumentation tests (aka androidTests).
*/
class SignalInstrumentationApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() {
val default = ApplicationDependencyProvider(this)
ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
}
}

Wyświetl plik

@ -0,0 +1,272 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber
import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyAccountResponseProcessor
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.MockProvider
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.testing.connectionFailure
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.testing.timeout
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ChangeNumberViewModelTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
private lateinit var kbsRepository: KbsRepository
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
kbsRepository = mock()
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application),
kbsRepository = kbsRepository
)
viewModel.setNewCountry(1)
viewModel.setNewNationalNumber("5555550102")
}
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// 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()
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
},
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)
}
/**
* If we encounter a server error, this means the server ack our request and rejected it. In this
* case we know the change *did not* take on the server and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenServerFailedApiCall() {
// GIVEN
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { MockResponse().failure(500) },
)
// WHEN
val processor: VerifyAccountResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs true
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our old details, so we can know the change *did not* take on the server
* and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToServer() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { MockResponse().connectionFailure() },
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) }
)
// WHEN
val processor: VerifyAccountResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs false
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our new details, so we can know the change *did* take on the server
* and need to keep the app in a locked state. The test then uses the ChangeNumberLockActivity to unlock
* and apply the pending state after confirming the change on the server.
*/
@Test
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToClient() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
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()
MockResponse().timeout()
},
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, newPni, "+15555550102")) },
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
val processor: VerifyAccountResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs true
SignalStore.misc().pendingChangeNumberMetadata.assertIsNotNull()
// WHEN AGAIN Processing lock
val scenario = harness.launchActivity<ChangeNumberLockActivity>()
scenario.onActivity {}
ThreadUtil.sleep(500)
// THEN AGAIN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// 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(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
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) {
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore = SignalStore.account().pniPreKeys
Recipient.self().requireE164() assertIs "+15555550102"
Recipient.self().requirePni() assertIs newPni
SignalStore.account().pniRegistrationId assertIs changeNumberRequest.pniRegistrationIds["1"]!!
SignalStore.account().pniIdentityKey.publicKey assertIs changeNumberRequest.pniIdentityKey
pniMetadataStore.activeSignedPreKeyId assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.keyId
val activeSignedPreKey: SignedPreKeyRecord = pniProtocolStore.loadSignedPreKey(pniMetadataStore.activeSignedPreKeyId)
activeSignedPreKey.keyPair.publicKey assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.publicKey
activeSignedPreKey.signature assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.signature
setPreKeysRequest.signedPreKey.publicKey assertIs activeSignedPreKey.keyPair.publicKey
setPreKeysRequest.preKeys assertIsSize 100
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
}

Wyświetl plik

@ -0,0 +1,109 @@
package org.thoughtcrime.securesms.dependencies
import android.app.Application
import okhttp3.ConnectionSpec
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.KbsEnclave
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import java.security.KeyStore
import java.util.Optional
/**
* Dependency provider used for instrumentation tests (aka androidTests).
*
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess] and
* [KeyBackupService].
*/
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val keyBackupService: KeyBackupService
init {
runSync {
webServer = MockWebServer()
baseUrl = webServer.url("").toString()
}
webServer.setDispatcher(object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val handler = handlers.firstOrNull {
request.method == it.verb && request.path.startsWith("/${it.path}")
}
return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500)
}
})
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
arrayOf(SignalContactDiscoveryUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {
on { getConfiguration() } doReturn uncensoredConfiguration
on { getConfiguration(any()) } doReturn uncensoredConfiguration
}
keyBackupService = mock()
}
override fun provideSignalServiceNetworkAccess(): SignalServiceNetworkAccess {
return serviceNetworkAccessMock
}
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
companion object {
lateinit var webServer: MockWebServer
private set
lateinit var baseUrl: String
private set
private val handlers: MutableList<Verb> = mutableListOf()
fun addMockWebRequestHandlers(vararg verbs: Verb) {
handlers.addAll(verbs)
}
fun clearHandlers() {
handlers.clear()
}
}
}

Wyświetl plik

@ -0,0 +1,86 @@
package org.thoughtcrime.securesms.testing
import io.reactivex.rxjava3.core.Single
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.kbs.HashedPin
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PushServiceSocket
import org.whispersystems.signalservice.internal.push.SenderCertificate
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import java.security.SecureRandom
/**
* Warehouse of reusable test data and mock configurations.
*/
object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
backupCredentials = AuthCredentials.create("username", "password")
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
devices = listOf(
DeviceInfo().apply {
id = 1
}
)
}
fun createVerifyAccountResponse(aci: ServiceId, newPni: ServiceId): VerifyAccountResponse {
return VerifyAccountResponse().apply {
uuid = aci.toString()
pni = newPni.toString()
storageCapable = false
}
}
fun createWhoAmIResponse(aci: ServiceId, pni: ServiceId, e164: String): WhoAmIResponse {
return WhoAmIResponse().apply {
this.uuid = aci.toString()
this.pni = pni.toString()
this.number = e164
}
}
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
val tokenData: TokenData = mock {
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
on { basicAuth } doReturn "basicAuth"
on { triesRemaining } doReturn 10
on { tokenResponse } doReturn TokenResponse()
}
kbsRepository.stub {
on { getToken(any() as String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
}
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: HashedPin?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(any(), any()) } doReturn session
}
}
}

Wyświetl plik

@ -0,0 +1,48 @@
package org.thoughtcrime.securesms.testing
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import okhttp3.mockwebserver.SocketPolicy
import org.thoughtcrime.securesms.util.JsonUtils
import java.util.concurrent.TimeUnit
typealias ResponseFactory = (request: RecordedRequest) -> MockResponse
/**
* Represent an HTTP verb for mocking web requests.
*/
sealed class Verb(val verb: String, val path: String, val responseFactory: ResponseFactory)
class Get(path: String, responseFactory: ResponseFactory) : Verb("GET", path, responseFactory)
class Put(path: String, responseFactory: ResponseFactory) : Verb("PUT", path, responseFactory)
fun MockResponse.success(response: Any? = null): MockResponse {
return setResponseCode(200).apply {
if (response != null) {
setBody(JsonUtils.toJson(response))
}
}
}
fun MockResponse.failure(code: Int, response: Any? = null): MockResponse {
return setResponseCode(code).apply {
if (response != null) {
setBody(JsonUtils.toJson(response))
}
}
}
fun MockResponse.connectionFailure(): MockResponse {
return setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
}
fun MockResponse.timeout(): MockResponse {
return setHeadersDelay(1, TimeUnit.DAYS)
.setBodyDelay(1, TimeUnit.DAYS)
}
inline fun <reified T> RecordedRequest.parsedRequestBody(): T {
val bodyString = String(body.readByteArray())
return JsonUtils.fromJson(bodyString, T::class.java)
}

Wyświetl plik

@ -8,6 +8,7 @@ import android.content.SharedPreferences
import android.preference.PreferenceManager import android.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.mockwebserver.MockResponse
import org.junit.rules.ExternalResource import org.junit.rules.ExternalResource
import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.SignalProtocolAddress import org.signal.libsignal.protocol.SignalProtocolAddress
@ -17,6 +18,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
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.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
@ -29,6 +31,8 @@ import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.ACI import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
import java.util.UUID import java.util.UUID
@ -54,6 +58,8 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
context = InstrumentationRegistry.getInstrumentation().targetContext context = InstrumentationRegistry.getInstrumentation().targetContext
self = setupSelf() self = setupSelf()
others = setupOthers() others = setupOthers()
InstrumentationApplicationDependencyProvider.clearHandlers()
} }
private fun setupSelf(): Recipient { private fun setupSelf(): Recipient {
@ -67,18 +73,22 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
val registrationRepository = RegistrationRepository(application) val registrationRepository = RegistrationRepository(application)
registrationRepository.registerAccountWithoutRegistrationLock( InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
val response: ServiceResponse<VerifyAccountResponse> = registrationRepository.registerAccountWithoutRegistrationLock(
RegistrationData( RegistrationData(
code = "123123", code = "123123",
e164 = "+15554045550101", e164 = "+15555550101",
password = Util.getSecret(18), password = Util.getSecret(18),
registrationId = registrationRepository.registrationId, registrationId = registrationRepository.registrationId,
profileKey = registrationRepository.getProfileKey("+15554045550101"), profileKey = registrationRepository.getProfileKey("+15555550101"),
fcmToken = null fcmToken = null,
pniRegistrationId = registrationRepository.pniRegistrationId
), ),
VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false) VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false)
).blockingGet() ).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut() SignalStore.kbsValues().optOut()
RegistrationUtil.maybeMarkRegistrationComplete(application) RegistrationUtil.maybeMarkRegistrationComplete(application)
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@ -107,7 +117,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
return others return others
} }
inline fun <reified T : Activity> launchActivity(initIntent: Intent.() -> Unit): ActivityScenario<T> { inline fun <reified T : Activity> launchActivity(initIntent: Intent.() -> Unit = {}): ActivityScenario<T> {
return androidx.test.core.app.launchActivity(Intent(context, T::class.java).apply(initIntent)) return androidx.test.core.app.launchActivity(Intent(context, T::class.java).apply(initIntent))
} }

Wyświetl plik

@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.testing
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import org.thoughtcrime.securesms.SignalInstrumentationApplicationContext
/**
* Custom runner that replaces application with [SignalInstrumentationApplicationContext].
*/
@Suppress("unused")
class SignalTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, SignalInstrumentationApplicationContext::class.java.name, context)
}
}

Wyświetl plik

@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.testing
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import java.util.concurrent.CountDownLatch
/**
* Run the given [runnable] on a new thread and wait for it to finish.
*/
fun runSync(runnable: () -> Unit) {
val lock = CountDownLatch(1)
Thread {
try {
runnable.invoke()
} finally {
lock.countDown()
}
}.start()
lock.await()
}
/* Various kotlin-ifications of hamcrest matchers */
fun <T : Any?> T.assertIsNull() {
assertThat(this, nullValue())
}
fun <T : Any?> T.assertIsNotNull() {
assertThat(this, notNullValue())
}
infix fun <T : Any> T.assertIs(expected: T) {
assertThat(this, `is`(expected))
}
infix fun <T : Any> T.assertIsNot(expected: T) {
assertThat(this, not(`is`(expected)))
}
infix fun <E, T : Collection<E>> T.assertIsSize(expected: Int) {
assertThat(this, hasSize(expected))
}

Wyświetl plik

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<application
android:usesCleartextTraffic="true"
tools:replace="android:usesCleartextTraffic"
tools:ignore="UnusedAttribute" />
</manifest>

Wyświetl plik

@ -21,6 +21,7 @@ import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
@ -329,7 +330,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getIncomingMessageObserver(); ApplicationDependencies.getIncomingMessageObserver();
} }
private void initializeAppDependencies() { @VisibleForTesting
void initializeAppDependencies() {
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this)); ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
} }

Wyświetl plik

@ -38,7 +38,7 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
setContentView(R.layout.activity_change_number_lock) setContentView(R.layout.activity_change_number_lock)
changeNumberRepository = ChangeNumberRepository(applicationContext) changeNumberRepository = ChangeNumberRepository()
checkWhoAmI() checkWhoAmI()
} }
@ -50,8 +50,8 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
override fun onBackPressed() = Unit override fun onBackPressed() = Unit
private fun checkWhoAmI() { private fun checkWhoAmI() {
disposables.add( disposables += changeNumberRepository
changeNumberRepository.whoAmI() .whoAmI()
.flatMap { whoAmI -> .flatMap { whoAmI ->
if (Objects.equals(whoAmI.number, SignalStore.account().e164)) { if (Objects.equals(whoAmI.number, SignalStore.account().e164)) {
Log.i(TAG, "Local and remote numbers match, nothing needs to be done.") Log.i(TAG, "Local and remote numbers match, nothing needs to be done.")
@ -64,11 +64,11 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = { onChangeStatusConfirmed() }, onError = this::onFailedToGetChangeNumberStatus) .subscribeBy(onSuccess = { onChangeStatusConfirmed() }, onError = this::onFailedToGetChangeNumberStatus)
)
} }
private fun onChangeStatusConfirmed() { private fun onChangeStatusConfirmed() {
SignalStore.misc().unlockChangeNumber() SignalStore.misc().unlockChangeNumber()
SignalStore.misc().clearPendingChangeNumberMetadata()
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(R.string.ChangeNumberLockActivity__change_status_confirmed) .setTitle(R.string.ChangeNumberLockActivity__change_status_confirmed)

Wyświetl plik

@ -1,11 +1,21 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber package org.thoughtcrime.securesms.components.settings.app.changenumber
import android.content.Context
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single 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.state.SignalProtocolStore
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.protocol.util.Medium
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
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.toProtoByteString
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.CertificateType import org.thoughtcrime.securesms.keyvalue.CertificateType
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
@ -17,22 +27,45 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.whispersystems.signalservice.api.KbsPinData import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.SignalServiceMessageSender
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.ServiceIdType
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.ServiceResponse
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
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 java.io.IOException import java.io.IOException
import java.security.MessageDigest import java.security.MessageDigest
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 context: Context) { class ChangeNumberRepository(private val accountManager: SignalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager()) {
private val accountManager = ApplicationDependencies.getSignalServiceAccountManager() fun ensureDecryptionsDrained(): Completable {
return Completable.create { emitter ->
ApplicationDependencies
.getIncomingMessageObserver()
.addDecryptionDrainedListener {
emitter.onComplete()
}
}.subscribeOn(Schedulers.io())
}
fun changeNumber(code: String, newE164: String): Single<ServiceResponse<VerifyAccountResponse>> { fun changeNumber(code: String, newE164: String): Single<ServiceResponse<VerifyAccountResponse>> {
return Single.fromCallable { accountManager.changeNumber(code, newE164, null) } return Single.fromCallable {
.subscribeOn(Schedulers.io()) val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, null)
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
accountManager.changeNumber(request)
}.subscribeOn(Schedulers.io())
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
} }
fun changeNumber( fun changeNumber(
@ -45,8 +78,11 @@ class ChangeNumberRepository(private val context: Context) {
try { try {
val kbsData: KbsPinData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!! val kbsData: KbsPinData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
val registrationLock: String = kbsData.masterKey.deriveRegistrationLock() val registrationLock: String = kbsData.masterKey.deriveRegistrationLock()
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(code, newE164, registrationLock)
val response: ServiceResponse<VerifyAccountResponse> = accountManager.changeNumber(code, newE164, registrationLock) SignalStore.misc().setPendingChangeNumberMetadata(metadata)
val response: ServiceResponse<VerifyAccountResponse> = accountManager.changeNumber(request)
VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(response, kbsData) VerifyAccountRepository.VerifyAccountWithRegistrationLockResponse.from(response, kbsData)
} catch (e: KeyBackupSystemWrongPinException) { } catch (e: KeyBackupSystemWrongPinException) {
ServiceResponse.forExecutionError(e) ServiceResponse.forExecutionError(e)
@ -56,6 +92,7 @@ class ChangeNumberRepository(private val context: Context) {
ServiceResponse.forExecutionError(e) ServiceResponse.forExecutionError(e)
} }
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
} }
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
@ -86,6 +123,49 @@ class ChangeNumberRepository(private val context: Context) {
SignalStore.account().setE164(e164) SignalStore.account().setE164(e164)
SignalStore.account().setPni(pni) SignalStore.account().setPni(pni)
ApplicationDependencies.getGroupsV2Authorization().clear()
val metadata: PendingChangeNumberMetadata? = SignalStore.misc().pendingChangeNumberMetadata
if (metadata == null) {
Log.w(TAG, "No change number metadata, this shouldn't happen")
throw AssertionError("No change number metadata")
}
val originalPni = ServiceId.fromByteString(metadata.previousPni)
if (originalPni == pni) {
Log.i(TAG, "No change has occurred, PNI is unchanged: $pni")
} else {
val pniIdentityKeyPair = IdentityKeyPair(metadata.pniIdentityKeyPair.toByteArray())
val pniRegistrationId = metadata.pniRegistrationId
val pniSignedPreyKeyId = metadata.pniSignedPreKeyId
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore = SignalStore.account().pniPreKeys
SignalStore.account().pniRegistrationId = pniRegistrationId
SignalStore.account().setPniIdentityKeyAfterChangeNumber(pniIdentityKeyPair)
val signedPreKey = pniProtocolStore.loadSignedPreKey(pniSignedPreyKeyId)
val oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(pniProtocolStore, pniMetadataStore)
pniMetadataStore.activeSignedPreKeyId = signedPreKey.id
accountManager.setPreKeys(ServiceIdType.PNI, pniProtocolStore.identityKeyPair.publicKey, signedPreKey, oneTimePreKeys)
pniMetadataStore.isSignedPreKeyRegistered = true
pniProtocolStore.identities().saveIdentityWithoutSideEffects(
Recipient.self().id,
pniProtocolStore.identityKeyPair.publicKey,
IdentityDatabase.VerifiedStatus.VERIFIED,
true,
System.currentTimeMillis(),
true
)
}
Recipient.self().live().refresh()
StorageSyncHelper.scheduleSyncForDataChange()
ApplicationDependencies.closeConnections() ApplicationDependencies.closeConnections()
ApplicationDependencies.getIncomingMessageObserver() ApplicationDependencies.getIncomingMessageObserver()
@ -112,4 +192,66 @@ class ChangeNumberRepository(private val context: Context) {
} }
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
} }
@Suppress("UsePropertyAccessSyntax")
@WorkerThread
private fun createChangeNumberRequest(
code: String,
newE164: String,
registrationLock: String?
): ChangeNumberRequestData {
val messageSender: SignalServiceMessageSender = ApplicationDependencies.getSignalServiceMessageSender()
val pniProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore: PreKeyMetadataStore = SignalStore.account().pniPreKeys
val devices: List<DeviceInfo> = accountManager.getDevices()
val pniIdentity: IdentityKeyPair = IdentityKeyUtil.generateIdentityKeyPair()
val deviceMessages = mutableListOf<OutgoingPushMessage>()
val devicePniSignedPreKeys = mutableMapOf<String, SignedPreKeyEntity>()
val pniRegistrationIds = mutableMapOf<String, Int>()
val primaryDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID.toString()
for (device in devices) {
val deviceId = device.id.toString()
// Signed Prekeys
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey, false)
} else {
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
}
devicePniSignedPreKeys[deviceId] = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
// Registration Ids
var pniRegistrationId = -1
while (pniRegistrationId < 0 || pniRegistrationIds.values.contains(pniRegistrationId)) {
pniRegistrationId = KeyHelper.generateRegistrationId(false)
}
pniRegistrationIds[deviceId] = pniRegistrationId
// Device Messages
if (deviceId != primaryDeviceId) {
val pniChangeNumber = SyncMessage.PniChangeNumber.newBuilder()
.setIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
.setSignedPreKey(signedPreKeyRecord.serialize().toProtoByteString())
.setRegistrationId(pniRegistrationId)
.build()
deviceMessages += messageSender.getEncryptedSyncPniChangeNumberMessage(device.id, pniChangeNumber)
}
}
val request = ChangePhoneNumberRequest(newE164, code, registrationLock, pniIdentity.publicKey, deviceMessages, devicePniSignedPreKeys, pniRegistrationIds)
val metadata = PendingChangeNumberMetadata.newBuilder()
.setPreviousPni(SignalStore.account().pni!!.toByteString())
.setPniIdentityKeyPair(pniIdentity.serialize().toProtoByteString())
.setPniRegistrationId(pniRegistrationIds[primaryDeviceId]!!)
.setPniSignedPreKeyId(devicePniSignedPreKeys[primaryDeviceId]!!.keyId)
.build()
return ChangeNumberRequestData(request, metadata)
}
data class ChangeNumberRequestData(val changeNumberRequest: ChangePhoneNumberRequest, val pendingChangeNumberMetadata: PendingChangeNumberMetadata)
} }

Wyświetl plik

@ -48,8 +48,9 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
} }
private fun requestCode() { private fun requestCode() {
lifecycleDisposable.add( lifecycleDisposable += viewModel
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER) .ensureDecryptionsDrained()
.andThen(viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { processor -> .subscribe { processor ->
if (processor.hasResult()) { if (processor.hasResult()) {
@ -71,6 +72,5 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
findNavController().navigateUp() findNavController().navigateUp()
} }
} }
)
} }
} }

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel
import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.SavedStateRegistryOwner
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@ -107,6 +108,10 @@ class ChangeNumberViewModel(
} }
} }
fun ensureDecryptionsDrained(): Completable {
return changeNumberRepository.ensureDecryptionsDrained()
}
override fun verifyCodeWithoutRegistrationLock(code: String): Single<VerifyAccountResponseProcessor> { override fun verifyCodeWithoutRegistrationLock(code: String): Single<VerifyAccountResponseProcessor> {
return super.verifyCodeWithoutRegistrationLock(code) return super.verifyCodeWithoutRegistrationLock(code)
.doOnSubscribe { SignalStore.misc().lockChangeNumber() } .doOnSubscribe { SignalStore.misc().lockChangeNumber() }
@ -122,6 +127,7 @@ class ChangeNumberViewModel(
private fun <T : VerifyProcessor> attemptToUnlockChangeNumber(processor: T): Single<T> { private fun <T : VerifyProcessor> attemptToUnlockChangeNumber(processor: T): Single<T> {
return if (processor.hasResult() || processor.isServerSentError()) { return if (processor.hasResult() || processor.isServerSentError()) {
SignalStore.misc().unlockChangeNumber() SignalStore.misc().unlockChangeNumber()
SignalStore.misc().clearPendingChangeNumberMetadata()
Single.just(processor) Single.just(processor)
} else { } else {
changeNumberRepository.whoAmI() changeNumberRepository.whoAmI()
@ -129,6 +135,7 @@ class ChangeNumberViewModel(
if (Objects.equals(whoAmI.number, localNumber)) { if (Objects.equals(whoAmI.number, localNumber)) {
Log.i(TAG, "Local and remote numbers match, we can unlock.") Log.i(TAG, "Local and remote numbers match, we can unlock.")
SignalStore.misc().unlockChangeNumber() SignalStore.misc().unlockChangeNumber()
SignalStore.misc().clearPendingChangeNumberMetadata()
} }
processor processor
} }
@ -172,7 +179,7 @@ class ChangeNumberViewModel(
val viewModel = ChangeNumberViewModel( val viewModel = ChangeNumberViewModel(
localNumber = localNumber, localNumber = localNumber,
changeNumberRepository = ChangeNumberRepository(context), changeNumberRepository = ChangeNumberRepository(),
savedState = handle, savedState = handle,
password = password, password = password,
verifyAccountRepository = VerifyAccountRepository(context), verifyAccountRepository = VerifyAccountRepository(context),

Wyświetl plik

@ -24,6 +24,7 @@ import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.InvalidKeyIdException;
import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.signal.libsignal.protocol.state.SignalProtocolStore;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
@ -64,13 +65,18 @@ public class PreKeyUtil {
} }
public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) { public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) {
return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey(), setAsActive);
}
public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore,
@NonNull PreKeyMetadataStore metadataStore,
@NonNull ECPrivateKey privateKey,
boolean setAsActive)
{
Log.i(TAG, "Generating signed prekeys..."); Log.i(TAG, "Generating signed prekeys...");
try {
int signedPreKeyId = metadataStore.getNextSignedPreKeyId(); int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
ECKeyPair keyPair = Curve.generateKeyPair(); SignedPreKeyRecord record = generateSignedPreKey(signedPreKeyId, privateKey);
byte[] signature = Curve.calculateSignature(protocolStore.getIdentityKeyPair().getPrivateKey(), keyPair.getPublicKey().serialize());
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
protocolStore.storeSignedPreKey(signedPreKeyId, record); protocolStore.storeSignedPreKey(signedPreKeyId, record);
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE); metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
@ -80,6 +86,14 @@ public class PreKeyUtil {
} }
return record; return record;
}
public synchronized static @NonNull SignedPreKeyRecord generateSignedPreKey(int signedPreKeyId, @NonNull ECPrivateKey privateKey) {
try {
ECKeyPair keyPair = Curve.generateKeyPair();
byte[] signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize());
return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

Wyświetl plik

@ -1,11 +1,16 @@
package org.thoughtcrime.securesms.database.model package org.thoughtcrime.securesms.database.model
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
/** /**
* Collection of extensions to make working with database protos cleaner. * Collection of extensions to make working with database protos cleaner.
*/ */
fun ByteArray.toProtoByteString(): ByteString {
return ByteString.copyFrom(this)
}
fun BodyRangeList.Builder.addStyle(style: BodyRangeList.BodyRange.Style, start: Int, length: Int): BodyRangeList.Builder { fun BodyRangeList.Builder.addStyle(style: BodyRangeList.BodyRange.Style, start: Int, length: Int): BodyRangeList.Builder {
addRanges( addRanges(
BodyRangeList.BodyRange.newBuilder() BodyRangeList.BodyRange.newBuilder()

Wyświetl plik

@ -1,12 +1,12 @@
package org.thoughtcrime.securesms.dependencies; package org.thoughtcrime.securesms.dependencies;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.signal.core.util.Hex;
import org.signal.core.util.concurrent.DeadlockDetector; import org.signal.core.util.concurrent.DeadlockDetector;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations; import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
@ -56,10 +56,12 @@ import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.api.services.DonationsService; import org.whispersystems.signalservice.api.services.DonationsService;
import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.api.util.Tls12SocketFactory; import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager; import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager;
import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.internal.util.Util;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@ -77,6 +79,7 @@ import okhttp3.OkHttpClient;
* All future application-scoped singletons should be written as normal objects, then placed here * All future application-scoped singletons should be written as normal objects, then placed here
* to manage their singleton-ness. * to manage their singleton-ness.
*/ */
@SuppressLint("StaticFieldLeak")
public class ApplicationDependencies { public class ApplicationDependencies {
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
@ -159,7 +162,7 @@ public class ApplicationDependencies {
synchronized (LOCK) { synchronized (LOCK) {
if (accountManager == null) { if (accountManager == null) {
accountManager = provider.provideSignalServiceAccountManager(); accountManager = provider.provideSignalServiceAccountManager(getSignalServiceNetworkAccess().getConfiguration(), getGroupsV2Operations());
} }
return accountManager; return accountManager;
} }
@ -183,7 +186,7 @@ public class ApplicationDependencies {
if (groupsV2Operations == null) { if (groupsV2Operations == null) {
synchronized (LOCK) { synchronized (LOCK) {
if (groupsV2Operations == null) { if (groupsV2Operations == null) {
groupsV2Operations = provider.provideGroupsV2Operations(); groupsV2Operations = provider.provideGroupsV2Operations(getSignalServiceNetworkAccess().getConfiguration());
} }
} }
} }
@ -192,11 +195,7 @@ public class ApplicationDependencies {
} }
public static @NonNull KeyBackupService getKeyBackupService(@NonNull KbsEnclave enclave) { public static @NonNull KeyBackupService getKeyBackupService(@NonNull KbsEnclave enclave) {
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application), return provider.provideKeyBackupService(getSignalServiceAccountManager(), IasKeyStore.getIasKeyStore(application), enclave);
enclave.getEnclaveName(),
Hex.fromStringOrThrow(enclave.getServiceId()),
enclave.getMrEnclave(),
10);
} }
public static @NonNull GroupsV2StateProcessor getGroupsV2StateProcessor() { public static @NonNull GroupsV2StateProcessor getGroupsV2StateProcessor() {
@ -220,7 +219,7 @@ public class ApplicationDependencies {
synchronized (LOCK) { synchronized (LOCK) {
if (messageSender == null) { if (messageSender == null) {
messageSender = provider.provideSignalServiceMessageSender(getSignalWebSocket(), getProtocolStore()); messageSender = provider.provideSignalServiceMessageSender(getSignalWebSocket(), getProtocolStore(), getSignalServiceNetworkAccess().getConfiguration());
} }
return messageSender; return messageSender;
} }
@ -229,7 +228,7 @@ public class ApplicationDependencies {
public static @NonNull SignalServiceMessageReceiver getSignalServiceMessageReceiver() { public static @NonNull SignalServiceMessageReceiver getSignalServiceMessageReceiver() {
synchronized (LOCK) { synchronized (LOCK) {
if (messageReceiver == null) { if (messageReceiver == null) {
messageReceiver = provider.provideSignalServiceMessageReceiver(); messageReceiver = provider.provideSignalServiceMessageReceiver(getSignalServiceNetworkAccess().getConfiguration());
} }
return messageReceiver; return messageReceiver;
} }
@ -571,7 +570,7 @@ public class ApplicationDependencies {
if (signalWebSocket == null) { if (signalWebSocket == null) {
synchronized (LOCK) { synchronized (LOCK) {
if (signalWebSocket == null) { if (signalWebSocket == null) {
signalWebSocket = provider.provideSignalWebSocket(); signalWebSocket = provider.provideSignalWebSocket(getSignalServiceNetworkAccess().getConfiguration());
} }
} }
} }
@ -627,7 +626,7 @@ public class ApplicationDependencies {
if (donationsService == null) { if (donationsService == null) {
synchronized (LOCK) { synchronized (LOCK) {
if (donationsService == null) { if (donationsService == null) {
donationsService = provider.provideDonationsService(); donationsService = provider.provideDonationsService(getSignalServiceNetworkAccess().getConfiguration(), getGroupsV2Operations());
} }
} }
} }
@ -651,7 +650,7 @@ public class ApplicationDependencies {
if (clientZkReceiptOperations == null) { if (clientZkReceiptOperations == null) {
synchronized (LOCK) { synchronized (LOCK) {
if (clientZkReceiptOperations == null) { if (clientZkReceiptOperations == null) {
clientZkReceiptOperations = provider.provideClientZkReceiptOperations(); clientZkReceiptOperations = provider.provideClientZkReceiptOperations(getSignalServiceNetworkAccess().getConfiguration());
} }
} }
} }
@ -670,10 +669,10 @@ public class ApplicationDependencies {
} }
public interface Provider { public interface Provider {
@NonNull GroupsV2Operations provideGroupsV2Operations(); @NonNull GroupsV2Operations provideGroupsV2Operations(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager(); @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations);
@NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore); @NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore, @NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(); @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess(); @NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess();
@NonNull IncomingMessageProcessor provideIncomingMessageProcessor(); @NonNull IncomingMessageProcessor provideIncomingMessageProcessor();
@NonNull BackgroundMessageRetriever provideBackgroundMessageRetriever(); @NonNull BackgroundMessageRetriever provideBackgroundMessageRetriever();
@ -697,14 +696,15 @@ public class ApplicationDependencies {
@NonNull SignalCallManager provideSignalCallManager(); @NonNull SignalCallManager provideSignalCallManager();
@NonNull PendingRetryReceiptManager providePendingRetryReceiptManager(); @NonNull PendingRetryReceiptManager providePendingRetryReceiptManager();
@NonNull PendingRetryReceiptCache providePendingRetryReceiptCache(); @NonNull PendingRetryReceiptCache providePendingRetryReceiptCache();
@NonNull SignalWebSocket provideSignalWebSocket(); @NonNull SignalWebSocket provideSignalWebSocket(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceDataStoreImpl provideProtocolStore(); @NonNull SignalServiceDataStoreImpl provideProtocolStore();
@NonNull GiphyMp4Cache provideGiphyMp4Cache(); @NonNull GiphyMp4Cache provideGiphyMp4Cache();
@NonNull SimpleExoPlayerPool provideExoPlayerPool(); @NonNull SimpleExoPlayerPool provideExoPlayerPool();
@NonNull AudioManagerCompat provideAndroidCallAudioManager(); @NonNull AudioManagerCompat provideAndroidCallAudioManager();
@NonNull DonationsService provideDonationsService(); @NonNull DonationsService provideDonationsService(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations);
@NonNull ProfileService provideProfileService(@NonNull ClientZkProfileOperations profileOperations, @NonNull SignalServiceMessageReceiver signalServiceMessageReceiver, @NonNull SignalWebSocket signalWebSocket); @NonNull ProfileService provideProfileService(@NonNull ClientZkProfileOperations profileOperations, @NonNull SignalServiceMessageReceiver signalServiceMessageReceiver, @NonNull SignalWebSocket signalWebSocket);
@NonNull DeadlockDetector provideDeadlockDetector(); @NonNull DeadlockDetector provideDeadlockDetector();
@NonNull ClientZkReceiptOperations provideClientZkReceiptOperations(); @NonNull ClientZkReceiptOperations provideClientZkReceiptOperations(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull KeyBackupService provideKeyBackupService(@NonNull SignalServiceAccountManager signalServiceAccountManager, @NonNull KeyStore keyStore, @NonNull KbsEnclave enclave);
} }
} }

Wyświetl plik

@ -5,12 +5,15 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.signal.core.util.Hex;
import org.signal.core.util.concurrent.DeadlockDetector; import org.signal.core.util.concurrent.DeadlockDetector;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations; import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.KbsEnclave;
import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
@ -70,6 +73,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.video.exo.GiphyMp4Cache; import org.thoughtcrime.securesms.video.exo.GiphyMp4Cache;
import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool; import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool;
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat; import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat;
import org.whispersystems.signalservice.api.KeyBackupService;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceDataStore; import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
@ -85,8 +89,10 @@ import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import java.security.KeyStore;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -101,45 +107,45 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
this.context = context; this.context = context;
} }
private @NonNull ClientZkOperations provideClientZkOperations() { private @NonNull ClientZkOperations provideClientZkOperations(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return ClientZkOperations.create(provideSignalServiceNetworkAccess().getConfiguration()); return ClientZkOperations.create(signalServiceConfiguration);
} }
@Override @Override
public @NonNull GroupsV2Operations provideGroupsV2Operations() { public @NonNull GroupsV2Operations provideGroupsV2Operations(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return new GroupsV2Operations(provideClientZkOperations(), FeatureFlags.groupLimits().getHardLimit()); return new GroupsV2Operations(provideClientZkOperations(signalServiceConfiguration), FeatureFlags.groupLimits().getHardLimit());
} }
@Override @Override
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager() { public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations) {
return new SignalServiceAccountManager(provideSignalServiceNetworkAccess().getConfiguration(), return new SignalServiceAccountManager(signalServiceConfiguration,
new DynamicCredentialsProvider(), new DynamicCredentialsProvider(),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
provideGroupsV2Operations(), groupsV2Operations,
FeatureFlags.okHttpAutomaticRetry()); FeatureFlags.okHttpAutomaticRetry());
} }
@Override @Override
public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore) { public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore, @NonNull SignalServiceConfiguration signalServiceConfiguration) {
return new SignalServiceMessageSender(provideSignalServiceNetworkAccess().getConfiguration(), return new SignalServiceMessageSender(signalServiceConfiguration,
new DynamicCredentialsProvider(), new DynamicCredentialsProvider(),
protocolStore, protocolStore,
ReentrantSessionLock.INSTANCE, ReentrantSessionLock.INSTANCE,
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
signalWebSocket, signalWebSocket,
Optional.of(new SecurityEventListener(context)), Optional.of(new SecurityEventListener(context)),
provideClientZkOperations().getProfileOperations(), provideGroupsV2Operations(signalServiceConfiguration).getProfileOperations(),
SignalExecutors.newCachedBoundedExecutor("signal-messages", 1, 16, 30), SignalExecutors.newCachedBoundedExecutor("signal-messages", 1, 16, 30),
ByteUnit.KILOBYTES.toBytes(256), ByteUnit.KILOBYTES.toBytes(256),
FeatureFlags.okHttpAutomaticRetry()); FeatureFlags.okHttpAutomaticRetry());
} }
@Override @Override
public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver() { public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return new SignalServiceMessageReceiver(provideSignalServiceNetworkAccess().getConfiguration(), return new SignalServiceMessageReceiver(signalServiceConfiguration,
new DynamicCredentialsProvider(), new DynamicCredentialsProvider(),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
provideClientZkOperations().getProfileOperations(), provideGroupsV2Operations(signalServiceConfiguration).getProfileOperations(),
FeatureFlags.okHttpAutomaticRetry()); FeatureFlags.okHttpAutomaticRetry());
} }
@ -275,10 +281,10 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
} }
@Override @Override
public @NonNull SignalWebSocket provideSignalWebSocket() { public @NonNull SignalWebSocket provideSignalWebSocket(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
SleepTimer sleepTimer = SignalStore.account().isFcmEnabled() ? new UptimeSleepTimer() : new AlarmSleepTimer(context); SleepTimer sleepTimer = SignalStore.account().isFcmEnabled() ? new UptimeSleepTimer() : new AlarmSleepTimer(context);
SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(context, sleepTimer); SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(context, sleepTimer);
SignalWebSocket signalWebSocket = new SignalWebSocket(provideWebSocketFactory(healthMonitor)); SignalWebSocket signalWebSocket = new SignalWebSocket(provideWebSocketFactory(signalServiceConfiguration, healthMonitor));
healthMonitor.monitor(signalWebSocket); healthMonitor.monitor(signalWebSocket);
@ -346,11 +352,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
} }
@Override @Override
public @NonNull DonationsService provideDonationsService() { public @NonNull DonationsService provideDonationsService(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations) {
return new DonationsService(provideSignalServiceNetworkAccess().getConfiguration(), return new DonationsService(signalServiceConfiguration,
new DynamicCredentialsProvider(), new DynamicCredentialsProvider(),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
provideGroupsV2Operations(), groupsV2Operations,
FeatureFlags.okHttpAutomaticRetry()); FeatureFlags.okHttpAutomaticRetry());
} }
@ -370,16 +376,25 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
} }
@Override @Override
public @NonNull ClientZkReceiptOperations provideClientZkReceiptOperations() { public @NonNull ClientZkReceiptOperations provideClientZkReceiptOperations(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return provideClientZkOperations().getReceiptOperations(); return provideClientZkOperations(signalServiceConfiguration).getReceiptOperations();
} }
private @NonNull WebSocketFactory provideWebSocketFactory(@NonNull SignalWebSocketHealthMonitor healthMonitor) { @Override
public @NonNull KeyBackupService provideKeyBackupService(@NonNull SignalServiceAccountManager signalServiceAccountManager, @NonNull KeyStore keyStore, @NonNull KbsEnclave enclave) {
return signalServiceAccountManager.getKeyBackupService(keyStore,
enclave.getEnclaveName(),
Hex.fromStringOrThrow(enclave.getServiceId()),
enclave.getMrEnclave(),
10);
}
private @NonNull WebSocketFactory provideWebSocketFactory(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull SignalWebSocketHealthMonitor healthMonitor) {
return new WebSocketFactory() { return new WebSocketFactory() {
@Override @Override
public WebSocketConnection createWebSocket() { public WebSocketConnection createWebSocket() {
return new WebSocketConnection("normal", return new WebSocketConnection("normal",
provideSignalServiceNetworkAccess().getConfiguration(), signalServiceConfiguration,
Optional.of(new DynamicCredentialsProvider()), Optional.of(new DynamicCredentialsProvider()),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
healthMonitor); healthMonitor);
@ -388,7 +403,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
@Override @Override
public WebSocketConnection createUnidentifiedWebSocket() { public WebSocketConnection createUnidentifiedWebSocket() {
return new WebSocketConnection("unidentified", return new WebSocketConnection("unidentified",
provideSignalServiceNetworkAccess().getConfiguration(), signalServiceConfiguration,
Optional.empty(), Optional.empty(),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
healthMonitor); healthMonitor);
@ -396,7 +411,8 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
}; };
} }
private static class DynamicCredentialsProvider implements CredentialsProvider { @VisibleForTesting
static class DynamicCredentialsProvider implements CredentialsProvider {
@Override @Override
public ACI getAci() { public ACI getAci() {

Wyświetl plik

@ -646,7 +646,7 @@ final class GroupManagerV2 {
for (int attempt = 0; attempt < 5; attempt++) { for (int attempt = 0; attempt < 5; attempt++) {
try { try {
return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers); return commitChange(change, allowWhenBlocked, sendToMembers);
} catch (GroupPatchNotAcceptedException e) { } catch (GroupPatchNotAcceptedException e) {
if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) { if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) {
refetchedAddMemberCredentials = true; refetchedAddMemberCredentials = true;
@ -724,7 +724,7 @@ final class GroupManagerV2 {
return change; return change;
} }
private GroupManager.GroupActionResult commitChange(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers) private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change, boolean allowWhenBlocked, boolean sendToMembers)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{ {
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
@ -741,7 +741,7 @@ final class GroupManagerV2 {
previousGroupState = v2GroupProperties.getDecryptedGroup(); previousGroupState = v2GroupProperties.getDecryptedGroup();
GroupChange signedGroupChange = commitToServer(authServiceId, changeActions); GroupChange signedGroupChange = commitToServer(changeActions);
try { try {
//noinspection OptionalGetWithoutIsPresent //noinspection OptionalGetWithoutIsPresent
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get(); decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
@ -761,7 +761,7 @@ final class GroupManagerV2 {
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers); return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
} }
private @NonNull GroupChange commitToServer(@NonNull ServiceId authServiceId, @NonNull GroupChange.Actions change) private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{ {
try { try {

Wyświetl plik

@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.KbsValues; import org.thoughtcrime.securesms.keyvalue.KbsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.registration.RegistrationRepository;
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher; import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
@ -86,6 +87,7 @@ public class RefreshAttributesJob extends BaseJob {
String registrationLockV1 = null; String registrationLockV1 = null;
String registrationLockV2 = null; String registrationLockV2 = null;
KbsValues kbsValues = SignalStore.kbsValues(); KbsValues kbsValues = SignalStore.kbsValues();
int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId();
if (kbsValues.isV2RegistrationLockEnabled()) { if (kbsValues.isV2RegistrationLockEnabled()) {
registrationLockV2 = kbsValues.getRegistrationLockToken(); registrationLockV2 = kbsValues.getRegistrationLockToken();
@ -115,12 +117,17 @@ public class RefreshAttributesJob extends BaseJob {
"\n UUID? " + capabilities.isUuid()); "\n UUID? " + capabilities.isUuid());
SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager(); SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
signalAccountManager.setAccountAttributes(null, registrationId, fetchesMessages, signalAccountManager.setAccountAttributes(null,
registrationLockV1, registrationLockV2, registrationId,
unidentifiedAccessKey, universalUnidentifiedAccess, fetchesMessages,
registrationLockV1,
registrationLockV2,
unidentifiedAccessKey,
universalUnidentifiedAccess,
capabilities, capabilities,
phoneNumberDiscoverable, phoneNumberDiscoverable,
encryptedDeviceName); encryptedDeviceName,
pniRegistrationId);
hasRefreshedThisAppCycle = true; hasRefreshedThisAppCycle = true;
} }

Wyświetl plik

@ -38,6 +38,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
private const val KEY_FCM_TOKEN_LAST_SET_TIME = "account.fcm_token_last_set_time" private const val KEY_FCM_TOKEN_LAST_SET_TIME = "account.fcm_token_last_set_time"
private const val KEY_DEVICE_NAME = "account.device_name" private const val KEY_DEVICE_NAME = "account.device_name"
private const val KEY_DEVICE_ID = "account.device_id" private const val KEY_DEVICE_ID = "account.device_id"
private const val KEY_PNI_REGISTRATION_ID = "account.pni_registration_id"
private const val KEY_ACI_IDENTITY_PUBLIC_KEY = "account.aci_identity_public_key" private const val KEY_ACI_IDENTITY_PUBLIC_KEY = "account.aci_identity_public_key"
private const val KEY_ACI_IDENTITY_PRIVATE_KEY = "account.aci_identity_private_key" private const val KEY_ACI_IDENTITY_PRIVATE_KEY = "account.aci_identity_private_key"
@ -135,6 +136,8 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
/** A randomly-generated value that represents this registration instance. Helps the server know if you reinstalled. */ /** A randomly-generated value that represents this registration instance. Helps the server know if you reinstalled. */
var registrationId: Int by integerValue(KEY_REGISTRATION_ID, 0) var registrationId: Int by integerValue(KEY_REGISTRATION_ID, 0)
var pniRegistrationId: Int by integerValue(KEY_PNI_REGISTRATION_ID, 0)
/** The identity key pair for the ACI identity. */ /** The identity key pair for the ACI identity. */
val aciIdentityKey: IdentityKeyPair val aciIdentityKey: IdentityKeyPair
get() { get() {
@ -202,7 +205,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
} }
/** When acting as a linked device, this method lets you store the identity keys sent from the primary device */ /** When acting as a linked device, this method lets you store the identity keys sent from the primary device */
fun setIdentityKeysFromPrimaryDevice(aciKeys: IdentityKeyPair) { fun setAciIdentityKeysFromPrimaryDevice(aciKeys: IdentityKeyPair) {
synchronized(this) { synchronized(this) {
require(isLinkedDevice) { "Must be a linked device!" } require(isLinkedDevice) { "Must be a linked device!" }
store store
@ -213,6 +216,19 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
} }
} }
/** Set an identity key pair for the PNI identity via change number. */
fun setPniIdentityKeyAfterChangeNumber(key: IdentityKeyPair) {
synchronized(this) {
Log.i(TAG, "Setting a new PNI identity key pair.")
store
.beginWrite()
.putBlob(KEY_PNI_IDENTITY_PUBLIC_KEY, key.publicKey.serialize())
.putBlob(KEY_PNI_IDENTITY_PRIVATE_KEY, key.privateKey.serialize())
.commit()
}
}
/** Only to be used when restoring an identity public key from an old backup */ /** Only to be used when restoring an identity public key from an old backup */
fun restoreLegacyIdentityPublicKeyFromBackup(base64: String) { fun restoreLegacyIdentityPublicKeyFromBackup(base64: String) {
Log.w(TAG, "Restoring legacy identity public key from backup.") Log.w(TAG, "Restoring legacy identity public key from backup.")

Wyświetl plik

@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -17,6 +19,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
private static final String OLD_DEVICE_TRANSFER_LOCKED = "misc.old_device.transfer.locked"; private static final String OLD_DEVICE_TRANSFER_LOCKED = "misc.old_device.transfer.locked";
private static final String HAS_EVER_HAD_AN_AVATAR = "misc.has.ever.had.an.avatar"; private static final String HAS_EVER_HAD_AN_AVATAR = "misc.has.ever.had.an.avatar";
private static final String CHANGE_NUMBER_LOCK = "misc.change_number.lock"; private static final String CHANGE_NUMBER_LOCK = "misc.change_number.lock";
private static final String PENDING_CHANGE_NUMBER_METADATA = "misc.pending_change_number.metadata";
private static final String CENSORSHIP_LAST_CHECK_TIME = "misc.censorship.last_check_time"; private static final String CENSORSHIP_LAST_CHECK_TIME = "misc.censorship.last_check_time";
private static final String CENSORSHIP_SERVICE_REACHABLE = "misc.censorship.service_reachable"; private static final String CENSORSHIP_SERVICE_REACHABLE = "misc.censorship.service_reachable";
private static final String LAST_GV2_PROFILE_CHECK_TIME = "misc.last_gv2_profile_check_time"; private static final String LAST_GV2_PROFILE_CHECK_TIME = "misc.last_gv2_profile_check_time";
@ -117,6 +120,20 @@ public final class MiscellaneousValues extends SignalStoreValues {
putBoolean(CHANGE_NUMBER_LOCK, false); putBoolean(CHANGE_NUMBER_LOCK, false);
} }
public @Nullable PendingChangeNumberMetadata getPendingChangeNumberMetadata() {
return getObject(PENDING_CHANGE_NUMBER_METADATA, null, PendingChangeNumberMetadataSerializer.INSTANCE);
}
/** Store pending new PNI data to be applied after successful change number */
public void setPendingChangeNumberMetadata(@NonNull PendingChangeNumberMetadata metadata) {
putObject(PENDING_CHANGE_NUMBER_METADATA, metadata, PendingChangeNumberMetadataSerializer.INSTANCE);
}
/** Clear pending new PNI data after confirmed successful or failed change number */
public void clearPendingChangeNumberMetadata() {
remove(PENDING_CHANGE_NUMBER_METADATA);
}
public long getLastCensorshipServiceReachabilityCheckTime() { public long getLastCensorshipServiceReachabilityCheckTime() {
return getLong(CENSORSHIP_LAST_CHECK_TIME, 0); return getLong(CENSORSHIP_LAST_CHECK_TIME, 0);
} }

Wyświetl plik

@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.keyvalue
import org.signal.core.util.ByteSerializer
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
/**
* Serialize [PendingChangeNumberMetadata]
*/
object PendingChangeNumberMetadataSerializer : ByteSerializer<PendingChangeNumberMetadata> {
override fun serialize(data: PendingChangeNumberMetadata): ByteArray = data.toByteArray()
override fun deserialize(data: ByteArray): PendingChangeNumberMetadata = PendingChangeNumberMetadata.parseFrom(data)
}

Wyświetl plik

@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.keyvalue; package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.ByteSerializer;
import org.signal.core.util.StringSerializer; import org.signal.core.util.StringSerializer;
import org.thoughtcrime.securesms.database.model.databaseprotos.SignalStoreList; import org.thoughtcrime.securesms.database.model.databaseprotos.SignalStoreList;
@ -51,6 +53,15 @@ abstract class SignalStoreValues {
return store.getBlob(key, defaultValue); return store.getBlob(key, defaultValue);
} }
<T> T getObject(@NonNull String key, @Nullable T defaultValue, @NonNull ByteSerializer<T> serializer) {
byte[] blob = store.getBlob(key, null);
if (blob == null) {
return defaultValue;
} else {
return serializer.deserialize(blob);
}
}
<T> List<T> getList(@NonNull String key, @NonNull StringSerializer<T> serializer) { <T> List<T> getList(@NonNull String key, @NonNull StringSerializer<T> serializer) {
byte[] blob = getBlob(key, null); byte[] blob = getBlob(key, null);
if (blob == null) { if (blob == null) {
@ -94,6 +105,10 @@ abstract class SignalStoreValues {
store.beginWrite().putString(key, value).apply(); store.beginWrite().putString(key, value).apply();
} }
<T> void putObject(@NonNull String key, T value, @NonNull ByteSerializer<T> serializer) {
putBlob(key, serializer.serialize(value));
}
<T> void putList(@NonNull String key, @NonNull List<T> values, @NonNull StringSerializer<T> serializer) { <T> void putList(@NonNull String key, @NonNull List<T> values, @NonNull StringSerializer<T> serializer) {
putBlob(key, SignalStoreList.newBuilder() putBlob(key, SignalStoreList.newBuilder()
.addAllContents(values.stream() .addAllContents(values.stream()

Wyświetl plik

@ -105,9 +105,10 @@ public class ApplicationMigrations {
static final int MY_STORY_PRIVACY_MODE = 61; static final int MY_STORY_PRIVACY_MODE = 61;
static final int REFRESH_EXPIRING_CREDENTIAL = 62; static final int REFRESH_EXPIRING_CREDENTIAL = 62;
static final int EMOJI_SEARCH_INDEX_10 = 63; static final int EMOJI_SEARCH_INDEX_10 = 63;
static final int REFRESH_PNI_REGISTRATION_ID = 64;
} }
public static final int CURRENT_VERSION = 63; public static final int CURRENT_VERSION = 64;
/** /**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@ -461,6 +462,10 @@ public class ApplicationMigrations {
jobs.put(Version.EMOJI_SEARCH_INDEX_10, new EmojiDownloadMigrationJob()); jobs.put(Version.EMOJI_SEARCH_INDEX_10, new EmojiDownloadMigrationJob());
} }
if (lastSeenVersion < Version.REFRESH_PNI_REGISTRATION_ID) {
jobs.put(Version.REFRESH_PNI_REGISTRATION_ID, new AttributesMigrationJob());
}
return jobs; return jobs;
} }

Wyświetl plik

@ -95,7 +95,7 @@ public class PhoneNumberFormatter {
return StringUtil.isolateBidi(phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)); return StringUtil.isolateBidi(phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL));
} }
} catch (NumberParseException e) { } catch (NumberParseException e) {
Log.w(TAG, "Failed to format number."); Log.w(TAG, "Failed to format number: " + e.toString());
return StringUtil.isolateBidi(e164); return StringUtil.isolateBidi(e164);
} }
} }
@ -136,7 +136,7 @@ public class PhoneNumberFormatter {
Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(processedNumber, localCountryCode); Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(processedNumber, localCountryCode);
return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
} catch (NumberParseException e) { } catch (NumberParseException e) {
Log.w(TAG, e); Log.w(TAG, e.toString());
if (bareNumber.charAt(0) == '+') { if (bareNumber.charAt(0) == '+') {
return bareNumber; return bareNumber;
} }

Wyświetl plik

@ -30,7 +30,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
* Using provided or already stored authorization, provides various get token data from KBS * Using provided or already stored authorization, provides various get token data from KBS
* and generate {@link KbsPinData}. * and generate {@link KbsPinData}.
*/ */
public final class KbsRepository { public class KbsRepository {
private static final String TAG = Log.tag(KbsRepository.class); private static final String TAG = Log.tag(KbsRepository.class);

Wyświetl plik

@ -65,7 +65,7 @@ public class AccountManagerFactory {
}); });
} }
return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number), return new SignalServiceAccountManager(ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(number),
null, null,
null, null,
number, number,

Wyświetl plik

@ -34,7 +34,7 @@ import java.util.Optional
* Provides a [SignalServiceConfiguration] to be used with our service layer. * Provides a [SignalServiceConfiguration] to be used with our service layer.
* If you're looking for a place to start, look at [getConfiguration]. * If you're looking for a place to start, look at [getConfiguration].
*/ */
class SignalServiceNetworkAccess(context: Context) { open class SignalServiceNetworkAccess(context: Context) {
companion object { companion object {
private val TAG = Log.tag(SignalServiceNetworkAccess::class.java) private val TAG = Log.tag(SignalServiceNetworkAccess::class.java)
@ -227,11 +227,11 @@ class SignalServiceNetworkAccess(context: Context) {
zkGroupServerPublicParams zkGroupServerPublicParams
) )
fun getConfiguration(): SignalServiceConfiguration { open fun getConfiguration(): SignalServiceConfiguration {
return getConfiguration(SignalStore.account().e164) return getConfiguration(SignalStore.account().e164)
} }
fun getConfiguration(localNumber: String?): SignalServiceConfiguration { open fun getConfiguration(localNumber: String?): SignalServiceConfiguration {
if (localNumber == null || SignalStore.proxy().isProxyEnabled) { if (localNumber == null || SignalStore.proxy().isProxyEnabled) {
return uncensoredConfiguration return uncensoredConfiguration
} }

Wyświetl plik

@ -8,7 +8,8 @@ data class RegistrationData(
val password: String, val password: String,
val registrationId: Int, val registrationId: Int,
val profileKey: ProfileKey, val profileKey: ProfileKey,
val fcmToken: String? val fcmToken: String?,
val pniRegistrationId: Int
) { ) {
val isFcm: Boolean = fcmToken != null val isFcm: Boolean = fcmToken != null
val isNotFcm: Boolean = fcmToken == null val isNotFcm: Boolean = fcmToken == null

Wyświetl plik

@ -73,6 +73,15 @@ public final class RegistrationRepository {
return registrationId; return registrationId;
} }
public int getPniRegistrationId() {
int pniRegistrationId = SignalStore.account().getPniRegistrationId();
if (pniRegistrationId == 0) {
pniRegistrationId = KeyHelper.generateRegistrationId(false);
SignalStore.account().setPniRegistrationId(pniRegistrationId);
}
return pniRegistrationId;
}
public @NonNull ProfileKey getProfileKey(@NonNull String e164) { public @NonNull ProfileKey getProfileKey(@NonNull String e164) {
ProfileKey profileKey = findExistingProfileKey(e164); ProfileKey profileKey = findExistingProfileKey(e164);

Wyświetl plik

@ -70,7 +70,8 @@ class VerifyAccountRepository(private val context: Application) {
unidentifiedAccessKey, unidentifiedAccessKey,
universalUnidentifiedAccess, universalUnidentifiedAccess,
AppCapabilities.getCapabilities(true), AppCapabilities.getCapabilities(true),
SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable,
registrationData.pniRegistrationId
) )
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
} }
@ -99,7 +100,8 @@ class VerifyAccountRepository(private val context: Application) {
unidentifiedAccessKey, unidentifiedAccessKey,
universalUnidentifiedAccess, universalUnidentifiedAccess,
AppCapabilities.getCapabilities(true), AppCapabilities.getCapabilities(true),
SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable,
registrationData.pniRegistrationId
) )
VerifyAccountWithRegistrationLockResponse.from(response, kbsData) VerifyAccountWithRegistrationLockResponse.from(response, kbsData)
} catch (e: KeyBackupSystemWrongPinException) { } catch (e: KeyBackupSystemWrongPinException) {

Wyświetl plik

@ -130,7 +130,8 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
getRegistrationSecret(), getRegistrationSecret(),
registrationRepository.getRegistrationId(), registrationRepository.getRegistrationId(),
registrationRepository.getProfileKey(getNumber().getE164Number()), registrationRepository.getProfileKey(getNumber().getE164Number()),
getFcmToken()); getFcmToken(),
registrationRepository.getPniRegistrationId());
} }
public static final class Factory extends AbstractSavedStateViewModelFactory { public static final class Factory extends AbstractSavedStateViewModelFactory {

Wyświetl plik

@ -239,3 +239,10 @@ message GiftBadge {
message SignalStoreList { message SignalStoreList {
repeated string contents = 1; repeated string contents = 1;
} }
message PendingChangeNumberMetadata {
bytes previousPni = 1;
bytes pniIdentityKeyPair = 2;
int32 pniRegistrationId = 3;
int32 pniSignedPreKeyId = 4;
}

Wyświetl plik

@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import org.signal.core.util.concurrent.DeadlockDetector; import org.signal.core.util.concurrent.DeadlockDetector;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations; import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
import org.thoughtcrime.securesms.KbsEnclave;
import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl; import org.thoughtcrime.securesms.crypto.storage.SignalServiceDataStoreImpl;
@ -32,6 +33,7 @@ import org.thoughtcrime.securesms.util.FrameRateTracker;
import org.thoughtcrime.securesms.video.exo.GiphyMp4Cache; import org.thoughtcrime.securesms.video.exo.GiphyMp4Cache;
import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool; import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool;
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat; import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat;
import org.whispersystems.signalservice.api.KeyBackupService;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceDataStore; import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
@ -40,27 +42,31 @@ import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.services.DonationsService; import org.whispersystems.signalservice.api.services.DonationsService;
import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import java.security.KeyStore;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@SuppressWarnings("ConstantConditions")
public class MockApplicationDependencyProvider implements ApplicationDependencies.Provider { public class MockApplicationDependencyProvider implements ApplicationDependencies.Provider {
@Override @Override
public @NonNull GroupsV2Operations provideGroupsV2Operations() { public @NonNull GroupsV2Operations provideGroupsV2Operations(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return null; return null;
} }
@Override @Override
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager() { public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations) {
return null; return null;
} }
@Override @Override
public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore) { public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore, @NonNull SignalServiceConfiguration signalServiceConfiguration) {
return null; return null;
} }
@Override @Override
public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver() { public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return null; return null;
} }
@ -180,7 +186,7 @@ public class MockApplicationDependencyProvider implements ApplicationDependencie
} }
@Override @Override
public @NonNull SignalWebSocket provideSignalWebSocket() { public @NonNull SignalWebSocket provideSignalWebSocket(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return null; return null;
} }
@ -205,7 +211,7 @@ public class MockApplicationDependencyProvider implements ApplicationDependencie
} }
@Override @Override
public @NonNull DonationsService provideDonationsService() { public @NonNull DonationsService provideDonationsService(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations) {
return null; return null;
} }
@ -220,7 +226,12 @@ public class MockApplicationDependencyProvider implements ApplicationDependencie
} }
@Override @Override
public @NonNull ClientZkReceiptOperations provideClientZkReceiptOperations() { public @NonNull ClientZkReceiptOperations provideClientZkReceiptOperations(@NonNull SignalServiceConfiguration signalServiceConfiguration) {
return null;
}
@Override
public @NonNull KeyBackupService provideKeyBackupService(@NonNull SignalServiceAccountManager signalServiceAccountManager, @NonNull KeyStore keyStore, @NonNull KbsEnclave enclave) {
return null; return null;
} }
} }

Wyświetl plik

@ -11,3 +11,5 @@ interface Serializer<T, R> {
interface StringSerializer<T> : Serializer<T, String> interface StringSerializer<T> : Serializer<T, String>
interface LongSerializer<T> : Serializer<T, Long> interface LongSerializer<T> : Serializer<T, Long>
interface ByteSerializer<T> : Serializer<T, ByteArray>

Wyświetl plik

@ -131,13 +131,15 @@ dependencyResolutionManagement {
alias('androidx-test-ext-junit').to('androidx.test.ext:junit:1.1.1') alias('androidx-test-ext-junit').to('androidx.test.ext:junit:1.1.1')
alias('androidx-test-ext-junit-ktx').to('androidx.test.ext:junit-ktx:1.1.1') alias('androidx-test-ext-junit-ktx').to('androidx.test.ext:junit-ktx:1.1.1')
alias('espresso-core').to('androidx.test.espresso:espresso-core:3.4.0') alias('espresso-core').to('androidx.test.espresso:espresso-core:3.4.0')
alias('mockito-core').to('org.mockito:mockito-inline:4.4.0') alias('mockito-core').to('org.mockito:mockito-inline:4.6.1')
alias('mockito-kotlin').to('org.mockito.kotlin:mockito-kotlin:4.0.0') alias('mockito-kotlin').to('org.mockito.kotlin:mockito-kotlin:4.0.0')
alias('mockito-android').to('org.mockito:mockito-android:4.6.1')
alias('robolectric-robolectric').to('org.robolectric', 'robolectric').versionRef('robolectric') alias('robolectric-robolectric').to('org.robolectric', 'robolectric').versionRef('robolectric')
alias('robolectric-shadows-multidex').to('org.robolectric', 'shadows-multidex').versionRef('robolectric') alias('robolectric-shadows-multidex').to('org.robolectric', 'shadows-multidex').versionRef('robolectric')
alias('bouncycastle-bcprov-jdk15on').to('org.bouncycastle:bcprov-jdk15on:1.70') alias('bouncycastle-bcprov-jdk15on').to('org.bouncycastle:bcprov-jdk15on:1.70')
alias('hamcrest-hamcrest').to('org.hamcrest:hamcrest:2.2') alias('hamcrest-hamcrest').to('org.hamcrest:hamcrest:2.2')
alias('assertj-core').to('org.assertj:assertj-core:3.11.1') alias('assertj-core').to('org.assertj:assertj-core:3.11.1')
alias('square-okhttp-mockserver').to('com.squareup.okhttp3:mockwebserver:3.12.13')
alias('conscrypt-openjdk-uber').to('org.conscrypt:conscrypt-openjdk-uber:2.0.0') alias('conscrypt-openjdk-uber').to('org.conscrypt:conscrypt-openjdk-uber:2.0.0')
} }

Wyświetl plik

@ -2795,6 +2795,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="916cc0257f6a66a18b7f7ad6ec510a1002fc5721cdb13e294edbb8475a2a80c5" origin="Generated by Gradle"/> <sha256 value="916cc0257f6a66a18b7f7ad6ec510a1002fc5721cdb13e294edbb8475a2a80c5" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="com.jakewharton.android.repackaged" name="dalvik-dx" version="9.0.0_r3">
<artifact name="dalvik-dx-9.0.0_r3.jar">
<sha256 value="b29c1c21e52ed6238cd3fed39d880a17ecf2360118604548cea8821be6801e1c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.jpardogo.materialtabstrip" name="library" version="1.0.9"> <component group="com.jpardogo.materialtabstrip" name="library" version="1.0.9">
<artifact name="library-1.0.9.aar"> <artifact name="library-1.0.9.aar">
<sha256 value="c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa" origin="Generated by Gradle"/> <sha256 value="c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa" origin="Generated by Gradle"/>
@ -3103,6 +3108,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="b20bd74ba01b55b30d6b7d10b9373f2a324b1f3638ecda0275c93e454223c7c8" origin="Generated by Gradle"/> <sha256 value="b20bd74ba01b55b30d6b7d10b9373f2a324b1f3638ecda0275c93e454223c7c8" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="com.squareup.okhttp3" name="mockwebserver" version="3.12.13">
<artifact name="mockwebserver-3.12.13.jar">
<sha256 value="ec92604c885f37eede54cd8504c871a5163616d94074f34ce27184845e7cfd4c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="okhttp" version="3.12.13"> <component group="com.squareup.okhttp3" name="okhttp" version="3.12.13">
<artifact name="okhttp-3.12.13.jar"> <artifact name="okhttp-3.12.13.jar">
<sha256 value="508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82" origin="Generated by Gradle"/> <sha256 value="508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82" origin="Generated by Gradle"/>
@ -3740,6 +3750,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="150ce4369f02782c78a04f359dc2b4f62e7521cd836d9f8a20bb481aeec4c500" origin="Generated by Gradle"/> <sha256 value="150ce4369f02782c78a04f359dc2b4f62e7521cd836d9f8a20bb481aeec4c500" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="net.bytebuddy" name="byte-buddy" version="1.12.10">
<artifact name="byte-buddy-1.12.10.jar">
<sha256 value="1a1ac9ce65eddcea54ead958387bb0b3863d02a2ffe856ab6a57ac79737c19cf" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy" version="1.12.8"> <component group="net.bytebuddy" name="byte-buddy" version="1.12.8">
<artifact name="byte-buddy-1.12.8.jar"> <artifact name="byte-buddy-1.12.8.jar">
<sha256 value="42ba43dcccd8d9d77bfe8776a83e72b67f1fa52c4038a98629e7d288b648da4e" origin="Generated by Gradle"/> <sha256 value="42ba43dcccd8d9d77bfe8776a83e72b67f1fa52c4038a98629e7d288b648da4e" origin="Generated by Gradle"/>
@ -3756,6 +3771,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="19cec6847112d179187327fa374fd19390f034b7baf1390ada16d3b0016d10b6" origin="Generated by Gradle"/> <sha256 value="19cec6847112d179187327fa374fd19390f034b7baf1390ada16d3b0016d10b6" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="net.bytebuddy" name="byte-buddy-agent" version="1.12.10">
<artifact name="byte-buddy-agent-1.12.10.jar">
<sha256 value="5e8606d14a844c1ec70d2eb8f50c4009fb16138905dee8ca50a328116c041257" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-agent" version="1.12.8"> <component group="net.bytebuddy" name="byte-buddy-agent" version="1.12.8">
<artifact name="byte-buddy-agent-1.12.8.jar"> <artifact name="byte-buddy-agent-1.12.8.jar">
<sha256 value="18faf4f33893c3e883a3220adfdab25d6ceff9e46b1ad760a5b6f83df9373307" origin="Generated by Gradle"/> <sha256 value="18faf4f33893c3e883a3220adfdab25d6ceff9e46b1ad760a5b6f83df9373307" origin="Generated by Gradle"/>
@ -3772,6 +3792,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="220162fbfd04a47104ad3408f98a97727cf5f38e78cd786949f8c5fe9c485c6e" origin="Generated by Gradle"/> <sha256 value="220162fbfd04a47104ad3408f98a97727cf5f38e78cd786949f8c5fe9c485c6e" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="net.bytebuddy" name="byte-buddy-android" version="1.12.10">
<artifact name="byte-buddy-android-1.12.10.jar">
<sha256 value="5e2c0b2ddb02e51fe95e4a58052fc7af91e566b627fe52f4e15699baa1f686d7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy-parent" version="1.12.8"> <component group="net.bytebuddy" name="byte-buddy-parent" version="1.12.8">
<artifact name="byte-buddy-parent-1.12.8.pom"> <artifact name="byte-buddy-parent-1.12.8.pom">
<sha256 value="9e5ac6370596087eb97a0f639ed655dbfe6df2deaa8422fba51946076a2a2751" origin="Generated by Gradle"/> <sha256 value="9e5ac6370596087eb97a0f639ed655dbfe6df2deaa8422fba51946076a2a2751" origin="Generated by Gradle"/>
@ -5229,6 +5254,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="8fc84f36ce6da6ce8c893b6538199a7f69a69a0706d9b17a3ee6a3a09452eed6" origin="Generated by Gradle"/> <sha256 value="8fc84f36ce6da6ce8c893b6538199a7f69a69a0706d9b17a3ee6a3a09452eed6" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.mockito" name="mockito-android" version="4.6.1">
<artifact name="mockito-android-4.6.1.jar">
<sha256 value="c631906a7909199f47c4e0d68137bd37a909d2577f06548fe2642b7189cf6358" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mockito" name="mockito-core" version="2.19.0"> <component group="org.mockito" name="mockito-core" version="2.19.0">
<artifact name="mockito-core-2.19.0.jar"> <artifact name="mockito-core-2.19.0.jar">
<sha256 value="d6ac2e04164c5d5c89e73838dc1c8b3856ca6582d3f2daf91816fd9d7ba3c9a9" origin="Generated by Gradle"/> <sha256 value="d6ac2e04164c5d5c89e73838dc1c8b3856ca6582d3f2daf91816fd9d7ba3c9a9" origin="Generated by Gradle"/>
@ -5245,6 +5275,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="3844e38af447014bb2c7a68b421be65f8c7d5a201971bda663302367c14297d3" origin="Generated by Gradle"/> <sha256 value="3844e38af447014bb2c7a68b421be65f8c7d5a201971bda663302367c14297d3" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.mockito" name="mockito-core" version="4.6.1">
<artifact name="mockito-core-4.6.1.jar">
<sha256 value="ee3b91cdf4c23cff92960c32364371c683ee6415f1ec4678317bcea79c9f9819" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mockito" name="mockito-inline" version="4.4.0"> <component group="org.mockito" name="mockito-inline" version="4.4.0">
<artifact name="mockito-inline-4.4.0.jar"> <artifact name="mockito-inline-4.4.0.jar">
<sha256 value="ee52e1c299a632184fba274a9370993e09140429f5e516e6c5570fd6574b297f" origin="Generated by Gradle"/> <sha256 value="ee52e1c299a632184fba274a9370993e09140429f5e516e6c5570fd6574b297f" origin="Generated by Gradle"/>
@ -5253,6 +5288,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="21de15a4e9e446d8a1219926c14b48ac8d2828329a69494bce100d65719b27e0" origin="Generated by Gradle"/> <sha256 value="21de15a4e9e446d8a1219926c14b48ac8d2828329a69494bce100d65719b27e0" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.mockito" name="mockito-inline" version="4.6.1">
<artifact name="mockito-inline-4.6.1.jar">
<sha256 value="ee52e1c299a632184fba274a9370993e09140429f5e516e6c5570fd6574b297f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mockito.kotlin" name="mockito-kotlin" version="4.0.0"> <component group="org.mockito.kotlin" name="mockito-kotlin" version="4.0.0">
<artifact name="mockito-kotlin-4.0.0.jar"> <artifact name="mockito-kotlin-4.0.0.jar">
<sha256 value="046eabba9c38816f75114163ac5074630f335dcdeeac52f228ce71c732c3d75f" origin="Generated by Gradle"/> <sha256 value="046eabba9c38816f75114163ac5074630f335dcdeeac52f228ce71c732c3d75f" origin="Generated by Gradle"/>

Wyświetl plik

@ -8,7 +8,8 @@ public final class KbsPinData {
private final MasterKey masterKey; private final MasterKey masterKey;
private final TokenResponse tokenResponse; private final TokenResponse tokenResponse;
KbsPinData(MasterKey masterKey, TokenResponse tokenResponse) { // Visible for testing
public KbsPinData(MasterKey masterKey, TokenResponse tokenResponse) {
this.masterKey = masterKey; this.masterKey = masterKey;
this.tokenResponse = tokenResponse; this.tokenResponse = tokenResponse;
} }

Wyświetl plik

@ -26,7 +26,7 @@ import java.security.SecureRandom;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.Locale; import java.util.Locale;
public final class KeyBackupService { public class KeyBackupService {
private static final String TAG = KeyBackupService.class.getSimpleName(); private static final String TAG = KeyBackupService.class.getSimpleName();

Wyświetl plik

@ -19,6 +19,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream; import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream;
@ -106,6 +107,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.Nonnull;
import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.core.Single;
import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisionMessage; import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisionMessage;
@ -181,7 +184,7 @@ public class SignalServiceAccountManager {
* V1 PINs are no longer used in favor of V2 PINs stored on KBS. * V1 PINs are no longer used in favor of V2 PINs stored on KBS.
* *
* You can remove a V1 PIN, but typically this is unnecessary, as setting a V2 PIN via * You can remove a V1 PIN, but typically this is unnecessary, as setting a V2 PIN via
* {@link KeyBackupService.Session#enableRegistrationLock(MasterKey)}} will automatically clear the * {@link KeyBackupService.PinChangeSession#enableRegistrationLock(MasterKey)}} will automatically clear the
* V1 PIN on the service. * V1 PIN on the service.
*/ */
public void removeRegistrationLockV1() throws IOException { public void removeRegistrationLockV1() throws IOException {
@ -280,7 +283,8 @@ public class SignalServiceAccountManager {
byte[] unidentifiedAccessKey, byte[] unidentifiedAccessKey,
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber) boolean discoverableByPhoneNumber,
int pniRegistrationId)
{ {
try { try {
VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode, VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode,
@ -292,7 +296,8 @@ public class SignalServiceAccountManager {
unidentifiedAccessKey, unidentifiedAccessKey,
unrestrictedUnidentifiedAccess, unrestrictedUnidentifiedAccess,
capabilities, capabilities,
discoverableByPhoneNumber); discoverableByPhoneNumber,
pniRegistrationId);
return ServiceResponse.forResult(response, 200, null); return ServiceResponse.forResult(response, 200, null);
} catch (IOException e) { } catch (IOException e) {
return ServiceResponse.forUnknownError(e); return ServiceResponse.forUnknownError(e);
@ -320,7 +325,8 @@ public class SignalServiceAccountManager {
byte[] unidentifiedAccessKey, byte[] unidentifiedAccessKey,
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber) boolean discoverableByPhoneNumber,
int pniRegistrationId)
{ {
try { try {
VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode, VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode,
@ -332,7 +338,8 @@ public class SignalServiceAccountManager {
unidentifiedAccessKey, unidentifiedAccessKey,
unrestrictedUnidentifiedAccess, unrestrictedUnidentifiedAccess,
capabilities, capabilities,
discoverableByPhoneNumber); discoverableByPhoneNumber,
pniRegistrationId);
return ServiceResponse.forResult(response, 200, null); return ServiceResponse.forResult(response, 200, null);
} catch (IOException e) { } catch (IOException e) {
return ServiceResponse.forUnknownError(e); return ServiceResponse.forUnknownError(e);
@ -346,7 +353,8 @@ public class SignalServiceAccountManager {
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber, boolean discoverableByPhoneNumber,
byte[] encryptedDeviceName) byte[] encryptedDeviceName,
int pniRegistrationId)
throws IOException throws IOException
{ {
AccountAttributes accountAttributes = new AccountAttributes( AccountAttributes accountAttributes = new AccountAttributes(
@ -359,15 +367,16 @@ public class SignalServiceAccountManager {
unrestrictedUnidentifiedAccess, unrestrictedUnidentifiedAccess,
capabilities, capabilities,
discoverableByPhoneNumber, discoverableByPhoneNumber,
Base64.encodeBytes(encryptedDeviceName) Base64.encodeBytes(encryptedDeviceName),
pniRegistrationId
); );
return this.pushServiceSocket.verifySecondaryDevice(verificationCode, accountAttributes); return this.pushServiceSocket.verifySecondaryDevice(verificationCode, accountAttributes);
} }
public ServiceResponse<VerifyAccountResponse> changeNumber(String code, String e164NewNumber, String registrationLock) { public @Nonnull ServiceResponse<VerifyAccountResponse> changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest) {
try { try {
VerifyAccountResponse response = this.pushServiceSocket.changeNumber(code, e164NewNumber, registrationLock); VerifyAccountResponse response = this.pushServiceSocket.changeNumber(changePhoneNumberRequest);
return ServiceResponse.forResult(response, 200, null); return ServiceResponse.forResult(response, 200, null);
} catch (IOException e) { } catch (IOException e) {
return ServiceResponse.forUnknownError(e); return ServiceResponse.forUnknownError(e);
@ -396,7 +405,8 @@ public class SignalServiceAccountManager {
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber, boolean discoverableByPhoneNumber,
byte[] encryptedDeviceName) byte[] encryptedDeviceName,
int pniRegistrationId)
throws IOException throws IOException
{ {
this.pushServiceSocket.setAccountAttributes( this.pushServiceSocket.setAccountAttributes(
@ -409,7 +419,8 @@ public class SignalServiceAccountManager {
unrestrictedUnidentifiedAccess, unrestrictedUnidentifiedAccess,
capabilities, capabilities,
discoverableByPhoneNumber, discoverableByPhoneNumber,
encryptedDeviceName encryptedDeviceName,
pniRegistrationId
); );
} }
@ -523,6 +534,13 @@ public class SignalServiceAccountManager {
ServiceResponse<CdsiV2Service.Response> serviceResponse; ServiceResponse<CdsiV2Service.Response> serviceResponse;
try { try {
serviceResponse = single.blockingGet(); serviceResponse = single.blockingGet();
} catch (RuntimeException e) {
Throwable cause = e.getCause();
if (cause instanceof InterruptedException) {
throw new IOException("Interrupted", cause);
} else {
throw e;
}
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Unexpected exception when retrieving registered users!", e); throw new RuntimeException("Unexpected exception when retrieving registered users!", e);
} }

Wyświetl plik

@ -141,6 +141,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/** /**
* The main interface for sending Signal Service messages. * The main interface for sending Signal Service messages.
* *
@ -584,6 +586,24 @@ public class SignalServiceMessageSender {
return sendMessage(localAddress, Optional.empty(), timestamp, envelopeContent, false, null, urgent); return sendMessage(localAddress, Optional.empty(), timestamp, envelopeContent, false, null, urgent);
} }
/**
* Create a device specific sync message that includes updated PNI details for that specific linked device. This message is
* sent to the server via the change number endpoint and not the normal sync message sending flow.
*
* @param deviceId - Device ID of linked device to build message for
* @param pniChangeNumber - Linked device specific updated PNI details
* @return Encrypted {@link OutgoingPushMessage} to be included in the change number request sent to the server
*/
public @Nonnull OutgoingPushMessage getEncryptedSyncPniChangeNumberMessage(int deviceId, @Nonnull SyncMessage.PniChangeNumber pniChangeNumber)
throws UntrustedIdentityException, IOException, InvalidKeyException
{
SyncMessage.Builder syncMessage = createSyncMessageBuilder().setPniChangeNumber(pniChangeNumber);
Content.Builder content = Content.newBuilder().setSyncMessage(syncMessage);
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content.build(), ContentHint.IMPLICIT, Optional.empty());
return getEncryptedMessage(socket, localAddress, Optional.empty(), deviceId, envelopeContent);
}
public void setSoTimeoutMillis(long soTimeoutMillis) { public void setSoTimeoutMillis(long soTimeoutMillis) {
socket.setSoTimeoutMillis(soTimeoutMillis); socket.setSoTimeoutMillis(soTimeoutMillis);
} }

Wyświetl plik

@ -1,4 +1,4 @@
/** /*
* Copyright (C) 2014-2016 Open Whisper Systems * Copyright (C) 2014-2016 Open Whisper Systems
* *
* Licensed according to the LICENSE file in this repository. * Licensed according to the LICENSE file in this repository.
@ -47,6 +47,9 @@ public class AccountAttributes {
@JsonProperty @JsonProperty
private String name; private String name;
@JsonProperty
private int pniRegistrationId;
public AccountAttributes(String signalingKey, public AccountAttributes(String signalingKey,
int registrationId, int registrationId,
boolean fetchesMessages, boolean fetchesMessages,
@ -56,7 +59,8 @@ public class AccountAttributes {
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
Capabilities capabilities, Capabilities capabilities,
boolean discoverableByPhoneNumber, boolean discoverableByPhoneNumber,
String name) String name,
int pniRegistrationId)
{ {
this.signalingKey = signalingKey; this.signalingKey = signalingKey;
this.registrationId = registrationId; this.registrationId = registrationId;
@ -70,6 +74,7 @@ public class AccountAttributes {
this.capabilities = capabilities; this.capabilities = capabilities;
this.discoverableByPhoneNumber = discoverableByPhoneNumber; this.discoverableByPhoneNumber = discoverableByPhoneNumber;
this.name = name; this.name = name;
this.pniRegistrationId = pniRegistrationId;
} }
public AccountAttributes() {} public AccountAttributes() {}
@ -122,6 +127,10 @@ public class AccountAttributes {
return name; return name;
} }
public int getPniRegistrationId() {
return pniRegistrationId;
}
public static class Capabilities { public static class Capabilities {
@JsonProperty @JsonProperty
private boolean uuid; private boolean uuid;

Wyświetl plik

@ -1,6 +1,16 @@
package org.whispersystems.signalservice.api.account; package org.whispersystems.signalservice.api.account;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import java.util.List;
import java.util.Map;
public final class ChangePhoneNumberRequest { public final class ChangePhoneNumberRequest {
@JsonProperty @JsonProperty
@ -12,10 +22,37 @@ public final class ChangePhoneNumberRequest {
@JsonProperty("reglock") @JsonProperty("reglock")
private String registrationLock; private String registrationLock;
public ChangePhoneNumberRequest(String number, String code, String registrationLock) { @JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey pniIdentityKey;
@JsonProperty
private List<OutgoingPushMessage> deviceMessages;
@JsonProperty
private Map<String, SignedPreKeyEntity> devicePniSignedPrekeys;
@JsonProperty
private Map<String, Integer> pniRegistrationIds;
public ChangePhoneNumberRequest() {}
public ChangePhoneNumberRequest(String number,
String code,
String registrationLock,
IdentityKey pniIdentityKey,
List<OutgoingPushMessage> deviceMessages,
Map<String, SignedPreKeyEntity> devicePniSignedPrekeys,
Map<String, Integer> pniRegistrationIds)
{
this.number = number; this.number = number;
this.code = code; this.code = code;
this.registrationLock = registrationLock; this.registrationLock = registrationLock;
this.pniIdentityKey = pniIdentityKey;
this.deviceMessages = deviceMessages;
this.devicePniSignedPrekeys = devicePniSignedPrekeys;
this.pniRegistrationIds = pniRegistrationIds;
} }
public String getNumber() { public String getNumber() {
@ -29,4 +66,20 @@ public final class ChangePhoneNumberRequest {
public String getRegistrationLock() { public String getRegistrationLock() {
return registrationLock; return registrationLock;
} }
public IdentityKey getPniIdentityKey() {
return pniIdentityKey;
}
public List<OutgoingPushMessage> getDeviceMessages() {
return deviceMessages;
}
public Map<String, SignedPreKeyEntity> getDevicePniSignedPrekeys() {
return devicePniSignedPrekeys;
}
public Map<String, Integer> getPniRegistrationIds() {
return pniRegistrationIds;
}
} }

Wyświetl plik

@ -1,4 +1,4 @@
/** /*
* Copyright (C) 2014-2016 Open Whisper Systems * Copyright (C) 2014-2016 Open Whisper Systems
* *
* Licensed according to the LICENSE file in this repository. * Licensed according to the LICENSE file in this repository.
@ -11,20 +11,20 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceInfo { public class DeviceInfo {
@JsonProperty @JsonProperty
private long id; public int id;
@JsonProperty @JsonProperty
private String name; public String name;
@JsonProperty @JsonProperty
private long created; public long created;
@JsonProperty @JsonProperty
private long lastSeen; public long lastSeen;
public DeviceInfo() {} public DeviceInfo() {}
public long getId() { public int getId() {
return id; return id;
} }

Wyświetl plik

@ -9,7 +9,7 @@ import java.util.List;
public class DeviceInfoList { public class DeviceInfoList {
@JsonProperty @JsonProperty
private List<DeviceInfo> devices; public List<DeviceInfo> devices;
public DeviceInfoList() {} public DeviceInfoList() {}

Wyświetl plik

@ -23,6 +23,7 @@ public class PreKeyState {
@JsonProperty @JsonProperty
private SignedPreKeyEntity signedPreKey; private SignedPreKeyEntity signedPreKey;
public PreKeyState() {}
public PreKeyState(List<PreKeyEntity> preKeys, SignedPreKeyEntity signedPreKey, IdentityKey identityKey) { public PreKeyState(List<PreKeyEntity> preKeys, SignedPreKeyEntity signedPreKey, IdentityKey identityKey) {
this.preKeys = preKeys; this.preKeys = preKeys;
@ -30,4 +31,15 @@ public class PreKeyState {
this.identityKey = identityKey; this.identityKey = identityKey;
} }
public IdentityKey getIdentityKey() {
return identityKey;
}
public List<PreKeyEntity> getPreKeys() {
return preKeys;
}
public SignedPreKeyEntity getSignedPreKey() {
return signedPreKey;
}
} }

Wyświetl plik

@ -354,24 +354,29 @@ public class PushServiceSocket {
return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class); return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class);
} }
public VerifyAccountResponse verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages, public VerifyAccountResponse verifyAccountCode(String verificationCode,
String pin, String registrationLock, String signalingKey,
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess, int registrationId,
boolean fetchesMessages,
String pin,
String registrationLock,
byte[] unidentifiedAccessKey,
boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber) boolean discoverableByPhoneNumber,
int pniRegistrationId)
throws IOException throws IOException
{ {
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin, registrationLock, unidentifiedAccessKey, unrestrictedUnidentifiedAccess, capabilities, discoverableByPhoneNumber, null); AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin, registrationLock, unidentifiedAccessKey, unrestrictedUnidentifiedAccess, capabilities, discoverableByPhoneNumber, null, pniRegistrationId);
String requestBody = JsonUtil.toJson(signalingKeyEntity); String requestBody = JsonUtil.toJson(signalingKeyEntity);
String responseBody = makeServiceRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), "PUT", requestBody); String responseBody = makeServiceRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), "PUT", requestBody);
return JsonUtil.fromJson(responseBody, VerifyAccountResponse.class); return JsonUtil.fromJson(responseBody, VerifyAccountResponse.class);
} }
public VerifyAccountResponse changeNumber(String code, String e164NewNumber, String registrationLock) public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest)
throws IOException throws IOException
{ {
ChangePhoneNumberRequest changePhoneNumberRequest = new ChangePhoneNumberRequest(e164NewNumber, code, registrationLock);
String requestBody = JsonUtil.toJson(changePhoneNumberRequest); String requestBody = JsonUtil.toJson(changePhoneNumberRequest);
String responseBody = makeServiceRequest(CHANGE_NUMBER_PATH, "PUT", requestBody); String responseBody = makeServiceRequest(CHANGE_NUMBER_PATH, "PUT", requestBody);
@ -387,7 +392,8 @@ public class PushServiceSocket {
boolean unrestrictedUnidentifiedAccess, boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities, AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber, boolean discoverableByPhoneNumber,
byte[] encryptedDeviceName) byte[] encryptedDeviceName,
int pniRegistrationId)
throws IOException throws IOException
{ {
if (registrationLock != null && pin != null) { if (registrationLock != null && pin != null) {
@ -396,9 +402,18 @@ public class PushServiceSocket {
String name = (encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName); String name = (encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName);
AccountAttributes accountAttributes = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin, registrationLock, AccountAttributes accountAttributes = new AccountAttributes(signalingKey,
unidentifiedAccessKey, unrestrictedUnidentifiedAccess, capabilities, registrationId,
discoverableByPhoneNumber, name); fetchesMessages,
pin,
registrationLock,
unidentifiedAccessKey,
unrestrictedUnidentifiedAccess,
capabilities,
discoverableByPhoneNumber,
name,
pniRegistrationId);
makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes)); makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes));
} }

Wyświetl plik

@ -20,7 +20,7 @@ public class SenderCertificate {
@JsonProperty @JsonProperty
@JsonDeserialize(using = ByteArrayDesieralizer.class) @JsonDeserialize(using = ByteArrayDesieralizer.class)
@JsonSerialize(using = ByteArraySerializer.class) @JsonSerialize(using = ByteArraySerializer.class)
private byte[] certificate; public byte[] certificate;
public SenderCertificate() {} public SenderCertificate() {}

Wyświetl plik

@ -5,13 +5,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class VerifyAccountResponse { public class VerifyAccountResponse {
@JsonProperty @JsonProperty
private String uuid; public String uuid;
@JsonProperty @JsonProperty
private String pni; public String pni;
@JsonProperty @JsonProperty
private boolean storageCapable; public boolean storageCapable;
@JsonCreator @JsonCreator
public VerifyAccountResponse() {} public VerifyAccountResponse() {}

Wyświetl plik

@ -4,13 +4,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class WhoAmIResponse { public class WhoAmIResponse {
@JsonProperty @JsonProperty
private String uuid; public String uuid;
@JsonProperty @JsonProperty
private String pni; public String pni;
@JsonProperty @JsonProperty
private String number; public String number;
public String getAci() { public String getAci() {
return uuid; return uuid;

Wyświetl plik

@ -569,6 +569,12 @@ message SyncMessage {
} }
} }
message PniChangeNumber {
optional bytes identityKeyPair = 1; // Serialized libsignal-client IdentityKeyPair
optional bytes signedPreKey = 2; // Serialized libsignal-client SignedPreKeyRecord
optional uint32 registrationId = 3;
}
optional Sent sent = 1; optional Sent sent = 1;
optional Contacts contacts = 2; optional Contacts contacts = 2;
optional Groups groups = 3; optional Groups groups = 3;
@ -586,6 +592,7 @@ message SyncMessage {
optional OutgoingPayment outgoingPayment = 15; optional OutgoingPayment outgoingPayment = 15;
repeated Viewed viewed = 16; repeated Viewed viewed = 16;
optional PniIdentity pniIdentity = 17; optional PniIdentity pniIdentity = 17;
optional PniChangeNumber pniChangeNumber = 18;
} }
message AttachmentPointer { message AttachmentPointer {

Wyświetl plik

@ -18,7 +18,8 @@ public final class AccountAttributesTest {
false, false,
new AccountAttributes.Capabilities(true, true, true, true, true, true, true, true, true), new AccountAttributes.Capabilities(true, true, true, true, true, true, true, true, true),
false, false,
null)); null,
321));
assertEquals("{\"signalingKey\":\"skey\"," + assertEquals("{\"signalingKey\":\"skey\"," +
"\"registrationId\":123," + "\"registrationId\":123," +
"\"voice\":true," + "\"voice\":true," +
@ -30,7 +31,7 @@ public final class AccountAttributesTest {
"\"unrestrictedUnidentifiedAccess\":false," + "\"unrestrictedUnidentifiedAccess\":false," +
"\"discoverableByPhoneNumber\":false," + "\"discoverableByPhoneNumber\":false," +
"\"capabilities\":{\"uuid\":true,\"storage\":true,\"senderKey\":true,\"announcementGroup\":true,\"changeNumber\":true,\"stories\":true,\"giftBadges\":true,\"gv2-3\":true,\"gv1-migration\":true}," + "\"capabilities\":{\"uuid\":true,\"storage\":true,\"senderKey\":true,\"announcementGroup\":true,\"changeNumber\":true,\"stories\":true,\"giftBadges\":true,\"gv2-3\":true,\"gv1-migration\":true}," +
"\"name\":null}", json); "\"name\":null,\"pniRegistrationId\":321}", json);
} }
@Test @Test