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

@@ -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<VerifyAccountResponse> registerAccount(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, boolean skipDeviceTransfer) {
public @Nonnull ServiceResponse<VerifyAccountResponse> 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);

View File

@@ -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<PreKeyRecord>,
val lastResortKyberPreKey: KyberPreKeyRecord,
val oneTimeKyberPreKeys: List<KyberPreKeyRecord>
)

View File

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

View File

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

View File

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

View File

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