Create account in single network request.

This commit is contained in:
Nicholas
2023-06-15 11:19:44 -04:00
committed by Cody Henthorne
parent 186a93f5d1
commit d16002546d
12 changed files with 243 additions and 77 deletions

View File

@@ -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<PreKeyRecord> generateAndStoreOneTimeEcPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
int preKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId();
final List<PreKeyRecord> records = generateOneTimeEcPreKeys(preKeyIdOffset);
storeOneTimeEcPreKeys(protocolStore, metadataStore, preKeyIdOffset, records);
return records;
}
public synchronized static List<PreKeyRecord> generateOneTimeEcPreKeys(int preKeyIdOffset) {
Log.i(TAG, "Generating one-time EC prekeys...");
List<PreKeyRecord> records = new LinkedList<>();
int preKeyIdOffset = metadataStore.getNextEcOneTimePreKeyId();
List<PreKeyRecord> 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<KyberPreKeyRecord> 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<PreKeyRecord> prekeys) {
Log.i(TAG, "Storing one-time EC prekeys...");
List<KyberPreKeyRecord> 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<KyberPreKeyRecord> generateAndStoreOneTimeKyberPreKeys(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
int preKeyIdOffset = metadataStore.getNextKyberPreKeyId();
List<KyberPreKeyRecord> records = generateOneTimeKyberPreKeyRecords(preKeyIdOffset, protocolStore.getIdentityKeyPair().getPrivateKey());
storeOneTimeKyberPreKeys(protocolStore, metadataStore, preKeyIdOffset, records);
return records;
}
@NonNull
public static List<KyberPreKeyRecord> generateOneTimeKyberPreKeyRecords(int preKeyIdOffset, @NonNull ECPrivateKey privateKey) {
Log.i(TAG, "Generating one-time kyber prekeys...");
List<KyberPreKeyRecord> 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<KyberPreKeyRecord> 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.

View File

@@ -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?

View File

@@ -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<PreKeyRecord> oneTimeEcPreKeys = PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore);
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore);
List<KyberPreKeyRecord> 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<PreKeyRecord> oneTimeEcPreKeys = PreKeyUtil.generateOneTimeEcPreKeys(ecOneTimePreKeyIdOffset);
int nextKyberPreKeyId = metadataStore.getNextKyberPreKeyId();
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateKyberPreKey(nextKyberPreKeyId, keyPair.getPrivateKey());
metadataStore.setLastResortKyberPreKeyId(nextKyberPreKeyId);
int oneTimeKyberPreKeyIdOffset = metadataStore.getNextKyberPreKeyId();
List<KyberPreKeyRecord> 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 {
}
});
}
}

View File

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

View File

@@ -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());