From 456bcf3d57d562a5f9a291189e1b8a4d66761399 Mon Sep 17 00:00:00 2001 From: Ehren Kret Date: Tue, 14 Apr 2020 08:33:09 -0700 Subject: [PATCH] Require CDN number match rather than use default CDN This marks messages as failed if the CDN number does not match a configured CDN number rather than falling back to the default CDN in the event the CDN is not recognized. --- .../dependencies/ApplicationDependencies.java | 22 +-- .../securesms/jobs/AttachmentDownloadJob.java | 3 +- .../jobs/AvatarGroupsV1DownloadJob.java | 3 +- .../push/SignalServiceNetworkAccess.java | 26 ++-- .../api/SignalServiceMessageReceiver.java | 8 +- .../api/messages/SignalServiceContent.java | 19 +-- .../MissingConfigurationException.java | 7 + .../SignalServiceConfiguration.java | 32 ++--- .../internal/push/PushServiceSocket.java | 126 +++++++++++------- .../push/UnsupportedDataMessageException.java | 24 ++-- ...edDataMessageProtocolVersionException.java | 25 ++++ 11 files changed, 176 insertions(+), 119 deletions(-) create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MissingConfigurationException.java create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageProtocolVersionException.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 53b1286d9..9f39970a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -36,17 +36,17 @@ public class ApplicationDependencies { private static Application application; private static Provider provider; - private static SignalServiceAccountManager accountManager; - private static SignalServiceMessageSender messageSender; - private static SignalServiceMessageReceiver messageReceiver; - private static IncomingMessageProcessor incomingMessageProcessor; - private static MessageRetriever messageRetriever; - private static LiveRecipientCache recipientCache; - private static JobManager jobManager; - private static FrameRateTracker frameRateTracker; - private static KeyValueStore keyValueStore; - private static MegaphoneRepository megaphoneRepository; - private static GroupsV2Operations groupsV2Operations; + private static SignalServiceAccountManager accountManager; + private static SignalServiceMessageSender messageSender; + private static SignalServiceMessageReceiver messageReceiver; + private static IncomingMessageProcessor incomingMessageProcessor; + private static MessageRetriever messageRetriever; + private static LiveRecipientCache recipientCache; + private static JobManager jobManager; + private static FrameRateTracker frameRateTracker; + private static KeyValueStore keyValueStore; + private static MegaphoneRepository megaphoneRepository; + private static GroupsV2Operations groupsV2Operations; public static synchronized void init(@NonNull Application application, @NonNull Provider provider) { if (ApplicationDependencies.application != null || ApplicationDependencies.provider != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 913648f1d..529f2dcdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -30,6 +30,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; @@ -168,7 +169,7 @@ public class AttachmentDownloadJob extends BaseJob { InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))); database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream); - } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) { + } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException | MissingConfigurationException e) { Log.w(TAG, "Experienced exception while trying to download an attachment.", e); markFailed(messageId, attachmentId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java index 9403f7c61..09aecf362 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarGroupsV1DownloadJob.java @@ -18,6 +18,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import java.io.File; @@ -92,7 +93,7 @@ public final class AvatarGroupsV1DownloadJob extends BaseJob { inputStream.close(); } - } catch (NonSuccessfulResponseCodeException | InvalidMessageException e) { + } catch (NonSuccessfulResponseCodeException | InvalidMessageException | MissingConfigurationException e) { Log.w(TAG, e); } finally { if (attachment != null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java index 3be38d674..705ddfc7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java +++ b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.java @@ -174,8 +174,8 @@ public class SignalServiceNetworkAccess { this.censorshipConfiguration = new HashMap() {{ put(COUNTRY_CODE_EGYPT, new SignalServiceConfiguration(new SignalServiceUrl[] {egyptGoogleService, baseGoogleService, baseAndroidService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, - new SignalCdnUrl[] {egyptGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn, mailAndroidCdn}, - new SignalCdnUrl[] {egyptGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2, mailAndroidCdn2}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[] {egyptGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn, mailAndroidCdn}, + new SignalCdnUrl[] {egyptGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2, mailAndroidCdn2}), new SignalContactDiscoveryUrl[] {egyptGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalKeyBackupServiceUrl[] {egyptGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalStorageUrl[] {egyptGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, @@ -184,8 +184,8 @@ public class SignalServiceNetworkAccess { zkGroupServerPublicParams)); put(COUNTRY_CODE_UAE, new SignalServiceConfiguration(new SignalServiceUrl[] {uaeGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, - new SignalCdnUrl[] {uaeGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, - new SignalCdnUrl[] {uaeGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[] {uaeGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, + new SignalCdnUrl[] {uaeGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}), new SignalContactDiscoveryUrl[] {uaeGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalKeyBackupServiceUrl[] {uaeGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalStorageUrl[] {uaeGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, @@ -194,8 +194,8 @@ public class SignalServiceNetworkAccess { zkGroupServerPublicParams)); put(COUNTRY_CODE_OMAN, new SignalServiceConfiguration(new SignalServiceUrl[] {omanGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, - new SignalCdnUrl[] {omanGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, - new SignalCdnUrl[] {omanGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[] {omanGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, + new SignalCdnUrl[] {omanGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}), new SignalContactDiscoveryUrl[] {omanGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalKeyBackupServiceUrl[] {omanGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalStorageUrl[] {omanGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, @@ -205,8 +205,8 @@ public class SignalServiceNetworkAccess { put(COUNTRY_CODE_QATAR, new SignalServiceConfiguration(new SignalServiceUrl[] {qatarGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, - new SignalCdnUrl[] {qatarGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, - new SignalCdnUrl[] {qatarGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[] {qatarGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, + new SignalCdnUrl[] {qatarGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2}), new SignalContactDiscoveryUrl[] {qatarGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalKeyBackupServiceUrl[] {qatarGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalStorageUrl[] {qatarGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, @@ -216,8 +216,8 @@ public class SignalServiceNetworkAccess { }}; this.uncensoredConfiguration = new SignalServiceConfiguration(new SignalServiceUrl[] {new SignalServiceUrl(BuildConfig.SIGNAL_URL, new SignalServiceTrustStore(context))}, - new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, new SignalServiceTrustStore(context))}, - new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN2_URL, new SignalServiceTrustStore(context))}, + makeSignalCdnUrlMapFor(new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, new SignalServiceTrustStore(context))}, + new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN2_URL, new SignalServiceTrustStore(context))}), new SignalContactDiscoveryUrl[] {new SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, new SignalServiceTrustStore(context))}, new SignalKeyBackupServiceUrl[] { new SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, new SignalServiceTrustStore(context)) }, new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))}, @@ -253,4 +253,10 @@ public class SignalServiceNetworkAccess { return getConfiguration(number) != this.uncensoredConfiguration; } + private static Map makeSignalCdnUrlMapFor(SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls) { + Map result = new HashMap<>(); + result.put(0, cdn0Urls); + result.put(2, cdn2Urls); + return Collections.unmodifiableMap(result); + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index ce44fe570..ff3a03196 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -16,7 +16,6 @@ import org.whispersystems.signalservice.FeatureFlags; import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream; import org.whispersystems.signalservice.api.crypto.ProfileCipherInputStream; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -25,6 +24,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifes import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.CredentialsProvider; @@ -118,8 +118,7 @@ public class SignalServiceMessageReceiver { * @throws InvalidMessageException */ public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes) - throws IOException, InvalidMessageException - { + throws IOException, InvalidMessageException, MissingConfigurationException { return retrieveAttachment(pointer, destination, maxSizeBytes, null); } @@ -174,8 +173,7 @@ public class SignalServiceMessageReceiver { * @throws InvalidMessageException */ public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes, ProgressListener listener) - throws IOException, InvalidMessageException - { + throws IOException, InvalidMessageException, MissingConfigurationException { if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!"); socket.retrieveAttachment(pointer.getCdnNumber(), pointer.getRemoteId(), destination, maxSizeBytes, listener); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java index dad39c3ed..dc80b5587 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java @@ -39,6 +39,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; +import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageProtocolVersionException; import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer; import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer; import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto; @@ -268,7 +269,8 @@ public final class SignalServiceContent { return null; } - private static SignalServiceDataMessage createSignalServiceMessage(SignalServiceMetadata metadata, SignalServiceProtos.DataMessage content) + private static SignalServiceDataMessage createSignalServiceMessage(SignalServiceMetadata metadata, + SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException, UnsupportedDataMessageException { SignalServiceGroup groupInfoV1 = createGroupV1Info(content); @@ -292,12 +294,12 @@ public final class SignalServiceContent { SignalServiceDataMessage.Sticker sticker = createSticker(content); SignalServiceDataMessage.Reaction reaction = createReaction(content); - if (content.getRequiredProtocolVersion() > SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT.getNumber()) { - throw new UnsupportedDataMessageException(SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT.getNumber(), - content.getRequiredProtocolVersion(), - metadata.getSender().getIdentifier(), - metadata.getSenderDevice(), - groupContext); + if (content.getRequiredProtocolVersion() > SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE) { + throw new UnsupportedDataMessageProtocolVersionException(SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE, + content.getRequiredProtocolVersion(), + metadata.getSender().getIdentifier(), + metadata.getSenderDevice(), + groupContext); } for (SignalServiceProtos.AttachmentPointer pointer : content.getAttachmentsList()) { @@ -327,7 +329,8 @@ public final class SignalServiceContent { reaction); } - private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMetadata metadata, SignalServiceProtos.SyncMessage content) + private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMetadata metadata, + SignalServiceProtos.SyncMessage content) throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException { if (content.hasSent()) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MissingConfigurationException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MissingConfigurationException.java new file mode 100644 index 000000000..a713c9bc4 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MissingConfigurationException.java @@ -0,0 +1,7 @@ +package org.whispersystems.signalservice.api.push.exceptions; + +public final class MissingConfigurationException extends Exception { + public MissingConfigurationException(String s) { + super(s); + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java index 22fb03794..44b065721 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java @@ -3,25 +3,24 @@ package org.whispersystems.signalservice.internal.configuration; import org.whispersystems.libsignal.util.guava.Optional; import java.util.List; +import java.util.Map; import okhttp3.Dns; import okhttp3.Interceptor; public final class SignalServiceConfiguration { - private final SignalServiceUrl[] signalServiceUrls; - private final SignalCdnUrl[] signalCdnUrls; - private final SignalCdnUrl[] signalCdn2Urls; - private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls; - private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls; - private final SignalStorageUrl[] signalStorageUrls; - private final List networkInterceptors; - private final Optional dns; - private final byte[] zkGroupServerPublicParams; + private final SignalServiceUrl[] signalServiceUrls; + private final Map signalCdnUrlMap; + private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls; + private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls; + private final SignalStorageUrl[] signalStorageUrls; + private final List networkInterceptors; + private final Optional dns; + private final byte[] zkGroupServerPublicParams; public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls, - SignalCdnUrl[] signalCdnUrls, - SignalCdnUrl[] signalCdn2Urls, + Map signalCdnUrlMap, SignalContactDiscoveryUrl[] signalContactDiscoveryUrls, SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls, SignalStorageUrl[] signalStorageUrls, @@ -30,8 +29,7 @@ public final class SignalServiceConfiguration { byte[] zkGroupServerPublicParams) { this.signalServiceUrls = signalServiceUrls; - this.signalCdnUrls = signalCdnUrls; - this.signalCdn2Urls = signalCdn2Urls; + this.signalCdnUrlMap = signalCdnUrlMap; this.signalContactDiscoveryUrls = signalContactDiscoveryUrls; this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls; this.signalStorageUrls = signalStorageUrls; @@ -44,12 +42,8 @@ public final class SignalServiceConfiguration { return signalServiceUrls; } - public SignalCdnUrl[] getSignalCdnUrls() { - return signalCdnUrls; - } - - public SignalCdnUrl[] getSignalCdn2Urls() { - return signalCdn2Urls; + public Map getSignalCdnUrlMap() { + return signalCdnUrlMap; } public SignalContactDiscoveryUrl[] getSignalContactDiscoveryUrls() { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index f3ab9e4bc..eefddf603 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -49,6 +49,7 @@ import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredExcep import org.whispersystems.signalservice.api.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException; import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException; +import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.NoContentException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; @@ -62,6 +63,7 @@ import org.whispersystems.signalservice.api.storage.StorageAuthResponse; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.Tls12SocketFactory; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.configuration.SignalUrl; import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest; @@ -98,6 +100,7 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -191,17 +194,16 @@ public class PushServiceSocket { private long soTimeoutMillis = TimeUnit.SECONDS.toMillis(30); private final Set connections = new HashSet<>(); - private final ServiceConnectionHolder[] serviceClients; - private final ConnectionHolder[] cdnClients; - private final ConnectionHolder[] cdn2Clients; - private final ConnectionHolder[] contactDiscoveryClients; - private final ConnectionHolder[] keyBackupServiceClients; - private final ConnectionHolder[] storageClients; + private final ServiceConnectionHolder[] serviceClients; + private final Map cdnClientsMap; + private final ConnectionHolder[] contactDiscoveryClients; + private final ConnectionHolder[] keyBackupServiceClients; + private final ConnectionHolder[] storageClients; - private final CredentialsProvider credentialsProvider; - private final String signalAgent; - private final SecureRandom random; - private final ClientZkProfileOperations clientZkProfileOperations; + private final CredentialsProvider credentialsProvider; + private final String signalAgent; + private final SecureRandom random; + private final ClientZkProfileOperations clientZkProfileOperations; public PushServiceSocket(SignalServiceConfiguration configuration, CredentialsProvider credentialsProvider, @@ -211,8 +213,7 @@ public class PushServiceSocket { this.credentialsProvider = credentialsProvider; this.signalAgent = signalAgent; this.serviceClients = createServiceConnectionHolders(configuration.getSignalServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); - this.cdnClients = createConnectionHolders(configuration.getSignalCdnUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); - this.cdn2Clients = createConnectionHolders(configuration.getSignalCdn2Urls(), configuration.getNetworkInterceptors(), configuration.getDns()); + this.cdnClientsMap = createCdnClientsMap(configuration.getSignalCdnUrlMap(), configuration.getNetworkInterceptors(), configuration.getDns()); this.contactDiscoveryClients = createConnectionHolders(configuration.getSignalContactDiscoveryUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.keyBackupServiceClients = createConnectionHolders(configuration.getSignalKeyBackupServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.storageClients = createConnectionHolders(configuration.getSignalStorageUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); @@ -517,8 +518,7 @@ public class PushServiceSocket { } public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener) - throws NonSuccessfulResponseCodeException, PushNetworkException - { + throws NonSuccessfulResponseCodeException, PushNetworkException, MissingConfigurationException { final String path; if (cdnPath.getV2().isPresent()) { path = String.format(Locale.US, ATTACHMENT_ID_DOWNLOAD_PATH, cdnPath.getV2().get()); @@ -529,30 +529,35 @@ public class PushServiceSocket { } public void retrieveSticker(File destination, byte[] packId, int stickerId) - throws NonSuccessfulResponseCodeException, PushNetworkException - { + throws NonSuccessfulResponseCodeException, PushNetworkException, MissingConfigurationException { String hexPackId = Hex.toStringCondensed(packId); downloadFromCdn(destination, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null); } public byte[] retrieveSticker(byte[] packId, int stickerId) - throws NonSuccessfulResponseCodeException, PushNetworkException - { + throws NonSuccessfulResponseCodeException, PushNetworkException { String hexPackId = Hex.toStringCondensed(packId); ByteArrayOutputStream output = new ByteArrayOutputStream(); - downloadFromCdn(output, 0, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null); + try { + downloadFromCdn(output, 0, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null); + } catch (MissingConfigurationException e) { + throw new AssertionError(e); + } return output.toByteArray(); } public byte[] retrieveStickerManifest(byte[] packId) - throws NonSuccessfulResponseCodeException, PushNetworkException - { + throws NonSuccessfulResponseCodeException, PushNetworkException { String hexPackId = Hex.toStringCondensed(packId); ByteArrayOutputStream output = new ByteArrayOutputStream(); - downloadFromCdn(output, 0, 0, String.format(STICKER_MANIFEST_PATH, hexPackId), 1024 * 1024, null); + try { + downloadFromCdn(output, 0, 0, String.format(STICKER_MANIFEST_PATH, hexPackId), 1024 * 1024, null); + } catch (MissingConfigurationException e) { + throw new AssertionError(e); + } return output.toByteArray(); } @@ -615,9 +620,12 @@ public class PushServiceSocket { } public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes) - throws NonSuccessfulResponseCodeException, PushNetworkException - { - downloadFromCdn(destination, 0, path, maxSizeBytes, null); + throws NonSuccessfulResponseCodeException, PushNetworkException { + try { + downloadFromCdn(destination, 0, path, maxSizeBytes, null); + } catch (MissingConfigurationException e) { + throw new AssertionError(e); + } } public void setProfileName(String name) throws NonSuccessfulResponseCodeException, PushNetworkException { @@ -646,7 +654,7 @@ public class PushServiceSocket { } if (profileAvatar != null) { - uploadToCdn(AVATAR_UPLOAD_PATH, formAttributes.getAcl(), formAttributes.getKey(), + uploadToCdn0(AVATAR_UPLOAD_PATH, formAttributes.getAcl(), formAttributes.getKey(), formAttributes.getPolicy(), formAttributes.getAlgorithm(), formAttributes.getCredential(), formAttributes.getDate(), formAttributes.getSignature(), profileAvatar.getData(), @@ -682,7 +690,7 @@ public class PushServiceSocket { throw new NonSuccessfulResponseCodeException("Unable to parse entity"); } - uploadToCdn(AVATAR_UPLOAD_PATH, formAttributes.getAcl(), formAttributes.getKey(), + uploadToCdn0(AVATAR_UPLOAD_PATH, formAttributes.getAcl(), formAttributes.getKey(), formAttributes.getPolicy(), formAttributes.getAlgorithm(), formAttributes.getCredential(), formAttributes.getDate(), formAttributes.getSignature(), profileAvatar.getData(), @@ -896,7 +904,7 @@ public class PushServiceSocket { public byte[] uploadGroupV2Avatar(byte[] avatarCipherText, AvatarUploadAttributes uploadAttributes) throws IOException { - return uploadToCdn(AVATAR_UPLOAD_PATH, uploadAttributes.getAcl(), uploadAttributes.getKey(), + return uploadToCdn0(AVATAR_UPLOAD_PATH, uploadAttributes.getAcl(), uploadAttributes.getKey(), uploadAttributes.getPolicy(), uploadAttributes.getAlgorithm(), uploadAttributes.getCredential(), uploadAttributes.getDate(), uploadAttributes.getSignature(), @@ -910,7 +918,7 @@ public class PushServiceSocket { throws PushNetworkException, NonSuccessfulResponseCodeException { long id = Long.parseLong(uploadAttributes.getAttachmentId()); - byte[] digest = uploadToCdn(ATTACHMENT_UPLOAD_PATH, uploadAttributes.getAcl(), uploadAttributes.getKey(), + byte[] digest = uploadToCdn0(ATTACHMENT_UPLOAD_PATH, uploadAttributes.getAcl(), uploadAttributes.getKey(), uploadAttributes.getPolicy(), uploadAttributes.getAlgorithm(), uploadAttributes.getCredential(), uploadAttributes.getDate(), uploadAttributes.getSignature(), attachment.getData(), @@ -933,8 +941,7 @@ public class PushServiceSocket { } private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener) - throws PushNetworkException, NonSuccessfulResponseCodeException - { + throws PushNetworkException, NonSuccessfulResponseCodeException, MissingConfigurationException { try (FileOutputStream outputStream = new FileOutputStream(destination, true)) { downloadFromCdn(outputStream, destination.length(), cdnNumber, path, maxSizeBytes, listener); } catch (IOException e) { @@ -943,14 +950,17 @@ public class PushServiceSocket { } private void downloadFromCdn(OutputStream outputStream, long offset, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener) - throws PushNetworkException, NonSuccessfulResponseCodeException - { - ConnectionHolder connectionHolder = getRandom(cdnNumber == 2 ? cdn2Clients : cdnClients, random); - OkHttpClient okHttpClient = connectionHolder.getClient() - .newBuilder() - .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) - .readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) - .build(); + throws PushNetworkException, NonSuccessfulResponseCodeException, MissingConfigurationException { + ConnectionHolder[] cdnNumberClients = cdnClientsMap.get(cdnNumber); + if (cdnNumberClients == null) { + throw new MissingConfigurationException("Attempted to download from unsupported CDN number: " + cdnNumber + ", Our configuration supports: " + cdnClientsMap.keySet()); + } + ConnectionHolder connectionHolder = getRandom(cdnNumberClients, random); + OkHttpClient okHttpClient = connectionHolder.getClient() + .newBuilder() + .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) + .readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) + .build(); Request.Builder request = new Request.Builder().url(connectionHolder.getUrl() + "/" + path).get(); @@ -1012,14 +1022,14 @@ public class PushServiceSocket { throw new NonSuccessfulResponseCodeException("Response: " + response); } - private byte[] uploadToCdn(String path, String acl, String key, String policy, String algorithm, - String credential, String date, String signature, - InputStream data, String contentType, long length, - OutputStreamFactory outputStreamFactory, ProgressListener progressListener, - CancelationSignal cancelationSignal) + private byte[] uploadToCdn0(String path, String acl, String key, String policy, String algorithm, + String credential, String date, String signature, + InputStream data, String contentType, long length, + OutputStreamFactory outputStreamFactory, ProgressListener progressListener, + CancelationSignal cancelationSignal) throws PushNetworkException, NonSuccessfulResponseCodeException { - ConnectionHolder connectionHolder = getRandom(cdnClients, random); + ConnectionHolder connectionHolder = getRandom(cdnClientsMap.get(0), random); OkHttpClient okHttpClient = connectionHolder.getClient() .newBuilder() .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) @@ -1074,7 +1084,7 @@ public class PushServiceSocket { } private String getResumableUploadUrl(String signedUrl, Map headers) throws IOException { - ConnectionHolder connectionHolder = getRandom(cdn2Clients, random); + ConnectionHolder connectionHolder = getRandom(cdnClientsMap.get(2), random); OkHttpClient okHttpClient = connectionHolder.getClient() .newBuilder() .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) @@ -1135,7 +1145,7 @@ public class PushServiceSocket { } private byte[] uploadToCdn2(String resumableUrl, InputStream data, String contentType, long length, OutputStreamFactory outputStreamFactory, ProgressListener progressListener, CancelationSignal cancelationSignal) throws IOException { - ConnectionHolder connectionHolder = getRandom(cdn2Clients, random); + ConnectionHolder connectionHolder = getRandom(cdnClientsMap.get(2), random); OkHttpClient okHttpClient = connectionHolder.getClient() .newBuilder() .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) @@ -1531,7 +1541,25 @@ public class PushServiceSocket { return serviceConnectionHolders.toArray(new ServiceConnectionHolder[0]); } - private ConnectionHolder[] createConnectionHolders(SignalUrl[] urls, List interceptors, Optional dns) { + private static Map createCdnClientsMap(final Map signalCdnUrlMap, + final List interceptors, + final Optional dns) { + validateConfiguration(signalCdnUrlMap); + final Map result = new HashMap<>(); + for (Map.Entry entry : signalCdnUrlMap.entrySet()) { + result.put(entry.getKey(), + createConnectionHolders(entry.getValue(), interceptors, dns)); + } + return Collections.unmodifiableMap(result); + } + + private static void validateConfiguration(Map signalCdnUrlMap) { + if (!signalCdnUrlMap.containsKey(0) || !signalCdnUrlMap.containsKey(2)) { + throw new AssertionError("Configuration used to create PushServiceSocket must support CDN 0 and CDN 2"); + } + } + + private static ConnectionHolder[] createConnectionHolders(SignalUrl[] urls, List interceptors, Optional dns) { List connectionHolders = new LinkedList<>(); for (SignalUrl url : urls) { @@ -1541,7 +1569,7 @@ public class PushServiceSocket { return connectionHolders.toArray(new ConnectionHolder[0]); } - private OkHttpClient createConnectionClient(SignalUrl url, List interceptors, Optional dns) { + private static OkHttpClient createConnectionClient(SignalUrl url, List interceptors, Optional dns) { try { TrustManager[] trustManagers = BlacklistingTrustManager.createFor(url.getTrustStore()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageException.java index 1891aa762..1684921ed 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageException.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageException.java @@ -4,33 +4,27 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; /** - * Exception that indicates that the data message has a higher required protocol version than the - * current client is capable of interpreting. + * Exception that indicates that the data message contains something that is not supported by this + * version of the application. Subclasses provide more specific information about what data was + * found that is not supported. */ -public class UnsupportedDataMessageException extends Exception { +public abstract class UnsupportedDataMessageException extends Exception { - private final int requiredVersion; private final String sender; private final int senderDevice; private final Optional group; - public UnsupportedDataMessageException(int currentVersion, - int requiredVersion, - String sender, - int senderDevice, - Optional group) + protected UnsupportedDataMessageException(String message, + String sender, + int senderDevice, + Optional group) { - super("Required version: " + requiredVersion + ", Our version: " + currentVersion); - this.requiredVersion = requiredVersion; + super(message); this.sender = sender; this.senderDevice = senderDevice; this.group = group; } - public int getRequiredVersion() { - return requiredVersion; - } - public String getSender() { return sender; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageProtocolVersionException.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageProtocolVersionException.java new file mode 100644 index 000000000..ed9d53393 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UnsupportedDataMessageProtocolVersionException.java @@ -0,0 +1,25 @@ +package org.whispersystems.signalservice.internal.push; + +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; + +/** + * Exception that indicates that the data message has a higher required protocol version than the + * current client is capable of interpreting. + */ +public final class UnsupportedDataMessageProtocolVersionException extends UnsupportedDataMessageException { + private final int requiredVersion; + + public UnsupportedDataMessageProtocolVersionException(int currentVersion, + int requiredVersion, + String sender, + int senderDevice, + Optional group) { + super("Required version: " + requiredVersion + ", Our version: " + currentVersion, sender, senderDevice, group); + this.requiredVersion = requiredVersion; + } + + public int getRequiredVersion() { + return requiredVersion; + } +}