Add libsignal-net CDSI implementation.
This commit is contained in:
parent
46c8b3b690
commit
56eae8c7bf
5 changed files with 66 additions and 12 deletions
|
@ -208,6 +208,7 @@ android {
|
|||
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
|
||||
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
|
||||
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
|
||||
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
|
||||
|
||||
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
|
||||
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
|
||||
|
@ -385,6 +386,7 @@ android {
|
|||
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
|
||||
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
|
||||
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
|
||||
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
|
||||
|
||||
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
|
||||
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
|
||||
|
|
|
@ -126,7 +126,8 @@ object ContactDiscoveryRefreshV2 {
|
|||
SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(),
|
||||
Optional.ofNullable(token),
|
||||
BuildConfig.CDSI_MRENCLAVE,
|
||||
timeoutMs
|
||||
timeoutMs,
|
||||
if (FeatureFlags.useLibsignalNetForCdsiLookup()) BuildConfig.LIBSIGNAL_NET_ENV else null
|
||||
) { tokenToSave ->
|
||||
stopwatch.split("network-pre-token")
|
||||
if (!isPartialRefresh) {
|
||||
|
|
|
@ -123,6 +123,7 @@ public final class FeatureFlags {
|
|||
private static final String RETRY_RECEIPT_MAX_COUNT = "android.retryReceipt.maxCount";
|
||||
private static final String RETRY_RECEIPT_MAX_COUNT_RESET_AGE = "android.retryReceipt.maxCountResetAge";
|
||||
private static final String PREKEY_FORCE_REFRESH_INTERVAL = "android.prekeyForceRefreshInterval";
|
||||
private static final String CDSI_LIBSIGNAL_NET = "android.cds.libsignal";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -198,7 +199,8 @@ public final class FeatureFlags {
|
|||
VIDEO_RECORD_1X_ZOOM,
|
||||
RETRY_RECEIPT_MAX_COUNT,
|
||||
RETRY_RECEIPT_MAX_COUNT_RESET_AGE,
|
||||
PREKEY_FORCE_REFRESH_INTERVAL
|
||||
PREKEY_FORCE_REFRESH_INTERVAL,
|
||||
CDSI_LIBSIGNAL_NET
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -271,7 +273,8 @@ public final class FeatureFlags {
|
|||
VIDEO_RECORD_1X_ZOOM,
|
||||
RETRY_RECEIPT_MAX_COUNT,
|
||||
RETRY_RECEIPT_MAX_COUNT_RESET_AGE,
|
||||
PREKEY_FORCE_REFRESH_INTERVAL
|
||||
PREKEY_FORCE_REFRESH_INTERVAL,
|
||||
CDSI_LIBSIGNAL_NET
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -706,6 +709,11 @@ public final class FeatureFlags {
|
|||
return getLong(PREKEY_FORCE_REFRESH_INTERVAL, TimeUnit.HOURS.toMillis(1));
|
||||
}
|
||||
|
||||
/** Make CDSI lookups via libsignal-net instead of native websocket. */
|
||||
public static boolean useLibsignalNetForCdsiLookup() {
|
||||
return getBoolean(CDSI_LIBSIGNAL_NET, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.signalservice.api;
|
|||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
|
@ -130,7 +131,6 @@ public class SignalServiceAccountManager {
|
|||
private final GroupsV2Operations groupsV2Operations;
|
||||
private final SignalServiceConfiguration configuration;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a SignalServiceAccountManager.
|
||||
* @param configuration The URL for the Signal Service.
|
||||
|
@ -365,11 +365,12 @@ public class SignalServiceAccountManager {
|
|||
Optional<byte[]> token,
|
||||
String mrEnclave,
|
||||
Long timeoutMs,
|
||||
@Nullable Network.Environment libsignalNetEnv,
|
||||
Consumer<byte[]> tokenSaver)
|
||||
throws IOException
|
||||
{
|
||||
CdsiAuthResponse auth = pushServiceSocket.getCdsiAuth();
|
||||
CdsiV2Service service = new CdsiV2Service(configuration, mrEnclave);
|
||||
CdsiV2Service service = new CdsiV2Service(configuration, mrEnclave, libsignalNetEnv);
|
||||
CdsiV2Service.Request request = new CdsiV2Service.Request(previousE164s, newE164s, serviceIds, token);
|
||||
Single<ServiceResponse<CdsiV2Service.Response>> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request, tokenSaver);
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ package org.whispersystems.signalservice.api.services;
|
|||
|
||||
import org.signal.cdsi.proto.ClientRequest;
|
||||
import org.signal.cdsi.proto.ClientResponse;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.net.CdsiLookupRequest;
|
||||
import org.signal.libsignal.net.CdsiLookupResponse;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.util.ByteUtil;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
|
@ -16,6 +20,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -24,9 +29,11 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import okio.ByteString;
|
||||
|
||||
|
@ -40,16 +47,35 @@ public final class CdsiV2Service {
|
|||
private static final UUID EMPTY_UUID = new UUID(0, 0);
|
||||
private static final int RESPONSE_ITEM_SIZE = 8 + 16 + 16; // 1 uint64 + 2 UUIDs
|
||||
|
||||
private final CdsiSocket cdsiSocket;
|
||||
private static final Duration LIBSIGNAL_CDSI_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
public CdsiV2Service(SignalServiceConfiguration configuration, String mrEnclave) {
|
||||
this.cdsiSocket = new CdsiSocket(configuration, mrEnclave);
|
||||
private final CdsiRequestHandler cdsiRequestHandler;
|
||||
|
||||
public CdsiV2Service(SignalServiceConfiguration configuration, String mrEnclave, Network.Environment libsignalEnv) {
|
||||
|
||||
if (libsignalEnv != null) {
|
||||
Network network = new Network(libsignalEnv);
|
||||
this.cdsiRequestHandler = (username, password, request, tokenSaver) -> {
|
||||
try {
|
||||
Log.i(TAG, "Starting CDSI lookup via libsignal-net");
|
||||
Future<CdsiLookupResponse> cdsiRequest = network.cdsiLookup(username, password, buildLibsignalRequest(request), LIBSIGNAL_CDSI_TIMEOUT, tokenSaver);
|
||||
return Single.fromFuture(cdsiRequest).map(CdsiV2Service::parseLibsignalResponse).toObservable();
|
||||
} catch (Exception exception) {
|
||||
return Observable.error(exception);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
CdsiSocket cdsiSocket = new CdsiSocket(configuration, mrEnclave);
|
||||
this.cdsiRequestHandler = (username, password, request, tokenSaver) -> {
|
||||
return cdsiSocket
|
||||
.connect(username, password, buildClientRequest(request), tokenSaver)
|
||||
.map(CdsiV2Service::parseEntries);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<Response>> getRegisteredUsers(String username, String password, Request request, Consumer<byte[]> tokenSaver) {
|
||||
return cdsiSocket
|
||||
.connect(username, password, buildClientRequest(request), tokenSaver)
|
||||
.map(CdsiV2Service::parseEntries)
|
||||
return cdsiRequestHandler.handleRequest(username, password, request, tokenSaver)
|
||||
.collect(Collectors.toList())
|
||||
.flatMap(pages -> {
|
||||
Map<String, ResponseItem> all = new HashMap<>();
|
||||
|
@ -139,6 +165,18 @@ public final class CdsiV2Service {
|
|||
return ByteString.of(os.toByteArray());
|
||||
}
|
||||
|
||||
private static CdsiLookupRequest buildLibsignalRequest(Request request) {
|
||||
HashMap<org.signal.libsignal.protocol.ServiceId, ProfileKey> serviceIds = new HashMap<>(request.serviceIds.size());
|
||||
request.serviceIds.forEach((key, value) -> serviceIds.put(key.getLibSignalServiceId(), value));
|
||||
return new CdsiLookupRequest(request.previousE164s, request.newE164s, serviceIds, false, Optional.ofNullable(request.token));
|
||||
}
|
||||
|
||||
private static Response parseLibsignalResponse(CdsiLookupResponse response) {
|
||||
HashMap<String, ResponseItem> responses = new HashMap<>(response.entries().size());
|
||||
response.entries().forEach((key, value) -> responses.put(key, new ResponseItem(new PNI(value.pni), Optional.ofNullable(value.aci).map(ACI::new))));
|
||||
return new Response(responses, response.debugPermitsUsed);
|
||||
}
|
||||
|
||||
private static List<Long> parseAndSortE164Strings(Collection<String> e164s) {
|
||||
return e164s.stream()
|
||||
.map(Long::parseLong)
|
||||
|
@ -216,4 +254,8 @@ public final class CdsiV2Service {
|
|||
return aci.isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
private interface CdsiRequestHandler {
|
||||
Observable<Response> handleRequest(String username, String password, Request request, Consumer<byte[]> tokenSaver);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue