Support for ephemeral provisioning communication channels.

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2015-01-18 18:09:25 -08:00
parent 715181f830
commit 79f83babb3
14 changed files with 724 additions and 17 deletions

View File

@@ -12,7 +12,7 @@ import org.whispersystems.textsecuregcm.storage.StoredMessages;
import org.whispersystems.websocket.session.WebSocketSessionContext;
import org.whispersystems.websocket.setup.WebSocketConnectListener;
public class ConnectListener implements WebSocketConnectListener {
public class AuthenticatedConnectListener implements WebSocketConnectListener {
private static final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class);
@@ -21,8 +21,8 @@ public class ConnectListener implements WebSocketConnectListener {
private final StoredMessages storedMessages;
private final PubSubManager pubSubManager;
public ConnectListener(AccountsManager accountsManager, PushSender pushSender,
StoredMessages storedMessages, PubSubManager pubSubManager)
public AuthenticatedConnectListener(AccountsManager accountsManager, PushSender pushSender,
StoredMessages storedMessages, PubSubManager pubSubManager)
{
this.accountsManager = accountsManager;
this.pushSender = pushSender;

View File

@@ -0,0 +1,38 @@
package org.whispersystems.textsecuregcm.websocket;
import org.whispersystems.textsecuregcm.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class ProvisioningAddress extends WebsocketAddress {
private static final String PREFIX = ">>ephemeral-";
private final String address;
public ProvisioningAddress(String address) throws InvalidWebsocketAddressException {
super(address, 0);
this.address = address;
if (address == null || !address.startsWith(PREFIX)) {
throw new InvalidWebsocketAddressException(address);
}
}
public String getAddress() {
return address;
}
public static ProvisioningAddress generate() {
try {
byte[] random = new byte[16];
SecureRandom.getInstance("SHA1PRNG").nextBytes(random);
return new ProvisioningAddress(PREFIX + Base64.encodeBytesWithoutPadding(random)
.replace('+', '-').replace('/', '_'));
} catch (NoSuchAlgorithmException | InvalidWebsocketAddressException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,27 @@
package org.whispersystems.textsecuregcm.websocket;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.websocket.session.WebSocketSessionContext;
import org.whispersystems.websocket.setup.WebSocketConnectListener;
public class ProvisioningConnectListener implements WebSocketConnectListener {
private final PubSubManager pubSubManager;
public ProvisioningConnectListener(PubSubManager pubSubManager) {
this.pubSubManager = pubSubManager;
}
@Override
public void onWebSocketConnect(WebSocketSessionContext context) {
final ProvisioningConnection connection = new ProvisioningConnection(pubSubManager, context.getClient());
connection.onConnected();
context.addListener(new WebSocketSessionContext.WebSocketEventListener() {
@Override
public void onWebSocketClose(WebSocketSessionContext context, int statusCode, String reason) {
connection.onConnectionLost();
}
});
}
}

View File

@@ -0,0 +1,61 @@
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;
import org.whispersystems.textsecuregcm.entities.MessageProtos.ProvisioningUuid;
import org.whispersystems.textsecuregcm.storage.PubSubListener;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessage;
import org.whispersystems.websocket.WebSocketClient;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
public class ProvisioningConnection implements PubSubListener {
private final PubSubManager pubSubManager;
private final ProvisioningAddress provisioningAddress;
private final WebSocketClient client;
public ProvisioningConnection(PubSubManager pubSubManager, WebSocketClient client) {
this.pubSubManager = pubSubManager;
this.client = client;
this.provisioningAddress = ProvisioningAddress.generate();
}
@Override
public void onPubSubMessage(PubSubMessage outgoingMessage) {
if (outgoingMessage.getType() == PubSubMessage.Type.DELIVER) {
Optional<byte[]> body = Optional.of(outgoingMessage.getContent().toByteArray());
ListenableFuture<WebSocketResponseMessage> response = client.sendRequest("PUT", "/v1/message", body);
Futures.addCallback(response, new FutureCallback<WebSocketResponseMessage>() {
@Override
public void onSuccess(WebSocketResponseMessage webSocketResponseMessage) {
pubSubManager.unsubscribe(provisioningAddress, ProvisioningConnection.this);
client.close(1001, "All you get.");
}
@Override
public void onFailure(Throwable throwable) {
pubSubManager.unsubscribe(provisioningAddress, ProvisioningConnection.this);
client.close(1001, "That's all!");
}
});
}
}
public void onConnected() {
this.pubSubManager.subscribe(provisioningAddress, this);
this.client.sendRequest("PUT", "/v1/address", Optional.of(ProvisioningUuid.newBuilder()
.setUuid(provisioningAddress.getAddress())
.build()
.toByteArray()));
}
public void onConnectionLost() {
this.pubSubManager.unsubscribe(provisioningAddress, this);
this.client.close(1001, "Done");
}
}