Fix prekey generation during registration.

This commit is contained in:
Greyson Parrelli
2023-07-10 23:05:36 -04:00
committed by GitHub
parent 61cd9767c8
commit f438ef543b
12 changed files with 137 additions and 126 deletions

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.registration
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.whispersystems.signalservice.api.account.PreKeyCollection
data class RegistrationData(
val code: String,
@@ -9,8 +8,6 @@ data class RegistrationData(
val password: String,
val registrationId: Int,
val profileKey: ProfileKey,
val aciPreKeyCollection: PreKeyCollection,
val pniPreKeyCollection: PreKeyCollection,
val fcmToken: String?,
val pniRegistrationId: Int,
val recoveryPassword: String?

View File

@@ -11,10 +11,8 @@ import androidx.core.app.NotificationManagerCompat;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.KeyHelper;
import org.signal.libsignal.protocol.util.Medium;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
@@ -27,6 +25,7 @@ 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.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationIds;
@@ -37,16 +36,14 @@ 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.account.PreKeyCollection;
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.api.util.Preconditions;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckProcessor;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import java.io.IOException;
import java.util.List;
@@ -106,7 +103,7 @@ public final class RegistrationRepository {
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
try {
String pin = response.getPin();
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData(), setRegistrationLockEnabled);
registerAccountInternal(registrationData, response, setRegistrationLockEnabled);
if (pin != null && !pin.isEmpty()) {
PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType());
@@ -128,15 +125,16 @@ public final class RegistrationRepository {
@WorkerThread
private void registerAccountInternal(@NonNull RegistrationData registrationData,
@NonNull VerifyAccountResponse response,
@Nullable String pin,
@Nullable KbsPinData kbsData,
@NonNull VerifyResponse response,
boolean setRegistrationLockEnabled)
throws IOException
{
ACI aci = ACI.parseOrThrow(response.getUuid());
PNI pni = PNI.parseOrThrow(response.getPni());
boolean hasPin = response.isStorageCapable();
Preconditions.checkNotNull(response.getAciPreKeyCollection(), "Missing ACI prekey collection!");
Preconditions.checkNotNull(response.getPniPreKeyCollection(), "Missing PNI prekey collection!");
ACI aci = ACI.parseOrThrow(response.getVerifyAccountResponse().getUuid());
PNI pni = PNI.parseOrThrow(response.getVerifyAccountResponse().getPni());
boolean hasPin = response.getVerifyAccountResponse().isStorageCapable();
SignalStore.account().setAci(aci);
SignalStore.account().setPni(pni);
@@ -145,17 +143,14 @@ public final class RegistrationRepository {
ApplicationDependencies.getProtocolStore().pni().sessions().archiveAllSessions();
SenderKeyUtil.clearAllState();
SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci();
PreKeyCollection aciPreKeyCollection = registrationData.getAciPreKeyCollection();
PreKeyMetadataStore aciMetadataStore = SignalStore.account().aciPreKeys();
SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci();
PreKeyMetadataStore aciMetadataStore = SignalStore.account().aciPreKeys();
SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni();
PreKeyCollection pniPreKeyCollection = registrationData.getPniPreKeyCollection();
PreKeyMetadataStore pniMetadataStore = SignalStore.account().pniPreKeys();
storePreKeys(aciProtocolStore, aciMetadataStore, aciPreKeyCollection);
storePreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection);
SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni();
PreKeyMetadataStore pniMetadataStore = SignalStore.account().pniPreKeys();
storeSignedAndLastResortPreKeys(aciProtocolStore, aciMetadataStore, response.getAciPreKeyCollection());
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, response.getPniPreKeyCollection());
RecipientTable recipientTable = SignalDatabase.recipients();
RecipientId selfId = Recipient.trustedPush(aci, pni, registrationData.getE164()).getId();
@@ -181,66 +176,33 @@ public final class RegistrationRepository {
TextSecurePreferences.setUnauthorizedReceived(context, false);
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
PinState.onRegistration(context, kbsData, pin, hasPin, setRegistrationLockEnabled);
PinState.onRegistration(context, response.getKbsData(), response.getPin(), hasPin, setRegistrationLockEnabled);
ApplicationDependencies.closeConnections();
ApplicationDependencies.getIncomingMessageObserver();
PreKeysSyncJob.enqueue();
}
public static PreKeyCollection generatePreKeysForType(ServiceIdType serviceIdType) {
IdentityKeyPair keyPair;
PreKeyMetadataStore metadataStore;
if (serviceIdType == ServiceIdType.ACI) {
if (!SignalStore.account().hasAciIdentityKey()) {
SignalStore.account().generateAciIdentityKeyIfNecessary();
}
keyPair = SignalStore.account().getAciIdentityKey();
metadataStore = SignalStore.account().aciPreKeys();
} else if (serviceIdType == ServiceIdType.PNI) {
if (!SignalStore.account().hasPniIdentityKey()) {
SignalStore.account().generatePniIdentityKeyIfNecessary();
}
keyPair = SignalStore.account().getPniIdentityKey();
metadataStore = SignalStore.account().pniPreKeys();
} else {
throw new IllegalArgumentException("serviceIdType is not one of {ACI, PNI}");
}
int nextSignedPreKeyId = metadataStore.getNextSignedPreKeyId();
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(nextSignedPreKeyId, keyPair.getPrivateKey());
metadataStore.setActiveSignedPreKeyId(signedPreKey.getId());
int ecOneTimePreKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId();
List<PreKeyRecord> oneTimeEcPreKeys = PreKeyUtil.generateOneTimeEcPreKeys(ecOneTimePreKeyIdOffset);
int nextKyberPreKeyId = metadataStore.getNextKyberPreKeyId();
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateKyberPreKey(nextKyberPreKeyId, keyPair.getPrivateKey());
metadataStore.setLastResortKyberPreKeyId(nextKyberPreKeyId);
int oneTimeKyberPreKeyIdOffset = (nextKyberPreKeyId + 1) % Medium.MAX_VALUE;
List<KyberPreKeyRecord> oneTimeKyberPreKeys = PreKeyUtil.generateOneTimeKyberPreKeyRecords(oneTimeKyberPreKeyIdOffset, keyPair.getPrivateKey());
public static PreKeyCollection generateSignedAndLastResortPreKeys(IdentityKeyPair identity, PreKeyMetadataStore metadataStore) {
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(metadataStore.getNextSignedPreKeyId(), identity.getPrivateKey());
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateLastRestortKyberPreKey(metadataStore.getNextKyberPreKeyId(), identity.getPrivateKey());
return new PreKeyCollection(
keyPair,
nextSignedPreKeyId,
ecOneTimePreKeyIdOffset,
nextKyberPreKeyId,
oneTimeKyberPreKeyIdOffset,
serviceIdType,
keyPair.getPublicKey(),
identity.getPublicKey(),
signedPreKey,
oneTimeEcPreKeys,
lastResortKyberPreKey,
oneTimeKyberPreKeys
lastResortKyberPreKey
);
}
private static void storePreKeys(SignalServiceAccountDataStoreImpl protocolStore, PreKeyMetadataStore metadataStore, PreKeyCollection preKeyCollection) {
PreKeyUtil.storeSignedPreKey(protocolStore, metadataStore, preKeyCollection.getNextSignedPreKeyId(), preKeyCollection.getSignedPreKey());
PreKeyUtil.storeOneTimeEcPreKeys(protocolStore, metadataStore, preKeyCollection.getEcOneTimePreKeyIdOffset(), preKeyCollection.getOneTimeEcPreKeys());
PreKeyUtil.storeLastResortKyberPreKey(protocolStore, metadataStore, preKeyCollection.getLastResortKyberPreKeyId(), preKeyCollection.getLastResortKyberPreKey());
PreKeyUtil.storeOneTimeKyberPreKeys(protocolStore, metadataStore, preKeyCollection.getOneTimeKyberPreKeyIdOffset(), preKeyCollection.getOneTimeKyberPreKeys());
private static void storeSignedAndLastResortPreKeys(SignalServiceAccountDataStoreImpl protocolStore, PreKeyMetadataStore metadataStore, PreKeyCollection preKeyCollection) {
PreKeyUtil.storeSignedPreKey(protocolStore, metadataStore, preKeyCollection.getSignedPreKey());
metadataStore.setSignedPreKeyRegistered(true);
metadataStore.setActiveSignedPreKeyId(preKeyCollection.getSignedPreKey().getId());
metadataStore.setLastSignedPreKeyRotationTime(System.currentTimeMillis());
PreKeyUtil.storeLastResortKyberPreKey(protocolStore, metadataStore, preKeyCollection.getLastResortKyberPreKey());
metadataStore.setLastResortKyberPreKeyId(preKeyCollection.getLastResortKyberPreKey().getId());
metadataStore.setLastResortKyberPreKeyRotationTime(System.currentTimeMillis());
}
private void saveOwnIdentityKey(@NonNull RecipientId selfId, @NonNull SignalServiceAccountDataStoreImpl protocolStore, long now) {

View File

@@ -6,6 +6,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.IdentityKeyPair
import org.thoughtcrime.securesms.AppCapabilities
import org.thoughtcrime.securesms.gcm.FcmUtil
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -185,9 +186,18 @@ class VerifyAccountRepository(private val context: Application) {
recoveryPassword = registrationData.recoveryPassword
)
SignalStore.account().generateAciIdentityKeyIfNecessary()
val aciIdentity: IdentityKeyPair = SignalStore.account().aciIdentityKey
SignalStore.account().generatePniIdentityKeyIfNecessary()
val pniIdentity: IdentityKeyPair = SignalStore.account().pniIdentityKey
val aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(aciIdentity, SignalStore.account().aciPreKeys)
val pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(pniIdentity, SignalStore.account().pniPreKeys)
return Single.fromCallable {
val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, registrationData.aciPreKeyCollection, registrationData.pniPreKeyCollection, registrationData.fcmToken, true)
VerifyResponse.from(response, kbsData, pin)
val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
VerifyResponse.from(response, kbsData, pin, aciPreKeyCollection, pniPreKeyCollection)
}.subscribeOn(Schedulers.io())
}

View File

@@ -1,14 +1,27 @@
package org.thoughtcrime.securesms.registration
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.account.PreKeyCollection
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
data class VerifyResponse(val verifyAccountResponse: VerifyAccountResponse, val kbsData: KbsPinData?, val pin: String?) {
data class VerifyResponse(
val verifyAccountResponse: VerifyAccountResponse,
val kbsData: KbsPinData?,
val pin: String?,
val aciPreKeyCollection: PreKeyCollection?,
val pniPreKeyCollection: PreKeyCollection?
) {
companion object {
fun from(response: ServiceResponse<VerifyAccountResponse>, kbsData: KbsPinData?, pin: String?): ServiceResponse<VerifyResponse> {
fun from(
response: ServiceResponse<VerifyAccountResponse>,
kbsData: KbsPinData?,
pin: String?,
aciPreKeyCollection: PreKeyCollection?,
pniPreKeyCollection: PreKeyCollection?
): ServiceResponse<VerifyResponse> {
return if (response.result.isPresent) {
ServiceResponse.forResult(VerifyResponse(response.result.get(), kbsData, pin), 200, null)
ServiceResponse.forResult(VerifyResponse(response.result.get(), kbsData, pin, aciPreKeyCollection, pniPreKeyCollection), 200, null)
} else {
ServiceResponse.coerceError(response)
}

View File

@@ -236,8 +236,6 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
getRegistrationSecret(),
registrationRepository.getRegistrationId(),
registrationRepository.getProfileKey(getNumber().getE164Number()),
RegistrationRepository.generatePreKeysForType(ServiceIdType.ACI),
RegistrationRepository.generatePreKeysForType(ServiceIdType.PNI),
getFcmToken(),
registrationRepository.getPniRegistrationId(),
getSessionId() != null ? null : getRecoveryPassword());
@@ -334,7 +332,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null;
if (!setRegistrationLockEnabled) {
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin);
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin, verifyResponse.getAciPreKeyCollection(), verifyResponse.getPniPreKeyCollection());
}
return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled)