From 7e026a70726038eda4f9a2a8bb0953503e45fd2d Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 24 Apr 2018 10:21:41 -0700 Subject: [PATCH] Secret sender --- pom.xml | 6 +- protobuf/TextSecure.proto | 52 +- .../dispatch/DispatchManager.java | 11 +- .../dispatch/redis/PubSubConnection.java | 6 +- .../dispatch/redis/PubSubReply.java | 6 +- .../WhisperServerConfiguration.java | 32 +- .../textsecuregcm/WhisperServerService.java | 37 +- .../auth/AccountAuthenticator.java | 15 +- .../textsecuregcm/auth/Anonymous.java | 24 + .../auth/CertificateGenerator.java | 50 + .../auth/FederatedPeerAuthenticator.java | 79 - .../textsecuregcm/auth/OptionalAccess.java | 74 + .../auth/UnidentifiedAccessChecksum.java | 27 + .../FederationConfiguration.java | 40 - .../configuration/RedPhoneConfiguration.java | 20 - .../UnidentifiedDeliveryConfiguration.java | 42 + .../controllers/AccountController.java | 15 +- .../controllers/AttachmentController.java | 39 +- .../controllers/CertificateController.java | 36 + .../controllers/DeviceController.java | 11 +- .../controllers/DirectoryController.java | 7 +- .../controllers/FederationController.java | 19 - .../controllers/FederationControllerV1.java | 123 - .../controllers/FederationControllerV2.java | 51 - .../controllers/KeysController.java | 128 +- .../controllers/MessageController.java | 153 +- .../controllers/ProfileController.java | 38 +- .../textsecuregcm/crypto/Curve.java | 98 + .../textsecuregcm/crypto/DjbECPrivateKey.java | 24 + .../textsecuregcm/crypto/DjbECPublicKey.java | 49 + .../textsecuregcm/crypto/ECKeyPair.java | 20 + .../textsecuregcm/crypto/ECPrivateKey.java | 7 + .../textsecuregcm/crypto/ECPublicKey.java | 10 + .../entities/AccountAttributes.java | 16 +- .../entities/DeliveryCertificate.java | 50 + .../entities/IncomingMessageList.java | 11 - .../textsecuregcm/entities/MessageProtos.java | 2670 ++++++++++++++++- .../entities/OutgoingMessageEntity.java | 41 +- .../textsecuregcm/entities/Profile.java | 26 +- .../federation/FederatedClient.java | 252 -- .../federation/FederatedClientManager.java | 67 - .../federation/FederatedPeer.java | 66 - .../federation/NoSuchPeerException.java | 24 - .../federation/NonLimitedAccount.java | 45 - .../textsecuregcm/push/APNSender.java | 2 +- .../push/ApnFallbackManager.java | 9 +- .../textsecuregcm/push/GCMSender.java | 4 +- .../textsecuregcm/push/ReceiptSender.java | 49 +- .../textsecuregcm/sms/SmsSender.java | 3 +- .../textsecuregcm/sms/TwilioSmsSender.java | 5 +- .../textsecuregcm/storage/Account.java | 83 +- .../storage/AccountsManager.java | 10 +- .../textsecuregcm/storage/Device.java | 62 +- .../storage/DirectoryManager.java | 10 +- .../storage/DirectoryReconciler.java | 198 +- .../storage/DirectoryReconciliationCache.java | 5 +- .../textsecuregcm/storage/Keys.java | 6 +- .../textsecuregcm/storage/Messages.java | 30 +- .../textsecuregcm/storage/MessagesCache.java | 61 +- .../storage/MessagesManager.java | 24 +- .../storage/PendingAccountsManager.java | 8 +- .../storage/PendingDevicesManager.java | 8 +- .../textsecuregcm/util/ByteUtil.java | 21 + .../textsecuregcm/util/Util.java | 7 + .../AuthenticatedConnectListener.java | 54 +- .../websocket/ProvisioningConnection.java | 3 +- .../WebSocketAccountAuthenticator.java | 8 +- .../websocket/WebSocketConnection.java | 30 +- .../workers/CertificateCommand.java | 92 + .../workers/DeleteUserCommand.java | 2 +- .../workers/DirectoryCommand.java | 8 - .../workers/DirectoryUpdater.java | 12 +- src/main/resources/lua/insert_item.lua | 18 +- .../resources/lua/remove_item_by_guid.lua | 28 + src/main/resources/lua/remove_item_by_id.lua | 6 + .../resources/lua/remove_item_by_sender.lua | 6 + src/main/resources/messagedb.xml | 16 + .../dispatch/DispatchManagerTest.java | 14 +- .../tests/auth/OptionalAccessTest.java | 137 + .../controllers/AccountControllerTest.java | 23 +- .../controllers/AttachmentControllerTest.java | 15 +- .../CertificateControllerTest.java | 113 + .../controllers/DeviceControllerTest.java | 17 +- .../controllers/DirectoryControllerTest.java | 9 +- .../controllers/FederatedControllerTest.java | 132 - .../tests/controllers/KeyControllerTest.java | 59 +- .../controllers/MessageControllerTest.java | 92 +- .../controllers/ProfileControllerTest.java | 20 +- .../tests/push/APNSenderTest.java | 2 +- .../tests/push/GCMSenderTest.java | 3 +- .../tests/storage/AccountTest.java | 9 +- .../storage/DirectoryReconcilerTest.java | 16 +- .../textsecuregcm/tests/util/AuthHelper.java | 36 +- .../websocket/WebSocketConnectionTest.java | 38 +- 94 files changed, 4523 insertions(+), 1717 deletions(-) create mode 100644 src/main/java/org/whispersystems/textsecuregcm/auth/Anonymous.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/auth/FederatedPeerAuthenticator.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/auth/UnidentifiedAccessChecksum.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/configuration/FederationConfiguration.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/configuration/RedPhoneConfiguration.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/entities/DeliveryCertificate.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClientManager.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/federation/FederatedPeer.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/federation/NoSuchPeerException.java delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/util/ByteUtil.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java create mode 100644 src/main/resources/lua/remove_item_by_guid.lua create mode 100644 src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java create mode 100644 src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java delete mode 100644 src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java diff --git a/pom.xml b/pom.xml index f0d0458c9..5506b5e24 100644 --- a/pom.xml +++ b/pom.xml @@ -102,12 +102,12 @@ org.whispersystems websocket-resources - 0.5.4 + 0.5.8 org.whispersystems - dropwizard-simpleauth - 0.3.0 + curve25519-java + 0.5.0 diff --git a/protobuf/TextSecure.proto b/protobuf/TextSecure.proto index 65add730f..4ef758e95 100644 --- a/protobuf/TextSecure.proto +++ b/protobuf/TextSecure.proto @@ -21,22 +21,48 @@ option java_outer_classname = "MessageProtos"; message Envelope { enum Type { - UNKNOWN = 0; - CIPHERTEXT = 1; - KEY_EXCHANGE = 2; - PREKEY_BUNDLE = 3; - RECEIPT = 5; + UNKNOWN = 0; + CIPHERTEXT = 1; + KEY_EXCHANGE = 2; + PREKEY_BUNDLE = 3; + RECEIPT = 5; + UNIDENTIFIED_SENDER = 6; } - optional Type type = 1; - optional string source = 2; - optional uint32 sourceDevice = 7; - optional string relay = 3; - optional uint64 timestamp = 5; - optional bytes legacyMessage = 6; // Contains an encrypted DataMessage XXX -- Remove after 10/01/15 - optional bytes content = 8; // Contains an encrypted Content + optional Type type = 1; + optional string source = 2; + optional uint32 sourceDevice = 7; + optional string relay = 3; + optional uint64 timestamp = 5; + optional bytes legacyMessage = 6; // Contains an encrypted DataMessage XXX -- Remove after 10/01/15 + optional bytes content = 8; // Contains an encrypted Content + optional string serverGuid = 9; + optional uint64 server_timestamp = 10; } message ProvisioningUuid { optional string uuid = 1; -} \ No newline at end of file +} + +message ServerCertificate { + message Certificate { + optional uint32 id = 1; + optional bytes key = 2; + } + + optional bytes certificate = 1; + optional bytes signature = 2; +} + +message SenderCertificate { + message Certificate { + optional string sender = 1; + optional uint32 senderDevice = 2; + optional fixed64 expires = 3; + optional bytes identityKey = 4; + optional ServerCertificate signer = 5; + } + + optional bytes certificate = 1; + optional bytes signature = 2; +} diff --git a/src/main/java/org/whispersystems/dispatch/DispatchManager.java b/src/main/java/org/whispersystems/dispatch/DispatchManager.java index 6edc2f9dd..f90dadb8a 100644 --- a/src/main/java/org/whispersystems/dispatch/DispatchManager.java +++ b/src/main/java/org/whispersystems/dispatch/DispatchManager.java @@ -1,6 +1,5 @@ package org.whispersystems.dispatch; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory; @@ -9,10 +8,12 @@ import org.whispersystems.dispatch.redis.PubSubReply; import java.io.IOException; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class DispatchManager extends Thread { private final Logger logger = LoggerFactory.getLogger(DispatchManager.class); @@ -45,7 +46,7 @@ public class DispatchManager extends Thread { } public synchronized void subscribe(String name, DispatchChannel dispatchChannel) { - Optional previous = Optional.fromNullable(subscriptions.get(name)); + Optional previous = Optional.ofNullable(subscriptions.get(name)); subscriptions.put(name, dispatchChannel); try { @@ -60,7 +61,7 @@ public class DispatchManager extends Thread { } public synchronized void unsubscribe(String name, DispatchChannel channel) { - Optional subscription = Optional.fromNullable(subscriptions.get(name)); + Optional subscription = Optional.ofNullable(subscriptions.get(name)); if (subscription.isPresent() && subscription.get() == channel) { subscriptions.remove(name); @@ -105,7 +106,7 @@ public class DispatchManager extends Thread { } private void dispatchSubscribe(final PubSubReply reply) { - Optional subscription = Optional.fromNullable(subscriptions.get(reply.getChannel())); + Optional subscription = Optional.ofNullable(subscriptions.get(reply.getChannel())); if (subscription.isPresent()) { dispatchSubscription(reply.getChannel(), subscription.get()); @@ -115,7 +116,7 @@ public class DispatchManager extends Thread { } private void dispatchMessage(PubSubReply reply) { - Optional subscription = Optional.fromNullable(subscriptions.get(reply.getChannel())); + Optional subscription = Optional.ofNullable(subscriptions.get(reply.getChannel())); if (subscription.isPresent()) { dispatchMessage(reply.getChannel(), subscription.get(), reply.getContent().get()); diff --git a/src/main/java/org/whispersystems/dispatch/redis/PubSubConnection.java b/src/main/java/org/whispersystems/dispatch/redis/PubSubConnection.java index 599c3930a..171fcc736 100644 --- a/src/main/java/org/whispersystems/dispatch/redis/PubSubConnection.java +++ b/src/main/java/org/whispersystems/dispatch/redis/PubSubConnection.java @@ -1,6 +1,5 @@ package org.whispersystems.dispatch.redis; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.dispatch.io.RedisInputStream; @@ -14,6 +13,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Arrays; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; public class PubSubConnection { @@ -98,12 +98,12 @@ public class PubSubConnection { private PubSubReply readUnsubscribeReply() throws IOException { String channelName = readSubscriptionReply(); - return new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, channelName, Optional.absent()); + return new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, channelName, Optional.empty()); } private PubSubReply readSubscribeReply() throws IOException { String channelName = readSubscriptionReply(); - return new PubSubReply(PubSubReply.Type.SUBSCRIBE, channelName, Optional.absent()); + return new PubSubReply(PubSubReply.Type.SUBSCRIBE, channelName, Optional.empty()); } private String readSubscriptionReply() throws IOException { diff --git a/src/main/java/org/whispersystems/dispatch/redis/PubSubReply.java b/src/main/java/org/whispersystems/dispatch/redis/PubSubReply.java index d57797fae..4e76e05b8 100644 --- a/src/main/java/org/whispersystems/dispatch/redis/PubSubReply.java +++ b/src/main/java/org/whispersystems/dispatch/redis/PubSubReply.java @@ -1,7 +1,9 @@ package org.whispersystems.dispatch.redis; -import com.google.common.base.Optional; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class PubSubReply { public enum Type { @@ -11,7 +13,7 @@ public class PubSubReply { } private final Type type; - private final String channel; + private final String channel; private final Optional content; public PubSubReply(Type type, String channel, Optional content) { diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index fe6a93cad..9c9938c60 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -17,20 +17,7 @@ package org.whispersystems.textsecuregcm; import com.fasterxml.jackson.annotation.JsonProperty; -import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; -import org.whispersystems.textsecuregcm.configuration.AttachmentsConfiguration; -import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration; -import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; -import org.whispersystems.textsecuregcm.configuration.GcmConfiguration; -import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration; -import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration; -import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration; -import org.whispersystems.textsecuregcm.configuration.PushConfiguration; -import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration; -import org.whispersystems.textsecuregcm.configuration.RedisConfiguration; -import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration; -import org.whispersystems.textsecuregcm.configuration.TurnConfiguration; -import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; +import org.whispersystems.textsecuregcm.configuration.*; import org.whispersystems.websocket.configuration.WebSocketConfiguration; import javax.validation.Valid; @@ -101,10 +88,6 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private List maxDevices = new LinkedList<>(); - @Valid - @JsonProperty - private FederationConfiguration federation = new FederationConfiguration(); - @Valid @NotNull @JsonProperty @@ -143,6 +126,11 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private ApnConfiguration apn; + @Valid + @NotNull + @JsonProperty + private UnidentifiedDeliveryConfiguration unidentifiedDelivery; + public WebSocketConfiguration getWebSocketConfiguration() { return webSocket; } @@ -195,10 +183,6 @@ public class WhisperServerConfiguration extends Configuration { return limits; } - public FederationConfiguration getFederationConfiguration() { - return federation; - } - public TurnConfiguration getTurnConfiguration() { return turn; } @@ -215,6 +199,10 @@ public class WhisperServerConfiguration extends Configuration { return profiles; } + public UnidentifiedDeliveryConfiguration getDeliveryCertificate() { + return unidentifiedDelivery; + } + public Map getTestDevices() { Map results = new HashMap<>(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index f6a13ce3c..8d138d821 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -20,31 +20,24 @@ import com.codahale.metrics.SharedMetricRegistries; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.google.common.base.Optional; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.skife.jdbi.v2.DBI; import org.whispersystems.dispatch.DispatchManager; -import org.whispersystems.dropwizard.simpleauth.AuthDynamicFeature; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; -import org.whispersystems.dropwizard.simpleauth.BasicCredentialAuthFilter; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; +import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator; -import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AttachmentController; +import org.whispersystems.textsecuregcm.controllers.CertificateController; import org.whispersystems.textsecuregcm.controllers.DeviceController; import org.whispersystems.textsecuregcm.controllers.DirectoryController; -import org.whispersystems.textsecuregcm.controllers.FederationControllerV1; -import org.whispersystems.textsecuregcm.controllers.FederationControllerV2; import org.whispersystems.textsecuregcm.controllers.KeepAliveController; import org.whispersystems.textsecuregcm.controllers.KeysController; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.ProvisioningController; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; @@ -75,6 +68,7 @@ import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler; import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener; import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator; +import org.whispersystems.textsecuregcm.workers.CertificateCommand; import org.whispersystems.textsecuregcm.workers.DeleteUserCommand; import org.whispersystems.textsecuregcm.workers.DirectoryCommand; import org.whispersystems.textsecuregcm.workers.PeriodicStatsCommand; @@ -88,9 +82,13 @@ import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; import java.security.Security; import java.util.EnumSet; +import java.util.Optional; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.Application; +import io.dropwizard.auth.AuthDynamicFeature; +import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.auth.basic.BasicCredentialAuthFilter; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.jdbi.DBIFactory; import io.dropwizard.setup.Bootstrap; @@ -109,6 +107,7 @@ public class WhisperServerService extends Application("accountdb", "accountsdb.xml") { @Override public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) { @@ -163,7 +162,6 @@ public class WhisperServerService extends Application() .setAuthenticator(deviceAuthenticator) - .setPrincipal(Account.class) - .buildAuthFilter(), - new BasicCredentialAuthFilter.Builder() - .setAuthenticator(federatedPeerAuthenticator) - .setPrincipal(FederatedPeer.class) .buildAuthFilter())); - environment.jersey().register(new AuthValueFactoryProvider.Binder()); + environment.jersey().register(new AuthValueFactoryProvider.Binder<>(Account.class)); environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices())); environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, directoryQueue, rateLimiters, config.getMaxDevices())); environment.jersey().register(new DirectoryController(rateLimiters, directory, directoryCredentialsGenerator)); - environment.jersey().register(new FederationControllerV1(accountsManager, attachmentController, messageController)); - environment.jersey().register(new FederationControllerV2(accountsManager, attachmentController, messageController, keysController)); environment.jersey().register(new ProvisioningController(rateLimiters, pushSender)); + environment.jersey().register(new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()))); environment.jersey().register(attachmentController); environment.jersey().register(keysController); environment.jersey().register(messageController); diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java index 20b41ee67..78aa3d89b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -19,18 +19,19 @@ package org.whispersystems.textsecuregcm.auth; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.dropwizard.simpleauth.Authenticator; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Util; +import java.util.Optional; + import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.auth.AuthenticationException; +import io.dropwizard.auth.Authenticator; import io.dropwizard.auth.basic.BasicCredentials; public class AccountAuthenticator implements Authenticator { @@ -56,13 +57,13 @@ public class AccountAuthenticator implements Authenticator account = accountsManager.get(authorizationHeader.getNumber()); if (!account.isPresent()) { - return Optional.absent(); + return Optional.empty(); } Optional device = account.get().getDevice(authorizationHeader.getDeviceId()); if (!device.isPresent()) { - return Optional.absent(); + return Optional.empty(); } if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) { @@ -73,9 +74,9 @@ public class AccountAuthenticator implements Authenticator. - */ -package org.whispersystems.textsecuregcm.auth; - -import com.codahale.metrics.Meter; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.dropwizard.simpleauth.Authenticator; -import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; -import org.whispersystems.textsecuregcm.util.Constants; - -import java.util.List; - -import static com.codahale.metrics.MetricRegistry.name; -import io.dropwizard.auth.AuthenticationException; -import io.dropwizard.auth.basic.BasicCredentials; - - -public class FederatedPeerAuthenticator implements Authenticator { - - private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - - private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), - "authentication", - "failed")); - - private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), - "authentication", - "succeeded")); - - private final Logger logger = LoggerFactory.getLogger(FederatedPeerAuthenticator.class); - - private final List peers; - - public FederatedPeerAuthenticator(FederationConfiguration config) { - this.peers = config.getPeers(); - } - - @Override - public Optional authenticate(BasicCredentials basicCredentials) - throws AuthenticationException - { - - if (peers == null) { - authenticationFailedMeter.mark(); - return Optional.absent(); - } - - for (FederatedPeer peer : peers) { - if (basicCredentials.getUsername().equals(peer.getName()) && - basicCredentials.getPassword().equals(peer.getAuthenticationToken())) - { - authenticationSucceededMeter.mark(); - return Optional.of(peer); - } - } - - authenticationFailedMeter.mark(); - return Optional.absent(); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java b/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java new file mode 100644 index 000000000..77eb5ecb7 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/OptionalAccess.java @@ -0,0 +1,74 @@ +package org.whispersystems.textsecuregcm.auth; + +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.util.Hex; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import java.security.MessageDigest; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalAccess { + + public static final String UNIDENTIFIED = "Unidentified-Access-Key"; + + public static void verify(Optional requestAccount, + Optional accessKey, + Optional targetAccount, + String deviceSelector) + { + try { + verify(requestAccount, accessKey, targetAccount); + + if (!deviceSelector.equals("*")) { + long deviceId = Long.parseLong(deviceSelector); + + Optional targetDevice = targetAccount.get().getDevice(deviceId); + + if (targetDevice.isPresent() && targetDevice.get().isActive()) { + return; + } + + if (requestAccount.isPresent()) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } else { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + } + } catch (NumberFormatException e) { + throw new WebApplicationException(Response.status(422).build()); + } + } + + public static void verify(Optional requestAccount, + Optional accessKey, + Optional targetAccount) + { + if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isActive()) { + return; + } + + //noinspection ConstantConditions + if (requestAccount.isPresent() && (!targetAccount.isPresent() || (targetAccount.isPresent() && !targetAccount.get().isActive()))) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + + if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isActive() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) { + return; + } + + if (accessKey.isPresent() && + targetAccount.isPresent() && + targetAccount.get().getUnidentifiedAccessKey().isPresent() && + targetAccount.get().isActive() && + MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get())) + { + return; + } + + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/UnidentifiedAccessChecksum.java b/src/main/java/org/whispersystems/textsecuregcm/auth/UnidentifiedAccessChecksum.java new file mode 100644 index 000000000..85661c946 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/UnidentifiedAccessChecksum.java @@ -0,0 +1,27 @@ +package org.whispersystems.textsecuregcm.auth; + +import org.whispersystems.textsecuregcm.util.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class UnidentifiedAccessChecksum { + + public static String generateFor(Optional unidentifiedAccessKey) { + try { + if (!unidentifiedAccessKey.isPresent()|| unidentifiedAccessKey.get().length != 16) return null; + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(unidentifiedAccessKey.get(), "HmacSHA256")); + + return Base64.encodeBytes(mac.doFinal(new byte[32])); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new AssertionError(e); + } + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/FederationConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/FederationConfiguration.java deleted file mode 100644 index 6bc5ec802..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/configuration/FederationConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.configuration; - - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; - -import java.util.List; - -public class FederationConfiguration { - - @JsonProperty - private List peers; - - @JsonProperty - private String name; - - public List getPeers() { - return peers; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/RedPhoneConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/RedPhoneConfiguration.java deleted file mode 100644 index 68ee2f7c6..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/configuration/RedPhoneConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.whispersystems.textsecuregcm.configuration; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Optional; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; - -public class RedPhoneConfiguration { - - @JsonProperty - private String authKey; - - public Optional getAuthorizationKey() throws DecoderException { - if (authKey == null || authKey.trim().length() == 0) { - return Optional.absent(); - } - - return Optional.of(Hex.decodeHex(authKey.toCharArray())); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java new file mode 100644 index 000000000..416ef303a --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java @@ -0,0 +1,42 @@ +package org.whispersystems.textsecuregcm.configuration; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.crypto.ECPrivateKey; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class UnidentifiedDeliveryConfiguration { + + @JsonProperty + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @NotNull + private byte[] certificate; + + @JsonProperty + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @NotNull + @Size(min = 32, max = 32) + private byte[] privateKey; + + @NotNull + private int expiresDays; + + public byte[] getCertificate() { + return certificate; + } + + public ECPrivateKey getPrivateKey() { + return Curve.decodePrivatePoint(privateKey); + } + + public int getExpiresDays() { + return expiresDays; + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 9ea97bdda..7b9d0e2e0 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -21,7 +21,6 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.annotation.Timed; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; @@ -66,11 +65,13 @@ import java.io.IOException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.auth.Auth; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/accounts") public class AccountController { @@ -311,13 +312,14 @@ public class AccountController { device.setFetchesMessages(attributes.getFetchesMessages()); device.setName(attributes.getName()); device.setLastSeen(Util.todayInMillis()); - device.setVoiceSupported(attributes.getVoice()); - device.setVideoSupported(attributes.getVideo()); + device.setUnauthenticatedDeliverySupported(attributes.getUnidentifiedAccessKey() != null); device.setRegistrationId(attributes.getRegistrationId()); device.setSignalingKey(attributes.getSignalingKey()); device.setUserAgent(userAgent); account.setPin(attributes.getPin()); + account.setUnidentifiedAccessKey(attributes.getUnidentifiedAccessKey()); + account.setUnrestrictedUnidentifiedAccess(attributes.isUnrestrictedUnidentifiedAccess()); accounts.update(account); } @@ -339,8 +341,7 @@ public class AccountController { device.setFetchesMessages(accountAttributes.getFetchesMessages()); device.setRegistrationId(accountAttributes.getRegistrationId()); device.setName(accountAttributes.getName()); - device.setVoiceSupported(accountAttributes.getVoice()); - device.setVideoSupported(accountAttributes.getVideo()); + device.setUnauthenticatedDeliverySupported(accountAttributes.getUnidentifiedAccessKey() != null); device.setCreated(System.currentTimeMillis()); device.setLastSeen(Util.todayInMillis()); device.setUserAgent(userAgent); @@ -349,6 +350,8 @@ public class AccountController { account.setNumber(number); account.addDevice(device); account.setPin(accountAttributes.getPin()); + account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey()); + account.setUnrestrictedUnidentifiedAccess(accountAttributes.isUnrestrictedUnidentifiedAccess()); if (accounts.create(account)) { newUserMeter.mark(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java index 060efd1e0..9f5800b6b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -18,26 +18,20 @@ package org.whispersystems.textsecuregcm.controllers; import com.amazonaws.HttpMethod; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptor; import org.whispersystems.textsecuregcm.entities.AttachmentUri; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.textsecuregcm.s3.UrlSigner; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.util.Conversions; -import org.whispersystems.textsecuregcm.s3.UrlSigner; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URL; import java.security.SecureRandom; @@ -49,21 +43,18 @@ import io.dropwizard.auth.Auth; @Path("/v1/attachments") public class AttachmentController { + @SuppressWarnings("unused") private final Logger logger = LoggerFactory.getLogger(AttachmentController.class); private static final String[] UNACCELERATED_REGIONS = {"+20", "+971", "+968", "+974"}; - private final RateLimiters rateLimiters; - private final FederatedClientManager federatedClientManager; - private final UrlSigner urlSigner; + private final RateLimiters rateLimiters; + private final UrlSigner urlSigner; - public AttachmentController(RateLimiters rateLimiters, - FederatedClientManager federatedClientManager, - UrlSigner urlSigner) + public AttachmentController(RateLimiters rateLimiters, UrlSigner urlSigner) { - this.rateLimiters = rateLimiters; - this.federatedClientManager = federatedClientManager; - this.urlSigner = urlSigner; + this.rateLimiters = rateLimiters; + this.urlSigner = urlSigner; } @Timed @@ -88,20 +79,10 @@ public class AttachmentController { @Produces(MediaType.APPLICATION_JSON) @Path("/{attachmentId}") public AttachmentUri redirectToAttachment(@Auth Account account, - @PathParam("attachmentId") long attachmentId, - @QueryParam("relay") Optional relay) + @PathParam("attachmentId") long attachmentId) throws IOException { - try { - if (!relay.isPresent()) { - return new AttachmentUri(urlSigner.getPreSignedUrl(attachmentId, HttpMethod.GET, Stream.of(UNACCELERATED_REGIONS).anyMatch(region -> account.getNumber().startsWith(region)))); - } else { - return new AttachmentUri(federatedClientManager.getClient(relay.get()).getSignedAttachmentUri(attachmentId)); - } - } catch (NoSuchPeerException e) { - logger.info("No such peer: " + relay); - throw new WebApplicationException(Response.status(404).build()); - } + return new AttachmentUri(urlSigner.getPreSignedUrl(attachmentId, HttpMethod.GET, Stream.of(UNACCELERATED_REGIONS).anyMatch(region -> account.getNumber().startsWith(region)))); } private long generateAttachmentId() { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java new file mode 100644 index 000000000..53ee52066 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java @@ -0,0 +1,36 @@ +package org.whispersystems.textsecuregcm.controllers; + +import com.codahale.metrics.annotation.Timed; +import org.whispersystems.textsecuregcm.auth.CertificateGenerator; +import org.whispersystems.textsecuregcm.entities.DeliveryCertificate; +import org.whispersystems.textsecuregcm.storage.Account; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import java.io.IOException; +import java.security.InvalidKeyException; + +import io.dropwizard.auth.Auth; + +@Path("/v1/certificate") +public class CertificateController { + + private final CertificateGenerator certificateGenerator; + + public CertificateController(CertificateGenerator certificateGenerator) { + this.certificateGenerator = certificateGenerator; + } + + @Timed + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/delivery") + public DeliveryCertificate getDeliveryCertificate(@Auth Account account) throws IOException, InvalidKeyException { + if (!account.getAuthenticatedDevice().isPresent()) throw new AssertionError(); + return new DeliveryCertificate(certificateGenerator.createFor(account, account.getAuthenticatedDevice().get())); + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 83eb090d8..b6414d30c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -18,7 +18,6 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; @@ -55,6 +54,7 @@ import java.security.SecureRandom; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import io.dropwizard.auth.Auth; @@ -213,6 +213,15 @@ public class DeviceController { } } + @Timed + @PUT + @Path("/unauthenticated_delivery") + public void setUnauthenticatedDelivery(@Auth Account account) { + assert(account.getAuthenticatedDevice().isPresent()); + account.getAuthenticatedDevice().get().setUnauthenticatedDeliverySupported(true); + accounts.update(account); + } + @VisibleForTesting protected VerificationCode generateVerificationCode() { SecureRandom random = new SecureRandom(); int randomInt = 100000 + random.nextInt(900000); diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java index cf5582ab3..9023eba9c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -20,13 +20,9 @@ import com.codahale.metrics.Histogram; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; -import org.apache.commons.codec.DecoderException; -import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration; import org.whispersystems.textsecuregcm.entities.ClientContact; import org.whispersystems.textsecuregcm.entities.ClientContactTokens; import org.whispersystems.textsecuregcm.entities.ClientContacts; @@ -49,6 +45,7 @@ import javax.ws.rs.core.Response; import java.io.IOException; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.auth.Auth; diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java deleted file mode 100644 index e00c98376..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.whispersystems.textsecuregcm.controllers; - -import org.whispersystems.textsecuregcm.storage.AccountsManager; - -public class FederationController { - - protected final AccountsManager accounts; - protected final AttachmentController attachmentController; - protected final MessageController messageController; - - public FederationController(AccountsManager accounts, - AttachmentController attachmentController, - MessageController messageController) - { - this.accounts = accounts; - this.attachmentController = attachmentController; - this.messageController = messageController; - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java deleted file mode 100644 index 5bdcba059..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV1.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.controllers; - -import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.entities.AccountCount; -import org.whispersystems.textsecuregcm.entities.AttachmentUri; -import org.whispersystems.textsecuregcm.entities.ClientContact; -import org.whispersystems.textsecuregcm.entities.ClientContacts; -import org.whispersystems.textsecuregcm.entities.IncomingMessageList; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; -import org.whispersystems.textsecuregcm.federation.NonLimitedAccount; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.util.Util; - -import javax.validation.Valid; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -import io.dropwizard.auth.Auth; - -@Path("/v1/federation") -public class FederationControllerV1 extends FederationController { - - private final Logger logger = LoggerFactory.getLogger(FederationControllerV1.class); - - private static final int ACCOUNT_CHUNK_SIZE = 10000; - - public FederationControllerV1(AccountsManager accounts, - AttachmentController attachmentController, - MessageController messageController) - { - super(accounts, attachmentController, messageController); - } - - @Timed - @GET - @Path("/attachment/{attachmentId}") - @Produces(MediaType.APPLICATION_JSON) - public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer, - @PathParam("attachmentId") long attachmentId) - throws IOException - { - return attachmentController.redirectToAttachment(new NonLimitedAccount("Unknown", -1, peer.getName()), - attachmentId, Optional.absent()); - } - - @Timed - @PUT - @Path("/messages/{source}/{sourceDeviceId}/{destination}") - public void sendMessages(@Auth FederatedPeer peer, - @PathParam("source") String source, - @PathParam("sourceDeviceId") long sourceDeviceId, - @PathParam("destination") String destination, - @Valid IncomingMessageList messages) - throws IOException - { - try { - messages.setRelay(null); - messageController.sendMessage(new NonLimitedAccount(source, sourceDeviceId, peer.getName()), destination, messages); - } catch (RateLimitExceededException e) { - logger.warn("Rate limiting on federated channel", e); - throw new IOException(e); - } - } - - @Timed - @GET - @Path("/user_count") - @Produces(MediaType.APPLICATION_JSON) - public AccountCount getUserCount(@Auth FederatedPeer peer) { - return new AccountCount((int)accounts.getCount()); - } - - @Timed - @GET - @Path("/user_tokens/{offset}") - @Produces(MediaType.APPLICATION_JSON) - public ClientContacts getUserTokens(@Auth FederatedPeer peer, - @PathParam("offset") int offset) - { - List accountList = accounts.getAll(offset, ACCOUNT_CHUNK_SIZE); - List clientContacts = new LinkedList<>(); - - for (Account account : accountList) { - byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); - - if (!account.isActive()) { - clientContact.setInactive(true); - } - - clientContacts.add(clientContact); - } - - return new ClientContacts(clientContacts); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java deleted file mode 100644 index 57d877799..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationControllerV2.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.whispersystems.textsecuregcm.controllers; - -import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.entities.PreKeyResponse; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; -import org.whispersystems.textsecuregcm.federation.NonLimitedAccount; -import org.whispersystems.textsecuregcm.storage.AccountsManager; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import java.io.IOException; - -import io.dropwizard.auth.Auth; - -@Path("/v2/federation") -public class FederationControllerV2 extends FederationController { - - private final Logger logger = LoggerFactory.getLogger(FederationControllerV2.class); - - private final KeysController keysController; - - public FederationControllerV2(AccountsManager accounts, AttachmentController attachmentController, MessageController messageController, KeysController keysController) { - super(accounts, attachmentController, messageController); - this.keysController = keysController; - } - - @Timed - @GET - @Path("/key/{number}/{device}") - @Produces(MediaType.APPLICATION_JSON) - public Optional getKeysV2(@Auth FederatedPeer peer, - @PathParam("number") String number, - @PathParam("device") String device) - throws IOException - { - try { - return keysController.getDeviceKeys(new NonLimitedAccount("Unknown", -1, peer.getName()), - number, device, Optional.absent()); - } catch (RateLimitExceededException e) { - logger.warn("Rate limiting on federated channel", e); - throw new IOException(e); - } - } - -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 68a11a8fb..ab27504e5 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -17,18 +17,17 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.entities.PreKeyCount; -import org.whispersystems.textsecuregcm.entities.PreKeyResponseItem; -import org.whispersystems.textsecuregcm.entities.PreKeyResponse; -import org.whispersystems.textsecuregcm.entities.PreKeyState; +import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.entities.PreKey; +import org.whispersystems.textsecuregcm.entities.PreKeyCount; +import org.whispersystems.textsecuregcm.entities.PreKeyResponse; +import org.whispersystems.textsecuregcm.entities.PreKeyResponseItem; +import org.whispersystems.textsecuregcm.entities.PreKeyState; import org.whispersystems.textsecuregcm.entities.SignedPreKey; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -39,36 +38,34 @@ import org.whispersystems.textsecuregcm.storage.Keys; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import io.dropwizard.auth.Auth; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v2/keys") public class KeysController { private static final Logger logger = LoggerFactory.getLogger(KeysController.class); - private final RateLimiters rateLimiters; - private final Keys keys; - private final AccountsManager accounts; - private final FederatedClientManager federatedClientManager; + private final RateLimiters rateLimiters; + private final Keys keys; + private final AccountsManager accounts; - public KeysController(RateLimiters rateLimiters, Keys keys, AccountsManager accounts, - FederatedClientManager federatedClientManager) - { - this.rateLimiters = rateLimiters; - this.keys = keys; - this.accounts = accounts; - this.federatedClientManager = federatedClientManager; + public KeysController(RateLimiters rateLimiters, Keys keys, AccountsManager accounts) { + this.rateLimiters = rateLimiters; + this.keys = keys; + this.accounts = accounts; } @GET @@ -111,50 +108,49 @@ public class KeysController { @GET @Path("/{number}/{device_id}") @Produces(MediaType.APPLICATION_JSON) - public Optional getDeviceKeys(@Auth Account account, - @PathParam("number") String number, - @PathParam("device_id") String deviceId, - @QueryParam("relay") Optional relay) + public Optional getDeviceKeys(@Auth Optional account, + @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, + @PathParam("number") String number, + @PathParam("device_id") String deviceId) throws RateLimitExceededException { - try { - if (relay.isPresent()) { - return federatedClientManager.getClient(relay.get()).getKeysV2(number, deviceId); - } + if (!account.isPresent() && !accessKey.isPresent()) { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } - Account target = getAccount(number, deviceId); + if (account.isPresent()) { + rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "__" + number + "." + deviceId); + } - if (account.isRateLimited()) { - rateLimiters.getPreKeysLimiter().validate(account.getNumber() + "__" + number + "." + deviceId); - } + Optional target = accounts.get(number); + OptionalAccess.verify(account, accessKey, target, deviceId); - Optional> targetKeys = getLocalKeys(target, deviceId); - List devices = new LinkedList<>(); + assert(target.isPresent()); - for (Device device : target.getDevices()) { - if (device.isActive() && (deviceId.equals("*") || device.getId() == Long.parseLong(deviceId))) { - SignedPreKey signedPreKey = device.getSignedPreKey(); - PreKey preKey = null; + Optional> targetKeys = getLocalKeys(target.get(), deviceId); + List devices = new LinkedList<>(); - if (targetKeys.isPresent()) { - for (KeyRecord keyRecord : targetKeys.get()) { - if (!keyRecord.isLastResort() && keyRecord.getDeviceId() == device.getId()) { - preKey = new PreKey(keyRecord.getKeyId(), keyRecord.getPublicKey()); - } + for (Device device : target.get().getDevices()) { + if (device.isActive() && (deviceId.equals("*") || device.getId() == Long.parseLong(deviceId))) { + SignedPreKey signedPreKey = device.getSignedPreKey(); + PreKey preKey = null; + + if (targetKeys.isPresent()) { + for (KeyRecord keyRecord : targetKeys.get()) { + if (!keyRecord.isLastResort() && keyRecord.getDeviceId() == device.getId()) { + preKey = new PreKey(keyRecord.getKeyId(), keyRecord.getPublicKey()); } } + } - if (signedPreKey != null || preKey != null) { - devices.add(new PreKeyResponseItem(device.getId(), device.getRegistrationId(), signedPreKey, preKey)); - } + if (signedPreKey != null || preKey != null) { + devices.add(new PreKeyResponseItem(device.getId(), device.getRegistrationId(), signedPreKey, preKey)); } } - - if (devices.isEmpty()) return Optional.absent(); - else return Optional.of(new PreKeyResponse(target.getIdentityKey(), devices)); - } catch (NoSuchPeerException | NoSuchUserException e) { - throw new WebApplicationException(Response.status(404).build()); } + + if (devices.isEmpty()) return Optional.empty(); + else return Optional.of(new PreKeyResponse(target.get().getIdentityKey(), devices)); } @Timed @@ -176,12 +172,10 @@ public class KeysController { SignedPreKey signedPreKey = device.getSignedPreKey(); if (signedPreKey != null) return Optional.of(signedPreKey); - else return Optional.absent(); + else return Optional.empty(); } - private Optional> getLocalKeys(Account destination, String deviceIdSelector) - throws NoSuchUserException - { + private Optional> getLocalKeys(Account destination, String deviceIdSelector) { try { if (deviceIdSelector.equals("*")) { return keys.get(destination.getNumber()); @@ -202,30 +196,4 @@ public class KeysController { throw new WebApplicationException(Response.status(422).build()); } } - - private Account getAccount(String number, String deviceSelector) - throws NoSuchUserException - { - try { - Optional account = accounts.get(number); - - if (!account.isPresent() || !account.get().isActive()) { - throw new NoSuchUserException("No active account"); - } - - if (!deviceSelector.equals("*")) { - long deviceId = Long.parseLong(deviceSelector); - - Optional targetDevice = account.get().getDevice(deviceId); - - if (!targetDevice.isPresent() || !targetDevice.get().isActive()) { - throw new NoSuchUserException("No active device"); - } - } - - return account.get(); - } catch (NumberFormatException e) { - throw new WebApplicationException(Response.status(422).build()); - } - } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index ff09f88b4..e53faf6d9 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -17,10 +17,11 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; import com.google.protobuf.ByteString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.entities.IncomingMessage; import org.whispersystems.textsecuregcm.entities.IncomingMessageList; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; @@ -29,15 +30,11 @@ import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.entities.SendMessageResponse; import org.whispersystems.textsecuregcm.entities.StaleDevices; -import org.whispersystems.textsecuregcm.federation.FederatedClient; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.push.ApnFallbackManager; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PushSender; import org.whispersystems.textsecuregcm.push.ReceiptSender; -import org.whispersystems.textsecuregcm.push.TransientPushFailureException; import org.whispersystems.textsecuregcm.redis.RedisOperation; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -51,6 +48,7 @@ import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -62,10 +60,13 @@ import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.UUID; import io.dropwizard.auth.Auth; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/messages") public class MessageController { @@ -74,7 +75,6 @@ public class MessageController { private final RateLimiters rateLimiters; private final PushSender pushSender; private final ReceiptSender receiptSender; - private final FederatedClientManager federatedClientManager; private final AccountsManager accountsManager; private final MessagesManager messagesManager; private final ApnFallbackManager apnFallbackManager; @@ -84,7 +84,6 @@ public class MessageController { ReceiptSender receiptSender, AccountsManager accountsManager, MessagesManager messagesManager, - FederatedClientManager federatedClientManager, ApnFallbackManager apnFallbackManager) { this.rateLimiters = rateLimiters; @@ -92,7 +91,6 @@ public class MessageController { this.receiptSender = receiptSender; this.accountsManager = accountsManager; this.messagesManager = messagesManager; - this.federatedClientManager = federatedClientManager; this.apnFallbackManager = apnFallbackManager; } @@ -101,22 +99,41 @@ public class MessageController { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public SendMessageResponse sendMessage(@Auth Account source, - @PathParam("destination") String destinationName, - @Valid IncomingMessageList messages) - throws IOException, RateLimitExceededException + public SendMessageResponse sendMessage(@Auth Optional source, + @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, + @PathParam("destination") String destinationName, + @Valid IncomingMessageList messages) + throws RateLimitExceededException { - if (!source.getNumber().equals(destinationName)) { - rateLimiters.getMessagesLimiter().validate(source.getNumber() + "__" + destinationName); + if (!source.isPresent() && !accessKey.isPresent()) { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + + if (source.isPresent() && !source.get().getNumber().equals(destinationName)) { + rateLimiters.getMessagesLimiter().validate(source.get().getNumber() + "__" + destinationName); } try { - boolean isSyncMessage = source.getNumber().equals(destinationName); + boolean isSyncMessage = source.isPresent() && source.get().getNumber().equals(destinationName); - if (Util.isEmpty(messages.getRelay())) sendLocalMessage(source, destinationName, messages, isSyncMessage); - else sendRelayMessage(source, destinationName, messages, isSyncMessage); + Optional destination; - return new SendMessageResponse(!isSyncMessage && source.getActiveDeviceCount() > 1); + if (!isSyncMessage) destination = accountsManager.get(destinationName); + else destination = source; + + OptionalAccess.verify(source, accessKey, destination); + validateCompleteDeviceList(destination.get(), messages.getMessages(), isSyncMessage); + validateRegistrationIds(destination.get(), messages.getMessages()); + + for (IncomingMessage incomingMessage : messages.getMessages()) { + Optional destinationDevice = destination.get().getDevice(incomingMessage.getDestinationDeviceId()); + + if (destinationDevice.isPresent()) { + sendMessage(source, destination.get(), destinationDevice.get(), messages.getTimestamp(), incomingMessage); + } + } + + return new SendMessageResponse(!isSyncMessage && source.isPresent() && source.get().getActiveDeviceCount() > 1); } catch (NoSuchUserException e) { throw new WebApplicationException(Response.status(404).build()); } catch (MismatchedDevicesException e) { @@ -130,8 +147,6 @@ public class MessageController { .type(MediaType.APPLICATION_JSON) .entity(new StaleDevices(e.getStaleDevices())) .build()); - } catch (InvalidDestinationException e) { - throw new WebApplicationException(Response.status(400).build()); } } @@ -155,7 +170,6 @@ public class MessageController { public void removePendingMessage(@Auth Account account, @PathParam("source") String source, @PathParam("timestamp") long timestamp) - throws IOException { try { WebSocketConnection.messageTime.update(System.currentTimeMillis() - timestamp); @@ -167,45 +181,41 @@ public class MessageController { if (message.isPresent() && message.get().getType() != Envelope.Type.RECEIPT_VALUE) { receiptSender.sendReceipt(account, message.get().getSource(), - message.get().getTimestamp(), - Optional.fromNullable(message.get().getRelay())); + message.get().getTimestamp()); } } catch (NotPushRegisteredException e) { logger.info("User no longer push registered for delivery receipt: " + e.getMessage()); - } catch (NoSuchUserException | TransientPushFailureException e) { + } catch (NoSuchUserException e) { logger.warn("Sending delivery receipt", e); } } + @Timed + @DELETE + @Path("/uuid/{uuid}") + public void removePendingMessage(@Auth Account account, @PathParam("uuid") UUID uuid) { + try { + Optional message = messagesManager.delete(account.getNumber(), + account.getAuthenticatedDevice().get().getId(), + uuid); - private void sendLocalMessage(Account source, - String destinationName, - IncomingMessageList messages, - boolean isSyncMessage) - throws NoSuchUserException, MismatchedDevicesException, StaleDevicesException - { - Account destination; + message.ifPresent(outgoingMessageEntity -> WebSocketConnection.messageTime.update(System.currentTimeMillis() - outgoingMessageEntity.getTimestamp())); - if (!isSyncMessage) destination = getDestinationAccount(destinationName); - else destination = source; - - validateCompleteDeviceList(destination, messages.getMessages(), isSyncMessage); - validateRegistrationIds(destination, messages.getMessages()); - - for (IncomingMessage incomingMessage : messages.getMessages()) { - Optional destinationDevice = destination.getDevice(incomingMessage.getDestinationDeviceId()); - - if (destinationDevice.isPresent()) { - sendLocalMessage(source, destination, destinationDevice.get(), messages.getTimestamp(), incomingMessage); + if (message.isPresent() && !Util.isEmpty(message.get().getSource()) && message.get().getType() != Envelope.Type.RECEIPT_VALUE) { + receiptSender.sendReceipt(account, message.get().getSource(), message.get().getTimestamp()); } + } catch (NoSuchUserException e) { + logger.warn("Sending delivery receipt", e); + } catch (NotPushRegisteredException e) { + logger.info("User no longer push registered for delivery receipt: " + e.getMessage()); } } - private void sendLocalMessage(Account source, - Account destinationAccount, - Device destinationDevice, - long timestamp, - IncomingMessage incomingMessage) + private void sendMessage(Optional source, + Account destinationAccount, + Device destinationDevice, + long timestamp, + IncomingMessage incomingMessage) throws NoSuchUserException { try { @@ -214,9 +224,13 @@ public class MessageController { Envelope.Builder messageBuilder = Envelope.newBuilder(); messageBuilder.setType(Envelope.Type.valueOf(incomingMessage.getType())) - .setSource(source.getNumber()) .setTimestamp(timestamp == 0 ? System.currentTimeMillis() : timestamp) - .setSourceDevice((int) source.getAuthenticatedDevice().get().getId()); + .setServerTimestamp(System.currentTimeMillis()); + + if (source.isPresent()) { + messageBuilder.setSource(source.get().getNumber()) + .setSourceDevice((int)source.get().getAuthenticatedDevice().get().getId()); + } if (messageBody.isPresent()) { messageBuilder.setLegacyMessage(ByteString.copyFrom(messageBody.get())); @@ -226,10 +240,6 @@ public class MessageController { messageBuilder.setContent(ByteString.copyFrom(messageContent.get())); } - if (source.getRelay().isPresent()) { - messageBuilder.setRelay(source.getRelay().get()); - } - pushSender.sendMessage(destinationAccount, destinationDevice, messageBuilder.build()); } catch (NotPushRegisteredException e) { if (destinationDevice.isMaster()) throw new NoSuchUserException(e); @@ -237,35 +247,6 @@ public class MessageController { } } - private void sendRelayMessage(Account source, - String destinationName, - IncomingMessageList messages, - boolean isSyncMessage) - throws IOException, NoSuchUserException, InvalidDestinationException - { - if (isSyncMessage) throw new InvalidDestinationException("Transcript messages can't be relayed!"); - - try { - FederatedClient client = federatedClientManager.getClient(messages.getRelay()); - client.sendMessages(source.getNumber(), source.getAuthenticatedDevice().get().getId(), - destinationName, messages); - } catch (NoSuchPeerException e) { - throw new NoSuchUserException(e); - } - } - - private Account getDestinationAccount(String destination) - throws NoSuchUserException - { - Optional account = accountsManager.get(destination); - - if (!account.isPresent() || !account.get().isActive()) { - throw new NoSuchUserException(destination); - } - - return account.get(); - } - private void validateRegistrationIds(Account account, List messages) throws StaleDevicesException { @@ -326,24 +307,24 @@ public class MessageController { } private Optional getMessageBody(IncomingMessage message) { - if (Util.isEmpty(message.getBody())) return Optional.absent(); + if (Util.isEmpty(message.getBody())) return Optional.empty(); try { return Optional.of(Base64.decode(message.getBody())); } catch (IOException ioe) { logger.debug("Bad B64", ioe); - return Optional.absent(); + return Optional.empty(); } } private Optional getMessageContent(IncomingMessage message) { - if (Util.isEmpty(message.getContent())) return Optional.absent(); + if (Util.isEmpty(message.getContent())) return Optional.empty(); try { return Optional.of(Base64.decode(message.getContent())); } catch (IOException ioe) { logger.debug("Bad B64", ioe); - return Optional.absent(); + return Optional.empty(); } } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index d73f74870..80cdb399b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -7,10 +7,12 @@ import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.codahale.metrics.annotation.Timed; -import com.google.common.base.Optional; import org.apache.commons.codec.binary.Base64; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.valuehandling.UnwrapValidatedValue; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; +import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration; import org.whispersystems.textsecuregcm.entities.Profile; import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes; @@ -22,6 +24,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.util.Pair; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -33,9 +36,11 @@ import javax.ws.rs.core.Response; import java.security.SecureRandom; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Optional; import io.dropwizard.auth.Auth; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/profile") public class ProfileController { @@ -75,22 +80,29 @@ public class ProfileController { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/{number}") - public Profile getProfile(@Auth Account account, - @PathParam("number") String number, - @QueryParam("ca") boolean useCaCertificate) + public Profile getProfile(@Auth Optional requestAccount, + @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, + @PathParam("number") String number, + @QueryParam("ca") boolean useCaCertificate) throws RateLimitExceededException { - rateLimiters.getProfileLimiter().validate(account.getNumber()); - - Optional accountProfile = accountsManager.get(number); - - if (!accountProfile.isPresent()) { - throw new WebApplicationException(Response.status(404).build()); + if (!requestAccount.isPresent() && !accessKey.isPresent()) { + throw new WebApplicationException(Response.Status.UNAUTHORIZED); } - return new Profile(accountProfile.get().getName(), + if (requestAccount.isPresent()) { + rateLimiters.getProfileLimiter().validate(requestAccount.get().getNumber()); + } + + Optional accountProfile = accountsManager.get(number); + OptionalAccess.verify(requestAccount, accessKey, accountProfile); + + //noinspection ConstantConditions,OptionalGetWithoutIsPresent + return new Profile(accountProfile.get().getProfileName(), accountProfile.get().getAvatar(), - accountProfile.get().getIdentityKey()); + accountProfile.get().getIdentityKey(), + accountProfile.get().isUnauthenticatedDeliverySupported() ? UnidentifiedAccessChecksum.generateFor(accountProfile.get().getUnidentifiedAccessKey()) : null, + accountProfile.get().isUnrestrictedUnidentifiedAccess()); } @Timed @@ -98,7 +110,7 @@ public class ProfileController { @Produces(MediaType.APPLICATION_JSON) @Path("/name/{name}") public void setProfile(@Auth Account account, @PathParam("name") @UnwrapValidatedValue(true) @Length(min = 72,max= 72) Optional name) { - account.setName(name.orNull()); + account.setProfileName(name.orElse(null)); accountsManager.update(account); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java new file mode 100644 index 000000000..d44dfb93c --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/Curve.java @@ -0,0 +1,98 @@ +package org.whispersystems.textsecuregcm.crypto; + +import org.whispersystems.curve25519.Curve25519; +import org.whispersystems.curve25519.Curve25519KeyPair; + +import java.security.InvalidKeyException; + +import static org.whispersystems.curve25519.Curve25519.BEST; + +public class Curve { + + public static final int DJB_TYPE = 0x05; + + public static ECKeyPair generateKeyPair() { + Curve25519KeyPair keyPair = Curve25519.getInstance(BEST).generateKeyPair(); + + return new ECKeyPair(new DjbECPublicKey(keyPair.getPublicKey()), + new DjbECPrivateKey(keyPair.getPrivateKey())); + } + + public static ECPublicKey decodePoint(byte[] bytes, int offset) + throws InvalidKeyException + { + if (bytes == null || bytes.length - offset < 1) { + throw new InvalidKeyException("No key type identifier"); + } + + int type = bytes[offset] & 0xFF; + + switch (type) { + case Curve.DJB_TYPE: + if (bytes.length - offset < 33) { + throw new InvalidKeyException("Bad key length: " + bytes.length); + } + + byte[] keyBytes = new byte[32]; + System.arraycopy(bytes, offset+1, keyBytes, 0, keyBytes.length); + return new DjbECPublicKey(keyBytes); + default: + throw new InvalidKeyException("Bad key type: " + type); + } + } + + public static ECPrivateKey decodePrivatePoint(byte[] bytes) { + return new DjbECPrivateKey(bytes); + } + + public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) + throws InvalidKeyException + { + if (publicKey == null) { + throw new InvalidKeyException("public value is null"); + } + + if (privateKey == null) { + throw new InvalidKeyException("private value is null"); + } + + if (publicKey.getType() != privateKey.getType()) { + throw new InvalidKeyException("Public and private keys must be of the same type!"); + } + + if (publicKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .calculateAgreement(((DjbECPublicKey) publicKey).getPublicKey(), + ((DjbECPrivateKey) privateKey).getPrivateKey()); + } else { + throw new InvalidKeyException("Unknown type: " + publicKey.getType()); + } + } + + public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message) + throws InvalidKeyException + { + if (signingKey == null || message == null) { + throw new InvalidKeyException("Values must not be null"); + } + + if (signingKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .calculateSignature(((DjbECPrivateKey) signingKey).getPrivateKey(), message); + } else { + throw new InvalidKeyException("Unknown type: " + signingKey.getType()); + } + } + + public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature) + throws InvalidKeyException + { + if (signingKey.getType() == DJB_TYPE) { + return Curve25519.getInstance(BEST) + .verifySignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature); + } else { + throw new InvalidKeyException("Unknown type: " + signingKey.getType()); + } + } + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java new file mode 100644 index 000000000..ff8c64cb1 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPrivateKey.java @@ -0,0 +1,24 @@ +package org.whispersystems.textsecuregcm.crypto; + +public class DjbECPrivateKey implements ECPrivateKey { + + private final byte[] privateKey; + + DjbECPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + + @Override + public byte[] serialize() { + return privateKey; + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + public byte[] getPrivateKey() { + return privateKey; + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java new file mode 100644 index 000000000..87fdbc42c --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/DjbECPublicKey.java @@ -0,0 +1,49 @@ +package org.whispersystems.textsecuregcm.crypto; + +import org.whispersystems.textsecuregcm.util.ByteUtil; + +import java.math.BigInteger; +import java.util.Arrays; + +public class DjbECPublicKey implements ECPublicKey { + + private final byte[] publicKey; + + DjbECPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + @Override + public byte[] serialize() { + byte[] type = {Curve.DJB_TYPE}; + return ByteUtil.combine(type, publicKey); + } + + @Override + public int getType() { + return Curve.DJB_TYPE; + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof DjbECPublicKey)) return false; + + DjbECPublicKey that = (DjbECPublicKey)other; + return Arrays.equals(this.publicKey, that.publicKey); + } + + @Override + public int hashCode() { + return Arrays.hashCode(publicKey); + } + + @Override + public int compareTo(ECPublicKey another) { + return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey)); + } + + public byte[] getPublicKey() { + return publicKey; + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java new file mode 100644 index 000000000..c0ae79031 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECKeyPair.java @@ -0,0 +1,20 @@ +package org.whispersystems.textsecuregcm.crypto; + +public class ECKeyPair { + + private final ECPublicKey publicKey; + private final ECPrivateKey privateKey; + + ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public ECPublicKey getPublicKey() { + return publicKey; + } + + public ECPrivateKey getPrivateKey() { + return privateKey; + } +} \ No newline at end of file diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java new file mode 100644 index 000000000..2cc5652f6 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPrivateKey.java @@ -0,0 +1,7 @@ +package org.whispersystems.textsecuregcm.crypto; + +public interface ECPrivateKey { + public byte[] serialize(); + public int getType(); +} + diff --git a/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java new file mode 100644 index 000000000..4dd1e27f9 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/crypto/ECPublicKey.java @@ -0,0 +1,10 @@ +package org.whispersystems.textsecuregcm.crypto; + +public interface ECPublicKey extends Comparable { + + public static final int KEY_SIZE = 33; + + public byte[] serialize(); + + public int getType(); +} \ No newline at end of file diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java b/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java index 5b0081e31..e598b060a 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -46,6 +46,12 @@ public class AccountAttributes { @JsonProperty private String pin; + @JsonProperty + private byte[] unidentifiedAccessKey; + + @JsonProperty + private boolean unrestrictedUnidentifiedAccess; + public AccountAttributes() {} @VisibleForTesting @@ -91,4 +97,12 @@ public class AccountAttributes { public String getPin() { return pin; } + + public byte[] getUnidentifiedAccessKey() { + return unidentifiedAccessKey; + } + + public boolean isUnrestrictedUnidentifiedAccess() { + return unrestrictedUnidentifiedAccess; + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/DeliveryCertificate.java b/src/main/java/org/whispersystems/textsecuregcm/entities/DeliveryCertificate.java new file mode 100644 index 000000000..c925bfff5 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/DeliveryCertificate.java @@ -0,0 +1,50 @@ +package org.whispersystems.textsecuregcm.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.annotations.VisibleForTesting; +import org.whispersystems.textsecuregcm.util.Base64; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + +import java.io.IOException; + +public class DeliveryCertificate { + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] certificate; + + public DeliveryCertificate(byte[] certificate) { + this.certificate = certificate; + } + + public DeliveryCertificate() {} + + @VisibleForTesting + public byte[] getCertificate() { + return certificate; + } + + public static class ByteArraySerializer extends JsonSerializer { + @Override + public void serialize(byte[] bytes, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(Base64.encodeBytes(bytes)); + } + } + + public static class ByteArrayDeserializer extends JsonDeserializer { + @Override + public byte[] deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return Base64.decode(jsonParser.getValueAsString()); + } + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java index 755744bda..092cc63a8 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java @@ -29,9 +29,6 @@ public class IncomingMessageList { @Valid private List messages; - @JsonProperty - private String relay; - @JsonProperty private long timestamp; @@ -41,14 +38,6 @@ public class IncomingMessageList { return messages; } - public String getRelay() { - return relay; - } - - public void setRelay(String relay) { - this.relay = relay; - } - public long getTimestamp() { return timestamp; } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java b/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java index 799beaaa5..5b52357ea 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java @@ -106,6 +106,31 @@ public final class MessageProtos { * */ com.google.protobuf.ByteString getContent(); + + // optional string serverGuid = 9; + /** + * optional string serverGuid = 9; + */ + boolean hasServerGuid(); + /** + * optional string serverGuid = 9; + */ + java.lang.String getServerGuid(); + /** + * optional string serverGuid = 9; + */ + com.google.protobuf.ByteString + getServerGuidBytes(); + + // optional uint64 server_timestamp = 10; + /** + * optional uint64 server_timestamp = 10; + */ + boolean hasServerTimestamp(); + /** + * optional uint64 server_timestamp = 10; + */ + long getServerTimestamp(); } /** * Protobuf type {@code textsecure.Envelope} @@ -199,6 +224,16 @@ public final class MessageProtos { content_ = input.readBytes(); break; } + case 74: { + bitField0_ |= 0x00000080; + serverGuid_ = input.readBytes(); + break; + } + case 80: { + bitField0_ |= 0x00000100; + serverTimestamp_ = input.readUInt64(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -263,6 +298,10 @@ public final class MessageProtos { * RECEIPT = 5; */ RECEIPT(4, 5), + /** + * UNIDENTIFIED_SENDER = 6; + */ + UNIDENTIFIED_SENDER(5, 6), ; /** @@ -285,6 +324,10 @@ public final class MessageProtos { * RECEIPT = 5; */ public static final int RECEIPT_VALUE = 5; + /** + * UNIDENTIFIED_SENDER = 6; + */ + public static final int UNIDENTIFIED_SENDER_VALUE = 6; public final int getNumber() { return value; } @@ -296,6 +339,7 @@ public final class MessageProtos { case 2: return KEY_EXCHANGE; case 3: return PREKEY_BUNDLE; case 5: return RECEIPT; + case 6: return UNIDENTIFIED_SENDER; default: return null; } } @@ -530,6 +574,65 @@ public final class MessageProtos { return content_; } + // optional string serverGuid = 9; + public static final int SERVERGUID_FIELD_NUMBER = 9; + private java.lang.Object serverGuid_; + /** + * optional string serverGuid = 9; + */ + public boolean hasServerGuid() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional string serverGuid = 9; + */ + public java.lang.String getServerGuid() { + java.lang.Object ref = serverGuid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + serverGuid_ = s; + } + return s; + } + } + /** + * optional string serverGuid = 9; + */ + public com.google.protobuf.ByteString + getServerGuidBytes() { + java.lang.Object ref = serverGuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + serverGuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional uint64 server_timestamp = 10; + public static final int SERVER_TIMESTAMP_FIELD_NUMBER = 10; + private long serverTimestamp_; + /** + * optional uint64 server_timestamp = 10; + */ + public boolean hasServerTimestamp() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional uint64 server_timestamp = 10; + */ + public long getServerTimestamp() { + return serverTimestamp_; + } + private void initFields() { type_ = org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope.Type.UNKNOWN; source_ = ""; @@ -538,6 +641,8 @@ public final class MessageProtos { timestamp_ = 0L; legacyMessage_ = com.google.protobuf.ByteString.EMPTY; content_ = com.google.protobuf.ByteString.EMPTY; + serverGuid_ = ""; + serverTimestamp_ = 0L; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -572,6 +677,12 @@ public final class MessageProtos { if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeBytes(8, content_); } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeBytes(9, getServerGuidBytes()); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + output.writeUInt64(10, serverTimestamp_); + } getUnknownFields().writeTo(output); } @@ -609,6 +720,14 @@ public final class MessageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(8, content_); } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(9, getServerGuidBytes()); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(10, serverTimestamp_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -739,6 +858,10 @@ public final class MessageProtos { bitField0_ = (bitField0_ & ~0x00000020); content_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000040); + serverGuid_ = ""; + bitField0_ = (bitField0_ & ~0x00000080); + serverTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000100); return this; } @@ -795,6 +918,14 @@ public final class MessageProtos { to_bitField0_ |= 0x00000040; } result.content_ = content_; + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000080; + } + result.serverGuid_ = serverGuid_; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000100; + } + result.serverTimestamp_ = serverTimestamp_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -836,6 +967,14 @@ public final class MessageProtos { if (other.hasContent()) { setContent(other.getContent()); } + if (other.hasServerGuid()) { + bitField0_ |= 0x00000080; + serverGuid_ = other.serverGuid_; + onChanged(); + } + if (other.hasServerTimestamp()) { + setServerTimestamp(other.getServerTimestamp()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -1217,6 +1356,113 @@ public final class MessageProtos { return this; } + // optional string serverGuid = 9; + private java.lang.Object serverGuid_ = ""; + /** + * optional string serverGuid = 9; + */ + public boolean hasServerGuid() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional string serverGuid = 9; + */ + public java.lang.String getServerGuid() { + java.lang.Object ref = serverGuid_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + serverGuid_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string serverGuid = 9; + */ + public com.google.protobuf.ByteString + getServerGuidBytes() { + java.lang.Object ref = serverGuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + serverGuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string serverGuid = 9; + */ + public Builder setServerGuid( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + serverGuid_ = value; + onChanged(); + return this; + } + /** + * optional string serverGuid = 9; + */ + public Builder clearServerGuid() { + bitField0_ = (bitField0_ & ~0x00000080); + serverGuid_ = getDefaultInstance().getServerGuid(); + onChanged(); + return this; + } + /** + * optional string serverGuid = 9; + */ + public Builder setServerGuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + serverGuid_ = value; + onChanged(); + return this; + } + + // optional uint64 server_timestamp = 10; + private long serverTimestamp_ ; + /** + * optional uint64 server_timestamp = 10; + */ + public boolean hasServerTimestamp() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional uint64 server_timestamp = 10; + */ + public long getServerTimestamp() { + return serverTimestamp_; + } + /** + * optional uint64 server_timestamp = 10; + */ + public Builder setServerTimestamp(long value) { + bitField0_ |= 0x00000100; + serverTimestamp_ = value; + onChanged(); + return this; + } + /** + * optional uint64 server_timestamp = 10; + */ + public Builder clearServerTimestamp() { + bitField0_ = (bitField0_ & ~0x00000100); + serverTimestamp_ = 0L; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.Envelope) } @@ -1699,6 +1945,2361 @@ public final class MessageProtos { // @@protoc_insertion_point(class_scope:textsecure.ProvisioningUuid) } + public interface ServerCertificateOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes certificate = 1; + /** + * optional bytes certificate = 1; + */ + boolean hasCertificate(); + /** + * optional bytes certificate = 1; + */ + com.google.protobuf.ByteString getCertificate(); + + // optional bytes signature = 2; + /** + * optional bytes signature = 2; + */ + boolean hasSignature(); + /** + * optional bytes signature = 2; + */ + com.google.protobuf.ByteString getSignature(); + } + /** + * Protobuf type {@code textsecure.ServerCertificate} + */ + public static final class ServerCertificate extends + com.google.protobuf.GeneratedMessage + implements ServerCertificateOrBuilder { + // Use ServerCertificate.newBuilder() to construct. + private ServerCertificate(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private ServerCertificate(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final ServerCertificate defaultInstance; + public static ServerCertificate getDefaultInstance() { + return defaultInstance; + } + + public ServerCertificate getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ServerCertificate( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + certificate_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + signature_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public ServerCertificate parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ServerCertificate(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + public interface CertificateOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 id = 1; + /** + * optional uint32 id = 1; + */ + boolean hasId(); + /** + * optional uint32 id = 1; + */ + int getId(); + + // optional bytes key = 2; + /** + * optional bytes key = 2; + */ + boolean hasKey(); + /** + * optional bytes key = 2; + */ + com.google.protobuf.ByteString getKey(); + } + /** + * Protobuf type {@code textsecure.ServerCertificate.Certificate} + */ + public static final class Certificate extends + com.google.protobuf.GeneratedMessage + implements CertificateOrBuilder { + // Use Certificate.newBuilder() to construct. + private Certificate(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Certificate(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Certificate defaultInstance; + public static Certificate getDefaultInstance() { + return defaultInstance; + } + + public Certificate getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Certificate( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + id_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + key_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_Certificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_Certificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Certificate parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Certificate(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional uint32 id = 1; + public static final int ID_FIELD_NUMBER = 1; + private int id_; + /** + * optional uint32 id = 1; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional uint32 id = 1; + */ + public int getId() { + return id_; + } + + // optional bytes key = 2; + public static final int KEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString key_; + /** + * optional bytes key = 2; + */ + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes key = 2; + */ + public com.google.protobuf.ByteString getKey() { + return key_; + } + + private void initFields() { + id_ = 0; + key_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, key_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, key_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code textsecure.ServerCertificate.Certificate} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.CertificateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_Certificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_Certificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + id_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + key_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_Certificate_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate result = new org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.id_ = id_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.key_ = key_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasKey()) { + setKey(other.getKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Certificate) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional uint32 id = 1; + private int id_ ; + /** + * optional uint32 id = 1; + */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional uint32 id = 1; + */ + public int getId() { + return id_; + } + /** + * optional uint32 id = 1; + */ + public Builder setId(int value) { + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + /** + * optional uint32 id = 1; + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + // optional bytes key = 2; + private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes key = 2; + */ + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes key = 2; + */ + public com.google.protobuf.ByteString getKey() { + return key_; + } + /** + * optional bytes key = 2; + */ + public Builder setKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + key_ = value; + onChanged(); + return this; + } + /** + * optional bytes key = 2; + */ + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000002); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.ServerCertificate.Certificate) + } + + static { + defaultInstance = new Certificate(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.ServerCertificate.Certificate) + } + + private int bitField0_; + // optional bytes certificate = 1; + public static final int CERTIFICATE_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString certificate_; + /** + * optional bytes certificate = 1; + */ + public boolean hasCertificate() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes certificate = 1; + */ + public com.google.protobuf.ByteString getCertificate() { + return certificate_; + } + + // optional bytes signature = 2; + public static final int SIGNATURE_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString signature_; + /** + * optional bytes signature = 2; + */ + public boolean hasSignature() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes signature = 2; + */ + public com.google.protobuf.ByteString getSignature() { + return signature_; + } + + private void initFields() { + certificate_ = com.google.protobuf.ByteString.EMPTY; + signature_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, certificate_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, signature_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, certificate_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, signature_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code textsecure.ServerCertificate} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + certificate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + signature_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_ServerCertificate_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate result = new org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.certificate_ = certificate_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.signature_ = signature_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance()) return this; + if (other.hasCertificate()) { + setCertificate(other.getCertificate()); + } + if (other.hasSignature()) { + setSignature(other.getSignature()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional bytes certificate = 1; + private com.google.protobuf.ByteString certificate_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes certificate = 1; + */ + public boolean hasCertificate() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes certificate = 1; + */ + public com.google.protobuf.ByteString getCertificate() { + return certificate_; + } + /** + * optional bytes certificate = 1; + */ + public Builder setCertificate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + certificate_ = value; + onChanged(); + return this; + } + /** + * optional bytes certificate = 1; + */ + public Builder clearCertificate() { + bitField0_ = (bitField0_ & ~0x00000001); + certificate_ = getDefaultInstance().getCertificate(); + onChanged(); + return this; + } + + // optional bytes signature = 2; + private com.google.protobuf.ByteString signature_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes signature = 2; + */ + public boolean hasSignature() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes signature = 2; + */ + public com.google.protobuf.ByteString getSignature() { + return signature_; + } + /** + * optional bytes signature = 2; + */ + public Builder setSignature(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + signature_ = value; + onChanged(); + return this; + } + /** + * optional bytes signature = 2; + */ + public Builder clearSignature() { + bitField0_ = (bitField0_ & ~0x00000002); + signature_ = getDefaultInstance().getSignature(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.ServerCertificate) + } + + static { + defaultInstance = new ServerCertificate(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.ServerCertificate) + } + + public interface SenderCertificateOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes certificate = 1; + /** + * optional bytes certificate = 1; + */ + boolean hasCertificate(); + /** + * optional bytes certificate = 1; + */ + com.google.protobuf.ByteString getCertificate(); + + // optional bytes signature = 2; + /** + * optional bytes signature = 2; + */ + boolean hasSignature(); + /** + * optional bytes signature = 2; + */ + com.google.protobuf.ByteString getSignature(); + } + /** + * Protobuf type {@code textsecure.SenderCertificate} + */ + public static final class SenderCertificate extends + com.google.protobuf.GeneratedMessage + implements SenderCertificateOrBuilder { + // Use SenderCertificate.newBuilder() to construct. + private SenderCertificate(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private SenderCertificate(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final SenderCertificate defaultInstance; + public static SenderCertificate getDefaultInstance() { + return defaultInstance; + } + + public SenderCertificate getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private SenderCertificate( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + certificate_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + signature_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public SenderCertificate parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new SenderCertificate(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + public interface CertificateOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional string sender = 1; + /** + * optional string sender = 1; + */ + boolean hasSender(); + /** + * optional string sender = 1; + */ + java.lang.String getSender(); + /** + * optional string sender = 1; + */ + com.google.protobuf.ByteString + getSenderBytes(); + + // optional uint32 senderDevice = 2; + /** + * optional uint32 senderDevice = 2; + */ + boolean hasSenderDevice(); + /** + * optional uint32 senderDevice = 2; + */ + int getSenderDevice(); + + // optional fixed64 expires = 3; + /** + * optional fixed64 expires = 3; + */ + boolean hasExpires(); + /** + * optional fixed64 expires = 3; + */ + long getExpires(); + + // optional bytes identityKey = 4; + /** + * optional bytes identityKey = 4; + */ + boolean hasIdentityKey(); + /** + * optional bytes identityKey = 4; + */ + com.google.protobuf.ByteString getIdentityKey(); + + // optional .textsecure.ServerCertificate signer = 5; + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + boolean hasSigner(); + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate getSigner(); + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder getSignerOrBuilder(); + } + /** + * Protobuf type {@code textsecure.SenderCertificate.Certificate} + */ + public static final class Certificate extends + com.google.protobuf.GeneratedMessage + implements CertificateOrBuilder { + // Use Certificate.newBuilder() to construct. + private Certificate(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Certificate(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Certificate defaultInstance; + public static Certificate getDefaultInstance() { + return defaultInstance; + } + + public Certificate getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Certificate( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + sender_ = input.readBytes(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + senderDevice_ = input.readUInt32(); + break; + } + case 25: { + bitField0_ |= 0x00000004; + expires_ = input.readFixed64(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + identityKey_ = input.readBytes(); + break; + } + case 42: { + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder subBuilder = null; + if (((bitField0_ & 0x00000010) == 0x00000010)) { + subBuilder = signer_.toBuilder(); + } + signer_ = input.readMessage(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(signer_); + signer_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000010; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_Certificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_Certificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Certificate parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Certificate(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional string sender = 1; + public static final int SENDER_FIELD_NUMBER = 1; + private java.lang.Object sender_; + /** + * optional string sender = 1; + */ + public boolean hasSender() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string sender = 1; + */ + public java.lang.String getSender() { + java.lang.Object ref = sender_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + sender_ = s; + } + return s; + } + } + /** + * optional string sender = 1; + */ + public com.google.protobuf.ByteString + getSenderBytes() { + java.lang.Object ref = sender_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sender_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional uint32 senderDevice = 2; + public static final int SENDERDEVICE_FIELD_NUMBER = 2; + private int senderDevice_; + /** + * optional uint32 senderDevice = 2; + */ + public boolean hasSenderDevice() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint32 senderDevice = 2; + */ + public int getSenderDevice() { + return senderDevice_; + } + + // optional fixed64 expires = 3; + public static final int EXPIRES_FIELD_NUMBER = 3; + private long expires_; + /** + * optional fixed64 expires = 3; + */ + public boolean hasExpires() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional fixed64 expires = 3; + */ + public long getExpires() { + return expires_; + } + + // optional bytes identityKey = 4; + public static final int IDENTITYKEY_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString identityKey_; + /** + * optional bytes identityKey = 4; + */ + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bytes identityKey = 4; + */ + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + + // optional .textsecure.ServerCertificate signer = 5; + public static final int SIGNER_FIELD_NUMBER = 5; + private org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate signer_; + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public boolean hasSigner() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate getSigner() { + return signer_; + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder getSignerOrBuilder() { + return signer_; + } + + private void initFields() { + sender_ = ""; + senderDevice_ = 0; + expires_ = 0L; + identityKey_ = com.google.protobuf.ByteString.EMPTY; + signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getSenderBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt32(2, senderDevice_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeFixed64(3, expires_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, identityKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeMessage(5, signer_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, getSenderBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(2, senderDevice_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeFixed64Size(3, expires_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, identityKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, signer_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code textsecure.SenderCertificate.Certificate} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.CertificateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_Certificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_Certificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getSignerFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + sender_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + senderDevice_ = 0; + bitField0_ = (bitField0_ & ~0x00000002); + expires_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + if (signerBuilder_ == null) { + signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); + } else { + signerBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_Certificate_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate result = new org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.sender_ = sender_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.senderDevice_ = senderDevice_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.expires_ = expires_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.identityKey_ = identityKey_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + if (signerBuilder_ == null) { + result.signer_ = signer_; + } else { + result.signer_ = signerBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate.getDefaultInstance()) return this; + if (other.hasSender()) { + bitField0_ |= 0x00000001; + sender_ = other.sender_; + onChanged(); + } + if (other.hasSenderDevice()) { + setSenderDevice(other.getSenderDevice()); + } + if (other.hasExpires()) { + setExpires(other.getExpires()); + } + if (other.hasIdentityKey()) { + setIdentityKey(other.getIdentityKey()); + } + if (other.hasSigner()) { + mergeSigner(other.getSigner()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Certificate) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional string sender = 1; + private java.lang.Object sender_ = ""; + /** + * optional string sender = 1; + */ + public boolean hasSender() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string sender = 1; + */ + public java.lang.String getSender() { + java.lang.Object ref = sender_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + sender_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string sender = 1; + */ + public com.google.protobuf.ByteString + getSenderBytes() { + java.lang.Object ref = sender_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sender_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string sender = 1; + */ + public Builder setSender( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + sender_ = value; + onChanged(); + return this; + } + /** + * optional string sender = 1; + */ + public Builder clearSender() { + bitField0_ = (bitField0_ & ~0x00000001); + sender_ = getDefaultInstance().getSender(); + onChanged(); + return this; + } + /** + * optional string sender = 1; + */ + public Builder setSenderBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + sender_ = value; + onChanged(); + return this; + } + + // optional uint32 senderDevice = 2; + private int senderDevice_ ; + /** + * optional uint32 senderDevice = 2; + */ + public boolean hasSenderDevice() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint32 senderDevice = 2; + */ + public int getSenderDevice() { + return senderDevice_; + } + /** + * optional uint32 senderDevice = 2; + */ + public Builder setSenderDevice(int value) { + bitField0_ |= 0x00000002; + senderDevice_ = value; + onChanged(); + return this; + } + /** + * optional uint32 senderDevice = 2; + */ + public Builder clearSenderDevice() { + bitField0_ = (bitField0_ & ~0x00000002); + senderDevice_ = 0; + onChanged(); + return this; + } + + // optional fixed64 expires = 3; + private long expires_ ; + /** + * optional fixed64 expires = 3; + */ + public boolean hasExpires() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional fixed64 expires = 3; + */ + public long getExpires() { + return expires_; + } + /** + * optional fixed64 expires = 3; + */ + public Builder setExpires(long value) { + bitField0_ |= 0x00000004; + expires_ = value; + onChanged(); + return this; + } + /** + * optional fixed64 expires = 3; + */ + public Builder clearExpires() { + bitField0_ = (bitField0_ & ~0x00000004); + expires_ = 0L; + onChanged(); + return this; + } + + // optional bytes identityKey = 4; + private com.google.protobuf.ByteString identityKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes identityKey = 4; + */ + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bytes identityKey = 4; + */ + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + /** + * optional bytes identityKey = 4; + */ + public Builder setIdentityKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + identityKey_ = value; + onChanged(); + return this; + } + /** + * optional bytes identityKey = 4; + */ + public Builder clearIdentityKey() { + bitField0_ = (bitField0_ & ~0x00000008); + identityKey_ = getDefaultInstance().getIdentityKey(); + onChanged(); + return this; + } + + // optional .textsecure.ServerCertificate signer = 5; + private org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder> signerBuilder_; + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public boolean hasSigner() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate getSigner() { + if (signerBuilder_ == null) { + return signer_; + } else { + return signerBuilder_.getMessage(); + } + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public Builder setSigner(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate value) { + if (signerBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + signer_ = value; + onChanged(); + } else { + signerBuilder_.setMessage(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public Builder setSigner( + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder builderForValue) { + if (signerBuilder_ == null) { + signer_ = builderForValue.build(); + onChanged(); + } else { + signerBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public Builder mergeSigner(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate value) { + if (signerBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010) && + signer_ != org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance()) { + signer_ = + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.newBuilder(signer_).mergeFrom(value).buildPartial(); + } else { + signer_ = value; + } + onChanged(); + } else { + signerBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public Builder clearSigner() { + if (signerBuilder_ == null) { + signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); + onChanged(); + } else { + signerBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder getSignerBuilder() { + bitField0_ |= 0x00000010; + onChanged(); + return getSignerFieldBuilder().getBuilder(); + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder getSignerOrBuilder() { + if (signerBuilder_ != null) { + return signerBuilder_.getMessageOrBuilder(); + } else { + return signer_; + } + } + /** + * optional .textsecure.ServerCertificate signer = 5; + */ + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder> + getSignerFieldBuilder() { + if (signerBuilder_ == null) { + signerBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder, org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificateOrBuilder>( + signer_, + getParentForChildren(), + isClean()); + signer_ = null; + } + return signerBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SenderCertificate.Certificate) + } + + static { + defaultInstance = new Certificate(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SenderCertificate.Certificate) + } + + private int bitField0_; + // optional bytes certificate = 1; + public static final int CERTIFICATE_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString certificate_; + /** + * optional bytes certificate = 1; + */ + public boolean hasCertificate() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes certificate = 1; + */ + public com.google.protobuf.ByteString getCertificate() { + return certificate_; + } + + // optional bytes signature = 2; + public static final int SIGNATURE_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString signature_; + /** + * optional bytes signature = 2; + */ + public boolean hasSignature() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes signature = 2; + */ + public com.google.protobuf.ByteString getSignature() { + return signature_; + } + + private void initFields() { + certificate_ = com.google.protobuf.ByteString.EMPTY; + signature_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, certificate_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, signature_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, certificate_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, signature_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code textsecure.SenderCertificate} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.class, org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.Builder.class); + } + + // Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + certificate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + signature_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_SenderCertificate_descriptor; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate getDefaultInstanceForType() { + return org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.getDefaultInstance(); + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate build() { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate buildPartial() { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate result = new org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.certificate_ = certificate_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.signature_ = signature_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate) { + return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate other) { + if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate.getDefaultInstance()) return this; + if (other.hasCertificate()) { + setCertificate(other.getCertificate()); + } + if (other.hasSignature()) { + setSignature(other.getSignature()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional bytes certificate = 1; + private com.google.protobuf.ByteString certificate_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes certificate = 1; + */ + public boolean hasCertificate() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes certificate = 1; + */ + public com.google.protobuf.ByteString getCertificate() { + return certificate_; + } + /** + * optional bytes certificate = 1; + */ + public Builder setCertificate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + certificate_ = value; + onChanged(); + return this; + } + /** + * optional bytes certificate = 1; + */ + public Builder clearCertificate() { + bitField0_ = (bitField0_ & ~0x00000001); + certificate_ = getDefaultInstance().getCertificate(); + onChanged(); + return this; + } + + // optional bytes signature = 2; + private com.google.protobuf.ByteString signature_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes signature = 2; + */ + public boolean hasSignature() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes signature = 2; + */ + public com.google.protobuf.ByteString getSignature() { + return signature_; + } + /** + * optional bytes signature = 2; + */ + public Builder setSignature(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + signature_ = value; + onChanged(); + return this; + } + /** + * optional bytes signature = 2; + */ + public Builder clearSignature() { + bitField0_ = (bitField0_ & ~0x00000002); + signature_ = getDefaultInstance().getSignature(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SenderCertificate) + } + + static { + defaultInstance = new SenderCertificate(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SenderCertificate) + } + private static com.google.protobuf.Descriptors.Descriptor internal_static_textsecure_Envelope_descriptor; private static @@ -1709,6 +4310,26 @@ public final class MessageProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_ProvisioningUuid_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_ServerCertificate_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_ServerCertificate_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_ServerCertificate_Certificate_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_ServerCertificate_Certificate_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SenderCertificate_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SenderCertificate_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SenderCertificate_Certificate_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SenderCertificate_Certificate_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -1718,16 +4339,25 @@ public final class MessageProtos { descriptor; static { java.lang.String[] descriptorData = { - "\n\020TextSecure.proto\022\ntextsecure\"\372\001\n\010Envel" + + "\n\020TextSecure.proto\022\ntextsecure\"\301\002\n\010Envel" + "ope\022\'\n\004type\030\001 \001(\0162\031.textsecure.Envelope." + "Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevice\030\007 \001" + "(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(\004\022\025\n\r" + - "legacyMessage\030\006 \001(\014\022\017\n\007content\030\010 \001(\014\"U\n\004" + - "Type\022\013\n\007UNKNOWN\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY" + - "_EXCHANGE\020\002\022\021\n\rPREKEY_BUNDLE\020\003\022\013\n\007RECEIP" + - "T\020\005\" \n\020ProvisioningUuid\022\014\n\004uuid\030\001 \001(\tB:\n" + - ")org.whispersystems.textsecuregcm.entiti" + - "esB\rMessageProtos" + "legacyMessage\030\006 \001(\014\022\017\n\007content\030\010 \001(\014\022\022\n\n" + + "serverGuid\030\t \001(\t\022\030\n\020server_timestamp\030\n \001" + + "(\004\"n\n\004Type\022\013\n\007UNKNOWN\020\000\022\016\n\nCIPHERTEXT\020\001\022" + + "\020\n\014KEY_EXCHANGE\020\002\022\021\n\rPREKEY_BUNDLE\020\003\022\013\n\007" + + "RECEIPT\020\005\022\027\n\023UNIDENTIFIED_SENDER\020\006\" \n\020Pr" + + "ovisioningUuid\022\014\n\004uuid\030\001 \001(\t\"c\n\021ServerCe", + "rtificate\022\023\n\013certificate\030\001 \001(\014\022\021\n\tsignat" + + "ure\030\002 \001(\014\032&\n\013Certificate\022\n\n\002id\030\001 \001(\r\022\013\n\003" + + "key\030\002 \001(\014\"\306\001\n\021SenderCertificate\022\023\n\013certi" + + "ficate\030\001 \001(\014\022\021\n\tsignature\030\002 \001(\014\032\210\001\n\013Cert" + + "ificate\022\016\n\006sender\030\001 \001(\t\022\024\n\014senderDevice\030" + + "\002 \001(\r\022\017\n\007expires\030\003 \001(\006\022\023\n\013identityKey\030\004 " + + "\001(\014\022-\n\006signer\030\005 \001(\0132\035.textsecure.ServerC" + + "ertificateB:\n)org.whispersystems.textsec" + + "uregcm.entitiesB\rMessageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -1739,13 +4369,37 @@ public final class MessageProtos { internal_static_textsecure_Envelope_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_Envelope_descriptor, - new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "LegacyMessage", "Content", }); + new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "LegacyMessage", "Content", "ServerGuid", "ServerTimestamp", }); internal_static_textsecure_ProvisioningUuid_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_textsecure_ProvisioningUuid_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_ProvisioningUuid_descriptor, new java.lang.String[] { "Uuid", }); + internal_static_textsecure_ServerCertificate_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_textsecure_ServerCertificate_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_ServerCertificate_descriptor, + new java.lang.String[] { "Certificate", "Signature", }); + internal_static_textsecure_ServerCertificate_Certificate_descriptor = + internal_static_textsecure_ServerCertificate_descriptor.getNestedTypes().get(0); + internal_static_textsecure_ServerCertificate_Certificate_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_ServerCertificate_Certificate_descriptor, + new java.lang.String[] { "Id", "Key", }); + internal_static_textsecure_SenderCertificate_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_textsecure_SenderCertificate_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SenderCertificate_descriptor, + new java.lang.String[] { "Certificate", "Signature", }); + internal_static_textsecure_SenderCertificate_Certificate_descriptor = + internal_static_textsecure_SenderCertificate_descriptor.getNestedTypes().get(0); + internal_static_textsecure_SenderCertificate_Certificate_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SenderCertificate_Certificate_descriptor, + new java.lang.String[] { "Sender", "SenderDevice", "Expires", "IdentityKey", "Signer", }); return null; } }; diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java b/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java index fb0228af2..becf61557 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java @@ -3,6 +3,8 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; + public class OutgoingMessageEntity { @JsonIgnore @@ -11,6 +13,9 @@ public class OutgoingMessageEntity { @JsonIgnore private boolean cached; + @JsonProperty + private UUID guid; + @JsonProperty private int type; @@ -32,21 +37,31 @@ public class OutgoingMessageEntity { @JsonProperty private byte[] content; + @JsonProperty + private long serverTimestamp; + public OutgoingMessageEntity() {} - public OutgoingMessageEntity(long id, boolean cached, int type, String relay, long timestamp, + public OutgoingMessageEntity(long id, boolean cached, + UUID guid, int type, String relay, long timestamp, String source, int sourceDevice, byte[] message, - byte[] content) + byte[] content, long serverTimestamp) { - this.id = id; - this.cached = cached; - this.type = type; - this.relay = relay; - this.timestamp = timestamp; - this.source = source; - this.sourceDevice = sourceDevice; - this.message = message; - this.content = content; + this.id = id; + this.cached = cached; + this.guid = guid; + this.type = type; + this.relay = relay; + this.timestamp = timestamp; + this.source = source; + this.sourceDevice = sourceDevice; + this.message = message; + this.content = content; + this.serverTimestamp = serverTimestamp; + } + + public UUID getGuid() { + return guid; } public int getType() { @@ -87,4 +102,8 @@ public class OutgoingMessageEntity { return cached; } + public long getServerTimestamp() { + return serverTimestamp; + } + } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java b/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java index 72a49dfab..9dec49ae1 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/Profile.java @@ -18,12 +18,20 @@ public class Profile { @JsonProperty private String avatar; + @JsonProperty + private String unidentifiedAccess; + + @JsonProperty + private boolean unrestrictedUnidentifiedAccess; + public Profile() {} - public Profile(String name, String avatar, String identityKey) { - this.name = name; - this.avatar = avatar; - this.identityKey = identityKey; + public Profile(String name, String avatar, String identityKey, String unidentifiedAccess, boolean unrestrictedUnidentifiedAccess) { + this.name = name; + this.avatar = avatar; + this.identityKey = identityKey; + this.unidentifiedAccess = unidentifiedAccess; + this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; } @VisibleForTesting @@ -41,4 +49,14 @@ public class Profile { return avatar; } + @VisibleForTesting + public String getUnidentifiedAccess() { + return unidentifiedAccess; + } + + @VisibleForTesting + public boolean isUnrestrictedUnidentifiedAccess() { + return unrestrictedUnidentifiedAccess; + } + } diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java deleted file mode 100644 index 6a2493ef4..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClient.java +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.federation; - - -import com.google.common.base.Optional; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.bouncycastle.openssl.PEMReader; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.client.RequestEntityProcessing; -import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.entities.AccountCount; -import org.whispersystems.textsecuregcm.entities.AttachmentUri; -import org.whispersystems.textsecuregcm.entities.ClientContact; -import org.whispersystems.textsecuregcm.entities.ClientContacts; -import org.whispersystems.textsecuregcm.entities.IncomingMessageList; -import org.whispersystems.textsecuregcm.entities.PreKeyResponse; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import javax.ws.rs.ProcessingException; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.List; - -import io.dropwizard.client.JerseyClientBuilder; -import io.dropwizard.client.JerseyClientConfiguration; -import io.dropwizard.setup.Environment; - -public class FederatedClient { - - private final Logger logger = LoggerFactory.getLogger(FederatedClient.class); - - private static final String USER_COUNT_PATH = "/v1/federation/user_count"; - private static final String USER_TOKENS_PATH = "/v1/federation/user_tokens/%d"; - private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%d/%s"; - private static final String PREKEY_PATH_DEVICE_V1 = "/v1/federation/key/%s/%s"; - private static final String PREKEY_PATH_DEVICE_V2 = "/v2/federation/key/%s/%s"; - private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d"; - private static final String RECEIPT_PATH = "/v1/receipt/%s/%d/%s/%d"; - - private final FederatedPeer peer; - private final Client client; - - public FederatedClient(Environment environment, JerseyClientConfiguration configuration, - String federationName, FederatedPeer peer) - throws IOException - { - try { - this.client = createClient(environment, configuration, federationName, peer); - this.peer = peer; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (KeyStoreException | KeyManagementException | CertificateException e) { - throw new IOException(e); - } - } - - public URL getSignedAttachmentUri(long attachmentId) throws IOException { - try { - AttachmentUri response = client.target(peer.getUrl()) - .path(String.format(ATTACHMENT_URI_PATH, attachmentId)) - .request() - .accept(MediaType.APPLICATION_JSON_TYPE) - .get(AttachmentUri.class); - - return response.getLocation(); - } catch (ProcessingException e) { - logger.warn("Bad URI", e); - throw new IOException(e); - } - } - - public Optional getKeysV2(String destination, String device) { - try { - PreKeyResponse response = client.target(peer.getUrl()) - .path(String.format(PREKEY_PATH_DEVICE_V2, destination, device)) - .request() - .accept(MediaType.APPLICATION_JSON_TYPE) - .get(PreKeyResponse.class); - - return Optional.of(response); - } catch (ProcessingException e) { - logger.warn("PreKey", e); - return Optional.absent(); - } - } - - public int getUserCount() { - try { - AccountCount count = client.target(peer.getUrl()) - .path(USER_COUNT_PATH) - .request() - .accept(MediaType.APPLICATION_JSON_TYPE) - .get(AccountCount.class); - - return count.getCount(); - } catch (ProcessingException e) { - logger.warn("User Count", e); - return 0; - } - } - - public List getUserTokens(int offset) { - try { - ClientContacts contacts = client.target(peer.getUrl()) - .path(String.format(USER_TOKENS_PATH, offset)) - .request() - .accept(MediaType.APPLICATION_JSON_TYPE) - .get(ClientContacts.class); - - return contacts.getContacts(); - } catch (ProcessingException e) { - logger.warn("User Tokens", e); - return null; - } - } - - public void sendMessages(String source, long sourceDeviceId, String destination, IncomingMessageList messages) - throws IOException - { - Response response = null; - - try { - response = client.target(peer.getUrl()) - .path(String.format(RELAY_MESSAGE_PATH, source, sourceDeviceId, destination)) - .request() - .put(Entity.json(messages)); - - if (response.getStatus() != 200 && response.getStatus() != 204) { - if (response.getStatus() == 411) throw new WebApplicationException(Response.status(413).build()); - else throw new WebApplicationException(Response.status(response.getStatusInfo()).build()); - } - - } catch (ProcessingException e) { - logger.warn("sendMessage", e); - throw new IOException(e); - } finally { - if (response != null) response.close(); - } - } - - public void sendDeliveryReceipt(String source, long sourceDeviceId, String destination, long messageId) - throws IOException - { - Response response = null; - - try { - response = client.target(peer.getUrl()) - .path(String.format(RECEIPT_PATH, source, sourceDeviceId, destination, messageId)) - .request() - .property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true) - .put(Entity.entity("", MediaType.APPLICATION_JSON_TYPE)); - - if (response.getStatus() != 200 && response.getStatus() != 204) { - if (response.getStatus() == 411) throw new WebApplicationException(Response.status(413).build()); - else throw new WebApplicationException(Response.status(response.getStatusInfo()).build()); - } - } catch (ProcessingException e) { - logger.warn("sendMessage", e); - throw new IOException(e); - } finally { - if (response != null) response.close(); - } - } - - private Client createClient(Environment environment, JerseyClientConfiguration configuration, - String federationName, FederatedPeer peer) - throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, CertificateException - { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); - trustManagerFactory.init(initializeTrustStore(peer.getName(), peer.getCertificate())); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); - - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier()); - Registry registry = RegistryBuilder.create().register("https", sslConnectionSocketFactory).build(); - - Client client = new JerseyClientBuilder(environment).using(configuration) - .using(registry) - .build("FederatedClient"); - - client.property(ClientProperties.CONNECT_TIMEOUT, 5000); - client.property(ClientProperties.READ_TIMEOUT, 10000); - client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); - client.register(HttpAuthenticationFeature.basic(federationName, peer.getAuthenticationToken())); - - return client; - } - - private KeyStore initializeTrustStore(String name, String pemCertificate) - throws CertificateException - { - try { - PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemCertificate.getBytes()))); - X509Certificate certificate = (X509Certificate) reader.readObject(); - - if (certificate == null) { - throw new CertificateException("No certificate found in parsing!"); - } - - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null); - keyStore.setCertificateEntry(name, certificate); - - return keyStore; - } catch (IOException | KeyStoreException e) { - throw new CertificateException(e); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - public String getPeerName() { - return peer.getName(); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClientManager.java b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClientManager.java deleted file mode 100644 index 41e57c37e..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedClientManager.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.federation; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; - -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -import io.dropwizard.client.JerseyClientConfiguration; -import io.dropwizard.setup.Environment; - -public class FederatedClientManager { - - private final Logger logger = LoggerFactory.getLogger(FederatedClientManager.class); - - private final HashMap clients = new HashMap<>(); - - public FederatedClientManager(Environment environment, - JerseyClientConfiguration clientConfig, - FederationConfiguration federationConfig) - throws IOException - { - List peers = federationConfig.getPeers(); - String identity = federationConfig.getName(); - - if (peers != null) { - for (FederatedPeer peer : peers) { - logger.info("Adding peer: " + peer.getName()); - clients.put(peer.getName(), new FederatedClient(environment, clientConfig, identity, peer)); - } - } - } - - public FederatedClient getClient(String name) throws NoSuchPeerException { - FederatedClient client = clients.get(name); - - if (client == null) { - throw new NoSuchPeerException(name); - } - - return client; - } - - public List getClients() { - return new LinkedList<>(clients.values()); - } - -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedPeer.java b/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedPeer.java deleted file mode 100644 index 197820638..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/FederatedPeer.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.federation; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotEmpty; -import org.hibernate.validator.constraints.URL; - -public class FederatedPeer { - - @NotEmpty - @JsonProperty - private String name; - - @NotEmpty - @URL - @JsonProperty - private String url; - - @NotEmpty - @JsonProperty - private String authenticationToken; - - @NotEmpty - @JsonProperty - private String certificate; - - public FederatedPeer() {} - - public FederatedPeer(String name, String url, String authenticationToken, String certificate) { - this.name = name; - this.url = url; - this.authenticationToken = authenticationToken; - this.certificate = certificate; - } - - public String getUrl() { - return url; - } - - public String getName() { - return name; - } - - public String getAuthenticationToken() { - return authenticationToken; - } - - public String getCertificate() { - return certificate; - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/NoSuchPeerException.java b/src/main/java/org/whispersystems/textsecuregcm/federation/NoSuchPeerException.java deleted file mode 100644 index ad23809ba..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/NoSuchPeerException.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.federation; - - -public class NoSuchPeerException extends Exception { - public NoSuchPeerException(String name) { - super(name); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java b/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java deleted file mode 100644 index 509ac88f9..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/federation/NonLimitedAccount.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.whispersystems.textsecuregcm.federation; - - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.base.Optional; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; - -public class NonLimitedAccount extends Account { - - @JsonIgnore - private final String number; - - @JsonIgnore - private final String relay; - - @JsonIgnore - private final long deviceId; - - public NonLimitedAccount(String number, long deviceId, String relay) { - this.number = number; - this.deviceId = deviceId; - this.relay = relay; - } - - @Override - public String getNumber() { - return number; - } - - @Override - public boolean isRateLimited() { - return false; - } - - @Override - public Optional getRelay() { - return Optional.of(relay); - } - - @Override - public Optional getAuthenticatedDevice() { - return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis(), false, false, "NA")); - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java index d7d2775f6..4905c677e 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java @@ -20,7 +20,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -37,6 +36,7 @@ import org.whispersystems.textsecuregcm.util.Constants; import javax.annotation.Nullable; import java.io.IOException; import java.util.Date; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/ApnFallbackManager.java b/src/main/java/org/whispersystems/textsecuregcm/push/ApnFallbackManager.java index 929f06c75..7534a491e 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/ApnFallbackManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/ApnFallbackManager.java @@ -4,7 +4,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.RatioGauge; import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.redis.LuaScript; @@ -21,6 +20,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import static com.codahale.metrics.MetricRegistry.name; @@ -28,7 +28,6 @@ import io.dropwizard.lifecycle.Managed; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisException; -@SuppressWarnings("Guava") public class ApnFallbackManager implements Managed, Runnable { private static final Logger logger = LoggerFactory.getLogger(ApnFallbackManager.class); @@ -167,19 +166,19 @@ public class ApnFallbackManager implements Managed, Runnable { private Optional> getSeparated(String encoded) { try { - if (encoded == null) return Optional.absent(); + if (encoded == null) return Optional.empty(); String[] parts = encoded.split(":"); if (parts.length != 2) { logger.warn("Got strange encoded number: " + encoded); - return Optional.absent(); + return Optional.empty(); } return Optional.of(new Pair<>(parts[0], Long.parseLong(parts[1]))); } catch (NumberFormatException e) { logger.warn("Badly formatted: " + encoded, e); - return Optional.absent(); + return Optional.empty(); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java index 92cf43c94..cc69aa8b4 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java @@ -4,7 +4,6 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -22,6 +21,7 @@ import org.whispersystems.textsecuregcm.util.Constants; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -174,7 +174,7 @@ public class GCMSender implements Managed { } } - return Optional.absent(); + return Optional.empty(); } private void markOutboundMeter(String key) { diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/ReceiptSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/ReceiptSender.java index f87ee86e5..f1ab1ad71 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/ReceiptSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/ReceiptSender.java @@ -1,64 +1,33 @@ package org.whispersystems.textsecuregcm.push; -import com.google.common.base.Optional; import org.whispersystems.textsecuregcm.controllers.NoSuchUserException; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; -import java.io.IOException; +import java.util.Optional; import java.util.Set; public class ReceiptSender { - private final PushSender pushSender; - private final FederatedClientManager federatedClientManager; - private final AccountsManager accountManager; + private final PushSender pushSender; + private final AccountsManager accountManager; - public ReceiptSender(AccountsManager accountManager, - PushSender pushSender, - FederatedClientManager federatedClientManager) + public ReceiptSender(AccountsManager accountManager, + PushSender pushSender) { - this.federatedClientManager = federatedClientManager; - this.accountManager = accountManager; - this.pushSender = pushSender; + this.accountManager = accountManager; + this.pushSender = pushSender; } - public void sendReceipt(Account source, String destination, - long messageId, Optional relay) - throws IOException, NoSuchUserException, - NotPushRegisteredException, TransientPushFailureException + public void sendReceipt(Account source, String destination, long messageId) + throws NoSuchUserException, NotPushRegisteredException { if (source.getNumber().equals(destination)) { return; } - if (relay.isPresent() && !relay.get().isEmpty()) { - sendRelayedReceipt(source, destination, messageId, relay.get()); - } else { - sendDirectReceipt(source, destination, messageId); - } - } - - private void sendRelayedReceipt(Account source, String destination, long messageId, String relay) - throws NoSuchUserException, IOException - { - try { - federatedClientManager.getClient(relay) - .sendDeliveryReceipt(source.getNumber(), - source.getAuthenticatedDevice().get().getId(), - destination, messageId); - } catch (NoSuchPeerException e) { - throw new NoSuchUserException(e); - } - } - - private void sendDirectReceipt(Account source, String destination, long messageId) - throws NotPushRegisteredException, TransientPushFailureException, NoSuchUserException - { Account destinationAccount = getDestinationAccount(destination); Set destinationDevices = destinationAccount.getDevices(); Envelope.Builder message = Envelope.newBuilder() diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java index ade564554..8b62e0f53 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java @@ -17,13 +17,14 @@ package org.whispersystems.textsecuregcm.sms; -import com.google.common.base.Optional; import com.twilio.sdk.TwilioRestException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Optional; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class SmsSender { static final String SMS_IOS_VERIFICATION_TEXT = "Your Signal verification code: %s\n\nOr tap: sgnl://verify/%s"; diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java index 17c695ac8..481965d83 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java @@ -19,7 +19,6 @@ package org.whispersystems.textsecuregcm.sms; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; import com.twilio.sdk.TwilioRestClient; import com.twilio.sdk.TwilioRestException; import com.twilio.sdk.resource.factory.CallFactory; @@ -36,10 +35,12 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Random; import static com.codahale.metrics.MetricRegistry.name; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class TwilioSmsSender { public static final String SAY_TWIML = "\n" + @@ -81,7 +82,7 @@ public class TwilioSmsSender { messageParams.add(new BasicNameValuePair("MessagingServiceSid", messagingServicesId)); } - if ("ios".equals(clientType.orNull())) { + if ("ios".equals(clientType.orElse(null))) { messageParams.add(new BasicNameValuePair("Body", String.format(SmsSender.SMS_IOS_VERIFICATION_TEXT, verificationCode, verificationCode))); } else { messageParams.add(new BasicNameValuePair("Body", String.format(SmsSender.SMS_VERIFICATION_TEXT, verificationCode))); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 66b8279ee..901c06b37 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -20,13 +20,15 @@ package org.whispersystems.textsecuregcm.storage; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; +import javax.security.auth.Subject; +import java.security.Principal; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; -public class Account { +public class Account implements Principal { public static final int MEMCACHE_VERION = 5; @@ -51,19 +53,26 @@ public class Account { @JsonProperty private String pin; + @JsonProperty("uak") + private byte[] unidentifiedAccessKey; + + @JsonProperty("uua") + private boolean unrestrictedUnidentifiedAccess; + @JsonIgnore private Device authenticatedDevice; public Account() {} @VisibleForTesting - public Account(String number, Set devices) { - this.number = number; - this.devices = devices; + public Account(String number, Set devices, byte[] unidentifiedAccessKey) { + this.number = number; + this.devices = devices; + this.unidentifiedAccessKey = unidentifiedAccessKey; } public Optional getAuthenticatedDevice() { - return Optional.fromNullable(authenticatedDevice); + return Optional.ofNullable(authenticatedDevice); } public void setAuthenticatedDevice(Device device) { @@ -84,7 +93,7 @@ public class Account { } public void removeDevice(long deviceId) { - this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, false, false, "NA")); + this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, "NA", false)); } public Set getDevices() { @@ -102,27 +111,11 @@ public class Account { } } - return Optional.absent(); + return Optional.empty(); } - public boolean isVoiceSupported() { - for (Device device : devices) { - if (device.isActive() && device.isVoiceSupported()) { - return true; - } - } - - return false; - } - - public boolean isVideoSupported() { - for (Device device : devices) { - if (device.isActive() && device.isVideoSupported()) { - return true; - } - } - - return false; + public boolean isUnauthenticatedDeliverySupported() { + return devices.stream().filter(Device::isActive).allMatch(Device::isUnauthenticatedDeliverySupported); } public boolean isActive() { @@ -161,7 +154,7 @@ public class Account { } public Optional getRelay() { - return Optional.absent(); + return Optional.empty(); } public void setIdentityKey(String identityKey) { @@ -184,11 +177,11 @@ public class Account { return lastSeen; } - public String getName() { + public String getProfileName() { return name; } - public void setName(String name) { + public void setProfileName(String name) { this.name = name; } @@ -209,10 +202,40 @@ public class Account { } public Optional getPin() { - return Optional.fromNullable(pin); + return Optional.ofNullable(pin); } public void setPin(String pin) { this.pin = pin; } + + public Optional getUnidentifiedAccessKey() { + return Optional.ofNullable(unidentifiedAccessKey); + } + + public void setUnidentifiedAccessKey(byte[] unidentifiedAccessKey) { + this.unidentifiedAccessKey = unidentifiedAccessKey; + } + + public boolean isUnrestrictedUnidentifiedAccess() { + return unrestrictedUnidentifiedAccess; + } + + public void setUnrestrictedUnidentifiedAccess(boolean unrestrictedUnidentifiedAccess) { + this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; + } + + // Principal implementation + + @Override + @JsonIgnore + public String getName() { + return null; + } + + @Override + @JsonIgnore + public boolean implies(Subject subject) { + return false; + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 75bfde110..66a7bd648 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -19,7 +19,6 @@ package org.whispersystems.textsecuregcm.storage; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; @@ -30,6 +29,7 @@ import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Optional; import redis.clients.jedis.Jedis; @@ -79,7 +79,7 @@ public class AccountsManager { Optional account = memcacheGet(number); if (!account.isPresent()) { - account = Optional.fromNullable(accounts.get(number)); + account = Optional.ofNullable(accounts.get(number)); if (account.isPresent()) { memcacheSet(number, account.get()); @@ -99,7 +99,7 @@ public class AccountsManager { private void updateDirectory(Account account) { if (account.isActive()) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); + ClientContact clientContact = new ClientContact(token, null, true, true); directory.add(clientContact); } else { directory.remove(account.getNumber()); @@ -123,10 +123,10 @@ public class AccountsManager { String json = jedis.get(getKey(number)); if (json != null) return Optional.of(mapper.readValue(json, Account.class)); - else return Optional.absent(); + else return Optional.empty(); } catch (IOException e) { logger.warn("AccountsManager", "Deserialization error", e); - return Optional.absent(); + return Optional.empty(); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java index 6e8402418..04d0adff7 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -70,40 +70,36 @@ public class Device { @JsonProperty private long created; - @JsonProperty - private boolean voice; - - @JsonProperty - private boolean video; - @JsonProperty private String userAgent; + @JsonProperty + private boolean unauthenticatedDelivery; + public Device() {} public Device(long id, String name, String authToken, String salt, String signalingKey, String gcmId, String apnId, String voipApnId, boolean fetchesMessages, int registrationId, SignedPreKey signedPreKey, - long lastSeen, long created, boolean voice, boolean video, - String userAgent) + long lastSeen, long created, String userAgent, + boolean unauthenticatedDelivery) { - this.id = id; - this.name = name; - this.authToken = authToken; - this.salt = salt; - this.signalingKey = signalingKey; - this.gcmId = gcmId; - this.apnId = apnId; - this.voipApnId = voipApnId; - this.fetchesMessages = fetchesMessages; - this.registrationId = registrationId; - this.signedPreKey = signedPreKey; - this.lastSeen = lastSeen; - this.created = created; - this.voice = voice; - this.video = video; - this.userAgent = userAgent; + this.id = id; + this.name = name; + this.authToken = authToken; + this.salt = salt; + this.signalingKey = signalingKey; + this.gcmId = gcmId; + this.apnId = apnId; + this.voipApnId = voipApnId; + this.fetchesMessages = fetchesMessages; + this.registrationId = registrationId; + this.signedPreKey = signedPreKey; + this.lastSeen = lastSeen; + this.created = created; + this.userAgent = userAgent; + this.unauthenticatedDelivery = unauthenticatedDelivery; } public String getApnId() { @@ -170,20 +166,12 @@ public class Device { this.name = name; } - public boolean isVoiceSupported() { - return voice; + public boolean isUnauthenticatedDeliverySupported() { + return unauthenticatedDelivery; } - public void setVoiceSupported(boolean voice) { - this.voice = voice; - } - - public boolean isVideoSupported() { - return video; - } - - public void setVideoSupported(boolean video) { - this.video = video; + public void setUnauthenticatedDeliverySupported(boolean unauthenticatedDelivery) { + this.unauthenticatedDelivery = unauthenticatedDelivery; } public void setAuthenticationCredentials(AuthenticationCredentials credentials) { diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java index cff2024af..36ae2b83c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2013 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -20,7 +20,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; @@ -32,6 +31,7 @@ import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import redis.clients.jedis.Jedis; import redis.clients.jedis.Pipeline; @@ -102,14 +102,14 @@ public class DirectoryManager { byte[] result = jedis.hget(DIRECTORY_KEY, token); if (result == null) { - return Optional.absent(); + return Optional.empty(); } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice, tokenValue.video)); } catch (IOException e) { logger.warn("JSON Error", e); - return Optional.absent(); + return Optional.empty(); } } @@ -205,7 +205,7 @@ public class DirectoryManager { byte[] result = response.get(); if (result == null) { - return Optional.absent(); + return Optional.empty(); } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java index 75b0f812a..9d7496012 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2018 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -21,8 +21,6 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.Timer; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import io.dropwizard.lifecycle.Managed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; @@ -37,10 +35,13 @@ import javax.ws.rs.ProcessingException; import java.security.SecureRandom; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.lifecycle.Managed; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class DirectoryReconciler implements Managed, Runnable { private static final Logger logger = LoggerFactory.getLogger(DirectoryReconciler.class); @@ -62,7 +63,6 @@ public class DirectoryReconciler implements Managed, Runnable { private final int chunkSize; private final long chunkIntervalMs; private final String workerId; - private final SecureRandom random; private boolean running; private boolean finished; @@ -72,21 +72,15 @@ public class DirectoryReconciler implements Managed, Runnable { DirectoryManager directoryManager, Accounts accounts, int chunkSize, - long chunkIntervalMs) { + long chunkIntervalMs) + { this.accounts = accounts; this.directoryManager = directoryManager; this.reconciliationClient = reconciliationClient; this.reconciliationCache = reconciliationCache; this.chunkSize = chunkSize; this.chunkIntervalMs = chunkIntervalMs; - this.random = new SecureRandom(); - this.workerId = generateWorkerId(random); - } - - private static String generateWorkerId(SecureRandom random) { - byte[] workerIdBytes = new byte[16]; - random.nextBytes(workerIdBytes); - return Hex.toString(workerIdBytes); + this.workerId = Hex.toString(Util.generateSecretBytes(16)); } @Override @@ -141,6 +135,94 @@ public class DirectoryReconciler implements Managed, Runnable { return intervalMs; } + private boolean processChunk() { + Optional fromNumber = reconciliationCache.getLastNumber(); + List chunkAccounts = readChunk(fromNumber, chunkSize); + + updateDirectoryCache(chunkAccounts); + + DirectoryReconciliationRequest request = createChunkRequest(fromNumber, chunkAccounts); + DirectoryReconciliationResponse sendChunkResponse = sendChunk(request); + + if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.MISSING || request.getToNumber() == null) { + reconciliationCache.clearAccelerate(); + } + + if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.OK) { + reconciliationCache.setLastNumber(Optional.ofNullable(request.getToNumber())); + } else if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.MISSING) { + reconciliationCache.setLastNumber(Optional.empty()); + } + + return sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.OK; + } + + private List readChunk(Optional fromNumber, int chunkSize) { + try (Timer.Context timer = readChunkTimer.time()) { + Optional> chunkAccounts; + + if (fromNumber.isPresent()) { + chunkAccounts = Optional.ofNullable(accounts.getAllFrom(fromNumber.get(), chunkSize)); + } else { + chunkAccounts = Optional.ofNullable(accounts.getAllFrom(chunkSize)); + } + + return chunkAccounts.orElse(Collections.emptyList()); + } + } + + private void updateDirectoryCache(List accounts) { + if (accounts.isEmpty()) { + return; + } + + BatchOperationHandle batchOperation = directoryManager.startBatchOperation(); + + try { + for (Account account : accounts) { + if (account.isActive()) { + byte[] token = Util.getContactToken(account.getNumber()); + ClientContact clientContact = new ClientContact(token, null, true, true); + + directoryManager.add(batchOperation, clientContact); + } else { + directoryManager.remove(batchOperation, account.getNumber()); + } + } + } finally { + directoryManager.stopBatchOperation(batchOperation); + } + } + + private DirectoryReconciliationRequest createChunkRequest(Optional fromNumber, List accounts) { + List numbers = accounts.stream() + .filter(Account::isActive) + .map(Account::getNumber) + .collect(Collectors.toList()); + + Optional toNumber = Optional.empty(); + if (!accounts.isEmpty()) { + toNumber = Optional.of(accounts.get(accounts.size() - 1).getNumber()); + } + + return new DirectoryReconciliationRequest(fromNumber.orElse(null), toNumber.orElse(null), numbers); + } + + private DirectoryReconciliationResponse sendChunk(DirectoryReconciliationRequest request) { + try (Timer.Context timer = sendChunkTimer.time()) { + DirectoryReconciliationResponse response = reconciliationClient.sendChunk(request); + if (response.getStatus() != DirectoryReconciliationResponse.Status.OK) { + sendChunkErrorMeter.mark(); + logger.warn("reconciliation error: " + response.getStatus()); + } + return response; + } catch (ProcessingException ex) { + sendChunkErrorMeter.mark(); + logger.warn("request error: ", ex); + throw new ProcessingException(ex); + } + } + private synchronized boolean sleepWhileRunning(long delayMs) { long startTimeMs = System.currentTimeMillis(); while (running && delayMs > 0) { @@ -162,96 +244,8 @@ public class DirectoryReconciler implements Managed, Runnable { } private long getDelayWithJitter(long delayMs) { - long randomJitterMs = (long) (random.nextDouble() * JITTER_MAX * delayMs); + long randomJitterMs = (long) (new SecureRandom().nextDouble() * JITTER_MAX * delayMs); return delayMs + randomJitterMs; } - private boolean processChunk() { - Optional fromNumber = reconciliationCache.getLastNumber(); - List chunkAccounts = readChunk(fromNumber, chunkSize); - - writeChunktoDirectoryCache(chunkAccounts); - - DirectoryReconciliationRequest request = createChunkRequest(fromNumber, chunkAccounts); - DirectoryReconciliationResponse sendChunkResponse = sendChunk(request); - - if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.MISSING || - request.getToNumber() == null) { - reconciliationCache.clearAccelerate(); - } - - if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.OK) { - reconciliationCache.setLastNumber(Optional.fromNullable(request.getToNumber())); - } else if (sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.MISSING) { - reconciliationCache.setLastNumber(Optional.absent()); - } - - return sendChunkResponse.getStatus() == DirectoryReconciliationResponse.Status.OK; - } - - private List readChunk(Optional fromNumber, int chunkSize) { - try (Timer.Context timer = readChunkTimer.time()) { - Optional> chunkAccounts; - - if (fromNumber.isPresent()) { - chunkAccounts = Optional.fromNullable(accounts.getAllFrom(fromNumber.get(), chunkSize)); - } else { - chunkAccounts = Optional.fromNullable(accounts.getAllFrom(chunkSize)); - } - - return chunkAccounts.or(Collections::emptyList); - } - } - - private void writeChunktoDirectoryCache(List accounts) { - if (accounts.isEmpty()) { - return; - } - - BatchOperationHandle batchOperation = directoryManager.startBatchOperation(); - try { - for (Account account : accounts) { - if (account.isActive()) { - byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported(), account.isVideoSupported()); - - directoryManager.add(batchOperation, clientContact); - } else { - directoryManager.remove(batchOperation, account.getNumber()); - } - } - } finally { - directoryManager.stopBatchOperation(batchOperation); - } - } - - private DirectoryReconciliationRequest createChunkRequest(Optional fromNumber, List accounts) { - List numbers = accounts.stream() - .filter(Account::isActive) - .map(Account::getNumber) - .collect(Collectors.toList()); - - Optional toNumber = Optional.absent(); - if (!accounts.isEmpty()) { - toNumber = Optional.of(accounts.get(accounts.size() - 1).getNumber()); - } - - return new DirectoryReconciliationRequest(fromNumber.orNull(), toNumber.orNull(), numbers); - } - - private DirectoryReconciliationResponse sendChunk(DirectoryReconciliationRequest request) { - try (Timer.Context timer = sendChunkTimer.time()) { - DirectoryReconciliationResponse response = reconciliationClient.sendChunk(request); - if (response.getStatus() != DirectoryReconciliationResponse.Status.OK) { - sendChunkErrorMeter.mark(); - logger.warn("reconciliation error: " + response.getStatus()); - } - return response; - } catch (ProcessingException ex) { - sendChunkErrorMeter.mark(); - logger.warn("request error: ", ex); - throw new ProcessingException(ex); - } - } - } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationCache.java b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationCache.java index a44c0dcff..118b2ea60 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationCache.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationCache.java @@ -16,7 +16,6 @@ */ package org.whispersystems.textsecuregcm.storage; -import com.google.common.base.Optional; import org.whispersystems.textsecuregcm.redis.LuaScript; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import redis.clients.jedis.Jedis; @@ -24,7 +23,9 @@ import redis.clients.jedis.Jedis; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Optional; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class DirectoryReconciliationCache { private static final String ACTIVE_WORKER_KEY = "directory_reconciliation_active_worker"; @@ -62,7 +63,7 @@ public class DirectoryReconciliationCache { public Optional getLastNumber() { try (Jedis jedis = jedisPool.getWriteResource()) { - return Optional.fromNullable(jedis.get(LAST_NUMBER_KEY)); + return Optional.ofNullable(jedis.get(LAST_NUMBER_KEY)); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java index 36a55cc2a..2082cf716 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java @@ -16,7 +16,6 @@ */ package org.whispersystems.textsecuregcm.storage; -import com.google.common.base.Optional; import org.skife.jdbi.v2.SQLStatement; import org.skife.jdbi.v2.StatementContext; import org.skife.jdbi.v2.TransactionIsolationLevel; @@ -41,6 +40,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; +import java.util.Optional; public abstract class Keys { @@ -84,7 +84,7 @@ public abstract class Keys { if (record != null && !record.isLastResort()) { removeKey(record.getId()); } else if (record == null) { - return Optional.absent(); + return Optional.empty(); } List results = new LinkedList<>(); @@ -106,7 +106,7 @@ public abstract class Keys { } if (preKeys != null) return Optional.of(preKeys); - else return Optional.absent(); + else return Optional.empty(); } @SqlUpdate("VACUUM keys") diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java index 1439a34bc..4175b479a 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java @@ -21,15 +21,18 @@ import java.lang.annotation.Target; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import java.util.UUID; public abstract class Messages { static final int RESULT_SET_CHUNK_SIZE = 100; private static final String ID = "id"; + private static final String GUID = "guid"; private static final String TYPE = "type"; private static final String RELAY = "relay"; private static final String TIMESTAMP = "timestamp"; + private static final String SERVER_TIMESTAMP = "server_timestamp"; private static final String SOURCE = "source"; private static final String SOURCE_DEVICE = "source_device"; private static final String DESTINATION = "destination"; @@ -37,11 +40,12 @@ public abstract class Messages { private static final String MESSAGE = "message"; private static final String CONTENT = "content"; - @SqlUpdate("INSERT INTO messages (" + TYPE + ", " + RELAY + ", " + TIMESTAMP + ", " + SOURCE + ", " + SOURCE_DEVICE + ", " + DESTINATION + ", " + DESTINATION_DEVICE + ", " + MESSAGE + ", " + CONTENT + ") " + - "VALUES (:type, :relay, :timestamp, :source, :source_device, :destination, :destination_device, :message, :content)") - abstract void store(@MessageBinder Envelope message, - @Bind("destination") String destination, - @Bind("destination_device") long destinationDevice); + @SqlUpdate("INSERT INTO messages (" + GUID + ", " + TYPE + ", " + RELAY + ", " + TIMESTAMP + ", " + SERVER_TIMESTAMP + ", " + SOURCE + ", " + SOURCE_DEVICE + ", " + DESTINATION + ", " + DESTINATION_DEVICE + ", " + MESSAGE + ", " + CONTENT + ") " + + "VALUES (:guid, :type, :relay, :timestamp, :server_timestamp, :source, :source_device, :destination, :destination_device, :message, :content)") + abstract void store(@Bind("guid") UUID guid, + @MessageBinder Envelope message, + @Bind("destination") String destination, + @Bind("destination_device") long destinationDevice); @Mapper(MessageMapper.class) @SqlQuery("SELECT * FROM messages WHERE " + DESTINATION + " = :destination AND " + DESTINATION_DEVICE + " = :destination_device ORDER BY " + TIMESTAMP + " ASC LIMIT " + RESULT_SET_CHUNK_SIZE) @@ -55,6 +59,10 @@ public abstract class Messages { @Bind("source") String source, @Bind("timestamp") long timestamp); + @Mapper(MessageMapper.class) + @SqlQuery("DELETE FROM messages WHERE "+ ID + " IN (SELECT " + ID + " FROM MESSAGES WHERE " + GUID + " = :guid AND " + DESTINATION + " = :destination ORDER BY " + ID + " LIMIT 1) RETURNING *") + abstract OutgoingMessageEntity remove(@Bind("destination") String destination, @Bind("guid") UUID guid); + @Mapper(MessageMapper.class) @SqlUpdate("DELETE FROM messages WHERE " + ID + " = :id AND " + DESTINATION + " = :destination") abstract void remove(@Bind("destination") String destination, @Bind("id") long id); @@ -79,6 +87,7 @@ public abstract class Messages { int type = resultSet.getInt(TYPE); byte[] legacyMessage = resultSet.getBytes(MESSAGE); + String guid = resultSet.getString(GUID); if (type == Envelope.Type.RECEIPT_VALUE && legacyMessage == null) { /// XXX - REMOVE AFTER 10/01/15 @@ -87,13 +96,15 @@ public abstract class Messages { return new OutgoingMessageEntity(resultSet.getLong(ID), false, + guid == null ? null : UUID.fromString(guid), type, resultSet.getString(RELAY), resultSet.getLong(TIMESTAMP), resultSet.getString(SOURCE), resultSet.getInt(SOURCE_DEVICE), legacyMessage, - resultSet.getBytes(CONTENT)); + resultSet.getBytes(CONTENT), + resultSet.getLong(SERVER_TIMESTAMP)); } } @@ -113,8 +124,9 @@ public abstract class Messages { sql.bind(TYPE, message.getType().getNumber()); sql.bind(RELAY, message.getRelay()); sql.bind(TIMESTAMP, message.getTimestamp()); - sql.bind(SOURCE, message.getSource()); - sql.bind(SOURCE_DEVICE, message.getSourceDevice()); + sql.bind(SERVER_TIMESTAMP, message.getServerTimestamp()); + sql.bind(SOURCE, message.hasSource() ? message.getSource() : null); + sql.bind(SOURCE_DEVICE, message.hasSourceDevice() ? message.getSourceDevice() : null); sql.bind(MESSAGE, message.hasLegacyMessage() ? message.getLegacyMessage().toByteArray() : null); sql.bind(CONTENT, message.hasContent() ? message.getContent().toByteArray() : null); } @@ -122,6 +134,4 @@ public abstract class Messages { } } } - - } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java b/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java index fd3db0678..aef6cbf2b 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java @@ -4,7 +4,6 @@ import com.codahale.metrics.Histogram; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.Timer; -import com.google.common.base.Optional; import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +24,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -43,6 +44,7 @@ public class MessagesCache implements Managed { private static final Timer insertTimer = metricRegistry.timer(name(MessagesCache.class, "insert" )); private static final Timer removeByIdTimer = metricRegistry.timer(name(MessagesCache.class, "removeById" )); private static final Timer removeByNameTimer = metricRegistry.timer(name(MessagesCache.class, "removeByName")); + private static final Timer removeByGuidTimer = metricRegistry.timer(name(MessagesCache.class, "removeByGuid")); private static final Timer getTimer = metricRegistry.timer(name(MessagesCache.class, "get" )); private static final Timer clearAccountTimer = metricRegistry.timer(name(MessagesCache.class, "clearAccount")); private static final Timer clearDeviceTimer = metricRegistry.timer(name(MessagesCache.class, "clearDevice" )); @@ -67,11 +69,13 @@ public class MessagesCache implements Managed { this.delayMinutes = delayMinutes; } - public void insert(String destination, long destinationDevice, Envelope message) { + public void insert(UUID guid, String destination, long destinationDevice, Envelope message) { + message = message.toBuilder().setServerGuid(guid.toString()).build(); + Timer.Context timer = insertTimer.time(); try { - insertOperation.insert(destination, destinationDevice, System.currentTimeMillis(), message); + insertOperation.insert(guid, destination, destinationDevice, System.currentTimeMillis(), message); } finally { timer.stop(); } @@ -103,7 +107,26 @@ public class MessagesCache implements Managed { timer.stop(); } - return Optional.absent(); + return Optional.empty(); + } + + public Optional remove(String destination, long destinationDevice, UUID guid) { + Timer.Context timer = removeByGuidTimer.time(); + + try { + byte[] serialized = removeOperation.remove(destination, destinationDevice, guid); + + if (serialized != null) { + Envelope envelope = Envelope.parseFrom(serialized); + return Optional.of(constructEntityFromEnvelope(0, envelope)); + } + } catch (InvalidProtocolBufferException e) { + logger.warn("Failed to parse envelope", e); + } finally { + timer.stop(); + } + + return Optional.empty(); } public List get(String destination, long destinationDevice, int limit) { @@ -175,13 +198,15 @@ public class MessagesCache implements Managed { private OutgoingMessageEntity constructEntityFromEnvelope(long id, Envelope envelope) { return new OutgoingMessageEntity(id, true, + envelope.hasServerGuid() ? UUID.fromString(envelope.getServerGuid()) : null, envelope.getType().getNumber(), envelope.getRelay(), envelope.getTimestamp(), envelope.getSource(), envelope.getSourceDevice(), envelope.hasLegacyMessage() ? envelope.getLegacyMessage().toByteArray() : null, - envelope.hasContent() ? envelope.getContent().toByteArray() : null); + envelope.hasContent() ? envelope.getContent().toByteArray() : null, + envelope.hasServerTimestamp() ? envelope.getServerTimestamp() : 0); } private static class Key { @@ -247,12 +272,12 @@ public class MessagesCache implements Managed { this.insert = LuaScript.fromResource(jedisPool, "lua/insert_item.lua"); } - public void insert(String destination, long destinationDevice, long timestamp, Envelope message) { + public void insert(UUID guid, String destination, long destinationDevice, long timestamp, Envelope message) { Key key = new Key(destination, destinationDevice); - String sender = message.getSource() + "::" + message.getTimestamp(); + String sender = message.hasSource() ? (message.getSource() + "::" + message.getTimestamp()) : "nil"; List keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex()); - List args = Arrays.asList(message.toByteArray(), String.valueOf(timestamp).getBytes(), sender.getBytes()); + List args = Arrays.asList(message.toByteArray(), String.valueOf(timestamp).getBytes(), sender.getBytes(), guid.toString().getBytes()); insert.execute(keys, args); } @@ -262,11 +287,13 @@ public class MessagesCache implements Managed { private final LuaScript removeById; private final LuaScript removeBySender; + private final LuaScript removeByGuid; private final LuaScript removeQueue; RemoveOperation(ReplicatedJedisPool jedisPool) throws IOException { this.removeById = LuaScript.fromResource(jedisPool, "lua/remove_item_by_id.lua" ); this.removeBySender = LuaScript.fromResource(jedisPool, "lua/remove_item_by_sender.lua"); + this.removeByGuid = LuaScript.fromResource(jedisPool, "lua/remove_item_by_guid.lua" ); this.removeQueue = LuaScript.fromResource(jedisPool, "lua/remove_queue.lua" ); } @@ -289,6 +316,15 @@ public class MessagesCache implements Managed { return (byte[])this.removeBySender.execute(keys, args); } + public byte[] remove(String destination, long destinationDevice, UUID guid) { + Key key = new Key(destination, destinationDevice); + + List keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex()); + List args = Collections.singletonList(guid.toString().getBytes()); + + return (byte[])this.removeByGuid.execute(keys, args); + } + public void clear(String destination, long deviceId) { Key key = new Key(destination, deviceId); @@ -445,8 +481,11 @@ public class MessagesCache implements Managed { private void persistMessage(Key key, long score, byte[] message) { try { Envelope envelope = Envelope.parseFrom(message); - database.store(envelope, key.getAddress(), key.getDeviceId()); + UUID guid = envelope.hasServerGuid() ? UUID.fromString(envelope.getServerGuid()) : null; + envelope = envelope.toBuilder().clearServerGuid().build(); + + database.store(guid, envelope, key.getAddress(), key.getDeviceId()); } catch (InvalidProtocolBufferException e) { logger.error("Error parsing envelope", e); } @@ -464,9 +503,7 @@ public class MessagesCache implements Managed { } } - private void notifyClients(AccountsManager accountsManager, PubSubManager pubSubManager, PushSender pushSender, Key key) - throws IOException - { + private void notifyClients(AccountsManager accountsManager, PubSubManager pubSubManager, PushSender pushSender, Key key) { Timer.Context timer = notifyTimer.time(); try { diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesManager.java index 228885e7a..d34d80444 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesManager.java @@ -4,13 +4,14 @@ package org.whispersystems.textsecuregcm.storage; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.util.Constants; import java.util.List; +import java.util.Optional; +import java.util.UUID; import static com.codahale.metrics.MetricRegistry.name; @@ -21,6 +22,9 @@ public class MessagesManager { private static final Meter cacheMissByIdMeter = metricRegistry.meter(name(MessagesManager.class, "cacheMissById" )); private static final Meter cacheHitByNameMeter = metricRegistry.meter(name(MessagesManager.class, "cacheHitByName" )); private static final Meter cacheMissByNameMeter = metricRegistry.meter(name(MessagesManager.class, "cacheMissByName")); + private static final Meter cacheHitByGuidMeter = metricRegistry.meter(name(MessagesManager.class, "cacheHitByGuid" )); + private static final Meter cacheMissByGuidMeter = metricRegistry.meter(name(MessagesManager.class, "cacheMissByGuid")); + private final Messages messages; private final MessagesCache messagesCache; @@ -31,7 +35,8 @@ public class MessagesManager { } public void insert(String destination, long destinationDevice, Envelope message) { - messagesCache.insert(destination, destinationDevice, message); + UUID guid = UUID.randomUUID(); + messagesCache.insert(guid, destination, destinationDevice, message); } public OutgoingMessageEntityList getMessagesForDevice(String destination, long destinationDevice) { @@ -59,7 +64,7 @@ public class MessagesManager { Optional removed = this.messagesCache.remove(destination, destinationDevice, source, timestamp); if (!removed.isPresent()) { - removed = Optional.fromNullable(this.messages.remove(destination, destinationDevice, source, timestamp)); + removed = Optional.ofNullable(this.messages.remove(destination, destinationDevice, source, timestamp)); cacheMissByNameMeter.mark(); } else { cacheHitByNameMeter.mark(); @@ -68,6 +73,19 @@ public class MessagesManager { return removed; } + public Optional delete(String destination, long deviceId, UUID guid) { + Optional removed = this.messagesCache.remove(destination, deviceId, guid); + + if (!removed.isPresent()) { + removed = Optional.ofNullable(this.messages.remove(destination, guid)); + cacheMissByGuidMeter.mark(); + } else { + cacheHitByGuidMeter.mark(); + } + + return removed; + } + public void delete(String destination, long deviceId, long id, boolean cached) { if (cached) { this.messagesCache.remove(destination, deviceId, id); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java index a271bdba2..0963a0214 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/PendingAccountsManager.java @@ -18,7 +18,6 @@ package org.whispersystems.textsecuregcm.storage; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; @@ -26,6 +25,7 @@ import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; +import java.util.Optional; import redis.clients.jedis.Jedis; @@ -60,7 +60,7 @@ public class PendingAccountsManager { Optional code = memcacheGet(number); if (!code.isPresent()) { - code = Optional.fromNullable(pendingAccounts.getCodeForNumber(number)); + code = Optional.ofNullable(pendingAccounts.getCodeForNumber(number)); if (code.isPresent()) { memcacheSet(number, code.get()); @@ -82,11 +82,11 @@ public class PendingAccountsManager { try (Jedis jedis = cacheClient.getReadResource()) { String json = jedis.get(CACHE_PREFIX + number); - if (json == null) return Optional.absent(); + if (json == null) return Optional.empty(); else return Optional.of(mapper.readValue(json, StoredVerificationCode.class)); } catch (IOException e) { logger.warn("Error deserializing value...", e); - return Optional.absent(); + return Optional.empty(); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java index 0c131f837..edd594073 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/PendingDevicesManager.java @@ -18,7 +18,6 @@ package org.whispersystems.textsecuregcm.storage; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; @@ -26,6 +25,7 @@ import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; +import java.util.Optional; import redis.clients.jedis.Jedis; @@ -59,7 +59,7 @@ public class PendingDevicesManager { Optional code = memcacheGet(number); if (!code.isPresent()) { - code = Optional.fromNullable(pendingDevices.getCodeForNumber(number)); + code = Optional.ofNullable(pendingDevices.getCodeForNumber(number)); if (code.isPresent()) { memcacheSet(number, code.get()); @@ -81,11 +81,11 @@ public class PendingDevicesManager { try (Jedis jedis = cacheClient.getReadResource()) { String json = jedis.get(CACHE_PREFIX + number); - if (json == null) return Optional.absent(); + if (json == null) return Optional.empty(); else return Optional.of(mapper.readValue(json, StoredVerificationCode.class)); } catch (IOException e) { logger.warn("Could not parse pending device stored verification json"); - return Optional.absent(); + return Optional.empty(); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/ByteUtil.java b/src/main/java/org/whispersystems/textsecuregcm/util/ByteUtil.java new file mode 100644 index 000000000..fa372630e --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/util/ByteUtil.java @@ -0,0 +1,21 @@ +package org.whispersystems.textsecuregcm.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ByteUtil { + + public static byte[] combine(byte[]... elements) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + for (byte[] element : elements) { + baos.write(element); + } + + return baos.toByteArray(); + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/Util.java b/src/main/java/org/whispersystems/textsecuregcm/util/Util.java index 849e83083..5d4e60afc 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/util/Util.java +++ b/src/main/java/org/whispersystems/textsecuregcm/util/Util.java @@ -20,6 +20,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -120,6 +121,12 @@ public class Util { return parts; } + public static byte[] generateSecretBytes(int size) { + byte[] data = new byte[size]; + new SecureRandom().nextBytes(data); + return data; + } + public static void sleep(long i) { try { Thread.sleep(i); diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java index b2864f696..083b5b2d4 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java @@ -25,9 +25,10 @@ import static com.codahale.metrics.MetricRegistry.name; public class AuthenticatedConnectListener implements WebSocketConnectListener { - private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class); - private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private static final Timer durationTimer = metricRegistry.timer(name(WebSocketConnection.class, "connected_duration")); + private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class); + private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private static final Timer durationTimer = metricRegistry.timer(name(WebSocketConnection.class, "connected_duration" )); + private static final Timer unauthenticatedDurationTimer = metricRegistry.timer(name(WebSocketConnection.class, "unauthenticated_connection_duration")); private final PushSender pushSender; private final ReceiptSender receiptSender; @@ -50,29 +51,34 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { @Override public void onWebSocketConnect(WebSocketSessionContext context) { - final Account account = context.getAuthenticated(Account.class); - final Device device = account.getAuthenticatedDevice().get(); - final String connectionId = String.valueOf(new SecureRandom().nextLong()); - final Timer.Context timer = durationTimer.time(); - final WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId()); - final WebSocketConnection connection = new WebSocketConnection(pushSender, receiptSender, - messagesManager, account, device, - context.getClient(), connectionId); - final PubSubMessage connectMessage = PubSubMessage.newBuilder().setType(PubSubMessage.Type.CONNECTED) - .setContent(ByteString.copyFrom(connectionId.getBytes())) - .build(); + if (context.getAuthenticated() != null) { + final Account account = context.getAuthenticated(Account.class); + final Device device = account.getAuthenticatedDevice().get(); + final String connectionId = String.valueOf(new SecureRandom().nextLong()); + final Timer.Context timer = durationTimer.time(); + final WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId()); + final WebSocketConnection connection = new WebSocketConnection(pushSender, receiptSender, + messagesManager, account, device, + context.getClient(), connectionId); + final PubSubMessage connectMessage = PubSubMessage.newBuilder().setType(PubSubMessage.Type.CONNECTED) + .setContent(ByteString.copyFrom(connectionId.getBytes())) + .build(); - RedisOperation.unchecked(() -> apnFallbackManager.cancel(account, device)); - pubSubManager.publish(address, connectMessage); - pubSubManager.subscribe(address, connection); + RedisOperation.unchecked(() -> apnFallbackManager.cancel(account, device)); + pubSubManager.publish(address, connectMessage); + pubSubManager.subscribe(address, connection); - context.addListener(new WebSocketSessionContext.WebSocketEventListener() { - @Override - public void onWebSocketClose(WebSocketSessionContext context, int statusCode, String reason) { - pubSubManager.unsubscribe(address, connection); - timer.stop(); - } - }); + context.addListener(new WebSocketSessionContext.WebSocketEventListener() { + @Override + public void onWebSocketClose(WebSocketSessionContext context, int statusCode, String reason) { + pubSubManager.unsubscribe(address, connection); + timer.stop(); + } + }); + } else { + final Timer.Context timer = unauthenticatedDurationTimer.time(); + context.addListener((context1, statusCode, reason) -> timer.stop()); + } } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java index 4855f63b7..44fefa510 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/ProvisioningConnection.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.websocket; -import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -13,6 +12,8 @@ import org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage; import org.whispersystems.websocket.WebSocketClient; import org.whispersystems.websocket.messages.WebSocketResponseMessage; +import java.util.Optional; + public class ProvisioningConnection implements DispatchChannel { private final Logger logger = LoggerFactory.getLogger(ProvisioningConnection.class); diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java index 65a28a2f9..b71ec38f6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.websocket; -import com.google.common.base.Optional; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.storage.Account; @@ -10,6 +9,7 @@ import org.whispersystems.websocket.auth.WebSocketAuthenticator; import java.util.List; import java.util.Map; +import java.util.Optional; import io.dropwizard.auth.basic.BasicCredentials; @@ -23,7 +23,7 @@ public class WebSocketAccountAuthenticator implements WebSocketAuthenticator authenticate(UpgradeRequest request) throws AuthenticationException { + public AuthenticationResult authenticate(UpgradeRequest request) throws AuthenticationException { try { Map> parameters = request.getParameterMap(); List usernames = parameters.get("login"); @@ -32,13 +32,13 @@ public class WebSocketAccountAuthenticator implements WebSocketAuthenticator(Optional.empty(), false); } BasicCredentials credentials = new BasicCredentials(usernames.get(0).replace(" ", "+"), passwords.get(0).replace(" ", "+")); - return accountAuthenticator.authenticate(credentials); + return new AuthenticationResult<>(accountAuthenticator.authenticate(credentials), true); } catch (io.dropwizard.auth.AuthenticationException e) { throw new AuthenticationException(e); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java index 11313393e..b6d9465b6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java @@ -3,7 +3,6 @@ package org.whispersystems.textsecuregcm.websocket; import com.codahale.metrics.Histogram; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; -import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -21,24 +20,25 @@ import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PushSender; import org.whispersystems.textsecuregcm.push.ReceiptSender; -import org.whispersystems.textsecuregcm.push.TransientPushFailureException; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.util.Constants; +import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.websocket.WebSocketClient; import org.whispersystems.websocket.messages.WebSocketResponseMessage; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.ws.rs.WebApplicationException; -import java.io.IOException; import java.util.Iterator; +import java.util.Optional; import static com.codahale.metrics.MetricRegistry.name; import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class WebSocketConnection implements DispatchChannel { private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); @@ -82,7 +82,7 @@ public class WebSocketConnection implements DispatchChannel { processStoredMessages(); break; case PubSubMessage.Type.DELIVER_VALUE: - sendMessage(Envelope.parseFrom(pubSubMessage.getContent()), Optional.absent(), false); + sendMessage(Envelope.parseFrom(pubSubMessage.getContent()), Optional.empty(), false); break; case PubSubMessage.Type.CONNECTED_VALUE: if (pubSubMessage.hasContent() && !new String(pubSubMessage.getContent().toByteArray()).equals(connectionId)) { @@ -112,7 +112,7 @@ public class WebSocketConnection implements DispatchChannel { { try { EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, device.getSignalingKey()); - Optional body = Optional.fromNullable(encryptedMessage.toByteArray()); + Optional body = Optional.ofNullable(encryptedMessage.toByteArray()); ListenableFuture response = client.sendRequest("PUT", "/api/v1/message", null, body); Futures.addCallback(response, new FutureCallback() { @@ -158,14 +158,12 @@ public class WebSocketConnection implements DispatchChannel { } private void sendDeliveryReceiptFor(Envelope message) { + if (!message.hasSource()) return; + try { - receiptSender.sendReceipt(account, message.getSource(), message.getTimestamp(), - message.hasRelay() ? Optional.of(message.getRelay()) : - Optional.absent()); + receiptSender.sendReceipt(account, message.getSource(), message.getTimestamp()); } catch (NoSuchUserException | NotPushRegisteredException e) { logger.info("No longer registered " + e.getMessage()); - } catch(IOException | TransientPushFailureException e) { - logger.warn("Something wrong while sending receipt", e); } catch (WebApplicationException e) { logger.warn("Bad federated response for receipt: " + e.getResponse().getStatus()); } @@ -179,9 +177,13 @@ public class WebSocketConnection implements DispatchChannel { OutgoingMessageEntity message = iterator.next(); Envelope.Builder builder = Envelope.newBuilder() .setType(Envelope.Type.valueOf(message.getType())) - .setSourceDevice(message.getSourceDevice()) - .setSource(message.getSource()) - .setTimestamp(message.getTimestamp()); + .setTimestamp(message.getTimestamp()) + .setServerTimestamp(message.getServerTimestamp()); + + if (!Util.isEmpty(message.getSource())) { + builder.setSource(message.getSource()) + .setSourceDevice(message.getSourceDevice()); + } if (message.getMessage() != null) { builder.setLegacyMessage(ByteString.copyFrom(message.getMessage())); @@ -199,7 +201,7 @@ public class WebSocketConnection implements DispatchChannel { } if (!messages.hasMore()) { - client.sendRequest("PUT", "/api/v1/queue/empty", null, Optional.absent()); + client.sendRequest("PUT", "/api/v1/queue/empty", null, Optional.empty()); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java b/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java new file mode 100644 index 000000000..ba199a41b --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java @@ -0,0 +1,92 @@ +package org.whispersystems.textsecuregcm.workers; + +import com.google.common.base.MoreObjects; +import com.google.protobuf.ByteString; +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; +import org.whispersystems.curve25519.Curve25519; +import org.whispersystems.curve25519.Curve25519KeyPair; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.crypto.ECKeyPair; +import org.whispersystems.textsecuregcm.crypto.ECPrivateKey; +import org.whispersystems.textsecuregcm.entities.MessageProtos; +import org.whispersystems.textsecuregcm.util.Base64; + +import java.io.IOException; +import java.security.InvalidKeyException; + +import io.dropwizard.cli.Command; +import io.dropwizard.setup.Bootstrap; + +public class CertificateCommand extends Command { + + public CertificateCommand() { + super("certificate", "Generates server certificates for unidentified delivery"); + } + + @Override + public void configure(Subparser subparser) { + subparser.addArgument("-ca", "--ca") + .dest("ca") + .action(Arguments.storeTrue()) + .setDefault(Boolean.FALSE) + .help("Generate CA parameters"); + + subparser.addArgument("-k", "--key") + .dest("key") + .type(String.class) + .help("The CA private signing key"); + + subparser.addArgument("-i", "--id") + .dest("keyId") + .type(Integer.class) + .help("The key ID to create"); + } + + @Override + public void run(Bootstrap bootstrap, Namespace namespace) throws Exception { + if (MoreObjects.firstNonNull(namespace.getBoolean("ca"), false)) runCaCommand(); + else runCertificateCommand(namespace); + } + + private void runCaCommand() { + ECKeyPair keyPair = Curve.generateKeyPair(); + System.out.println("Public key : " + Base64.encodeBytes(keyPair.getPublicKey().serialize())); + System.out.println("Private key: " + Base64.encodeBytes(keyPair.getPrivateKey().serialize())); + } + + private void runCertificateCommand(Namespace namespace) throws IOException, InvalidKeyException { + if (namespace.getString("key") == null) { + System.out.println("No key specified!"); + return; + } + + if (namespace.getInt("keyId") == null) { + System.out.print("No key id specified!"); + return; + } + + ECPrivateKey key = Curve.decodePrivatePoint(Base64.decode(namespace.getString("key"))); + int keyId = namespace.getInt("keyId"); + + ECKeyPair keyPair = Curve.generateKeyPair(); + + byte[] certificate = MessageProtos.ServerCertificate.Certificate.newBuilder() + .setId(keyId) + .setKey(ByteString.copyFrom(keyPair.getPublicKey().serialize())) + .build() + .toByteArray(); + + byte[] signature = Curve.calculateSignature(key, certificate); + + byte[] signedCertificate = MessageProtos.ServerCertificate.newBuilder() + .setCertificate(ByteString.copyFrom(certificate)) + .setSignature(ByteString.copyFrom(signature)) + .build() + .toByteArray(); + + System.out.println("Certificate: " + Base64.encodeBytes(signedCertificate)); + System.out.println("Private key: " + Base64.encodeBytes(keyPair.getPrivateKey().serialize())); + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java b/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java index 6c82cbf67..a7f04a0e1 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java @@ -1,7 +1,6 @@ package org.whispersystems.textsecuregcm.workers; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.google.common.base.Optional; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.skife.jdbi.v2.DBI; @@ -19,6 +18,7 @@ import org.whispersystems.textsecuregcm.storage.DirectoryManager; import org.whispersystems.textsecuregcm.util.Base64; import java.security.SecureRandom; +import java.util.Optional; import io.dropwizard.Application; import io.dropwizard.cli.EnvironmentCommand; diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java index 824b89778..2f9970b35 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java @@ -22,7 +22,6 @@ import org.skife.jdbi.v2.DBI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; import org.whispersystems.textsecuregcm.providers.RedisClientFactory; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.storage.Accounts; @@ -30,16 +29,13 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DirectoryManager; import io.dropwizard.Application; -import io.dropwizard.cli.ConfiguredCommand; import io.dropwizard.cli.EnvironmentCommand; import io.dropwizard.db.DataSourceFactory; import io.dropwizard.jdbi.ImmutableListContainerFactory; import io.dropwizard.jdbi.ImmutableSetContainerFactory; import io.dropwizard.jdbi.OptionalContainerFactory; import io.dropwizard.jdbi.args.OptionalArgumentFactory; -import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; -import redis.clients.jedis.JedisPool; public class DirectoryCommand extends EnvironmentCommand { @@ -77,14 +73,10 @@ public class DirectoryCommand extends EnvironmentCommandCREATE RULE bounded_message_queue AS ON INSERT TO messages DO ALSO DELETE FROM messages WHERE id IN (SELECT id FROM messages WHERE destination = NEW.destination AND destination_device = NEW.destination_device ORDER BY timestamp DESC OFFSET 1000); + + + + + + + + + + + + + + + CREATE INDEX CONCURRENTLY guid_index ON messages (guid); + diff --git a/src/test/java/org/whispersystems/dispatch/DispatchManagerTest.java b/src/test/java/org/whispersystems/dispatch/DispatchManagerTest.java index d6e81c518..784a26024 100644 --- a/src/test/java/org/whispersystems/dispatch/DispatchManagerTest.java +++ b/src/test/java/org/whispersystems/dispatch/DispatchManagerTest.java @@ -1,6 +1,6 @@ package org.whispersystems.dispatch; -import com.google.common.base.Optional; +import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExternalResource; @@ -42,7 +42,7 @@ public class DispatchManagerTest { } }); - dispatchManager = new DispatchManager(socketFactory, Optional.absent()); + dispatchManager = new DispatchManager(socketFactory, Optional.empty()); dispatchManager.start(); } @@ -61,7 +61,7 @@ public class DispatchManagerTest { public void testSubscribe() throws IOException { DispatchChannel dispatchChannel = mock(DispatchChannel.class); dispatchManager.subscribe("foo", dispatchChannel); - pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.absent())); + pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty())); verify(dispatchChannel, timeout(1000)).onDispatchSubscribed(eq("foo")); } @@ -72,8 +72,8 @@ public class DispatchManagerTest { dispatchManager.subscribe("foo", dispatchChannel); dispatchManager.unsubscribe("foo", dispatchChannel); - pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.absent())); - pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, "foo", Optional.absent())); + pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty())); + pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, "foo", Optional.empty())); verify(dispatchChannel, timeout(1000)).onDispatchUnsubscribed(eq("foo")); } @@ -86,8 +86,8 @@ public class DispatchManagerTest { dispatchManager.subscribe("foo", fooChannel); dispatchManager.subscribe("bar", barChannel); - pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.absent())); - pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "bar", Optional.absent())); + pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty())); + pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "bar", Optional.empty())); verify(fooChannel, timeout(1000)).onDispatchSubscribed(eq("foo")); verify(barChannel, timeout(1000)).onDispatchSubscribed(eq("bar")); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java new file mode 100644 index 000000000..049349f67 --- /dev/null +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/auth/OptionalAccessTest.java @@ -0,0 +1,137 @@ +package org.whispersystems.textsecuregcm.tests.auth; + +import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.Anonymous; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.util.Base64; + +import javax.ws.rs.WebApplicationException; +import java.util.Optional; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OptionalAccessTest { + + @Test + public void testUnidentifiedMissingTarget() { + try { + OptionalAccess.verify(Optional.empty(), Optional.empty(), Optional.empty()); + throw new AssertionError("should fail"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + } + } + + @Test + public void testUnidentifiedMissingTargetDevice() { + Account account = mock(Account.class); + when(account.isActive()).thenReturn(true); + when(account.getDevice(eq(10))).thenReturn(Optional.empty()); + when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); + + try { + OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account), "10"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + } + } + + @Test + public void testUnidentifiedBadTargetDevice() { + Account account = mock(Account.class); + when(account.isActive()).thenReturn(true); + when(account.getDevice(eq(10))).thenReturn(Optional.empty()); + when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); + + try { + OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account), "$$"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 422); + } + } + + + @Test + public void testUnidentifiedBadCode() { + Account account = mock(Account.class); + when(account.isActive()).thenReturn(true); + when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); + + try { + OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("5678".getBytes()))), Optional.of(account)); + throw new AssertionError("should fail"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + } + } + + @Test + public void testIdentifiedMissingTarget() { + Account account = mock(Account.class); + when(account.isActive()).thenReturn(true); + + try { + OptionalAccess.verify(Optional.of(account), Optional.empty(), Optional.empty()); + throw new AssertionError("should fail"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 404); + } + } + + @Test + public void testUnsolicitedBadTarget() { + Account account = mock(Account.class); + when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(false); + when(account.isActive()).thenReturn(true); + + try { + OptionalAccess.verify(Optional.empty(), Optional.empty(), Optional.of(account)); + throw new AssertionError("shold fai"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + } + } + + @Test + public void testUnsolicitedGoodTarget() { + Account account = mock(Account.class); + Anonymous random = mock(Anonymous.class); + when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(true); + when(account.isActive()).thenReturn(true); + OptionalAccess.verify(Optional.empty(), Optional.of(random), Optional.of(account)); + } + + @Test + public void testUnidentifiedGoodTarget() { + Account account = mock(Account.class); + when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); + when(account.isActive()).thenReturn(true); + OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account)); + } + + @Test + public void testUnidentifiedInactive() { + Account account = mock(Account.class); + when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes())); + when(account.isActive()).thenReturn(false); + + try { + OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account)); + throw new AssertionError(); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), 401); + } + } + + @Test + public void testIdentifiedGoodTarget() { + Account source = mock(Account.class); + Account target = mock(Account.class); + when(target.isActive()).thenReturn(true); + OptionalAccess.verify(Optional.of(source), Optional.empty(), Optional.of(target));; + } +} diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index 0b057f831..a3752f701 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -1,11 +1,9 @@ package org.whispersystems.textsecuregcm.tests.controllers; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.controllers.AccountController; @@ -30,8 +28,10 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.HashMap; +import java.util.Optional; import java.util.concurrent.TimeUnit; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyString; @@ -59,7 +59,7 @@ public class AccountControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .addProvider(new RateLimitExceededExceptionMapper()) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) @@ -94,8 +94,8 @@ public class AccountControllerTest { when(accountsManager.get(eq(SENDER_PIN))).thenReturn(Optional.of(senderPinAccount)); when(accountsManager.get(eq(SENDER_OVER_PIN))).thenReturn(Optional.of(senderPinAccount)); - when(accountsManager.get(eq(SENDER))).thenReturn(Optional.absent()); - when(accountsManager.get(eq(SENDER_OLD))).thenReturn(Optional.absent()); + when(accountsManager.get(eq(SENDER))).thenReturn(Optional.empty()); + when(accountsManager.get(eq(SENDER_OLD))).thenReturn(Optional.empty()); doThrow(new RateLimitExceededException(SENDER_OVER_PIN)).when(pinLimiter).validate(eq(SENDER_OVER_PIN)); } @@ -110,7 +110,7 @@ public class AccountControllerTest { assertThat(response.getStatus()).isEqualTo(200); - verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.absent()), anyString()); + verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString()); } @Test @@ -270,6 +270,17 @@ public class AccountControllerTest { verify(AuthHelper.VALID_ACCOUNT).setPin(eq("31337")); } + @Test + public void testSetPinUnauthorized() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/pin/") + .request() + .put(Entity.json(new RegistrationLock("31337"))); + + assertThat(response.getStatus()).isEqualTo(401); + } + @Test public void testSetShortPin() throws Exception { Response response = diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java index f65451bf9..3e34d01cd 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java @@ -3,20 +3,20 @@ package org.whispersystems.textsecuregcm.tests.controllers; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.ClassRule; import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; import org.whispersystems.textsecuregcm.configuration.AttachmentsConfiguration; import org.whispersystems.textsecuregcm.controllers.AttachmentController; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptor; import org.whispersystems.textsecuregcm.entities.AttachmentUri; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.s3.UrlSigner; +import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; import java.net.MalformedURLException; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -24,10 +24,9 @@ import static org.mockito.Mockito.when; public class AttachmentControllerTest { - private static AttachmentsConfiguration configuration = mock(AttachmentsConfiguration.class); - private static FederatedClientManager federatedClientManager = mock(FederatedClientManager.class ); - private static RateLimiters rateLimiters = mock(RateLimiters.class ); - private static RateLimiter rateLimiter = mock(RateLimiter.class ); + private static AttachmentsConfiguration configuration = mock(AttachmentsConfiguration.class); + private static RateLimiters rateLimiters = mock(RateLimiters.class ); + private static RateLimiter rateLimiter = mock(RateLimiter.class ); private static UrlSigner urlSigner; @@ -43,10 +42,10 @@ public class AttachmentControllerTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new AttachmentController(rateLimiters, federatedClientManager, urlSigner)) + .addResource(new AttachmentController(rateLimiters, urlSigner)) .build(); @Test diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java new file mode 100644 index 000000000..c77aa8dd4 --- /dev/null +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -0,0 +1,113 @@ +package org.whispersystems.textsecuregcm.tests.controllers; + +import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; +import org.junit.ClassRule; +import org.junit.Test; +import org.whispersystems.textsecuregcm.auth.CertificateGenerator; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; +import org.whispersystems.textsecuregcm.controllers.CertificateController; +import org.whispersystems.textsecuregcm.crypto.Curve; +import org.whispersystems.textsecuregcm.entities.DeliveryCertificate; +import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate; +import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate; +import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.Base64; +import org.whispersystems.textsecuregcm.util.SystemMapper; + +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.Arrays; + +import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.testing.junit.ResourceTestRule; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; + +public class CertificateControllerTest { + + private static final String caPublicKey = "BWh+UOhT1hD8bkb+MFRvb6tVqhoG8YYGCzOd7mgjo8cV"; + private static final String caPrivateKey = "EO3Mnf0kfVlVnwSaqPoQnAxhnnGL1JTdXqktCKEe9Eo="; + + private static final String signingCertificate = "CiUIDBIhBbTz4h1My+tt+vw+TVscgUe/DeHS0W02tPWAWbTO2xc3EkD+go4bJnU0AcnFfbOLKoiBfCzouZtDYMOVi69rE7r4U9cXREEqOkUmU2WJBjykAxWPCcSTmVTYHDw7hkSp/puG"; + private static final String signingKey = "ABOxG29xrfq4E7IrW11Eg7+HBbtba9iiS0500YoBjn4="; + + private static CertificateGenerator certificateGenerator; + + static { + try { + certificateGenerator = new CertificateGenerator(Base64.decode(signingCertificate), Curve.decodePrivatePoint(Base64.decode(signingKey)), 1); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + + @ClassRule + public static final ResourceTestRule resources = ResourceTestRule.builder() + .addProvider(AuthHelper.getAuthFilter()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) + .setMapper(SystemMapper.getMapper()) + .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) + .addResource(new CertificateController(certificateGenerator)) + .build(); + + + @Test + public void testValidCertificate() throws Exception { + DeliveryCertificate certificateObject = resources.getJerseyTest() + .target("/v1/certificate/delivery") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .get(DeliveryCertificate.class); + + + SenderCertificate certificateHolder = SenderCertificate.parseFrom(certificateObject.getCertificate()); + SenderCertificate.Certificate certificate = SenderCertificate.Certificate.parseFrom(certificateHolder.getCertificate()); + + ServerCertificate serverCertificateHolder = certificate.getSigner(); + ServerCertificate.Certificate serverCertificate = ServerCertificate.Certificate.parseFrom(serverCertificateHolder.getCertificate()); + + assertTrue(Curve.verifySignature(Curve.decodePoint(serverCertificate.getKey().toByteArray(), 0), certificateHolder.getCertificate().toByteArray(), certificateHolder.getSignature().toByteArray())); + assertTrue(Curve.verifySignature(Curve.decodePoint(Base64.decode(caPublicKey), 0), serverCertificateHolder.getCertificate().toByteArray(), serverCertificateHolder.getSignature().toByteArray())); + + assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER); + assertEquals(certificate.getSenderDevice(), 1L); + assertTrue(Arrays.equals(certificate.getIdentityKey().toByteArray(), Base64.decode(AuthHelper.VALID_IDENTITY))); + } + + @Test + public void testBadAuthentication() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/certificate/delivery") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD)) + .get(); + + assertEquals(response.getStatus(), 401); + } + + + @Test + public void testNoAuthentication() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/certificate/delivery") + .request() + .get(); + + assertEquals(response.getStatus(), 401); + } + + + @Test + public void testUnidentifiedAuthentication() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/certificate/delivery") + .request() + .header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1234".getBytes())) + .get(); + + assertEquals(response.getStatus(), 401); + } + +} diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 5b4019160..02155987a 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -16,12 +16,10 @@ */ package org.whispersystems.textsecuregcm.tests.controllers; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.controllers.DeviceController; import org.whispersystems.textsecuregcm.entities.AccountAttributes; @@ -40,8 +38,10 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -83,7 +83,7 @@ public class DeviceControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addProvider(new DeviceLimitExceededExceptionMapper()) .addResource(new DumbVerificationDeviceController(pendingDevicesManager, @@ -202,15 +202,4 @@ public class DeviceControllerTest { verifyNoMoreInteractions(messagesManager); } - @Test - public void removeDeviceTest() throws Exception { - Response response = resources.getJerseyTest() - .target("/v1/devices/12345") - .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) - .delete(); - - assertEquals(204, response.getStatus()); - verify(directoryQueue).deleteRegisteredUser(eq(AuthHelper.VALID_NUMBER)); - } } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java index 342d3e124..553f7dc92 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java @@ -1,21 +1,18 @@ package org.whispersystems.textsecuregcm.tests.controllers; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; import org.whispersystems.textsecuregcm.auth.DirectoryCredentials; import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration; -import org.whispersystems.textsecuregcm.configuration.DirectoryClientConfiguration; import org.whispersystems.textsecuregcm.controllers.DirectoryController; import org.whispersystems.textsecuregcm.entities.ClientContactTokens; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.DirectoryManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.Base64; @@ -26,11 +23,11 @@ import javax.ws.rs.core.Response; import java.util.LinkedList; import java.util.List; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyListOf; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Matchers.anyList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -46,7 +43,7 @@ public class DirectoryControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new DirectoryController(rateLimiters, directoryManager, diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java deleted file mode 100644 index fe2516d12..000000000 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.whispersystems.textsecuregcm.tests.controllers; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; -import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; -import org.whispersystems.textsecuregcm.controllers.FederationControllerV1; -import org.whispersystems.textsecuregcm.controllers.FederationControllerV2; -import org.whispersystems.textsecuregcm.controllers.KeysController; -import org.whispersystems.textsecuregcm.controllers.MessageController; -import org.whispersystems.textsecuregcm.entities.IncomingMessageList; -import org.whispersystems.textsecuregcm.entities.MessageProtos; -import org.whispersystems.textsecuregcm.entities.PreKeyResponseItem; -import org.whispersystems.textsecuregcm.entities.PreKeyResponse; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; -import org.whispersystems.textsecuregcm.limits.RateLimiter; -import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.push.ApnFallbackManager; -import org.whispersystems.textsecuregcm.push.PushSender; -import org.whispersystems.textsecuregcm.push.ReceiptSender; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.MessagesManager; -import org.whispersystems.textsecuregcm.tests.util.AuthHelper; - -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; - -import io.dropwizard.testing.junit.ResourceTestRule; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; -import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture; - -public class FederatedControllerTest { - - private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111"; - private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; - - private PushSender pushSender = mock(PushSender.class ); - private ReceiptSender receiptSender = mock(ReceiptSender.class); - private FederatedClientManager federatedClientManager = mock(FederatedClientManager.class); - private AccountsManager accountsManager = mock(AccountsManager.class ); - private MessagesManager messagesManager = mock(MessagesManager.class); - private RateLimiters rateLimiters = mock(RateLimiters.class ); - private RateLimiter rateLimiter = mock(RateLimiter.class ); - private ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class); - - private final SignedPreKey signedPreKey = new SignedPreKey(3333, "foo", "baar"); - private final PreKeyResponse preKeyResponseV2 = new PreKeyResponse("foo", new LinkedList()); - - private final ObjectMapper mapper = new ObjectMapper(); - - private final MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, federatedClientManager, apnFallbackManager); - private final KeysController keysControllerV2 = mock(KeysController.class); - - @Rule - public final ResourceTestRule resources = ResourceTestRule.builder() - .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) - .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new FederationControllerV1(accountsManager, null, messageController)) - .addResource(new FederationControllerV2(accountsManager, null, messageController, keysControllerV2)) - .build(); - - - - @Before - public void setup() throws Exception { - Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); - }}; - - Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(222, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(333, "rad", "mad"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); - }}; - - Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); - Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, multiDeviceList); - - when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount)); - when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); - - when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter); - - when(keysControllerV2.getSignedKey(any(Account.class))).thenReturn(Optional.of(signedPreKey)); - when(keysControllerV2.getDeviceKeys(any(Account.class), anyString(), anyString(), any(Optional.class))) - .thenReturn(Optional.of(preKeyResponseV2)); - } - - @Test - public void testSingleDeviceCurrent() throws Exception { - Response response = - resources.getJerseyTest() - .target(String.format("/v1/federation/messages/+14152223333/1/%s", SINGLE_DEVICE_RECIPIENT)) - .request() - .header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo")) - .put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), - MediaType.APPLICATION_JSON_TYPE)); - - assertThat("Good Response", response.getStatus(), is(equalTo(204))); - - verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.Envelope.class)); - } - - @Test - public void testSignedPreKeyV2() throws Exception { - PreKeyResponse response = - resources.getJerseyTest() - .target("/v2/federation/key/+14152223333/1") - .request() - .header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo")) - .get(PreKeyResponse.class); - - assertThat("good response", response.getIdentityKey().equals(preKeyResponseV2.getIdentityKey())); - } - -} diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java index 2c585f44f..90377a81c 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java @@ -1,12 +1,11 @@ package org.whispersystems.textsecuregcm.tests.controllers; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.KeysController; import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.entities.PreKeyCount; @@ -28,8 +27,10 @@ import javax.ws.rs.core.Response; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @@ -63,9 +64,9 @@ public class KeyControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new KeysController(rateLimiters, keys, accounts, null)) + .addResource(new KeysController(rateLimiters, keys, accounts)) .build(); @Before @@ -103,14 +104,15 @@ public class KeyControllerTest { when(existsAccount.getDevice(2L)).thenReturn(Optional.of(sampleDevice2)); when(existsAccount.getDevice(3L)).thenReturn(Optional.of(sampleDevice3)); when(existsAccount.getDevice(4L)).thenReturn(Optional.of(sampleDevice4)); - when(existsAccount.getDevice(22L)).thenReturn(Optional.absent()); + when(existsAccount.getDevice(22L)).thenReturn(Optional.empty()); when(existsAccount.getDevices()).thenReturn(allDevices); when(existsAccount.isActive()).thenReturn(true); when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey"); when(existsAccount.getNumber()).thenReturn(EXISTS_NUMBER); + when(existsAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of("1337".getBytes())); when(accounts.get(EXISTS_NUMBER)).thenReturn(Optional.of(existsAccount)); - when(accounts.get(NOT_EXISTS_NUMBER)).thenReturn(Optional.absent()); + when(accounts.get(NOT_EXISTS_NUMBER)).thenReturn(Optional.empty()); when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter); @@ -118,7 +120,7 @@ public class KeyControllerTest { singleDevice.add(SAMPLE_KEY); when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenReturn(Optional.of(singleDevice)); - when(keys.get(eq(NOT_EXISTS_NUMBER), eq(1L))).thenReturn(Optional.>absent()); + when(keys.get(eq(NOT_EXISTS_NUMBER), eq(1L))).thenReturn(Optional.>empty()); List multiDevice = new LinkedList<>(); multiDevice.add(SAMPLE_KEY); @@ -191,6 +193,49 @@ public class KeyControllerTest { verifyNoMoreInteractions(keys); } + @Test + public void testUnidentifiedRequest() throws Exception { + PreKeyResponse result = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/1", EXISTS_NUMBER)) + .request() + .header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1337".getBytes())) + .get(PreKeyResponse.class); + + assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey()); + assertThat(result.getDevicesCount()).isEqualTo(1); + assertThat(result.getDevice(1).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId()); + assertThat(result.getDevice(1).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey()); + assertThat(result.getDevice(1).getSignedPreKey()).isEqualTo(existsAccount.getDevice(1).get().getSignedPreKey()); + + verify(keys).get(eq(EXISTS_NUMBER), eq(1L)); + verifyNoMoreInteractions(keys); + } + + @Test + public void testUnauthorizedUnidentifiedRequest() throws Exception { + Response response = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/1", EXISTS_NUMBER)) + .request() + .header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("9999".getBytes())) + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + verifyNoMoreInteractions(keys); + } + + @Test + public void testMalformedUnidentifiedRequest() throws Exception { + Response response = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/1", EXISTS_NUMBER)) + .request() + .header(OptionalAccess.UNIDENTIFIED, "$$$$$$$$$") + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + verifyNoMoreInteractions(keys); + } + + @Test public void validMultiRequestTestV2() throws Exception { PreKeyResponse results = resources.getJerseyTest() diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index 8261e2928..1738ac6b2 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -1,12 +1,12 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; +import org.mockito.ArgumentCaptor; +import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.entities.IncomingMessageList; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; @@ -15,7 +15,6 @@ import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.entities.StaleDevices; -import org.whispersystems.textsecuregcm.federation.FederatedClientManager; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.push.ApnFallbackManager; @@ -26,6 +25,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.Base64; import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; @@ -33,14 +33,17 @@ import javax.ws.rs.core.Response; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @@ -54,7 +57,6 @@ public class MessageControllerTest { private final PushSender pushSender = mock(PushSender.class ); private final ReceiptSender receiptSender = mock(ReceiptSender.class); - private final FederatedClientManager federatedClientManager = mock(FederatedClientManager.class); private final AccountsManager accountsManager = mock(AccountsManager.class ); private final MessagesManager messagesManager = mock(MessagesManager.class); private final RateLimiters rateLimiters = mock(RateLimiters.class ); @@ -66,27 +68,27 @@ public class MessageControllerTest { @Rule public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, - messagesManager, federatedClientManager, apnFallbackManager)) + messagesManager, apnFallbackManager)) .build(); @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true)); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), false, false, "Test")); - add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), false, false, "Test")); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true)); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true)); + add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", true)); }}; - Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); - Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, multiDeviceList); + Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList, "1234".getBytes()); + Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, multiDeviceList, "1234".getBytes()); when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount)); when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); @@ -106,7 +108,43 @@ public class MessageControllerTest { assertThat("Good Response", response.getStatus(), is(equalTo(200))); - verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), any(Envelope.class)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); + verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), captor.capture()); + + assertTrue(captor.getValue().hasSource()); + assertTrue(captor.getValue().hasSourceDevice()); + } + + @Test + public synchronized void testSingleDeviceCurrentUnidentified() throws Exception { + Response response = + resources.getJerseyTest() + .target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT)) + .request() + .header(OptionalAccess.UNIDENTIFIED, Base64.encodeBytes("1234".getBytes())) + .put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), + MediaType.APPLICATION_JSON_TYPE)); + + assertThat("Good Response", response.getStatus(), is(equalTo(200))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); + verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), captor.capture()); + + assertFalse(captor.getValue().hasSource()); + assertFalse(captor.getValue().hasSourceDevice()); + } + + + @Test + public synchronized void testSendBadAuth() throws Exception { + Response response = + resources.getJerseyTest() + .target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT)) + .request() + .put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), + MediaType.APPLICATION_JSON_TYPE)); + + assertThat("Good Response", response.getStatus(), is(equalTo(401))); } @Test @@ -187,9 +225,11 @@ public class MessageControllerTest { final long timestampOne = 313377; final long timestampTwo = 313388; + final UUID uuidOne = UUID.randomUUID(); + List messages = new LinkedList() {{ - add(new OutgoingMessageEntity(1L, false, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null)); - add(new OutgoingMessageEntity(2L, false, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null)); + add(new OutgoingMessageEntity(1L, false, uuidOne, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0)); + add(new OutgoingMessageEntity(2L, false, null, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0)); }}; OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false); @@ -211,6 +251,9 @@ public class MessageControllerTest { assertEquals(response.getMessages().get(0).getTimestamp(), timestampOne); assertEquals(response.getMessages().get(1).getTimestamp(), timestampTwo); + + assertEquals(response.getMessages().get(0).getGuid(), uuidOne); + assertEquals(response.getMessages().get(1).getGuid(), null); } @Test @@ -219,8 +262,8 @@ public class MessageControllerTest { final long timestampTwo = 313388; List messages = new LinkedList() {{ - add(new OutgoingMessageEntity(1L, false, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null)); - add(new OutgoingMessageEntity(2L, false, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null)); + add(new OutgoingMessageEntity(1L, false, UUID.randomUUID(), Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0)); + add(new OutgoingMessageEntity(2L, false, UUID.randomUUID(), Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0)); }}; OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false); @@ -240,21 +283,22 @@ public class MessageControllerTest { @Test public synchronized void testDeleteMessages() throws Exception { long timestamp = System.currentTimeMillis(); + when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31337)) - .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, + .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null, Envelope.Type.CIPHERTEXT_VALUE, null, timestamp, - "+14152222222", 1, "hi".getBytes(), null))); + "+14152222222", 1, "hi".getBytes(), null, 0))); when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31338)) - .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, + .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null, Envelope.Type.RECEIPT_VALUE, null, System.currentTimeMillis(), - "+14152222222", 1, null, null))); + "+14152222222", 1, null, null, 0))); when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31339)) - .thenReturn(Optional.absent()); + .thenReturn(Optional.empty()); Response response = resources.getJerseyTest() .target(String.format("/v1/messages/%s/%d", "+14152222222", 31337)) @@ -263,7 +307,7 @@ public class MessageControllerTest { .delete(); assertThat("Good Response Code", response.getStatus(), is(equalTo(204))); - verify(receiptSender).sendReceipt(any(Account.class), eq("+14152222222"), eq(timestamp), eq(Optional.absent())); + verify(receiptSender).sendReceipt(any(Account.class), eq("+14152222222"), eq(timestamp)); response = resources.getJerseyTest() .target(String.format("/v1/messages/%s/%d", "+14152222222", 31338)) diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java index c105cbb08..fa427abc4 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java @@ -1,11 +1,9 @@ package org.whispersystems.textsecuregcm.tests.controllers; -import com.google.common.base.Optional; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; -import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider; import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; @@ -17,6 +15,10 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; +import javax.ws.rs.core.Response; +import java.util.Optional; + +import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.*; @@ -38,7 +40,7 @@ public class ProfileControllerTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new AuthValueFactoryProvider.Binder()) + .addProvider(new AuthValueFactoryProvider.Binder<>(Account.class)) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new ProfileController(rateLimiters, @@ -53,9 +55,10 @@ public class ProfileControllerTest { Account profileAccount = mock(Account.class); when(profileAccount.getIdentityKey()).thenReturn("bar"); - when(profileAccount.getName()).thenReturn("baz"); + when(profileAccount.getProfileName()).thenReturn("baz"); when(profileAccount.getAvatar()).thenReturn("profiles/bang"); when(profileAccount.getAvatarDigest()).thenReturn("buh"); + when(profileAccount.isActive()).thenReturn(true); when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount)); } @@ -78,5 +81,14 @@ public class ProfileControllerTest { verify(rateLimiter, times(1)).validate(AuthHelper.VALID_NUMBER); } + @Test + public void testProfileGetUnauthorized() throws Exception { + Response response = resources.getJerseyTest() + .target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO) + .request() + .get(); + + assertThat(response.getStatus()).isEqualTo(401); + } } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/push/APNSenderTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/push/APNSenderTest.java index 050854448..cbfc4e7f9 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/push/APNSenderTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/push/APNSenderTest.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.tests.push; -import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; import com.relayrides.pushy.apns.ApnsClient; import com.relayrides.pushy.apns.ApnsServerException; @@ -22,6 +21,7 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService; import java.util.Date; +import java.util.Optional; import java.util.concurrent.TimeUnit; import io.netty.util.concurrent.DefaultEventExecutor; diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/push/GCMSenderTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/push/GCMSenderTest.java index 2b4742de5..1d84ba5a3 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/push/GCMSenderTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/push/GCMSenderTest.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.tests.push; -import com.google.common.base.Optional; import com.google.common.util.concurrent.SettableFuture; import org.junit.Test; import org.mockito.Matchers; @@ -15,6 +14,8 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService; +import java.util.Optional; + import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java index 62250f834..66e17bd87 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java @@ -6,6 +6,7 @@ import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; import java.util.HashSet; +import java.util.Optional; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertFalse; @@ -49,21 +50,21 @@ public class AccountTest { Account recentAccount = new Account("+14152222222", new HashSet() {{ add(recentMasterDevice); add(recentSecondaryDevice); - }}); + }}, "1234".getBytes()); assertTrue(recentAccount.isActive()); Account oldSecondaryAccount = new Account("+14152222222", new HashSet() {{ add(recentMasterDevice); add(agingSecondaryDevice); - }}); + }}, "1234".getBytes()); assertTrue(oldSecondaryAccount.isActive()); Account agingPrimaryAccount = new Account("+14152222222", new HashSet() {{ add(oldMasterDevice); add(agingSecondaryDevice); - }}); + }}, "1234".getBytes()); assertTrue(agingPrimaryAccount.isActive()); } @@ -73,7 +74,7 @@ public class AccountTest { Account oldPrimaryAccount = new Account("+14152222222", new HashSet() {{ add(oldMasterDevice); add(oldSecondaryDevice); - }}); + }}, "1234".getBytes()); assertFalse(oldPrimaryAccount.isActive()); } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java index b225272ba..3120c59fb 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.tests.storage; -import com.google.common.base.Optional; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -18,17 +17,14 @@ import org.whispersystems.textsecuregcm.util.Util; import java.util.Arrays; import java.util.Collections; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class DirectoryReconcilerTest { @@ -53,8 +49,6 @@ public class DirectoryReconcilerTest { public void setup() { when(account.getNumber()).thenReturn(VALID_NUMBER); when(account.isActive()).thenReturn(true); - when(account.isVideoSupported()).thenReturn(true); - when(account.isVoiceSupported()).thenReturn(true); when(inactiveAccount.getNumber()).thenReturn(INACTIVE_NUMBER); when(inactiveAccount.isActive()).thenReturn(false); @@ -66,7 +60,7 @@ public class DirectoryReconcilerTest { when(reconciliationClient.sendChunk(any())).thenReturn(successResponse); - when(reconciliationCache.getLastNumber()).thenReturn(Optional.absent()); + when(reconciliationCache.getLastNumber()).thenReturn(Optional.empty()); when(reconciliationCache.claimActiveWork(any(), anyLong())).thenReturn(true); when(reconciliationCache.isAccelerated()).thenReturn(false); } @@ -155,7 +149,7 @@ public class DirectoryReconcilerTest { assertThat(request.getValue().getNumbers()).isEqualTo(Collections.emptyList()); verify(reconciliationCache, times(1)).getLastNumber(); - verify(reconciliationCache, times(1)).setLastNumber(eq(Optional.absent())); + verify(reconciliationCache, times(1)).setLastNumber(eq(Optional.empty())); verify(reconciliationCache, times(1)).clearAccelerate(); verify(reconciliationCache, times(1)).isAccelerated(); verify(reconciliationCache, times(2)).claimActiveWork(any(), anyLong()); @@ -192,7 +186,7 @@ public class DirectoryReconcilerTest { assertThat(addedContact.getValue().getToken()).isEqualTo(Util.getContactToken(VALID_NUMBER)); verify(reconciliationCache, times(1)).getLastNumber(); - verify(reconciliationCache, times(1)).setLastNumber(eq(Optional.absent())); + verify(reconciliationCache, times(1)).setLastNumber(eq(Optional.empty())); verify(reconciliationCache, times(1)).clearAccelerate(); verify(reconciliationCache, times(1)).claimActiveWork(any(), anyLong()); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index 69e511f6c..be160d9e3 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -1,21 +1,16 @@ package org.whispersystems.textsecuregcm.tests.util; -import com.google.common.base.Optional; -import org.whispersystems.dropwizard.simpleauth.AuthDynamicFeature; -import org.whispersystems.dropwizard.simpleauth.BasicCredentialAuthFilter; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; -import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator; -import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; -import org.whispersystems.textsecuregcm.federation.FederatedPeer; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.util.Base64; -import java.util.LinkedList; -import java.util.List; +import java.util.Optional; +import io.dropwizard.auth.AuthDynamicFeature; +import io.dropwizard.auth.basic.BasicCredentialAuthFilter; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -31,6 +26,8 @@ public class AuthHelper { public static final String INVVALID_NUMBER = "+14151111111"; public static final String INVALID_PASSWORD = "bar"; + public static final String VALID_IDENTITY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"; + public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class ); public static Account VALID_ACCOUNT = mock(Account.class ); public static Account VALID_ACCOUNT_TWO = mock(Account.class); @@ -53,29 +50,24 @@ public class AuthHelper { when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO); when(VALID_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE)); when(VALID_ACCOUNT_TWO.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE_TWO)); - when(VALID_ACCOUNT.getRelay()).thenReturn(Optional.absent()); - when(VALID_ACCOUNT_TWO.getRelay()).thenReturn(Optional.absent()); + when(VALID_ACCOUNT.getRelay()).thenReturn(Optional.empty()); + when(VALID_ACCOUNT_TWO.getRelay()).thenReturn(Optional.empty()); + when(VALID_ACCOUNT.isActive()).thenReturn(true); + when(VALID_ACCOUNT_TWO.isActive()).thenReturn(true); + when(VALID_ACCOUNT.getIdentityKey()).thenReturn(VALID_IDENTITY); when(ACCOUNTS_MANAGER.get(VALID_NUMBER)).thenReturn(Optional.of(VALID_ACCOUNT)); when(ACCOUNTS_MANAGER.get(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); - List peer = new LinkedList() {{ - add(new FederatedPeer("cyanogen", "https://foo", "foofoo", "bazzzzz")); - }}; - - FederationConfiguration federationConfiguration = mock(FederationConfiguration.class); - when(federationConfiguration.getPeers()).thenReturn(peer); - return new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder() .setAuthenticator(new AccountAuthenticator(ACCOUNTS_MANAGER)) - .setPrincipal(Account.class) - .buildAuthFilter(), - new BasicCredentialAuthFilter.Builder() - .setAuthenticator(new FederatedPeerAuthenticator(federationConfiguration)) - .setPrincipal(FederatedPeer.class) .buildAuthFilter()); } public static String getAuthHeader(String number, String password) { return "Basic " + Base64.encodeBytes((number + ":" + password).getBytes()); } + + public static String getUnidentifiedAccessHeader(byte[] key) { + return Base64.encodeBytes(key); + } } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java index fb7eb8c34..5de50f645 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java @@ -1,6 +1,5 @@ package org.whispersystems.textsecuregcm.tests.websocket; -import com.google.common.base.Optional; import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.ByteString; import org.eclipse.jetty.websocket.api.UpgradeRequest; @@ -27,6 +26,8 @@ import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator; import org.whispersystems.textsecuregcm.websocket.WebSocketConnection; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; import org.whispersystems.websocket.WebSocketClient; +import org.whispersystems.websocket.auth.WebSocketAuthenticator; +import org.whispersystems.websocket.auth.WebSocketAuthenticator.AuthenticationResult; import org.whispersystems.websocket.messages.WebSocketResponseMessage; import org.whispersystems.websocket.session.WebSocketSessionContext; @@ -35,7 +36,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.UUID; import io.dropwizard.auth.basic.BasicCredentials; import static org.junit.Assert.*; @@ -72,7 +75,7 @@ public class WebSocketConnectionTest { .thenReturn(Optional.of(account)); when(accountAuthenticator.authenticate(eq(new BasicCredentials(INVALID_USER, INVALID_PASSWORD)))) - .thenReturn(Optional.absent()); + .thenReturn(Optional.empty()); when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device)); @@ -81,8 +84,8 @@ public class WebSocketConnectionTest { put("password", new LinkedList() {{add(VALID_PASSWORD);}}); }}); - Optional account = webSocketAuthenticator.authenticate(upgradeRequest); - when(sessionContext.getAuthenticated(Account.class)).thenReturn(account.get()); + AuthenticationResult account = webSocketAuthenticator.authenticate(upgradeRequest); + when(sessionContext.getAuthenticated(Account.class)).thenReturn(account.getUser().orElse(null)); connectListener.onWebSocketConnect(sessionContext); @@ -94,7 +97,8 @@ public class WebSocketConnectionTest { }}); account = webSocketAuthenticator.authenticate(upgradeRequest); - assertFalse(account.isPresent()); + assertFalse(account.getUser().isPresent()); + assertTrue(account.isRequired()); } @Test @@ -125,7 +129,7 @@ public class WebSocketConnectionTest { when(sender1.getDevices()).thenReturn(sender1devices); when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1)); - when(accountsManager.get("sender2")).thenReturn(Optional.absent()); + when(accountsManager.get("sender2")).thenReturn(Optional.empty()); when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId())) .thenReturn(outgoingMessagesList); @@ -160,7 +164,7 @@ public class WebSocketConnectionTest { futures.get(2).setException(new IOException()); verify(storedMessages, times(1)).delete(eq(account.getNumber()), eq(2L), eq(2L), eq(false)); - verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender1"), eq(2222L), eq(Optional.absent())); + verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender1"), eq(2222L)); connection.onDispatchUnsubscribed(websocketAddress.serialize()); verify(client).close(anyInt(), anyString()); @@ -208,7 +212,7 @@ public class WebSocketConnectionTest { when(sender1.getDevices()).thenReturn(sender1devices); when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1)); - when(accountsManager.get("sender2")).thenReturn(Optional.absent()); + when(accountsManager.get("sender2")).thenReturn(Optional.empty()); when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId())) .thenReturn(pendingMessagesList); @@ -250,7 +254,7 @@ public class WebSocketConnectionTest { futures.get(1).set(response); futures.get(0).setException(new IOException()); - verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()), eq(Optional.absent())); + verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp())); verify(websocketSender, times(1)).queueMessage(eq(account), eq(device), any(Envelope.class)); verify(pushSender, times(1)).sendQueuedNotification(eq(account), eq(device)); @@ -285,14 +289,14 @@ public class WebSocketConnectionTest { .build(); List pendingMessages = new LinkedList() {{ - add(new OutgoingMessageEntity(1, true, firstMessage.getType().getNumber(), firstMessage.getRelay(), + add(new OutgoingMessageEntity(1, true, UUID.randomUUID(), firstMessage.getType().getNumber(), firstMessage.getRelay(), firstMessage.getTimestamp(), firstMessage.getSource(), firstMessage.getSourceDevice(), firstMessage.getLegacyMessage().toByteArray(), - firstMessage.getContent().toByteArray())); - add(new OutgoingMessageEntity(2, false, secondMessage.getType().getNumber(), secondMessage.getRelay(), + firstMessage.getContent().toByteArray(), 0)); + add(new OutgoingMessageEntity(2, false, UUID.randomUUID(), secondMessage.getType().getNumber(), secondMessage.getRelay(), secondMessage.getTimestamp(), secondMessage.getSource(), secondMessage.getSourceDevice(), secondMessage.getLegacyMessage().toByteArray(), - secondMessage.getContent().toByteArray())); + secondMessage.getContent().toByteArray(), 0)); }}; OutgoingMessageEntityList pendingMessagesList = new OutgoingMessageEntityList(pendingMessages, false); @@ -313,7 +317,7 @@ public class WebSocketConnectionTest { when(sender1.getDevices()).thenReturn(sender1devices); when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1)); - when(accountsManager.get("sender2")).thenReturn(Optional.absent()); + when(accountsManager.get("sender2")).thenReturn(Optional.empty()); when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId())) .thenReturn(pendingMessagesList); @@ -346,7 +350,7 @@ public class WebSocketConnectionTest { futures.get(1).set(response); futures.get(0).setException(new IOException()); - verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()), eq(Optional.absent())); + verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp())); verifyNoMoreInteractions(websocketSender); verifyNoMoreInteractions(pushSender); @@ -356,8 +360,8 @@ public class WebSocketConnectionTest { private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, long timestamp, boolean receipt, String content) { - return new OutgoingMessageEntity(id, cached, receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE, - null, timestamp, sender, 1, content.getBytes(), null); + return new OutgoingMessageEntity(id, cached, UUID.randomUUID(), receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE, + null, timestamp, sender, 1, content.getBytes(), null, 0); } }