From f438ef543b13021b4048f1670ec26a330b6cbad6 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 10 Jul 2023 23:05:36 -0400 Subject: [PATCH] Fix prekey generation during registration. --- .../changenumber/ChangeNumberRepository.kt | 18 +++- .../securesms/crypto/PreKeyUtil.java | 72 ++++++++----- .../securesms/jobs/PnpInitializeDevicesJob.kt | 10 +- .../securesms/jobs/PreKeysSyncJob.kt | 2 +- .../registration/RegistrationData.kt | 3 - .../registration/RegistrationRepository.java | 100 ++++++------------ .../registration/VerifyAccountRepository.kt | 14 ++- .../securesms/registration/VerifyResponse.kt | 19 +++- .../viewmodel/RegistrationViewModel.java | 4 +- .../api/account/PreKeyCollection.kt | 15 +-- .../internal/push/PushServiceSocket.java | 3 +- .../push/RegistrationSessionRequestBody.kt | 3 +- 12 files changed, 137 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index 6008676c1f..8830475108 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -141,7 +141,13 @@ class ChangeNumberRepository( } } - VerifyResponse.from(changeNumberResponse, null, null) + VerifyResponse.from( + response = changeNumberResponse, + kbsData = null, + pin = null, + aciPreKeyCollection = null, + pniPreKeyCollection = null + ) }.subscribeOn(Schedulers.single()) .onErrorReturn { t -> ServiceResponse.forExecutionError(t) } } @@ -191,7 +197,13 @@ class ChangeNumberRepository( } } - VerifyResponse.from(changeNumberResponse, kbsData, pin) + VerifyResponse.from( + response = changeNumberResponse, + kbsData = kbsData, + pin = pin, + aciPreKeyCollection = null, + pniPreKeyCollection = null + ) }.subscribeOn(Schedulers.single()) .onErrorReturn { t -> ServiceResponse.forExecutionError(t) } } @@ -341,7 +353,7 @@ class ChangeNumberRepository( val lastResortKyberPreKeyRecord: KyberPreKeyRecord = if (deviceId == primaryDeviceId) { PreKeyUtil.generateAndStoreLastResortKyberPreKey(ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, pniIdentity.privateKey) } else { - PreKeyUtil.generateKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey) + PreKeyUtil.generateLastRestortKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey) } devicePniLastResortKyberPreKeys[deviceId] = KyberPreKeyEntity(lastResortKyberPreKeyRecord.id, lastResortKyberPreKeyRecord.keyPair.publicKey, lastResortKyberPreKeyRecord.signature) diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java index 3e2b9c3eb6..989e529de6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java @@ -50,21 +50,21 @@ public class PreKeyUtil { private static final long ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30); public synchronized static @NonNull List generateAndStoreOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { - int preKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId(); - final List records = generateOneTimeEcPreKeys(preKeyIdOffset); + int startingId = metadataStore.getNextEcOneTimePreKeyId(); + final List records = generateOneTimeEcPreKeys(startingId); - storeOneTimeEcPreKeys(protocolStore, metadataStore, preKeyIdOffset, records); + storeOneTimeEcPreKeys(protocolStore, metadataStore, records); return records; } - public synchronized static List generateOneTimeEcPreKeys(int preKeyIdOffset) { + public synchronized static List generateOneTimeEcPreKeys(int startingId) { Log.i(TAG, "Generating one-time EC prekeys..."); List records = new ArrayList<>(BATCH_SIZE); for (int i = 0; i < BATCH_SIZE; i++) { - int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE; + int preKeyId = (startingId + i) % Medium.MAX_VALUE; ECKeyPair keyPair = Curve.generateKeyPair(); PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair); @@ -74,47 +74,64 @@ public class PreKeyUtil { return records; } - public synchronized static void storeOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, int preKeyIdOffset, List prekeys) { + public synchronized static void storeOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, List prekeys) { Log.i(TAG, "Storing one-time EC prekeys..."); + if (prekeys.isEmpty()) { + Log.w(TAG, "Empty list of one-time EC prekeys! Nothing to store."); + return; + } + for (PreKeyRecord record : prekeys) { protocolStore.storePreKey(record.getId(), record); } - metadataStore.setNextEcOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); + + int lastId = prekeys.get(prekeys.size() - 1).getId(); + + metadataStore.setNextEcOneTimePreKeyId((lastId + 1) % Medium.MAX_VALUE); } public synchronized static @NonNull List generateAndStoreOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { - int preKeyIdOffset = metadataStore.getNextKyberPreKeyId(); - List records = generateOneTimeKyberPreKeyRecords(preKeyIdOffset, protocolStore.getIdentityKeyPair().getPrivateKey()); + int startingId = metadataStore.getNextKyberPreKeyId(); + List records = generateOneTimeKyberPreKeyRecords(startingId, protocolStore.getIdentityKeyPair().getPrivateKey()); - storeOneTimeKyberPreKeys(protocolStore, metadataStore, preKeyIdOffset, records); + storeOneTimeKyberPreKeys(protocolStore, metadataStore, records); return records; } @NonNull - public static List generateOneTimeKyberPreKeyRecords(int preKeyIdOffset, @NonNull ECPrivateKey privateKey) { + public static List generateOneTimeKyberPreKeyRecords(int startingId, @NonNull ECPrivateKey privateKey) { Log.i(TAG, "Generating one-time kyber prekeys..."); List records = new LinkedList<>(); for (int i = 0; i < BATCH_SIZE; i++) { - int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE; + int preKeyId = (startingId + i) % Medium.MAX_VALUE; KyberPreKeyRecord record = generateKyberPreKey(preKeyId, privateKey); records.add(record); } + return records; } - public synchronized static void storeOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, int preKeyIdOffset, List prekeys) { + public synchronized static void storeOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, List prekeys) { Log.i(TAG, "Storing one-time kyber prekeys..."); + if (prekeys.isEmpty()) { + Log.w(TAG, "Empty list of kyber prekeys! Nothing to store."); + return; + } + for (KyberPreKeyRecord record : prekeys) { protocolStore.storeKyberPreKey(record.getId(), record); } - metadataStore.setNextKyberPreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); + + int lastId = prekeys.get(prekeys.size() - 1).getId(); + + metadataStore.setNextKyberPreKeyId((lastId + 1) % Medium.MAX_VALUE); } public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { @@ -127,7 +144,8 @@ public class PreKeyUtil { { int signedPreKeyId = metadataStore.getNextSignedPreKeyId(); SignedPreKeyRecord record = generateSignedPreKey(signedPreKeyId, privateKey); - storeSignedPreKey(protocolStore, metadataStore, signedPreKeyId, record); + + storeSignedPreKey(protocolStore, metadataStore, record); return record; } @@ -145,11 +163,11 @@ public class PreKeyUtil { } } - public synchronized static void storeSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, int signedPreKeyId, SignedPreKeyRecord record) { - Log.i(TAG, "Storing signed prekeys..."); + public synchronized static void storeSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, SignedPreKeyRecord record) { + Log.i(TAG, "Storing signed prekey..."); - protocolStore.storeSignedPreKey(signedPreKeyId, record); - metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE); + protocolStore.storeSignedPreKey(record.getId(), record); + metadataStore.setNextSignedPreKeyId((record.getId() + 1) % Medium.MAX_VALUE); } public synchronized static @NonNull KyberPreKeyRecord generateAndStoreLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { @@ -162,24 +180,28 @@ public class PreKeyUtil { { int id = metadataStore.getNextKyberPreKeyId(); KyberPreKeyRecord record = generateKyberPreKey(id, privateKey); - storeLastResortKyberPreKey(protocolStore, metadataStore, id, record); + + storeLastResortKyberPreKey(protocolStore, metadataStore, record); return record; } - public synchronized static @NonNull KyberPreKeyRecord generateKyberPreKey(int id, @NonNull ECPrivateKey privateKey) { - Log.i(TAG, "Generating kyber prekeys..."); + public synchronized static @NonNull KyberPreKeyRecord generateLastRestortKyberPreKey(int id, @NonNull ECPrivateKey privateKey) { + Log.i(TAG, "Generating last resort kyber prekey..."); + return generateKyberPreKey(id, privateKey); + } + private synchronized static @NonNull KyberPreKeyRecord generateKyberPreKey(int id, @NonNull ECPrivateKey privateKey) { KEMKeyPair keyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024); byte[] signature = privateKey.calculateSignature(keyPair.getPublicKey().serialize()); return new KyberPreKeyRecord(id, System.currentTimeMillis(), keyPair, signature); } - public synchronized static void storeLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, int id, KyberPreKeyRecord record) { + public synchronized static void storeLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, KyberPreKeyRecord record) { Log.i(TAG, "Storing kyber prekeys..."); - protocolStore.storeKyberPreKey(id, record); - metadataStore.setNextKyberPreKeyId((id + 1) % Medium.MAX_VALUE); + protocolStore.storeKyberPreKey(record.getId(), record); + metadataStore.setNextKyberPreKeyId((record.getId() + 1) % Medium.MAX_VALUE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt index 67a8493d3d..9eeb39351e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt @@ -150,7 +150,13 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base } } - VerifyResponse.from(distributionResponse, null, null) + VerifyResponse.from( + response = distributionResponse, + kbsData = null, + pin = null, + aciPreKeyCollection = null, + pniPreKeyCollection = null + ) }.subscribeOn(Schedulers.single()) .onErrorReturn { t -> ServiceResponse.forExecutionError(t) } } @@ -188,7 +194,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base val lastResortKyberPreKeyRecord: KyberPreKeyRecord = if (deviceId == primaryDeviceId) { pniProtocolStore.loadKyberPreKey(SignalStore.account().pniPreKeys.lastResortKyberPreKeyId) } else { - PreKeyUtil.generateKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey) + PreKeyUtil.generateLastRestortKyberPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey) } devicePniLastResortKyberPreKeys[deviceId] = KyberPreKeyEntity(lastResortKyberPreKeyRecord.id, lastResortKyberPreKeyRecord.keyPair.publicKey, lastResortKyberPreKeyRecord.signature) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt index da32c202da..777681c85b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt @@ -204,7 +204,7 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param log(serviceIdType, "Rotating last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore) } else { - log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") + log(serviceIdType, "No need to rotate last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)") null } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt index 0afef57c98..41a7db86d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt @@ -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? diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index a01dfbfe2d..023f398726 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -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.>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 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 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt index b01e860eca..92b1b7e638 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt @@ -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()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt index a84f00b5c5..5692784946 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponse.kt @@ -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, kbsData: KbsPinData?, pin: String?): ServiceResponse { + fun from( + response: ServiceResponse, + kbsData: KbsPinData?, + pin: String?, + aciPreKeyCollection: PreKeyCollection?, + pniPreKeyCollection: PreKeyCollection? + ): ServiceResponse { 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) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index 397b7a2220..f0e63d06de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -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) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollection.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollection.kt index 8bd7f0c528..08a02dfa21 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollection.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollection.kt @@ -6,27 +6,16 @@ package org.whispersystems.signalservice.api.account import org.signal.libsignal.protocol.IdentityKey -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.whispersystems.signalservice.api.push.ServiceIdType /** * Holder class to pass around a bunch of prekeys that we send off to the service during registration. - * As the service does not return the submitted prekeys,we need to hold them in memory so that when + * As the service does not return the submitted prekeys, we need to hold them in memory so that when * the service approves the keys we have a local copy to persist. */ data class PreKeyCollection( - val identityKeyPair: IdentityKeyPair, - val nextSignedPreKeyId: Int, - val ecOneTimePreKeyIdOffset: Int, - val lastResortKyberPreKeyId: Int, - val oneTimeKyberPreKeyIdOffset: Int, - val serviceIdType: ServiceIdType, val identityKey: IdentityKey, val signedPreKey: SignedPreKeyRecord, - val oneTimeEcPreKeys: List, - val lastResortKyberPreKey: KyberPreKeyRecord, - val oneTimeKyberPreKeys: List + val lastResortKyberPreKey: KyberPreKeyRecord ) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index a5b53c3615..8b219b4583 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -433,7 +433,8 @@ public class PushServiceSocket { aciLastResortKyberPreKey, pniLastResortKyberPreKey, gcmRegistrationId, - skipDeviceTransfer); + skipDeviceTransfer, + true); String response = makeServiceRequest(path, "POST", JsonUtil.toJson(body), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty()); return JsonUtil.fromJson(response, VerifyAccountResponse.class); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt index 60edee8827..4657b58373 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt @@ -17,5 +17,6 @@ data class RegistrationSessionRequestBody( @JsonProperty val aciPqLastResortPreKey: KyberPreKeyEntity, @JsonProperty val pniPqLastResortPreKey: KyberPreKeyEntity, @JsonProperty val gcmToken: GcmRegistrationId?, - @JsonProperty val skipDeviceTransfer: Boolean + @JsonProperty val skipDeviceTransfer: Boolean, + @JsonProperty val requireAtomic: Boolean = true )