kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fetch PNI Credential during own profile refresh.
rodzic
dda5ce4809
commit
3c08b070fc
|
@ -184,12 +184,8 @@ class ContactDiscoveryRefreshV1 {
|
||||||
.filter(ContactDiscoveryRefreshV1::hasCommunicatedWith)
|
.filter(ContactDiscoveryRefreshV1::hasCommunicatedWith)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
|
|
||||||
ApplicationDependencies.getSignalServiceMessageReceiver(),
|
|
||||||
ApplicationDependencies.getSignalWebSocket());
|
|
||||||
|
|
||||||
List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(possiblyUnlisted)
|
List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(possiblyUnlisted)
|
||||||
.map(r -> ProfileUtil.retrieveProfile(context, r, SignalServiceProfile.RequestType.PROFILE, profileService)
|
.map(r -> ProfileUtil.retrieveProfile(context, r, SignalServiceProfile.RequestType.PROFILE)
|
||||||
.toObservable()
|
.toObservable()
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.onErrorReturn(t -> new Pair<>(r, ServiceResponse.forUnknownError(t))))
|
.onErrorReturn(t -> new Pair<>(r, ServiceResponse.forUnknownError(t))))
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import org.signal.core.util.Hex;
|
import org.signal.core.util.Hex;
|
||||||
import org.signal.core.util.concurrent.DeadlockDetector;
|
import org.signal.core.util.concurrent.DeadlockDetector;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||||
import org.thoughtcrime.securesms.KbsEnclave;
|
import org.thoughtcrime.securesms.KbsEnclave;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
|
@ -53,6 +54,7 @@ import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||||
|
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||||
import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
|
import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
|
||||||
import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager;
|
import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager;
|
||||||
import org.whispersystems.signalservice.internal.util.Util;
|
import org.whispersystems.signalservice.internal.util.Util;
|
||||||
|
@ -120,6 +122,7 @@ public class ApplicationDependencies {
|
||||||
private static volatile SimpleExoPlayerPool exoPlayerPool;
|
private static volatile SimpleExoPlayerPool exoPlayerPool;
|
||||||
private static volatile AudioManagerCompat audioManagerCompat;
|
private static volatile AudioManagerCompat audioManagerCompat;
|
||||||
private static volatile DonationsService donationsService;
|
private static volatile DonationsService donationsService;
|
||||||
|
private static volatile ProfileService profileService;
|
||||||
private static volatile DeadlockDetector deadlockDetector;
|
private static volatile DeadlockDetector deadlockDetector;
|
||||||
private static volatile ClientZkReceiptOperations clientZkReceiptOperations;
|
private static volatile ClientZkReceiptOperations clientZkReceiptOperations;
|
||||||
|
|
||||||
|
@ -632,6 +635,19 @@ public class ApplicationDependencies {
|
||||||
return donationsService;
|
return donationsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull ProfileService getProfileService() {
|
||||||
|
if (profileService == null) {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (profileService == null) {
|
||||||
|
profileService = provider.provideProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
|
||||||
|
ApplicationDependencies.getSignalServiceMessageReceiver(),
|
||||||
|
ApplicationDependencies.getSignalWebSocket());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return profileService;
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull ClientZkReceiptOperations getClientZkReceiptOperations() {
|
public static @NonNull ClientZkReceiptOperations getClientZkReceiptOperations() {
|
||||||
if (clientZkReceiptOperations == null) {
|
if (clientZkReceiptOperations == null) {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
|
@ -688,6 +704,7 @@ public class ApplicationDependencies {
|
||||||
@NonNull SimpleExoPlayerPool provideExoPlayerPool();
|
@NonNull SimpleExoPlayerPool provideExoPlayerPool();
|
||||||
@NonNull AudioManagerCompat provideAndroidCallAudioManager();
|
@NonNull AudioManagerCompat provideAndroidCallAudioManager();
|
||||||
@NonNull DonationsService provideDonationsService();
|
@NonNull DonationsService provideDonationsService();
|
||||||
|
@NonNull ProfileService provideProfileService(@NonNull ClientZkProfileOperations profileOperations, @NonNull SignalServiceMessageReceiver signalServiceMessageReceiver, @NonNull SignalWebSocket signalWebSocket);
|
||||||
@NonNull DeadlockDetector provideDeadlockDetector();
|
@NonNull DeadlockDetector provideDeadlockDetector();
|
||||||
@NonNull ClientZkReceiptOperations provideClientZkReceiptOperations();
|
@NonNull ClientZkReceiptOperations provideClientZkReceiptOperations();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.DeadlockDetector;
|
import org.signal.core.util.concurrent.DeadlockDetector;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
|
@ -79,6 +80,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.api.push.PNI;
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||||
|
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.signalservice.api.util.SleepTimer;
|
import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
|
@ -352,6 +354,14 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
||||||
FeatureFlags.okHttpAutomaticRetry());
|
FeatureFlags.okHttpAutomaticRetry());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull ProfileService provideProfileService(@NonNull ClientZkProfileOperations clientZkProfileOperations,
|
||||||
|
@NonNull SignalServiceMessageReceiver receiver,
|
||||||
|
@NonNull SignalWebSocket signalWebSocket)
|
||||||
|
{
|
||||||
|
return new ProfileService(clientZkProfileOperations, receiver, signalWebSocket);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull DeadlockDetector provideDeadlockDetector() {
|
public @NonNull DeadlockDetector provideDeadlockDetector() {
|
||||||
HandlerThread handlerThread = new HandlerThread("signal-DeadlockDetector");
|
HandlerThread handlerThread = new HandlerThread("signal-DeadlockDetector");
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredential;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeRepository;
|
import org.thoughtcrime.securesms.badges.BadgeRepository;
|
||||||
|
@ -33,6 +34,7 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.ServiceResponseProcessor;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -134,6 +136,17 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||||
if (profileKeyCredential.isPresent()) {
|
if (profileKeyCredential.isPresent()) {
|
||||||
setProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), profileKeyCredential.get());
|
setProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), profileKeyCredential.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SignalStore.account().getAci() != null) {
|
||||||
|
PniCredential pniCredential = ApplicationDependencies.getProfileService()
|
||||||
|
.getPniProfileCredential(SignalStore.account().requireAci(),
|
||||||
|
SignalStore.account().requirePni(),
|
||||||
|
ProfileKeyUtil.getSelfProfileKey())
|
||||||
|
.map(ServiceResponseProcessor.DefaultProcessor::new)
|
||||||
|
.blockingGet()
|
||||||
|
.getResultOrThrow();
|
||||||
|
SignalStore.account().setPniCredential(pniCredential);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
||||||
|
|
|
@ -253,13 +253,9 @@ public class RetrieveProfileJob extends BaseJob {
|
||||||
List<Recipient> recipients = Recipient.resolvedList(recipientIds);
|
List<Recipient> recipients = Recipient.resolvedList(recipientIds);
|
||||||
stopwatch.split("resolve-ensure");
|
stopwatch.split("resolve-ensure");
|
||||||
|
|
||||||
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
|
|
||||||
ApplicationDependencies.getSignalServiceMessageReceiver(),
|
|
||||||
ApplicationDependencies.getSignalWebSocket());
|
|
||||||
|
|
||||||
List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(recipients)
|
List<Observable<Pair<Recipient, ServiceResponse<ProfileAndCredential>>>> requests = Stream.of(recipients)
|
||||||
.filter(Recipient::hasServiceId)
|
.filter(Recipient::hasServiceId)
|
||||||
.map(r -> ProfileUtil.retrieveProfile(context, r, getRequestType(r), profileService).toObservable())
|
.map(r -> ProfileUtil.retrieveProfile(context, r, getRequestType(r)).toObservable())
|
||||||
.toList();
|
.toList();
|
||||||
stopwatch.split("requests");
|
stopwatch.split("requests");
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.signal.libsignal.protocol.IdentityKey
|
||||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
import org.signal.libsignal.protocol.ecc.Curve
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
import org.signal.libsignal.protocol.util.Medium
|
import org.signal.libsignal.protocol.util.Medium
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredential
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher
|
import org.thoughtcrime.securesms.crypto.MasterCipher
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||||
|
@ -53,6 +54,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||||
private const val KEY_PNI_ACTIVE_SIGNED_PREKEY_ID = "account.pni_active_signed_prekey_id"
|
private const val KEY_PNI_ACTIVE_SIGNED_PREKEY_ID = "account.pni_active_signed_prekey_id"
|
||||||
private const val KEY_PNI_SIGNED_PREKEY_FAILURE_COUNT = "account.pni_signed_prekey_failure_count"
|
private const val KEY_PNI_SIGNED_PREKEY_FAILURE_COUNT = "account.pni_signed_prekey_failure_count"
|
||||||
private const val KEY_PNI_NEXT_ONE_TIME_PREKEY_ID = "account.pni_next_one_time_prekey_id"
|
private const val KEY_PNI_NEXT_ONE_TIME_PREKEY_ID = "account.pni_next_one_time_prekey_id"
|
||||||
|
private const val KEY_PNI_CREDENTIAL = "account.pni_credential"
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
const val KEY_E164 = "account.e164"
|
const val KEY_E164 = "account.e164"
|
||||||
|
@ -305,6 +307,10 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
|
||||||
val isLinkedDevice: Boolean
|
val isLinkedDevice: Boolean
|
||||||
get() = !isPrimaryDevice
|
get() = !isPrimaryDevice
|
||||||
|
|
||||||
|
var pniCredential: PniCredential?
|
||||||
|
set(value) = putBlob(KEY_PNI_CREDENTIAL, value?.serialize())
|
||||||
|
get() = getBlob(KEY_PNI_CREDENTIAL, null)?.let { PniCredential(it) }
|
||||||
|
|
||||||
private fun clearLocalCredentials(context: Context) {
|
private fun clearLocalCredentials(context: Context) {
|
||||||
putString(KEY_SERVICE_PASSWORD, Util.getSecret(18))
|
putString(KEY_SERVICE_PASSWORD, Util.getSecret(18))
|
||||||
|
|
||||||
|
|
|
@ -102,28 +102,23 @@ public final class ProfileUtil {
|
||||||
boolean allowUnidentifiedAccess)
|
boolean allowUnidentifiedAccess)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
|
Pair<Recipient, ServiceResponse<ProfileAndCredential>> response = retrieveProfile(context, recipient, requestType, allowUnidentifiedAccess).blockingGet();
|
||||||
ApplicationDependencies.getSignalServiceMessageReceiver(),
|
|
||||||
ApplicationDependencies.getSignalWebSocket());
|
|
||||||
|
|
||||||
Pair<Recipient, ServiceResponse<ProfileAndCredential>> response = retrieveProfile(context, recipient, requestType, profileService, allowUnidentifiedAccess).blockingGet();
|
|
||||||
return new ProfileService.ProfileResponseProcessor(response.second()).getResultOrThrow();
|
return new ProfileService.ProfileResponseProcessor(response.second()).getResultOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Single<Pair<Recipient, ServiceResponse<ProfileAndCredential>>> retrieveProfile(@NonNull Context context,
|
public static Single<Pair<Recipient, ServiceResponse<ProfileAndCredential>>> retrieveProfile(@NonNull Context context,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull SignalServiceProfile.RequestType requestType,
|
@NonNull SignalServiceProfile.RequestType requestType)
|
||||||
@NonNull ProfileService profileService)
|
|
||||||
{
|
{
|
||||||
return retrieveProfile(context, recipient, requestType, profileService, true);
|
return retrieveProfile(context, recipient, requestType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Single<Pair<Recipient, ServiceResponse<ProfileAndCredential>>> retrieveProfile(@NonNull Context context,
|
private static Single<Pair<Recipient, ServiceResponse<ProfileAndCredential>>> retrieveProfile(@NonNull Context context,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull SignalServiceProfile.RequestType requestType,
|
@NonNull SignalServiceProfile.RequestType requestType,
|
||||||
@NonNull ProfileService profileService,
|
|
||||||
boolean allowUnidentifiedAccess)
|
boolean allowUnidentifiedAccess)
|
||||||
{
|
{
|
||||||
|
ProfileService profileService = ApplicationDependencies.getProfileService();
|
||||||
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.empty();
|
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.empty();
|
||||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.dependencies;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.DeadlockDetector;
|
import org.signal.core.util.concurrent.DeadlockDetector;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||||
|
@ -38,6 +39,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.SignalWebSocket;
|
import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||||
|
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
@ -207,6 +209,11 @@ public class MockApplicationDependencyProvider implements ApplicationDependencie
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull ProfileService provideProfileService(@NonNull ClientZkProfileOperations profileOperations, @NonNull SignalServiceMessageReceiver signalServiceMessageReceiver, @NonNull SignalWebSocket signalWebSocket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull DeadlockDetector provideDeadlockDetector() {
|
public @NonNull DeadlockDetector provideDeadlockDetector() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest;
|
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
||||||
|
@ -110,6 +111,10 @@ public class SignalServiceMessageReceiver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<SignalServiceProfile> retrievePniProfile(ACI aci, String version, String credentialRequest, Locale locale) {
|
||||||
|
return socket.retrievePniCredential(aci.uuid(), version, credentialRequest, locale);
|
||||||
|
}
|
||||||
|
|
||||||
public SignalServiceProfile retrieveProfileByUsername(String username, Optional<UnidentifiedAccess> unidentifiedAccess, Locale locale)
|
public SignalServiceProfile retrieveProfileByUsername(String username, Optional<UnidentifiedAccess> unidentifiedAccess, Locale locale)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
|
||||||
import org.signal.libsignal.protocol.logging.Log;
|
import org.signal.libsignal.protocol.logging.Log;
|
||||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredentialResponse;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||||
|
@ -63,6 +64,9 @@ public class SignalServiceProfile {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private List<Badge> badges;
|
private List<Badge> badges;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private byte[] pniCredential;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private RequestType requestType;
|
private RequestType requestType;
|
||||||
|
|
||||||
|
@ -116,6 +120,10 @@ public class SignalServiceProfile {
|
||||||
return requestType;
|
return requestType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getPniCredential() {
|
||||||
|
return pniCredential;
|
||||||
|
}
|
||||||
|
|
||||||
public void setRequestType(RequestType requestType) {
|
public void setRequestType(RequestType requestType) {
|
||||||
this.requestType = requestType;
|
this.requestType = requestType;
|
||||||
}
|
}
|
||||||
|
@ -255,4 +263,15 @@ public class SignalServiceProfile {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PniCredentialResponse getPniCredentialResponse() {
|
||||||
|
if (pniCredential == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new PniCredentialResponse(pniCredential);
|
||||||
|
} catch (InvalidInputException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package org.whispersystems.signalservice.api.services;
|
||||||
import org.signal.libsignal.protocol.util.Pair;
|
import org.signal.libsignal.protocol.util.Pair;
|
||||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredential;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredentialRequestContext;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
|
||||||
|
@ -13,6 +15,8 @@ import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
|
import org.whispersystems.signalservice.api.push.PNI;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException;
|
import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException;
|
||||||
|
@ -23,7 +27,7 @@ import org.whispersystems.signalservice.internal.util.Hex;
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||||
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
|
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
|
||||||
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
|
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
|
||||||
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos;
|
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -31,7 +35,10 @@ import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Scheduler;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
import io.reactivex.rxjava3.core.SingleSource;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide Profile-related API services, encapsulating the logic to make the request, parse the response,
|
* Provide Profile-related API services, encapsulating the logic to make the request, parse the response,
|
||||||
|
@ -64,9 +71,9 @@ public final class ProfileService {
|
||||||
SecureRandom random = new SecureRandom();
|
SecureRandom random = new SecureRandom();
|
||||||
ProfileKeyCredentialRequestContext requestContext = null;
|
ProfileKeyCredentialRequestContext requestContext = null;
|
||||||
|
|
||||||
WebSocketProtos.WebSocketRequestMessage.Builder builder = WebSocketProtos.WebSocketRequestMessage.newBuilder()
|
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
|
||||||
.setId(random.nextLong())
|
.setId(random.nextLong())
|
||||||
.setVerb("GET");
|
.setVerb("GET");
|
||||||
|
|
||||||
if (profileKey.isPresent()) {
|
if (profileKey.isPresent()) {
|
||||||
ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(serviceId.uuid());
|
ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(serviceId.uuid());
|
||||||
|
@ -88,7 +95,7 @@ public final class ProfileService {
|
||||||
|
|
||||||
builder.addHeaders(AcceptLanguagesUtil.getAcceptLanguageHeader(locale));
|
builder.addHeaders(AcceptLanguagesUtil.getAcceptLanguageHeader(locale));
|
||||||
|
|
||||||
WebSocketProtos.WebSocketRequestMessage requestMessage = builder.build();
|
WebSocketRequestMessage requestMessage = builder.build();
|
||||||
|
|
||||||
ResponseMapper<ProfileAndCredential> responseMapper = DefaultResponseMapper.extend(ProfileAndCredential.class)
|
ResponseMapper<ProfileAndCredential> responseMapper = DefaultResponseMapper.extend(ProfileAndCredential.class)
|
||||||
.withResponseMapper(new ProfileResponseMapper(requestType, requestContext))
|
.withResponseMapper(new ProfileResponseMapper(requestType, requestContext))
|
||||||
|
@ -111,6 +118,40 @@ public final class ProfileService {
|
||||||
.map(p -> ServiceResponse.forResult(p, 0, null));
|
.map(p -> ServiceResponse.forResult(p, 0, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Single<ServiceResponse<PniCredential>> getPniProfileCredential(ACI aci,
|
||||||
|
PNI pni,
|
||||||
|
ProfileKey profileKey)
|
||||||
|
{
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(aci.uuid());
|
||||||
|
String version = profileKeyIdentifier.serialize();
|
||||||
|
PniCredentialRequestContext requestContext = clientZkProfileOperations.createPniCredentialRequestContext(random, aci.uuid(), pni.uuid(), profileKey);
|
||||||
|
ProfileKeyCredentialRequest request = requestContext.getRequest();
|
||||||
|
String credentialRequest = Hex.toStringCondensed(request.serialize());
|
||||||
|
|
||||||
|
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
|
||||||
|
.setId(random.nextLong())
|
||||||
|
.setVerb("GET")
|
||||||
|
.setPath(String.format("/v1/profile/%s/%s/%s?credentialType=pni", aci.uuid(), version, credentialRequest))
|
||||||
|
.addHeaders(AcceptLanguagesUtil.getAcceptLanguageHeader(Locale.getDefault()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PniCredentialMapper pniCredentialMapper = new PniCredentialMapper(requestContext);
|
||||||
|
ResponseMapper<PniCredential> responseMapper = DefaultResponseMapper.extend(PniCredential.class)
|
||||||
|
.withResponseMapper(pniCredentialMapper)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return signalWebSocket.request(requestMessage, Optional.empty())
|
||||||
|
.map(responseMapper::map)
|
||||||
|
.onErrorResumeNext(t -> restFallbackForPni(pniCredentialMapper, aci, version, credentialRequest, Locale.getDefault()))
|
||||||
|
.onErrorReturn(ServiceResponse::forUnknownError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Single<ServiceResponse<PniCredential>> restFallbackForPni(PniCredentialMapper responseMapper, ACI aci, String version, String credentialRequest, Locale locale) {
|
||||||
|
return Single.fromFuture(receiver.retrievePniProfile(aci, version, credentialRequest, locale), 10, TimeUnit.SECONDS)
|
||||||
|
.map(responseMapper::map);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the API {@link SignalServiceProfile} model into the desired {@link ProfileAndCredential} domain model.
|
* Maps the API {@link SignalServiceProfile} model into the desired {@link ProfileAndCredential} domain model.
|
||||||
*/
|
*/
|
||||||
|
@ -141,6 +182,42 @@ public final class ProfileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the API {@link SignalServiceProfile} model into the desired {@link org.signal.libsignal.zkgroup.profiles.PniCredential} domain model.
|
||||||
|
*/
|
||||||
|
private class PniCredentialMapper implements DefaultResponseMapper.CustomResponseMapper<PniCredential> {
|
||||||
|
private final PniCredentialRequestContext requestContext;
|
||||||
|
|
||||||
|
public PniCredentialMapper(PniCredentialRequestContext requestContext) {
|
||||||
|
this.requestContext = requestContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServiceResponse<PniCredential> map(int status, String body, Function<String, String> getHeader, boolean unidentified)
|
||||||
|
throws MalformedResponseException
|
||||||
|
{
|
||||||
|
SignalServiceProfile signalServiceProfile = JsonUtil.fromJsonResponse(body, SignalServiceProfile.class);
|
||||||
|
return map(signalServiceProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResponse<PniCredential> map(SignalServiceProfile signalServiceProfile) {
|
||||||
|
try {
|
||||||
|
PniCredential pniCredential = null;
|
||||||
|
if (requestContext != null && signalServiceProfile.getPniCredentialResponse() != null) {
|
||||||
|
pniCredential = clientZkProfileOperations.receivePniCredential(requestContext, signalServiceProfile.getPniCredentialResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pniCredential == null) {
|
||||||
|
return ServiceResponse.forApplicationError(new MalformedResponseException("No PNI credential in response"), 0, null);
|
||||||
|
} else {
|
||||||
|
return ServiceResponse.forResult(pniCredential, 200, null);
|
||||||
|
}
|
||||||
|
} catch (VerificationFailedException e) {
|
||||||
|
return ServiceResponse.forUnknownError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response processor for {@link ProfileAndCredential} service response.
|
* Response processor for {@link ProfileAndCredential} service response.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -126,4 +126,10 @@ public abstract class ServiceResponseProcessor<T> {
|
||||||
error instanceof TimeoutException ||
|
error instanceof TimeoutException ||
|
||||||
error instanceof InterruptedException;
|
error instanceof InterruptedException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class DefaultProcessor<T> extends ServiceResponseProcessor<T> {
|
||||||
|
public DefaultProcessor(ServiceResponse<T> response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||||
import org.signal.libsignal.protocol.util.Pair;
|
import org.signal.libsignal.protocol.util.Pair;
|
||||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.PniCredential;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
|
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
|
||||||
|
@ -814,6 +815,20 @@ public class PushServiceSocket {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<SignalServiceProfile> retrievePniCredential(UUID target, String version, String credentialRequest, Locale locale) {
|
||||||
|
String subPath = String.format("%s/%s/%s?credentialType=pni", target, version, credentialRequest);
|
||||||
|
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), Optional.empty());
|
||||||
|
|
||||||
|
return FutureTransformers.map(response, body -> {
|
||||||
|
try {
|
||||||
|
return JsonUtil.fromJson(body, SignalServiceProfile.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
throw new MalformedResponseException("Unable to parse entity", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
|
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
|
Ładowanie…
Reference in New Issue