diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt index b38112e2f3..a3ffd95ac8 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt @@ -36,7 +36,6 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.ServiceResponseProcessor import org.whispersystems.signalservice.internal.push.VerifyAccountResponse -import java.lang.IllegalArgumentException import java.util.UUID /** @@ -88,6 +87,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() password = Util.getSecret(18), registrationId = registrationRepository.registrationId, profileKey = registrationRepository.getProfileKey("+15555550101"), + preKeyCollections = RegistrationRepository.generatePreKeys()!!, fcmToken = null, pniRegistrationId = registrationRepository.pniRegistrationId, recoveryPassword = "asdfasdfasdfasdf" 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 9d1830c9c5..3e2b9c3eb6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java @@ -35,6 +35,7 @@ import org.signal.libsignal.protocol.util.Medium; import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore; import org.whispersystems.signalservice.api.SignalServiceAccountDataStore; +import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; @@ -49,45 +50,73 @@ 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); + + storeOneTimeEcPreKeys(protocolStore, metadataStore, preKeyIdOffset, records); + + return records; + } + + public synchronized static List generateOneTimeEcPreKeys(int preKeyIdOffset) { Log.i(TAG, "Generating one-time EC prekeys..."); - List records = new LinkedList<>(); - int preKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId(); + List records = new ArrayList<>(BATCH_SIZE); for (int i = 0; i < BATCH_SIZE; i++) { int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE; ECKeyPair keyPair = Curve.generateKeyPair(); PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair); - protocolStore.storePreKey(preKeyId, record); records.add(record); } - metadataStore.setNextEcOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); - return records; } - public synchronized static @NonNull List generateAndStoreOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { - Log.i(TAG, "Generating one-time kyber prekeys..."); + public synchronized static void storeOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, int preKeyIdOffset, List prekeys) { + Log.i(TAG, "Storing one-time EC prekeys..."); - List records = new LinkedList<>(); - int preKeyIdOffset = metadataStore.getNextKyberPreKeyId(); - - - for (int i = 0; i < BATCH_SIZE; i++) { - int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE; - KyberPreKeyRecord record = generateKyberPreKey(preKeyId, protocolStore.getIdentityKeyPair().getPrivateKey()); - - protocolStore.storeKyberPreKey(preKeyId, record); - records.add(record); + for (PreKeyRecord record : prekeys) { + protocolStore.storePreKey(record.getId(), record); } + metadataStore.setNextEcOneTimePreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); - metadataStore.setNextKyberPreKeyId((preKeyIdOffset + BATCH_SIZE + 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()); + + storeOneTimeKyberPreKeys(protocolStore, metadataStore, preKeyIdOffset, records); return records; } + @NonNull + public static List generateOneTimeKyberPreKeyRecords(int preKeyIdOffset, @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; + KyberPreKeyRecord record = generateKyberPreKey(preKeyId, privateKey); + + records.add(record); + } + return records; + } + + public synchronized static void storeOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, PreKeyMetadataStore metadataStore, int preKeyIdOffset, List prekeys) { + Log.i(TAG, "Storing one-time kyber prekeys..."); + + for (KyberPreKeyRecord record : prekeys) { + protocolStore.storeKyberPreKey(record.getId(), record); + } + metadataStore.setNextKyberPreKeyId((preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); + } + public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey()); } @@ -96,18 +125,16 @@ public class PreKeyUtil { @NonNull PreKeyMetadataStore metadataStore, @NonNull ECPrivateKey privateKey) { - 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); + storeSignedPreKey(protocolStore, metadataStore, signedPreKeyId, record); return record; } public synchronized static @NonNull SignedPreKeyRecord generateSignedPreKey(int signedPreKeyId, @NonNull ECPrivateKey privateKey) { + Log.i(TAG, "Generating signed prekeys..."); + try { ECKeyPair keyPair = Curve.generateKeyPair(); byte[] signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize()); @@ -118,6 +145,13 @@ public class PreKeyUtil { } } + public synchronized static void storeSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, int signedPreKeyId, SignedPreKeyRecord record) { + Log.i(TAG, "Storing signed prekeys..."); + + protocolStore.storeSignedPreKey(signedPreKeyId, record); + metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE); + } + public synchronized static @NonNull KyberPreKeyRecord generateAndStoreLastResortKyberPreKey(@NonNull SignalServiceAccountDataStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { return generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey()); } @@ -128,20 +162,26 @@ public class PreKeyUtil { { int id = metadataStore.getNextKyberPreKeyId(); KyberPreKeyRecord record = generateKyberPreKey(id, privateKey); - - protocolStore.storeKyberPreKey(id, record); - metadataStore.setNextKyberPreKeyId((id + 1) % Medium.MAX_VALUE); + storeLastResortKyberPreKey(protocolStore, metadataStore, id, record); return record; } public synchronized static @NonNull KyberPreKeyRecord generateKyberPreKey(int id, @NonNull ECPrivateKey privateKey) { + Log.i(TAG, "Generating kyber prekeys..."); + 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) { + Log.i(TAG, "Storing kyber prekeys..."); + protocolStore.storeKyberPreKey(id, record); + metadataStore.setNextKyberPreKeyId((id + 1) % Medium.MAX_VALUE); + } + /** * Finds all of the signed prekeys that are older than the archive age, and archive all but the youngest of those. 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 41a7db86d5..42d83e92c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.registration import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.whispersystems.signalservice.api.account.PreKeyCollections data class RegistrationData( val code: String, @@ -8,6 +9,7 @@ data class RegistrationData( val password: String, val registrationId: Int, val profileKey: ProfileKey, + val preKeyCollections: PreKeyCollections, 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 bc21831e56..e065aa3988 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -9,12 +9,13 @@ import androidx.annotation.WorkerThread; 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.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.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.SenderKeyUtil; @@ -37,8 +38,9 @@ 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.SignalServiceAccountDataStore; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.account.PreKeyCollection; +import org.whispersystems.signalservice.api.account.PreKeyCollections; import org.whispersystems.signalservice.api.account.PreKeyUpload; import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.PNI; @@ -145,16 +147,17 @@ public final class RegistrationRepository { ApplicationDependencies.getProtocolStore().pni().sessions().archiveAllSessions(); SenderKeyUtil.clearAllState(); - SignalServiceAccountManager accountManager = AccountManagerFactory.getInstance().createAuthenticated(context, aci, pni, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword()); - SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci(); - SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni(); + SignalServiceAccountDataStoreImpl aciProtocolStore = ApplicationDependencies.getProtocolStore().aci(); + PreKeyCollection aciPreKeyCollection = registrationData.getPreKeyCollections().getAciPreKeyCollection(); + PreKeyMetadataStore aciMetadataStore = SignalStore.account().aciPreKeys(); - generateAndRegisterPreKeys(ServiceIdType.ACI, accountManager, aciProtocolStore, SignalStore.account().aciPreKeys()); - generateAndRegisterPreKeys(ServiceIdType.PNI, accountManager, pniProtocolStore, SignalStore.account().pniPreKeys()); + SignalServiceAccountDataStoreImpl pniProtocolStore = ApplicationDependencies.getProtocolStore().pni(); + PreKeyCollection pniPreKeyCollection = registrationData.getPreKeyCollections().getPniPreKeyCollection(); + PreKeyMetadataStore pniMetadataStore = SignalStore.account().pniPreKeys(); + + storePreKeys(aciProtocolStore, aciMetadataStore, aciPreKeyCollection); + storePreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection); - if (registrationData.isFcm()) { - accountManager.setGcmId(Optional.ofNullable(registrationData.getFcmToken())); - } RecipientTable recipientTable = SignalDatabase.recipients(); RecipientId selfId = Recipient.trustedPush(aci, pni, registrationData.getE164()).getId(); @@ -186,24 +189,57 @@ public final class RegistrationRepository { ApplicationDependencies.getIncomingMessageObserver(); } - private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType, - @NonNull SignalServiceAccountManager accountManager, - @NonNull SignalServiceAccountDataStore protocolStore, - @NonNull PreKeyMetadataStore metadataStore) - throws IOException - { - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore); - List oneTimeEcPreKeys = PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore); - KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore); - List oneTimeKyberPreKeys = PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(protocolStore, metadataStore); + public static @Nullable PreKeyCollections generatePreKeys() { + final IdentityKeyPair keyPair = IdentityKeyUtil.generateIdentityKeyPair(); + final PreKeyMetadataStore aciMetadataStore = SignalStore.account().aciPreKeys(); + final PreKeyMetadataStore pniMetadataStore = SignalStore.account().pniPreKeys(); - accountManager.setPreKeys(new PreKeyUpload(serviceIdType, - protocolStore.getIdentityKeyPair().getPublicKey(), - signedPreKey, - oneTimeEcPreKeys, - lastResortKyberPreKey, - oneTimeKyberPreKeys)); + try { + return new PreKeyCollections(keyPair, + generatePreKeysForType(ServiceIdType.ACI, keyPair, aciMetadataStore), + generatePreKeysForType(ServiceIdType.PNI, keyPair, pniMetadataStore) + ); + } catch (IOException e) { + Log.e(TAG, "Failed to generate prekeys!", e); + return null; + } + } + + private static PreKeyCollection generatePreKeysForType(ServiceIdType serviceIdType, IdentityKeyPair keyPair, PreKeyMetadataStore metadataStore) throws IOException { + 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 = metadataStore.getNextKyberPreKeyId(); + List oneTimeKyberPreKeys = PreKeyUtil.generateOneTimeKyberPreKeyRecords(oneTimeKyberPreKeyIdOffset, keyPair.getPrivateKey()); + + return new PreKeyCollection( + nextSignedPreKeyId, + ecOneTimePreKeyIdOffset, + nextKyberPreKeyId, + oneTimeKyberPreKeyIdOffset, + serviceIdType, + keyPair.getPublicKey(), + signedPreKey, + oneTimeEcPreKeys, + lastResortKyberPreKey, + oneTimeKyberPreKeys + ); + } + + 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()); metadataStore.setSignedPreKeyRegistered(true); } @@ -239,4 +275,5 @@ public final class RegistrationRepository { } }); } + } 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 1fbede1058..e636845cd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt @@ -186,7 +186,7 @@ class VerifyAccountRepository(private val context: Application) { ) return Single.fromCallable { - val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, true) + val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, registrationData.preKeyCollections, registrationData.fcmToken, true) VerifyResponse.from(response, kbsData, pin) }.subscribeOn(Schedulers.io()) } 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 4b7a09d982..e2d747ed14 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 @@ -235,6 +235,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { getRegistrationSecret(), registrationRepository.getRegistrationId(), registrationRepository.getProfileKey(getNumber().getE164Number()), + Objects.requireNonNull(RegistrationRepository.generatePreKeys()), getFcmToken(), registrationRepository.getPniRegistrationId(), getSessionId() != null ? null : getRecoveryPassword()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 0ba375b337..295a3e29bf 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -19,6 +19,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest; import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest; +import org.whispersystems.signalservice.api.account.PreKeyCollections; import org.whispersystems.signalservice.api.account.PreKeyUpload; import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream; @@ -316,9 +317,9 @@ public class SignalServiceAccountManager { } } - public @Nonnull ServiceResponse registerAccount(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, boolean skipDeviceTransfer) { + public @Nonnull ServiceResponse registerAccount(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, PreKeyCollections preKeys, String fcmToken, boolean skipDeviceTransfer) { try { - VerifyAccountResponse response = pushServiceSocket.submitRegistrationRequest(sessionId, recoveryPassword, attributes, skipDeviceTransfer); + VerifyAccountResponse response = pushServiceSocket.submitRegistrationRequest(sessionId, recoveryPassword, attributes, preKeys, fcmToken, skipDeviceTransfer); return ServiceResponse.forResult(response, 200, null); } catch (IOException e) { return ServiceResponse.forUnknownError(e); 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 new file mode 100644 index 0000000000..070886944b --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollection.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.signalservice.api.account + +import org.signal.libsignal.protocol.IdentityKey +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 + * the service approves the keys we have a local copy to persist. + */ +data class PreKeyCollection( + 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 +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollections.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollections.kt new file mode 100644 index 0000000000..348dbf4ec6 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/PreKeyCollections.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.signalservice.api.account + +import org.signal.libsignal.protocol.IdentityKeyPair + +/** + * A holder class to hold onto two [PreKeyCollection] that are very similar but share an [IdentityKeyPair] + */ +data class PreKeyCollections( + val identityKeyPair: IdentityKeyPair, + val aciPreKeyCollection: PreKeyCollection, + val pniPreKeyCollection: PreKeyCollection +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/GcmRegistrationId.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/GcmRegistrationId.kt new file mode 100644 index 0000000000..f510aa3af5 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/GcmRegistrationId.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonProperty + +data class GcmRegistrationId( + @JsonProperty val gcmRegistrationId: String, + @JsonProperty val webSocketChannel: Boolean +) 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 b6c3929c07..a295cc6239 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 @@ -40,8 +40,10 @@ import org.signal.storageservice.protos.groups.GroupExternalCredential; import org.signal.storageservice.protos.groups.GroupJoinInfo; import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest; +import org.whispersystems.signalservice.api.account.PreKeyCollections; import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest; import org.whispersystems.signalservice.api.account.PreKeyUpload; +import org.whispersystems.signalservice.api.account.PreKeyCollection; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.groupsv2.CredentialResponse; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; @@ -163,6 +165,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -391,21 +394,52 @@ public class PushServiceSocket { } } - public VerifyAccountResponse submitRegistrationRequest(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, boolean skipDeviceTransfer) throws IOException { + public VerifyAccountResponse submitRegistrationRequest(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, PreKeyCollections preKeys, @Nullable String fcmToken, boolean skipDeviceTransfer) throws IOException { String path = REGISTRATION_PATH; if (sessionId == null && recoveryPassword == null) { throw new IllegalArgumentException("Neither Session ID nor Recovery Password provided."); - } if (sessionId != null && recoveryPassword != null) { throw new IllegalArgumentException("You must supply one and only one of either: Session ID, or Recovery Password."); } + + GcmRegistrationId gcmRegistrationId; + if (attributes.getFetchesMessages()) { + gcmRegistrationId = null; + } else { + gcmRegistrationId = new GcmRegistrationId(fcmToken, true); + } + RegistrationSessionRequestBody body; if (sessionId != null) { - body = new RegistrationSessionRequestBody(sessionId, null, attributes, skipDeviceTransfer); + final PreKeyCollection aciPreKeys = preKeys.getAciPreKeyCollection(); + final PreKeyCollection pniPreKeys = preKeys.getPniPreKeyCollection(); + final SignedPreKeyEntity aciSignedPreKey = new SignedPreKeyEntity(Objects.requireNonNull(aciPreKeys.getSignedPreKey()).getId(), + aciPreKeys.getSignedPreKey().getKeyPair().getPublicKey(), + aciPreKeys.getSignedPreKey().getSignature()); + final SignedPreKeyEntity pniSignedPreKey = new SignedPreKeyEntity(Objects.requireNonNull(pniPreKeys.getSignedPreKey()).getId(), + pniPreKeys.getSignedPreKey().getKeyPair().getPublicKey(), + pniPreKeys.getSignedPreKey().getSignature()); + final KyberPreKeyEntity aciLastResortKyberPreKey = new KyberPreKeyEntity(Objects.requireNonNull(aciPreKeys.getLastResortKyberPreKey()).getId(), + aciPreKeys.getLastResortKyberPreKey().getKeyPair().getPublicKey(), + aciPreKeys.getLastResortKyberPreKey().getSignature()); + final KyberPreKeyEntity pniLastResortKyberPreKey = new KyberPreKeyEntity(Objects.requireNonNull(pniPreKeys.getLastResortKyberPreKey()).getId(), + pniPreKeys.getLastResortKyberPreKey().getKeyPair().getPublicKey(), + pniPreKeys.getLastResortKyberPreKey().getSignature()); + body = new RegistrationSessionRequestBody(sessionId, + null, + attributes, + Base64.encodeBytesWithoutPadding(aciPreKeys.getIdentityKey().serialize()), + Base64.encodeBytesWithoutPadding(pniPreKeys.getIdentityKey().serialize()), + aciSignedPreKey, + pniSignedPreKey, + aciLastResortKyberPreKey, + pniLastResortKyberPreKey, + gcmRegistrationId, + skipDeviceTransfer); } else { - body = new RegistrationSessionRequestBody(null, recoveryPassword, attributes, skipDeviceTransfer); + body = new RegistrationSessionRequestBody(null, recoveryPassword, attributes, null, null, null, null, null, null, null, skipDeviceTransfer); } String response = makeServiceRequest(path, "POST", JsonUtil.toJson(body), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty()); @@ -486,7 +520,7 @@ public class PushServiceSocket { JsonUtil.toJson(new ProvisioningMessage(Base64.encodeBytes(body)))); } - public void registerGcmId(String gcmRegistrationId) throws IOException { + public void registerGcmId(@Nonnull String gcmRegistrationId) throws IOException { GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true); makeServiceRequest(REGISTER_GCM_PATH, "PUT", JsonUtil.toJson(registration)); } @@ -2271,22 +2305,6 @@ public class PushServiceSocket { return readBodyJson(response.body(), clazz); } - private static class GcmRegistrationId { - - @JsonProperty - private String gcmRegistrationId; - - @JsonProperty - private boolean webSocketChannel; - - public GcmRegistrationId() {} - - public GcmRegistrationId(String gcmRegistrationId, boolean webSocketChannel) { - this.gcmRegistrationId = gcmRegistrationId; - this.webSocketChannel = webSocketChannel; - } - } - public enum VerificationCodeTransport { SMS, VOICE } private static class RegistrationLock { 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 714da0ab47..60edee8827 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 @@ -3,11 +3,19 @@ package org.whispersystems.signalservice.internal.push import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import org.whispersystems.signalservice.api.account.AccountAttributes +import org.whispersystems.signalservice.api.push.SignedPreKeyEntity @JsonInclude(JsonInclude.Include.NON_NULL) data class RegistrationSessionRequestBody( @JsonProperty val sessionId: String? = null, @JsonProperty val recoveryPassword: String? = null, @JsonProperty val accountAttributes: AccountAttributes, + @JsonProperty val aciIdentityKey: String, + @JsonProperty val pniIdentityKey: String, + @JsonProperty val aciSignedPreKey: SignedPreKeyEntity, + @JsonProperty val pniSignedPreKey: SignedPreKeyEntity, + @JsonProperty val aciPqLastResortPreKey: KyberPreKeyEntity, + @JsonProperty val pniPqLastResortPreKey: KyberPreKeyEntity, + @JsonProperty val gcmToken: GcmRegistrationId?, @JsonProperty val skipDeviceTransfer: Boolean )