Signal-Android/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java

211 wiersze
9.4 KiB
Java

package org.thoughtcrime.securesms.registration;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignalProtocolStore;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.KeyHelper;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl;
import org.thoughtcrime.securesms.database.IdentityTable;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.pin.PinState;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Operations required for finalizing the registration of an account. This is
* to be used after verifying the code and registration lock (if necessary) with
* the server and being issued a UUID.
*/
public final class RegistrationRepository {
private static final String TAG = Log.tag(RegistrationRepository.class);
private final Application context;
public RegistrationRepository(@NonNull Application context) {
this.context = context;
}
public int getRegistrationId() {
int registrationId = SignalStore.account().getRegistrationId();
if (registrationId == 0) {
registrationId = KeyHelper.generateRegistrationId(false);
SignalStore.account().setRegistrationId(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) {
ProfileKey profileKey = findExistingProfileKey(e164);
if (profileKey == null) {
profileKey = ProfileKeyUtil.createNew();
Log.i(TAG, "No profile key found, created a new one");
}
return profileKey;
}
public Single<ServiceResponse<VerifyResponse>> registerAccount(@NonNull RegistrationData registrationData,
@NonNull VerifyResponse response)
{
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
try {
String pin = response.getPin();
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData());
if (pin != null && !pin.isEmpty()) {
PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType());
}
JobManager jobManager = ApplicationDependencies.getJobManager();
jobManager.add(new DirectoryRefreshJob(false));
jobManager.add(new RotateCertificateJob());
DirectoryRefreshListener.schedule(context);
RotateSignedPreKeyListener.schedule(context);
return ServiceResponse.forResult(response, 200, null);
} catch (IOException e) {
return ServiceResponse.forUnknownError(e);
}
}).subscribeOn(Schedulers.io());
}
@WorkerThread
private void registerAccountInternal(@NonNull RegistrationData registrationData,
@NonNull VerifyAccountResponse response,
@Nullable String pin,
@Nullable KbsPinData kbsData)
throws IOException
{
ACI aci = ACI.parseOrThrow(response.getUuid());
PNI pni = PNI.parseOrThrow(response.getPni());
boolean hasPin = response.isStorageCapable();
SignalStore.account().setAci(aci);
SignalStore.account().setPni(pni);
ApplicationDependencies.getProtocolStore().aci().sessions().archiveAllSessions();
ApplicationDependencies.getProtocolStore().pni().sessions().archiveAllSessions();
SenderKeyUtil.clearAllState();
SignalServiceAccountManager accountManager = AccountManagerFactory.createAuthenticated(context, aci, pni, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci();
SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni();
generateAndRegisterPreKeys(ServiceIdType.ACI, accountManager, aciProtocolStore, SignalStore.account().aciPreKeys());
generateAndRegisterPreKeys(ServiceIdType.PNI, accountManager, pniProtocolStore, SignalStore.account().pniPreKeys());
if (registrationData.isFcm()) {
accountManager.setGcmId(Optional.ofNullable(registrationData.getFcmToken()));
}
RecipientTable recipientTable = SignalDatabase.recipients();
RecipientId selfId = Recipient.trustedPush(aci, pni, registrationData.getE164()).getId();
recipientTable.setProfileSharing(selfId, true);
recipientTable.markRegisteredOrThrow(selfId, aci);
recipientTable.linkIdsForSelf(aci, pni, registrationData.getE164());
recipientTable.setProfileKey(selfId, registrationData.getProfileKey());
ApplicationDependencies.getRecipientCache().clearSelf();
SignalStore.account().setE164(registrationData.getE164());
SignalStore.account().setFcmToken(registrationData.getFcmToken());
SignalStore.account().setFcmEnabled(registrationData.isFcm());
long now = System.currentTimeMillis();
saveOwnIdentityKey(selfId, aciProtocolStore, now);
saveOwnIdentityKey(selfId, pniProtocolStore, now);
SignalStore.account().setServicePassword(registrationData.getPassword());
SignalStore.account().setRegistered(true);
TextSecurePreferences.setPromptedPushRegistration(context, true);
TextSecurePreferences.setUnauthorizedReceived(context, false);
PinState.onRegistration(context, kbsData, pin, hasPin);
}
private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType,
@NonNull SignalServiceAccountManager accountManager,
@NonNull SignalProtocolStore protocolStore,
@NonNull PreKeyMetadataStore metadataStore)
throws IOException
{
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore);
List<PreKeyRecord> oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore);
accountManager.setPreKeys(serviceIdType, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys);
metadataStore.setActiveSignedPreKeyId(signedPreKey.getId());
metadataStore.setSignedPreKeyRegistered(true);
}
private void saveOwnIdentityKey(@NonNull RecipientId selfId, @NonNull SignalServiceAccountDataStoreImpl protocolStore, long now) {
protocolStore.identities().saveIdentityWithoutSideEffects(selfId,
protocolStore.getIdentityKeyPair().getPublicKey(),
IdentityTable.VerifiedStatus.VERIFIED,
true,
now,
true);
}
@WorkerThread
private static @Nullable ProfileKey findExistingProfileKey(@NonNull String e164number) {
RecipientTable recipientTable = SignalDatabase.recipients();
Optional<RecipientId> recipient = recipientTable.getByE164(e164number);
if (recipient.isPresent()) {
return ProfileKeyUtil.profileKeyOrNull(Recipient.resolved(recipient.get()).getProfileKey());
}
return null;
}
}