Add batch identity key check call for improved safety number change performance.

fork-5.53.8
Cody Henthorne 2022-07-11 10:50:24 -04:00 zatwierdzone przez Alex Hart
rodzic 524adcb6a4
commit b0dc7fe6df
13 zmienionych plików z 206 dodań i 29 usunięć

Wyświetl plik

@ -41,4 +41,5 @@
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
</issue>
<issue id="OptionalUsedAsFieldOrParameterType" severity="ignore" />
</lint>

Wyświetl plik

@ -61,6 +61,7 @@ dependencyResolutionManagement {
alias('google-zxing-android-integration').to('com.google.zxing:android-integration:3.3.0')
alias('google-zxing-core').to('com.google.zxing:core:3.4.1')
alias('google-ez-vcard').to('com.googlecode.ez-vcard:ez-vcard:0.9.11')
alias('google-jsr305').to('com.google.code.findbugs:jsr305:3.0.2')
// Exoplayer
alias('exoplayer-core').to('com.google.android.exoplayer', 'exoplayer-core').versionRef('exoplayer')
@ -86,7 +87,6 @@ dependencyResolutionManagement {
alias('square-okhttp3').to('com.squareup.okhttp3:okhttp:3.12.13')
alias('square-okio').to('com.squareup.okio:okio:2.2.2')
alias('square-leakcanary').to('com.squareup.leakcanary:leakcanary-android:2.7')
alias('threeten-threetenbp').to('org.threeten:threetenbp:1.3.6')
alias('rxjava3-rxjava').to('io.reactivex.rxjava3:rxjava:3.0.13')
alias('rxjava3-rxandroid').to('io.reactivex.rxjava3:rxandroid:3.0.0')
alias('rxjava3-rxkotlin').to('io.reactivex.rxjava3:rxkotlin:3.0.1')

Wyświetl plik

@ -5602,14 +5602,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="d138716ebaf33b9d964f07bf387bb5419f5cba57bff305d5c831e2e7a5a1a4bb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.threeten" name="threetenbp" version="1.3.6">
<artifact name="threetenbp-1.3.6.jar">
<sha256 value="f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7" origin="Generated by Gradle"/>
</artifact>
<artifact name="threetenbp-1.3.6.pom">
<sha256 value="80fb638c216cfe5b7b14854f68fe6372127e21b4f40a3a4249744fab062b8fb9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="pl.tajchert" name="waitingdots" version="0.1.0">
<artifact name="waitingdots-0.1.0.aar">
<sha256 value="2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c" origin="Generated by Gradle"/>

Wyświetl plik

@ -37,7 +37,7 @@ dependencies {
implementation libs.libsignal.client
api libs.square.okhttp3
api libs.square.okio
implementation libs.threeten.threetenbp
implementation libs.google.jsr305
api libs.rxjava3.rxjava

Wyświetl plik

@ -2,4 +2,5 @@
<lint>
<!-- It's a bummer, but Android Studio doesn't seem to be smart enough to recognize Optional and stream() are available to us in this lib -->
<issue id="NewApi" severity="ignore" />
<issue id="OptionalUsedAsFieldOrParameterType" severity="ignore" />
</lint>

Wyświetl plik

@ -23,7 +23,10 @@ import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.push.IdentityCheckRequest;
import org.whispersystems.signalservice.internal.push.IdentityCheckResponse;
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
import org.whispersystems.signalservice.internal.push.SignalServiceEnvelopeEntity;
import org.whispersystems.signalservice.internal.push.SignalServiceMessagesResult;
@ -31,6 +34,7 @@ import org.whispersystems.signalservice.internal.sticker.StickerProtos;
import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers;
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -43,6 +47,10 @@ import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.annotation.Nonnull;
import io.reactivex.rxjava3.core.Single;
/**
* The primary interface for receiving Signal Service messages.
*
@ -130,6 +138,10 @@ public class SignalServiceMessageReceiver {
return new FileInputStream(destination);
}
public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull IdentityCheckRequest request, @Nonnull Optional<UnidentifiedAccess> unidentifiedAccess, @Nonnull ResponseMapper<IdentityCheckResponse> responseMapper) {
return socket.performIdentityCheck(request, unidentifiedAccess, responseMapper);
}
/**
* Retrieves a SignalServiceAttachment.
*

Wyświetl plik

@ -220,7 +220,7 @@ public final class SignalWebSocket {
/**
* <p>
* A blocking call that reads a message off the pipe. When this call returns, the message has been
* acknowledged and will not be retransmitted. This will return {@link Optional#absent()} when an
* acknowledged and will not be retransmitted. This will return {@link Optional#empty()} when an
* empty response is hit, which indicates the WebSocket is empty.
* <p>
* You can specify a {@link MessageReceivedCallback} that will be called before the received message is acknowledged.

Wyświetl plik

@ -1,5 +1,6 @@
package org.whispersystems.signalservice.api.services;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.util.Pair;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
@ -18,6 +19,9 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.ServiceResponseProcessor;
import org.whispersystems.signalservice.internal.push.IdentityCheckRequest;
import org.whispersystems.signalservice.internal.push.IdentityCheckRequest.AciFingerprintPair;
import org.whispersystems.signalservice.internal.push.IdentityCheckResponse;
import org.whispersystems.signalservice.internal.push.http.AcceptLanguagesUtil;
import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.signalservice.internal.util.JsonUtil;
@ -26,17 +30,25 @@ import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Single;
/**
* Provide Profile-related API services, encapsulating the logic to make the request, parse the response,
* and fallback to appropriate WebSocket or RESTful alternatives.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public final class ProfileService {
private static final String TAG = ProfileService.class.getSimpleName();
@ -54,11 +66,11 @@ public final class ProfileService {
this.signalWebSocket = signalWebSocket;
}
public Single<ServiceResponse<ProfileAndCredential>> getProfile(SignalServiceAddress address,
Optional<ProfileKey> profileKey,
Optional<UnidentifiedAccess> unidentifiedAccess,
SignalServiceProfile.RequestType requestType,
Locale locale)
public Single<ServiceResponse<ProfileAndCredential>> getProfile(@Nonnull SignalServiceAddress address,
@Nonnull Optional<ProfileKey> profileKey,
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
@Nonnull SignalServiceProfile.RequestType requestType,
@Nonnull Locale locale)
{
ServiceId serviceId = address.getServiceId();
SecureRandom random = new SecureRandom();
@ -96,21 +108,51 @@ public final class ProfileService {
return signalWebSocket.request(requestMessage, unidentifiedAccess)
.map(responseMapper::map)
.onErrorResumeNext(t -> restFallback(address, profileKey, unidentifiedAccess, requestType, locale))
.onErrorResumeNext(t -> getProfileRestFallback(address, profileKey, unidentifiedAccess, requestType, locale))
.onErrorReturn(ServiceResponse::forUnknownError);
}
private Single<ServiceResponse<ProfileAndCredential>> restFallback(SignalServiceAddress address,
Optional<ProfileKey> profileKey,
Optional<UnidentifiedAccess> unidentifiedAccess,
SignalServiceProfile.RequestType requestType,
Locale locale)
public @NonNull Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull Map<ServiceId, IdentityKey> aciIdentityKeyMap, @Nonnull Optional<UnidentifiedAccess> unidentifiedAccess) {
List<AciFingerprintPair> aciKeyPairs = aciIdentityKeyMap.entrySet()
.stream()
.map(e -> new AciFingerprintPair(e.getKey(), e.getValue()))
.collect(Collectors.toList());
IdentityCheckRequest request = new IdentityCheckRequest(aciKeyPairs);
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("POST")
.setPath("/v1/profile/identity_check/batch")
.addAllHeaders(Collections.singleton("content-type:application/json"))
.setBody(JsonUtil.toJsonByteString(request));
ResponseMapper<IdentityCheckResponse> responseMapper = DefaultResponseMapper.getDefault(IdentityCheckResponse.class);
return signalWebSocket.request(builder.build(), unidentifiedAccess)
.map(responseMapper::map)
.onErrorResumeNext(t -> performIdentityCheckRestFallback(request, unidentifiedAccess, responseMapper))
.onErrorReturn(ServiceResponse::forUnknownError);
}
private Single<ServiceResponse<ProfileAndCredential>> getProfileRestFallback(@Nonnull SignalServiceAddress address,
@Nonnull Optional<ProfileKey> profileKey,
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
@Nonnull SignalServiceProfile.RequestType requestType,
@Nonnull Locale locale)
{
return Single.fromFuture(receiver.retrieveProfile(address, profileKey, unidentifiedAccess, requestType, locale), 10, TimeUnit.SECONDS)
.onErrorResumeNext(t -> Single.fromFuture(receiver.retrieveProfile(address, profileKey, Optional.empty(), requestType, locale), 10, TimeUnit.SECONDS))
.map(p -> ServiceResponse.forResult(p, 0, null));
}
private @NonNull Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheckRestFallback(@Nonnull IdentityCheckRequest request,
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
@Nonnull ResponseMapper<IdentityCheckResponse> responseMapper) {
return receiver.performIdentityCheck(request, unidentifiedAccess, responseMapper)
.onErrorResumeNext(t -> receiver.performIdentityCheck(request, Optional.empty(), responseMapper));
}
/**
* Maps the API {@link SignalServiceProfile} model into the desired {@link ProfileAndCredential} domain model.
*/

Wyświetl plik

@ -1,12 +1,6 @@
package org.whispersystems.signalservice.internal.contacts.crypto;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.Period;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.DateTimeFormatter;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse;
import org.whispersystems.signalservice.internal.util.Hex;
@ -18,6 +12,12 @@ import java.security.MessageDigest;
import java.security.SignatureException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

Wyświetl plik

@ -0,0 +1,57 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import org.whispersystems.util.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.annotation.Nonnull;
public class IdentityCheckRequest {
@JsonProperty("elements")
private final List<AciFingerprintPair> aciFingerprintPairs;
public IdentityCheckRequest(@Nonnull List<AciFingerprintPair> aciKeyPairs) {
this.aciFingerprintPairs = aciKeyPairs;
}
public List<AciFingerprintPair> getAciFingerprintPairs() {
return aciFingerprintPairs;
}
public static final class AciFingerprintPair {
@JsonProperty
@JsonSerialize(using = JsonUtil.ServiceIdSerializer.class)
private final ServiceId aci;
@JsonProperty
private final String fingerprint;
public AciFingerprintPair(@Nonnull ServiceId aci, @Nonnull IdentityKey identityKey) {
this.aci = aci;
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
this.fingerprint = Base64.encodeBytes(messageDigest.digest(identityKey.serialize()), 0, 4);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public ServiceId getAci() {
return aci;
}
public String getFingerprint() {
return fingerprint;
}
}
}

Wyświetl plik

@ -0,0 +1,41 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import java.util.List;
import javax.annotation.Nullable;
public class IdentityCheckResponse {
@JsonProperty("elements")
private List<AciIdentityPair> aciKeyPairs;
public @Nullable List<AciIdentityPair> getAciKeyPairs() {
return aciKeyPairs;
}
public static final class AciIdentityPair {
@JsonProperty
@JsonDeserialize(using = JsonUtil.ServiceIdDeserializer.class)
private ServiceId aci;
@JsonProperty
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
public @Nullable ServiceId getAci() {
return aci;
}
public @Nullable IdentityKey getIdentityKey() {
return identityKey;
}
}
}

Wyświetl plik

@ -87,6 +87,7 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
import org.whispersystems.signalservice.api.util.TlsProxySocketFactory;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
@ -124,6 +125,7 @@ import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers;
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
import org.whispersystems.util.Base64;
import org.whispersystems.util.Base64UrlSafe;
@ -152,10 +154,13 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.ConnectionPool;
@ -216,6 +221,7 @@ public class PushServiceSocket {
private static final String PROFILE_PATH = "/v1/profile/%s";
private static final String PROFILE_USERNAME_PATH = "/v1/profile/username/%s";
private static final String PROFILE_BATCH_CHECK_PATH = "/v1/profile/identity_check/batch";
private static final String SENDER_CERTIFICATE_PATH = "/v1/certificate/delivery";
private static final String SENDER_CERTIFICATE_NO_E164_PATH = "/v1/certificate/delivery?includeE164=false";
@ -861,6 +867,23 @@ public class PushServiceSocket {
return Optional.empty();
}
public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull IdentityCheckRequest request,
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
@Nonnull ResponseMapper<IdentityCheckResponse> responseMapper)
{
Single<ServiceResponse<IdentityCheckResponse>> requestSingle = Single.fromCallable(() -> {
Response response = getServiceConnection(PROFILE_BATCH_CHECK_PATH, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), unidentifiedAccess, false);
String body = response.body() != null ? response.body().string() : "";
Log.e("CODY", "body: " + body);
return responseMapper.map(response.code(), body, response::header, unidentifiedAccess.isPresent());
});
return requestSingle
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.onErrorReturn(ServiceResponse::forUnknownError);
}
public void setUsername(String username) throws IOException {
makeServiceRequest(String.format(SET_USERNAME_PATH, username), "PUT", "", NO_HEADERS, (responseCode, body) -> {
switch (responseCode) {

Wyświetl plik

@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.protobuf.ByteString;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
@ -30,6 +31,9 @@ import org.whispersystems.util.Base64;
import java.io.IOException;
import java.util.UUID;
import javax.annotation.Nonnull;
@SuppressWarnings("unused")
public class JsonUtil {
private static final String TAG = JsonUtil.class.getSimpleName();
@ -49,6 +53,10 @@ public class JsonUtil {
}
}
public static @Nonnull ByteString toJsonByteString(@Nonnull Object object) {
return ByteString.copyFrom(toJson(object).getBytes());
}
public static <T> T fromJson(String json, Class<T> clazz)
throws IOException
{