Skip to content

Commit

Permalink
Fix a RRP recovery path.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal committed Jul 27, 2023
1 parent 859efab commit 6bcced3
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 29 deletions.
Expand Up @@ -31,7 +31,6 @@
import org.whispersystems.signalservice.api.SvrNoDataException;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.kbs.PinHashUtil;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException;
import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException;
import org.whispersystems.signalservice.internal.ServiceResponse;
Expand Down Expand Up @@ -346,12 +345,12 @@ private Single<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@No
if (hasRecoveryPassword) {
return Single.just(true);
} else {
return checkForValidKbsAuthCredentials();
return checkForValidSvrAuthCredentials();
}
});
}

private Single<Boolean> checkForValidKbsAuthCredentials() {
private Single<Boolean> checkForValidSvrAuthCredentials() {
final List<String> svrAuthTokenList = SignalStore.svr().getAuthTokenList();
List<String> usernamePasswords = svrAuthTokenList
.stream()
Expand All @@ -370,7 +369,14 @@ private Single<Boolean> checkForValidKbsAuthCredentials() {
}

return registrationRepository.getSvrAuthCredential(getRegistrationData(), usernamePasswords)
.flatMap(p -> Single.just(p.getValid() != null))
.flatMap(p -> {
if (p.hasValidSvr2AuthCredential()) {
setSvrAuthCredentials(new SvrAuthCredentialSet(null, p.requireSvr2AuthCredential()));
return Single.just(true);
} else {
return Single.just(false);
}
})
.onErrorReturnItem(false)
.observeOn(AndroidSchedulers.mainThread());
}
Expand Down
Expand Up @@ -4,6 +4,7 @@ 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.io.IOException
import java.nio.charset.StandardCharsets

/**
Expand All @@ -23,14 +24,27 @@ data class BackupAuthCheckResponse @JsonCreator constructor(
) {
private val actualMatches = matches["matches"] ?: emptyMap()

val match: String? = actualMatches.entries.firstOrNull { it.value.toString() == "match" }?.key?.toBasic()
val match: AuthCredentials? = actualMatches.entries.firstOrNull { it.value.toString() == "match" }?.key?.toAuthCredential()
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()}"
}

private fun String.toAuthCredential(): AuthCredentials {
val firstColonIndex = this.indexOf(":")

if (firstColonIndex < 0) {
throw IOException("Invalid credential returned!")
}

val username = this.substring(0, firstColonIndex)
val password = this.substring(firstColonIndex + 1)

return AuthCredentials.create(username, password)
}

fun merge(other: BackupAuthCheckResponse): BackupAuthCheckResponse {
return BackupAuthCheckResponse(this.matches + other.matches)
}
Expand All @@ -44,7 +58,11 @@ class BackupAuthCheckProcessor(response: ServiceResponse<BackupAuthCheckResponse
return response.result.map { it.invalid }.orElse(emptyList())
}

fun getValid(): String? {
return response.result.map { it.match }.orElse(null)
fun hasValidSvr2AuthCredential(): Boolean {
return response.result.map { it.match }.orElse(null) != null
}

fun requireSvr2AuthCredential(): AuthCredentials {
return response.result.get().match!!
}
}
Expand Up @@ -297,8 +297,7 @@ public class PushServiceSocket {

private static final String REPORT_SPAM = "/v1/messages/report/%s/%s";

private static final String BACKUP_AUTH_CHECK_V1 = "/v1/backup/auth/check";
private static final String BACKUP_AUTH_CHECK_V2 = "/v2/backup/auth/check";
private static final String BACKUP_AUTH_CHECK = "/v2/backup/auth/check";

private static final String CALL_LINK_CREATION_AUTH = "/v1/call-link/create-auth";
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
Expand Down Expand Up @@ -974,26 +973,14 @@ public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonn
public Single<ServiceResponse<BackupAuthCheckResponse>> checkBackupAuthCredentials(@Nonnull BackupAuthCheckRequest request,
@Nonnull ResponseMapper<BackupAuthCheckResponse> responseMapper)
{
return Single
.zip(
createBackupAuthCheckSingle(BACKUP_AUTH_CHECK_V1, request, responseMapper),
createBackupAuthCheckSingle(BACKUP_AUTH_CHECK_V2, request, responseMapper),
(v1, v2) -> {
if (v1.getResult().isPresent() && v2.getResult().isPresent()) {
BackupAuthCheckResponse v1Result = v1.getResult().get();
BackupAuthCheckResponse v2Result = v2.getResult().get();
BackupAuthCheckResponse merged = v1Result.merge(v2Result);

return new ServiceResponse<>(v2.getStatus(), null, merged, null, null);
} else if (v1.getResult().isEmpty() && v2.getResult().isEmpty()) {
return v2;
} else if (v2.getResult().isEmpty()) {
return v1;
} else {
return v2;
}
}
)
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);
Expand Down

0 comments on commit 6bcced3

Please sign in to comment.