Add support for PNI registration ids and PNP change number.

This commit is contained in:
Cody Henthorne
2022-08-03 11:50:16 -04:00
parent 0d3ea22641
commit 83b97d274f
54 changed files with 1273 additions and 188 deletions

View File

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

View File

@@ -38,7 +38,7 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
setContentView(R.layout.activity_change_number_lock)
changeNumberRepository = ChangeNumberRepository(applicationContext)
changeNumberRepository = ChangeNumberRepository()
checkWhoAmI()
}
@@ -50,25 +50,25 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
override fun onBackPressed() = Unit
private fun checkWhoAmI() {
disposables.add(
changeNumberRepository.whoAmI()
.flatMap { whoAmI ->
if (Objects.equals(whoAmI.number, SignalStore.account().e164)) {
Log.i(TAG, "Local and remote numbers match, nothing needs to be done.")
Single.just(false)
} else {
Log.i(TAG, "Local (${SignalStore.account().e164}) and remote (${whoAmI.number}) numbers do not match, updating local.")
changeNumberRepository.changeLocalNumber(whoAmI.number, PNI.parseOrThrow(whoAmI.pni))
.map { true }
}
disposables += changeNumberRepository
.whoAmI()
.flatMap { whoAmI ->
if (Objects.equals(whoAmI.number, SignalStore.account().e164)) {
Log.i(TAG, "Local and remote numbers match, nothing needs to be done.")
Single.just(false)
} else {
Log.i(TAG, "Local (${SignalStore.account().e164}) and remote (${whoAmI.number}) numbers do not match, updating local.")
changeNumberRepository.changeLocalNumber(whoAmI.number, PNI.parseOrThrow(whoAmI.pni))
.map { true }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = { onChangeStatusConfirmed() }, onError = this::onFailedToGetChangeNumberStatus)
)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = { onChangeStatusConfirmed() }, onError = this::onFailedToGetChangeNumberStatus)
}
private fun onChangeStatusConfirmed() {
SignalStore.misc().unlockChangeNumber()
SignalStore.misc().clearPendingChangeNumberMetadata()
MaterialAlertDialogBuilder(this)
.setTitle(R.string.ChangeNumberLockActivity__change_status_confirmed)

View File

@@ -1,11 +1,21 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber
import android.content.Context
import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
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.model.databaseprotos.PendingChangeNumberMetadata
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.CertificateType
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -17,22 +27,45 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.whispersystems.signalservice.api.KbsPinData
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.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.push.OutgoingPushMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import java.io.IOException
import java.security.MessageDigest
import java.security.SecureRandom
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>> {
return Single.fromCallable { accountManager.changeNumber(code, newE164, null) }
.subscribeOn(Schedulers.io())
return Single.fromCallable {
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(
@@ -45,8 +78,11 @@ class ChangeNumberRepository(private val context: Context) {
try {
val kbsData: KbsPinData = KbsRepository.restoreMasterKey(pin, tokenData.enclave, tokenData.basicAuth, tokenData.tokenResponse)!!
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)
} catch (e: KeyBackupSystemWrongPinException) {
ServiceResponse.forExecutionError(e)
@@ -56,6 +92,7 @@ class ChangeNumberRepository(private val context: Context) {
ServiceResponse.forExecutionError(e)
}
}.subscribeOn(Schedulers.io())
.onErrorReturn { t -> ServiceResponse.forExecutionError(t) }
}
@Suppress("UsePropertyAccessSyntax")
@@ -86,6 +123,49 @@ class ChangeNumberRepository(private val context: Context) {
SignalStore.account().setE164(e164)
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.getIncomingMessageObserver()
@@ -112,4 +192,66 @@ class ChangeNumberRepository(private val context: Context) {
}
}.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)
}

View File

@@ -48,29 +48,29 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
}
private fun requestCode() {
lifecycleDisposable.add(
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processor ->
if (processor.hasResult()) {
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
} else if (processor.localRateLimit()) {
Log.i(TAG, "Unable to request sms code due to local rate limit")
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
} else if (processor.captchaRequired()) {
Log.i(TAG, "Unable to request sms code due to captcha required")
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_captchaFragment, getCaptchaArguments())
requestingCaptcha = true
} else if (processor.rateLimit()) {
Log.i(TAG, "Unable to request sms code due to rate limit")
Toast.makeText(requireContext(), R.string.RegistrationActivity_rate_limited_to_service, Toast.LENGTH_LONG).show()
findNavController().navigateUp()
} else {
Log.w(TAG, "Unable to request sms code", processor.error)
Toast.makeText(requireContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show()
findNavController().navigateUp()
}
lifecycleDisposable += viewModel
.ensureDecryptionsDrained()
.andThen(viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER))
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processor ->
if (processor.hasResult()) {
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
} else if (processor.localRateLimit()) {
Log.i(TAG, "Unable to request sms code due to local rate limit")
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
} else if (processor.captchaRequired()) {
Log.i(TAG, "Unable to request sms code due to captcha required")
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_captchaFragment, getCaptchaArguments())
requestingCaptcha = true
} else if (processor.rateLimit()) {
Log.i(TAG, "Unable to request sms code due to rate limit")
Toast.makeText(requireContext(), R.string.RegistrationActivity_rate_limited_to_service, Toast.LENGTH_LONG).show()
findNavController().navigateUp()
} else {
Log.w(TAG, "Unable to request sms code", processor.error)
Toast.makeText(requireContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show()
findNavController().navigateUp()
}
)
}
}
}

View File

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

View File

@@ -24,6 +24,7 @@ import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidKeyIdException;
import org.signal.libsignal.protocol.ecc.Curve;
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.SignalProtocolStore;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
@@ -64,22 +65,35 @@ public class PreKeyUtil {
}
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...");
int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
SignedPreKeyRecord record = generateSignedPreKey(signedPreKeyId, privateKey);
protocolStore.storeSignedPreKey(signedPreKeyId, record);
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
if (setAsActive) {
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
}
return record;
}
public synchronized static @NonNull SignedPreKeyRecord generateSignedPreKey(int signedPreKeyId, @NonNull ECPrivateKey privateKey) {
try {
int signedPreKeyId = metadataStore.getNextSignedPreKeyId();
ECKeyPair keyPair = Curve.generateKeyPair();
byte[] signature = Curve.calculateSignature(protocolStore.getIdentityKeyPair().getPrivateKey(), keyPair.getPublicKey().serialize());
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
ECKeyPair keyPair = Curve.generateKeyPair();
byte[] signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize());
protocolStore.storeSignedPreKey(signedPreKeyId, record);
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);
if (setAsActive) {
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
}
return record;
return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}

View File

@@ -1,11 +1,16 @@
package org.thoughtcrime.securesms.database.model
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
/**
* 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 {
addRanges(
BodyRangeList.BodyRange.newBuilder()

View File

@@ -1,12 +1,12 @@
package org.thoughtcrime.securesms.dependencies;
import android.annotation.SuppressLint;
import android.app.Application;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.signal.core.util.Hex;
import org.signal.core.util.concurrent.DeadlockDetector;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
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.ProfileService;
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.Util;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
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
* to manage their singleton-ness.
*/
@SuppressLint("StaticFieldLeak")
public class ApplicationDependencies {
private static final Object LOCK = new Object();
@@ -159,7 +162,7 @@ public class ApplicationDependencies {
synchronized (LOCK) {
if (accountManager == null) {
accountManager = provider.provideSignalServiceAccountManager();
accountManager = provider.provideSignalServiceAccountManager(getSignalServiceNetworkAccess().getConfiguration(), getGroupsV2Operations());
}
return accountManager;
}
@@ -183,7 +186,7 @@ public class ApplicationDependencies {
if (groupsV2Operations == null) {
synchronized (LOCK) {
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) {
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
enclave.getEnclaveName(),
Hex.fromStringOrThrow(enclave.getServiceId()),
enclave.getMrEnclave(),
10);
return provider.provideKeyBackupService(getSignalServiceAccountManager(), IasKeyStore.getIasKeyStore(application), enclave);
}
public static @NonNull GroupsV2StateProcessor getGroupsV2StateProcessor() {
@@ -220,7 +219,7 @@ public class ApplicationDependencies {
synchronized (LOCK) {
if (messageSender == null) {
messageSender = provider.provideSignalServiceMessageSender(getSignalWebSocket(), getProtocolStore());
messageSender = provider.provideSignalServiceMessageSender(getSignalWebSocket(), getProtocolStore(), getSignalServiceNetworkAccess().getConfiguration());
}
return messageSender;
}
@@ -229,7 +228,7 @@ public class ApplicationDependencies {
public static @NonNull SignalServiceMessageReceiver getSignalServiceMessageReceiver() {
synchronized (LOCK) {
if (messageReceiver == null) {
messageReceiver = provider.provideSignalServiceMessageReceiver();
messageReceiver = provider.provideSignalServiceMessageReceiver(getSignalServiceNetworkAccess().getConfiguration());
}
return messageReceiver;
}
@@ -571,7 +570,7 @@ public class ApplicationDependencies {
if (signalWebSocket == null) {
synchronized (LOCK) {
if (signalWebSocket == null) {
signalWebSocket = provider.provideSignalWebSocket();
signalWebSocket = provider.provideSignalWebSocket(getSignalServiceNetworkAccess().getConfiguration());
}
}
}
@@ -627,7 +626,7 @@ public class ApplicationDependencies {
if (donationsService == null) {
synchronized (LOCK) {
if (donationsService == null) {
donationsService = provider.provideDonationsService();
donationsService = provider.provideDonationsService(getSignalServiceNetworkAccess().getConfiguration(), getGroupsV2Operations());
}
}
}
@@ -651,7 +650,7 @@ public class ApplicationDependencies {
if (clientZkReceiptOperations == null) {
synchronized (LOCK) {
if (clientZkReceiptOperations == null) {
clientZkReceiptOperations = provider.provideClientZkReceiptOperations();
clientZkReceiptOperations = provider.provideClientZkReceiptOperations(getSignalServiceNetworkAccess().getConfiguration());
}
}
}
@@ -670,10 +669,10 @@ public class ApplicationDependencies {
}
public interface Provider {
@NonNull GroupsV2Operations provideGroupsV2Operations();
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager();
@NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore);
@NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver();
@NonNull GroupsV2Operations provideGroupsV2Operations(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager(@NonNull SignalServiceConfiguration signalServiceConfiguration, @NonNull GroupsV2Operations groupsV2Operations);
@NonNull SignalServiceMessageSender provideSignalServiceMessageSender(@NonNull SignalWebSocket signalWebSocket, @NonNull SignalServiceDataStore protocolStore, @NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess();
@NonNull IncomingMessageProcessor provideIncomingMessageProcessor();
@NonNull BackgroundMessageRetriever provideBackgroundMessageRetriever();
@@ -697,14 +696,15 @@ public class ApplicationDependencies {
@NonNull SignalCallManager provideSignalCallManager();
@NonNull PendingRetryReceiptManager providePendingRetryReceiptManager();
@NonNull PendingRetryReceiptCache providePendingRetryReceiptCache();
@NonNull SignalWebSocket provideSignalWebSocket();
@NonNull SignalWebSocket provideSignalWebSocket(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull SignalServiceDataStoreImpl provideProtocolStore();
@NonNull GiphyMp4Cache provideGiphyMp4Cache();
@NonNull SimpleExoPlayerPool provideExoPlayerPool();
@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 DeadlockDetector provideDeadlockDetector();
@NonNull ClientZkReceiptOperations provideClientZkReceiptOperations();
@NonNull ClientZkReceiptOperations provideClientZkReceiptOperations(@NonNull SignalServiceConfiguration signalServiceConfiguration);
@NonNull KeyBackupService provideKeyBackupService(@NonNull SignalServiceAccountManager signalServiceAccountManager, @NonNull KeyStore keyStore, @NonNull KbsEnclave enclave);
}
}

View File

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

View File

@@ -646,7 +646,7 @@ final class GroupManagerV2 {
for (int attempt = 0; attempt < 5; attempt++) {
try {
return commitChange(authServiceId, change, allowWhenBlocked, sendToMembers);
return commitChange(change, allowWhenBlocked, sendToMembers);
} catch (GroupPatchNotAcceptedException e) {
if (change.getAddMembersCount() > 0 && !refetchedAddMemberCredentials) {
refetchedAddMemberCredentials = true;
@@ -724,7 +724,7 @@ final class GroupManagerV2 {
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
{
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
@@ -741,7 +741,7 @@ final class GroupManagerV2 {
previousGroupState = v2GroupProperties.getDecryptedGroup();
GroupChange signedGroupChange = commitToServer(authServiceId, changeActions);
GroupChange signedGroupChange = commitToServer(changeActions);
try {
//noinspection OptionalGetWithoutIsPresent
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
@@ -761,7 +761,7 @@ final class GroupManagerV2 {
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
{
try {

View File

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

View File

@@ -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_DEVICE_NAME = "account.device_name"
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_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. */
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. */
val aciIdentityKey: IdentityKeyPair
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 */
fun setIdentityKeysFromPrimaryDevice(aciKeys: IdentityKeyPair) {
fun setAciIdentityKeysFromPrimaryDevice(aciKeys: IdentityKeyPair) {
synchronized(this) {
require(isLinkedDevice) { "Must be a linked device!" }
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 */
fun restoreLegacyIdentityPublicKeyFromBackup(base64: String) {
Log.w(TAG, "Restoring legacy identity public key from backup.")

View File

@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata;
import java.util.Collections;
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 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 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_SERVICE_REACHABLE = "misc.censorship.service_reachable";
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);
}
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() {
return getLong(CENSORSHIP_LAST_CHECK_TIME, 0);
}

View File

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

View File

@@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.core.util.ByteSerializer;
import org.signal.core.util.StringSerializer;
import org.thoughtcrime.securesms.database.model.databaseprotos.SignalStoreList;
@@ -51,6 +53,15 @@ abstract class SignalStoreValues {
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) {
byte[] blob = getBlob(key, null);
if (blob == null) {
@@ -94,6 +105,10 @@ abstract class SignalStoreValues {
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) {
putBlob(key, SignalStoreList.newBuilder()
.addAllContents(values.stream()

View File

@@ -105,9 +105,10 @@ public class ApplicationMigrations {
static final int MY_STORY_PRIVACY_MODE = 61;
static final int REFRESH_EXPIRING_CREDENTIAL = 62;
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
@@ -461,6 +462,10 @@ public class ApplicationMigrations {
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,6 +73,15 @@ public final class RegistrationRepository {
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) {
ProfileKey profileKey = findExistingProfileKey(e164);

View File

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

View File

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