mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Add skip SMS flow.
This commit is contained in:
committed by
Greyson Parrelli
parent
a47e3900c1
commit
4f458a022f
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user