Add skip SMS flow.

This commit is contained in:
Cody Henthorne
2023-02-22 11:03:10 -05:00
committed by Greyson Parrelli
parent a47e3900c1
commit 4f458a022f
19 changed files with 657 additions and 131 deletions

View File

@@ -56,6 +56,8 @@ import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher;
import org.whispersystems.signalservice.internal.push.AuthCredentials;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckRequest;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckResponse;
import org.whispersystems.signalservice.internal.push.CdsiAuthResponse;
import org.whispersystems.signalservice.internal.push.ProfileAvatarData;
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
@@ -73,9 +75,11 @@ import org.whispersystems.signalservice.internal.storage.protos.StorageManifest;
import org.whispersystems.signalservice.internal.storage.protos.WriteOperation;
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
import org.whispersystems.util.Base64;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -93,6 +97,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -206,6 +211,22 @@ public class SignalServiceAccountManager {
}
}
public Single<ServiceResponse<BackupAuthCheckResponse>> checkBackupAuthCredentials(@Nonnull String e164, @Nonnull List<String> basicAuthTokens) {
List<String> usernamePasswords = basicAuthTokens
.stream()
.limit(10)
.map(t -> {
try {
return new String(Base64.decode(t.replace("Basic ", "").trim()), StandardCharsets.ISO_8859_1);
} catch (IOException e) {
return null;
}
})
.collect(Collectors.toList());
return pushServiceSocket.checkBackupAuthCredentials(new BackupAuthCheckRequest(e164, usernamePasswords), DefaultResponseMapper.getDefault(BackupAuthCheckResponse.class));
}
/**
* Request a push challenge. A number will be pushed to the GCM (FCM) id. This can then be used
* during SMS/call requests to bypass the CAPTCHA.
@@ -326,44 +347,12 @@ public class SignalServiceAccountManager {
/**
* Refresh account attributes with server.
*
* @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, concatenated.
* @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install.
* This value should remain consistent across registrations for the same
* install, but probabilistically differ across registrations for
* separate installs.
* @param pin Only supply if pin has not yet been migrated to KBS.
* @param registrationLock Only supply if found on KBS.
*
* @throws IOException
*/
public void setAccountAttributes(String signalingKey,
int signalProtocolRegistrationId,
boolean fetchesMessages,
String pin,
String registrationLock,
byte[] unidentifiedAccessKey,
boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber,
byte[] encryptedDeviceName,
int pniRegistrationId,
String recoveryPassword)
public void setAccountAttributes(@Nonnull AccountAttributes accountAttributes)
throws IOException
{
this.pushServiceSocket.setAccountAttributes(
signalingKey,
signalProtocolRegistrationId,
fetchesMessages,
pin,
registrationLock,
unidentifiedAccessKey,
unrestrictedUnidentifiedAccess,
capabilities,
discoverableByPhoneNumber,
encryptedDeviceName,
pniRegistrationId,
recoveryPassword
);
this.pushServiceSocket.setAccountAttributes(accountAttributes);
}
/**

View File

@@ -0,0 +1,46 @@
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.annotation.JsonCreator
import okio.ByteString.Companion.encode
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import java.nio.charset.StandardCharsets
/**
* Request body JSON for verifying stored KBS auth credentials.
*/
@Suppress("unused")
class BackupAuthCheckRequest @JsonCreator constructor(
val number: String?,
val passwords: List<String>
)
/**
* Verify KBS auth credentials JSON response.
*/
data class BackupAuthCheckResponse @JsonCreator constructor(
private val matches: Map<String, Map<String, Any>>
) {
private val actualMatches = matches["matches"] ?: emptyMap()
val match: String? = actualMatches.entries.firstOrNull { it.value.toString() == "match" }?.key?.toBasic()
val invalid: List<String> = actualMatches.filterValues { it.toString() == "invalid" }.keys.map { it.toBasic() }
/** Server expects and returns values as <username>:<password> but we prefer the full encoded Basic auth header format */
private fun String.toBasic(): String {
return "Basic ${encode(StandardCharsets.ISO_8859_1).base64()}"
}
}
/**
* Processes a response from the verify stored KBS auth credentials request.
*/
class BackupAuthCheckProcessor(response: ServiceResponse<BackupAuthCheckResponse>) : ServiceResponseProcessor<BackupAuthCheckResponse>(response) {
fun getInvalid(): List<String> {
return response.result.map { it.invalid }.orElse(emptyList())
}
fun getValid(): String? {
return response.result.map { it.match }.orElse(null)
}
}

View File

@@ -286,6 +286,8 @@ public class PushServiceSocket {
private static final String REPORT_SPAM = "/v1/messages/report/%s/%s";
private static final String BACKUP_AUTH_CHECK = "/v1/backup/auth/check";
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
@@ -428,39 +430,13 @@ public class PushServiceSocket {
return JsonUtil.fromJson(responseBody, VerifyAccountResponse.class);
}
public void setAccountAttributes(String signalingKey,
int registrationId,
boolean fetchesMessages,
String pin,
String registrationLock,
byte[] unidentifiedAccessKey,
boolean unrestrictedUnidentifiedAccess,
AccountAttributes.Capabilities capabilities,
boolean discoverableByPhoneNumber,
byte[] encryptedDeviceName,
int pniRegistrationId,
String recoveryPassword)
public void setAccountAttributes(@Nonnull AccountAttributes accountAttributes)
throws IOException
{
if (registrationLock != null && pin != null) {
if (accountAttributes.getRegistrationLock() != null && accountAttributes.getPin() != null) {
throw new AssertionError("Pin should be null if registrationLock is set.");
}
String name = (encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName);
AccountAttributes accountAttributes = new AccountAttributes(signalingKey,
registrationId,
fetchesMessages,
pin,
registrationLock,
unidentifiedAccessKey,
unrestrictedUnidentifiedAccess,
capabilities,
discoverableByPhoneNumber,
name,
pniRegistrationId,
recoveryPassword);
makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes));
}
@@ -929,6 +905,22 @@ public class PushServiceSocket {
.onErrorReturn(ServiceResponse::forUnknownError);
}
public Single<ServiceResponse<BackupAuthCheckResponse>> checkBackupAuthCredentials(@Nonnull BackupAuthCheckRequest request,
@Nonnull ResponseMapper<BackupAuthCheckResponse> responseMapper)
{
Single<ServiceResponse<BackupAuthCheckResponse>> requestSingle = Single.fromCallable(() -> {
try (Response response = getServiceConnection(BACKUP_AUTH_CHECK, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), Optional.empty(), false)) {
String body = response.body() != null ? readBodyString(response.body()): "";
return responseMapper.map(response.code(), body, response::header, false);
}
});
return requestSingle
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.onErrorReturn(ServiceResponse::forUnknownError);
}
/**
* GET /v1/accounts/username_hash/{usernameHash}
*