mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 01:07:47 +00:00
libtextsecure javadoc and minor API refactoring
// FREEBIE
This commit is contained in:
parent
fcde642563
commit
004f050741
@ -30,19 +30,40 @@ import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
|||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.internal.crypto.ProvisioningCipher;
|
import org.whispersystems.textsecure.internal.crypto.ProvisioningCipher;
|
||||||
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Base64;
|
||||||
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.whispersystems.textsecure.internal.push.ProvisioningProtos.ProvisionMessage;
|
import static org.whispersystems.textsecure.internal.push.ProvisioningProtos.ProvisionMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main interface for creating, registering, and
|
||||||
|
* managing a TextSecure account.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureAccountManager {
|
public class TextSecureAccountManager {
|
||||||
|
|
||||||
private final PushServiceSocket pushServiceSocket;
|
private final PushServiceSocket pushServiceSocket;
|
||||||
private final String user;
|
private final String user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureAccountManager.
|
||||||
|
*
|
||||||
|
* @param url The URL for the TextSecure server.
|
||||||
|
* @param trustStore The {@link org.whispersystems.textsecure.api.push.TrustStore} for the TextSecure server's TLS certificate.
|
||||||
|
* @param user A TextSecure phone number.
|
||||||
|
* @param password A TextSecure password.
|
||||||
|
*/
|
||||||
public TextSecureAccountManager(String url, TrustStore trustStore,
|
public TextSecureAccountManager(String url, TrustStore trustStore,
|
||||||
String user, String password)
|
String user, String password)
|
||||||
{
|
{
|
||||||
@ -50,6 +71,12 @@ public class TextSecureAccountManager {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register/Unregister a Google Cloud Messaging registration ID.
|
||||||
|
*
|
||||||
|
* @param gcmRegistrationId The GCM id to register. A call with an absent value will unregister.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void setGcmId(Optional<String> gcmRegistrationId) throws IOException {
|
public void setGcmId(Optional<String> gcmRegistrationId) throws IOException {
|
||||||
if (gcmRegistrationId.isPresent()) {
|
if (gcmRegistrationId.isPresent()) {
|
||||||
this.pushServiceSocket.registerGcmId(gcmRegistrationId.get());
|
this.pushServiceSocket.registerGcmId(gcmRegistrationId.get());
|
||||||
@ -58,14 +85,42 @@ public class TextSecureAccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request an SMS verification code. On success, the server will send
|
||||||
|
* an SMS verification code to this TextSecure user.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void requestSmsVerificationCode() throws IOException {
|
public void requestSmsVerificationCode() throws IOException {
|
||||||
this.pushServiceSocket.createAccount(false);
|
this.pushServiceSocket.createAccount(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a Voice verification code. On success, the server will
|
||||||
|
* make a voice call to this TextSecure user.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void requestVoiceVerificationCode() throws IOException {
|
public void requestVoiceVerificationCode() throws IOException {
|
||||||
this.pushServiceSocket.createAccount(true);
|
this.pushServiceSocket.createAccount(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a TextSecure account.
|
||||||
|
*
|
||||||
|
* @param verificationCode The verification code received via SMS or Voice
|
||||||
|
* (see {@link #requestSmsVerificationCode} and
|
||||||
|
* {@link #requestVoiceVerificationCode}).
|
||||||
|
* @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key,
|
||||||
|
* concatenated.
|
||||||
|
* @param supportsSms Indicate whether this client is capable of supporting encrypted SMS.
|
||||||
|
* @param axolotlRegistrationId A random 14-bit number that identifies this TextSecure install.
|
||||||
|
* This value should remain consistent across registrations for the
|
||||||
|
* same install, but probabilistically differ across registrations
|
||||||
|
* for separate installs.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void verifyAccount(String verificationCode, String signalingKey,
|
public void verifyAccount(String verificationCode, String signalingKey,
|
||||||
boolean supportsSms, int axolotlRegistrationId)
|
boolean supportsSms, int axolotlRegistrationId)
|
||||||
throws IOException
|
throws IOException
|
||||||
@ -74,6 +129,17 @@ public class TextSecureAccountManager {
|
|||||||
supportsSms, axolotlRegistrationId);
|
supportsSms, axolotlRegistrationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an identity key, last resort key, signed prekey, and list of one time prekeys
|
||||||
|
* with the server.
|
||||||
|
*
|
||||||
|
* @param identityKey The client's long-term identity keypair.
|
||||||
|
* @param lastResortKey The client's "last resort" prekey.
|
||||||
|
* @param signedPreKey The client's signed prekey.
|
||||||
|
* @param oneTimePreKeys The client's list of one-time prekeys.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void setPreKeys(IdentityKey identityKey, PreKeyRecord lastResortKey,
|
public void setPreKeys(IdentityKey identityKey, PreKeyRecord lastResortKey,
|
||||||
SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
SignedPreKeyRecord signedPreKey, List<PreKeyRecord> oneTimePreKeys)
|
||||||
throws IOException
|
throws IOException
|
||||||
@ -81,26 +147,68 @@ public class TextSecureAccountManager {
|
|||||||
this.pushServiceSocket.registerPreKeys(identityKey, lastResortKey, signedPreKey, oneTimePreKeys);
|
this.pushServiceSocket.registerPreKeys(identityKey, lastResortKey, signedPreKey, oneTimePreKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The server's count of currently available (eg. unused) prekeys for this user.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public int getPreKeysCount() throws IOException {
|
public int getPreKeysCount() throws IOException {
|
||||||
return this.pushServiceSocket.getAvailablePreKeys();
|
return this.pushServiceSocket.getAvailablePreKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the client's signed prekey.
|
||||||
|
*
|
||||||
|
* @param signedPreKey The client's new signed prekey.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void setSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
public void setSignedPreKey(SignedPreKeyRecord signedPreKey) throws IOException {
|
||||||
this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey);
|
this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The server's view of the client's current signed prekey.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public SignedPreKeyEntity getSignedPreKey() throws IOException {
|
public SignedPreKeyEntity getSignedPreKey() throws IOException {
|
||||||
return this.pushServiceSocket.getCurrentSignedPreKey();
|
return this.pushServiceSocket.getCurrentSignedPreKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ContactTokenDetails> getContact(String contactToken) throws IOException {
|
/**
|
||||||
return Optional.fromNullable(this.pushServiceSocket.getContactTokenDetails(contactToken));
|
* Checks whether a contact is currently registered with the server.
|
||||||
|
*
|
||||||
|
* @param e164number The contact to check.
|
||||||
|
* @return An optional ContactTokenDetails, present if registered, absent if not.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public Optional<ContactTokenDetails> getContact(String e164number) throws IOException {
|
||||||
|
String contactToken = createDirectoryServerToken(e164number);
|
||||||
|
ContactTokenDetails contactTokenDetails = this.pushServiceSocket.getContactTokenDetails(contactToken);
|
||||||
|
|
||||||
|
if (contactTokenDetails != null) {
|
||||||
|
contactTokenDetails.setNumber(e164number);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ContactTokenDetails> getContacts(Set<String> contactTokens)
|
return Optional.fromNullable(contactTokenDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks which contacts in a set are registered with the server.
|
||||||
|
*
|
||||||
|
* @param e164numbers The contacts to check.
|
||||||
|
* @return A list of ContactTokenDetails for the registered users.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public List<ContactTokenDetails> getContacts(Set<String> e164numbers)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
return this.pushServiceSocket.retrieveDirectory(contactTokens);
|
Map<String, String> contactTokensMap = createDirectoryServerTokenMap(e164numbers);
|
||||||
|
List<ContactTokenDetails> activeTokens = this.pushServiceSocket.retrieveDirectory(contactTokensMap.keySet());
|
||||||
|
|
||||||
|
for (ContactTokenDetails activeToken : activeTokens) {
|
||||||
|
activeToken.setNumber(contactTokensMap.get(activeToken.getToken()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewDeviceVerificationCode() throws IOException {
|
public String getNewDeviceVerificationCode() throws IOException {
|
||||||
@ -125,4 +233,24 @@ public class TextSecureAccountManager {
|
|||||||
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
|
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String createDirectoryServerToken(String e164number) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||||
|
byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10);
|
||||||
|
return Base64.encodeBytesWithoutPadding(token);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> createDirectoryServerTokenMap(Collection<String> e164numbers) {
|
||||||
|
Map<String,String> tokenMap = new HashMap<>(e164numbers.size());
|
||||||
|
|
||||||
|
for (String number : e164numbers) {
|
||||||
|
tokenMap.put(createDirectoryServerToken(number), number);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenMap;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,24 +12,59 @@ import java.util.concurrent.TimeoutException;
|
|||||||
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
||||||
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
|
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TextSecureMessagePipe represents a dedicated connection
|
||||||
|
* to the TextSecure server, which the server can push messages
|
||||||
|
* down.
|
||||||
|
*/
|
||||||
public class TextSecureMessagePipe {
|
public class TextSecureMessagePipe {
|
||||||
|
|
||||||
private final WebSocketConnection websocket;
|
private final WebSocketConnection websocket;
|
||||||
private final CredentialsProvider credentialsProvider;
|
private final CredentialsProvider credentialsProvider;
|
||||||
|
|
||||||
public TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) {
|
TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) {
|
||||||
this.websocket = websocket;
|
this.websocket = websocket;
|
||||||
this.credentialsProvider = credentialsProvider;
|
this.credentialsProvider = credentialsProvider;
|
||||||
|
|
||||||
this.websocket.connect();
|
this.websocket.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A blocking call that reads a message off the pipe. When this
|
||||||
|
* call returns, the message has been acknowledged and will not
|
||||||
|
* be retransmitted.
|
||||||
|
*
|
||||||
|
* @param timeout The timeout to wait for.
|
||||||
|
* @param unit The timeout time unit.
|
||||||
|
* @return A new message.
|
||||||
|
*
|
||||||
|
* @throws InvalidVersionException
|
||||||
|
* @throws IOException
|
||||||
|
* @throws TimeoutException
|
||||||
|
*/
|
||||||
public TextSecureEnvelope read(long timeout, TimeUnit unit)
|
public TextSecureEnvelope read(long timeout, TimeUnit unit)
|
||||||
throws InvalidVersionException, IOException, TimeoutException
|
throws InvalidVersionException, IOException, TimeoutException
|
||||||
{
|
{
|
||||||
return read(timeout, unit, new NullMessagePipeCallback());
|
return read(timeout, unit, new NullMessagePipeCallback());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A blocking call that reads a message off the pipe (see {@link #read(long, java.util.concurrent.TimeUnit)}
|
||||||
|
*
|
||||||
|
* Unlike {@link #read(long, java.util.concurrent.TimeUnit)}, this method allows you
|
||||||
|
* to specify a callback that will be called before the received message is acknowledged.
|
||||||
|
* This allows you to write the received message to durable storage before acknowledging
|
||||||
|
* receipt of it to the server.
|
||||||
|
*
|
||||||
|
* @param timeout The timeout to wait for.
|
||||||
|
* @param unit The timeout time unit.
|
||||||
|
* @param callback A callback that will be called before the message receipt is
|
||||||
|
* acknowledged to the server.
|
||||||
|
* @return The message read (same as the message sent through the callback).
|
||||||
|
* @throws TimeoutException
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InvalidVersionException
|
||||||
|
*/
|
||||||
public TextSecureEnvelope read(long timeout, TimeUnit unit, MessagePipeCallback callback)
|
public TextSecureEnvelope read(long timeout, TimeUnit unit, MessagePipeCallback callback)
|
||||||
throws TimeoutException, IOException, InvalidVersionException
|
throws TimeoutException, IOException, InvalidVersionException
|
||||||
{
|
{
|
||||||
@ -51,6 +86,9 @@ public class TextSecureMessagePipe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close this connection to the server.
|
||||||
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
websocket.disconnect();
|
websocket.disconnect();
|
||||||
}
|
}
|
||||||
@ -75,6 +113,10 @@ public class TextSecureMessagePipe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For receiving a callback when a new message has been
|
||||||
|
* received.
|
||||||
|
*/
|
||||||
public static interface MessagePipeCallback {
|
public static interface MessagePipeCallback {
|
||||||
public void onMessage(TextSecureEnvelope envelope);
|
public void onMessage(TextSecureEnvelope envelope);
|
||||||
}
|
}
|
||||||
|
@ -17,25 +17,23 @@
|
|||||||
package org.whispersystems.textsecure.api;
|
package org.whispersystems.textsecure.api;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
|
||||||
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
|
||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
import org.whispersystems.textsecure.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
||||||
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
||||||
import org.whispersystems.textsecure.internal.websocket.WebSocketConnection;
|
import org.whispersystems.textsecure.internal.websocket.WebSocketConnection;
|
||||||
import org.whispersystems.textsecure.internal.websocket.WebSocketProtos;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import static org.whispersystems.textsecure.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primary interface for receiving TextSecure messages.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureMessageReceiver {
|
public class TextSecureMessageReceiver {
|
||||||
|
|
||||||
private final PushServiceSocket socket;
|
private final PushServiceSocket socket;
|
||||||
@ -43,12 +41,30 @@ public class TextSecureMessageReceiver {
|
|||||||
private final String url;
|
private final String url;
|
||||||
private final CredentialsProvider credentialsProvider;
|
private final CredentialsProvider credentialsProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessageReceiver.
|
||||||
|
*
|
||||||
|
* @param url The URL of the TextSecure server.
|
||||||
|
* @param trustStore The {@link org.whispersystems.textsecure.api.push.TrustStore} containing
|
||||||
|
* the server's TLS signing certificate.
|
||||||
|
* @param user The TextSecure user's username (eg. phone number).
|
||||||
|
* @param password The TextSecure user's password.
|
||||||
|
* @param signalingKey The 52 byte signaling key assigned to this user at registration.
|
||||||
|
*/
|
||||||
public TextSecureMessageReceiver(String url, TrustStore trustStore,
|
public TextSecureMessageReceiver(String url, TrustStore trustStore,
|
||||||
String user, String password, String signalingKey)
|
String user, String password, String signalingKey)
|
||||||
{
|
{
|
||||||
this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey));
|
this(url, trustStore, new StaticCredentialsProvider(user, password, signalingKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessageReceiver.
|
||||||
|
*
|
||||||
|
* @param url The URL of the TextSecure server.
|
||||||
|
* @param trustStore The {@link org.whispersystems.textsecure.api.push.TrustStore} containing
|
||||||
|
* the server's TLS signing certificate.
|
||||||
|
* @param credentials The TextSecure user's credentials.
|
||||||
|
*/
|
||||||
public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsProvider credentials) {
|
public TextSecureMessageReceiver(String url, TrustStore trustStore, CredentialsProvider credentials) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.trustStore = trustStore;
|
this.trustStore = trustStore;
|
||||||
@ -56,6 +72,17 @@ public class TextSecureMessageReceiver {
|
|||||||
this.socket = new PushServiceSocket(url, trustStore, credentials);
|
this.socket = new PushServiceSocket(url, trustStore, credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a TextSecure attachment.
|
||||||
|
*
|
||||||
|
* @param pointer The {@link org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer}
|
||||||
|
* received in a {@link org.whispersystems.textsecure.api.messages.TextSecureMessage}.
|
||||||
|
* @param destination The download destination for this attachment.
|
||||||
|
*
|
||||||
|
* @return An InputStream that streams the plaintext attachment contents.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InvalidMessageException
|
||||||
|
*/
|
||||||
public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination)
|
public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination)
|
||||||
throws IOException, InvalidMessageException
|
throws IOException, InvalidMessageException
|
||||||
{
|
{
|
||||||
@ -63,6 +90,13 @@ public class TextSecureMessageReceiver {
|
|||||||
return new AttachmentCipherInputStream(destination, pointer.getKey());
|
return new AttachmentCipherInputStream(destination, pointer.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a pipe for receiving TextSecure messages.
|
||||||
|
*
|
||||||
|
* Callers must call {@link TextSecureMessagePipe#shutdown()} when finished with the pipe.
|
||||||
|
*
|
||||||
|
* @return A TextSecureMessagePipe for receiving TextSecure messages.
|
||||||
|
*/
|
||||||
public TextSecureMessagePipe createMessagePipe() {
|
public TextSecureMessagePipe createMessagePipe() {
|
||||||
WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider);
|
WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider);
|
||||||
return new TextSecureMessagePipe(webSocket, credentialsProvider);
|
return new TextSecureMessagePipe(webSocket, credentialsProvider);
|
||||||
|
@ -61,6 +61,11 @@ import static org.whispersystems.textsecure.internal.push.PushMessageProtos.Push
|
|||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
|
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
|
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main interface for sending TextSecure messages.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureMessageSender {
|
public class TextSecureMessageSender {
|
||||||
|
|
||||||
private static final String TAG = TextSecureMessageSender.class.getSimpleName();
|
private static final String TAG = TextSecureMessageSender.class.getSimpleName();
|
||||||
@ -70,6 +75,18 @@ public class TextSecureMessageSender {
|
|||||||
private final PushAddress syncAddress;
|
private final PushAddress syncAddress;
|
||||||
private final Optional<EventListener> eventListener;
|
private final Optional<EventListener> eventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessageSender.
|
||||||
|
*
|
||||||
|
* @param url The URL of the TextSecure server.
|
||||||
|
* @param trustStore The trust store containing the TextSecure server's signing TLS certificate.
|
||||||
|
* @param user The TextSecure username (eg phone number).
|
||||||
|
* @param password The TextSecure user's password.
|
||||||
|
* @param userId The axolotl recipient id for the local TextSecure user.
|
||||||
|
* @param store The AxolotlStore.
|
||||||
|
* @param eventListener An optional event listener, which fires whenever sessions are
|
||||||
|
* setup or torn down for a recipient.
|
||||||
|
*/
|
||||||
public TextSecureMessageSender(String url, TrustStore trustStore,
|
public TextSecureMessageSender(String url, TrustStore trustStore,
|
||||||
String user, String password,
|
String user, String password,
|
||||||
long userId, AxolotlStore store,
|
long userId, AxolotlStore store,
|
||||||
@ -81,10 +98,25 @@ public class TextSecureMessageSender {
|
|||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a delivery receipt for a received message. It is not necessary to call this
|
||||||
|
* when receiving messages through {@link org.whispersystems.textsecure.api.TextSecureMessagePipe}.
|
||||||
|
* @param recipient The sender of the received message you're acknowledging.
|
||||||
|
* @param messageId The message id of the received message you're acknowledging.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void sendDeliveryReceipt(PushAddress recipient, long messageId) throws IOException {
|
public void sendDeliveryReceipt(PushAddress recipient, long messageId) throws IOException {
|
||||||
this.socket.sendReceipt(recipient.getNumber(), messageId, recipient.getRelay());
|
this.socket.sendReceipt(recipient.getNumber(), messageId, recipient.getRelay());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a single recipient.
|
||||||
|
*
|
||||||
|
* @param recipient The message's destination.
|
||||||
|
* @param message The message.
|
||||||
|
* @throws UntrustedIdentityException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void sendMessage(PushAddress recipient, TextSecureMessage message)
|
public void sendMessage(PushAddress recipient, TextSecureMessage message)
|
||||||
throws UntrustedIdentityException, IOException
|
throws UntrustedIdentityException, IOException
|
||||||
{
|
{
|
||||||
@ -106,6 +138,14 @@ public class TextSecureMessageSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a group.
|
||||||
|
*
|
||||||
|
* @param recipients The group members.
|
||||||
|
* @param message The group message.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws EncapsulatedExceptions
|
||||||
|
*/
|
||||||
public void sendMessage(List<PushAddress> recipients, TextSecureMessage message)
|
public void sendMessage(List<PushAddress> recipients, TextSecureMessage message)
|
||||||
throws IOException, EncapsulatedExceptions
|
throws IOException, EncapsulatedExceptions
|
||||||
{
|
{
|
||||||
|
@ -44,6 +44,11 @@ import java.util.List;
|
|||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
|
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent;
|
||||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
|
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to decrypt received {@link org.whispersystems.textsecure.api.messages.TextSecureEnvelope}s.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureCipher {
|
public class TextSecureCipher {
|
||||||
|
|
||||||
private final SessionCipher sessionCipher;
|
private final SessionCipher sessionCipher;
|
||||||
@ -57,6 +62,20 @@ public class TextSecureCipher {
|
|||||||
return sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
return sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a received {@link org.whispersystems.textsecure.api.messages.TextSecureEnvelope}
|
||||||
|
*
|
||||||
|
* @param envelope The received TextSecureEnvelope
|
||||||
|
* @return a decrypted TextSecureMessage
|
||||||
|
* @throws InvalidVersionException
|
||||||
|
* @throws InvalidMessageException
|
||||||
|
* @throws InvalidKeyException
|
||||||
|
* @throws DuplicateMessageException
|
||||||
|
* @throws InvalidKeyIdException
|
||||||
|
* @throws UntrustedIdentityException
|
||||||
|
* @throws LegacyMessageException
|
||||||
|
* @throws NoSessionException
|
||||||
|
*/
|
||||||
public TextSecureMessage decrypt(TextSecureEnvelope envelope)
|
public TextSecureMessage decrypt(TextSecureEnvelope envelope)
|
||||||
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
throws InvalidVersionException, InvalidMessageException, InvalidKeyException,
|
||||||
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException,
|
||||||
|
@ -18,6 +18,13 @@ package org.whispersystems.textsecure.api.messages;
|
|||||||
|
|
||||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a received TextSecureMessage attachment "handle." This
|
||||||
|
* is a pointer to the actual attachment content, which needs to be
|
||||||
|
* retrieved using {@link org.whispersystems.textsecure.api.TextSecureMessageReceiver#retrieveAttachment(TextSecureAttachmentPointer, java.io.File)}
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureAttachmentPointer extends TextSecureAttachment {
|
public class TextSecureAttachmentPointer extends TextSecureAttachment {
|
||||||
|
|
||||||
private final long id;
|
private final long id;
|
||||||
|
@ -18,6 +18,9 @@ package org.whispersystems.textsecure.api.messages;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a local TextSecureAttachment to be sent.
|
||||||
|
*/
|
||||||
public class TextSecureAttachmentStream extends TextSecureAttachment {
|
public class TextSecureAttachmentStream extends TextSecureAttachment {
|
||||||
|
|
||||||
private final InputStream inputStream;
|
private final InputStream inputStream;
|
||||||
|
@ -39,6 +39,14 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an encrypted TextSecure envelope.
|
||||||
|
*
|
||||||
|
* The envelope contains the wrapping information, such as the sender, the
|
||||||
|
* message timestamp, the encrypted message type, etc.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureEnvelope {
|
public class TextSecureEnvelope {
|
||||||
|
|
||||||
private static final String TAG = TextSecureEnvelope.class.getSimpleName();
|
private static final String TAG = TextSecureEnvelope.class.getSimpleName();
|
||||||
@ -56,12 +64,29 @@ public class TextSecureEnvelope {
|
|||||||
|
|
||||||
private final IncomingPushMessageSignal signal;
|
private final IncomingPushMessageSignal signal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an envelope from a serialized, Base64 encoded TextSecureEnvelope, encrypted
|
||||||
|
* with a signaling key.
|
||||||
|
*
|
||||||
|
* @param message The serialized TextSecureEnvelope, base64 encoded and encrypted.
|
||||||
|
* @param signalingKey The signaling key.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InvalidVersionException
|
||||||
|
*/
|
||||||
public TextSecureEnvelope(String message, String signalingKey)
|
public TextSecureEnvelope(String message, String signalingKey)
|
||||||
throws IOException, InvalidVersionException
|
throws IOException, InvalidVersionException
|
||||||
{
|
{
|
||||||
this(Base64.decode(message), signalingKey);
|
this(Base64.decode(message), signalingKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an envelope from a serialized TextSecureEnvelope, encrypted with a signaling key.
|
||||||
|
*
|
||||||
|
* @param ciphertext The serialized and encrypted TextSecureEnvelope.
|
||||||
|
* @param signalingKey The signaling key.
|
||||||
|
* @throws InvalidVersionException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
public TextSecureEnvelope(byte[] ciphertext, String signalingKey)
|
public TextSecureEnvelope(byte[] ciphertext, String signalingKey)
|
||||||
throws InvalidVersionException, IOException
|
throws InvalidVersionException, IOException
|
||||||
{
|
{
|
||||||
@ -89,42 +114,72 @@ public class TextSecureEnvelope {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope's sender.
|
||||||
|
*/
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return signal.getSource();
|
return signal.getSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope's sender device ID.
|
||||||
|
*/
|
||||||
public int getSourceDevice() {
|
public int getSourceDevice() {
|
||||||
return signal.getSourceDevice();
|
return signal.getSourceDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope content type.
|
||||||
|
*/
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return signal.getType().getNumber();
|
return signal.getType().getNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The federated server this envelope came from.
|
||||||
|
*/
|
||||||
public String getRelay() {
|
public String getRelay() {
|
||||||
return signal.getRelay();
|
return signal.getRelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The timestamp this envelope was sent.
|
||||||
|
*/
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
return signal.getTimestamp();
|
return signal.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The envelope's containing message.
|
||||||
|
*/
|
||||||
public byte[] getMessage() {
|
public byte[] getMessage() {
|
||||||
return signal.getMessage().toByteArray();
|
return signal.getMessage().toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
|
||||||
|
*/
|
||||||
public boolean isWhisperMessage() {
|
public boolean isWhisperMessage() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE;
|
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}
|
||||||
|
*/
|
||||||
public boolean isPreKeyWhisperMessage() {
|
public boolean isPreKeyWhisperMessage() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the containing message is plaintext.
|
||||||
|
*/
|
||||||
public boolean isPlaintext() {
|
public boolean isPlaintext() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PLAINTEXT_VALUE;
|
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PLAINTEXT_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the containing message is a delivery receipt.
|
||||||
|
*/
|
||||||
public boolean isReceipt() {
|
public boolean isReceipt() {
|
||||||
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.RECEIPT_VALUE;
|
return signal.getType().getNumber() == IncomingPushMessageSignal.Type.RECEIPT_VALUE;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,19 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group information to include in TextSecureMessages destined to groups.
|
||||||
|
*
|
||||||
|
* This class represents a "context" that is included with textsecure messages
|
||||||
|
* to make them group messages. There are three types of context:
|
||||||
|
*
|
||||||
|
* 1) Update -- Sent when either creating a group, or updating the properties
|
||||||
|
* of a group (such as the avatar icon, membership list, or title).
|
||||||
|
* 2) Deliver -- Sent when a message is to be delivered to an existing group.
|
||||||
|
* 3) Quit -- Sent when the sender wishes to leave an existing group.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
public class TextSecureGroup {
|
public class TextSecureGroup {
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
@ -36,10 +49,22 @@ public class TextSecureGroup {
|
|||||||
private final Optional<TextSecureAttachment> avatar;
|
private final Optional<TextSecureAttachment> avatar;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a DELIVER group context.
|
||||||
|
* @param groupId
|
||||||
|
*/
|
||||||
public TextSecureGroup(byte[] groupId) {
|
public TextSecureGroup(byte[] groupId) {
|
||||||
this(Type.DELIVER, groupId, null, null, null);
|
this(Type.DELIVER, groupId, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a group context.
|
||||||
|
* @param type The group message type (update, deliver, quit).
|
||||||
|
* @param groupId The group ID.
|
||||||
|
* @param name The group title.
|
||||||
|
* @param members The group membership list.
|
||||||
|
* @param avatar The group avatar icon.
|
||||||
|
*/
|
||||||
public TextSecureGroup(Type type, byte[] groupId, String name,
|
public TextSecureGroup(Type type, byte[] groupId, String name,
|
||||||
List<String> members,
|
List<String> members,
|
||||||
TextSecureAttachment avatar)
|
TextSecureAttachment avatar)
|
||||||
|
@ -20,6 +20,9 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a decrypted text secure message.
|
||||||
|
*/
|
||||||
public class TextSecureMessage {
|
public class TextSecureMessage {
|
||||||
|
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
@ -29,18 +32,49 @@ public class TextSecureMessage {
|
|||||||
private final boolean secure;
|
private final boolean secure;
|
||||||
private final boolean endSession;
|
private final boolean endSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessage with a body and no attachments.
|
||||||
|
*
|
||||||
|
* @param timestamp The sent timestamp.
|
||||||
|
* @param body The message contents.
|
||||||
|
*/
|
||||||
public TextSecureMessage(long timestamp, String body) {
|
public TextSecureMessage(long timestamp, String body) {
|
||||||
this(timestamp, null, body);
|
this(timestamp, null, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessage with a body and list of attachments.
|
||||||
|
*
|
||||||
|
* @param timestamp The sent timestamp.
|
||||||
|
* @param attachments The attachments.
|
||||||
|
* @param body The message contents.
|
||||||
|
*/
|
||||||
public TextSecureMessage(long timestamp, List<TextSecureAttachment> attachments, String body) {
|
public TextSecureMessage(long timestamp, List<TextSecureAttachment> attachments, String body) {
|
||||||
this(timestamp, null, attachments, body);
|
this(timestamp, null, attachments, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecure group message with attachments and body.
|
||||||
|
*
|
||||||
|
* @param timestamp The sent timestamp.
|
||||||
|
* @param group The group information.
|
||||||
|
* @param attachments The attachments.
|
||||||
|
* @param body The message contents.
|
||||||
|
*/
|
||||||
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
|
||||||
this(timestamp, group, attachments, body, true, false);
|
this(timestamp, group, attachments, body, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a TextSecureMessage.
|
||||||
|
*
|
||||||
|
* @param timestamp The sent timestamp.
|
||||||
|
* @param group The group information (or null if none).
|
||||||
|
* @param attachments The attachments (or null if none).
|
||||||
|
* @param body The message contents.
|
||||||
|
* @param secure Flag indicating whether this message is to be encrypted.
|
||||||
|
* @param endSession Flag indicating whether this message should close a session.
|
||||||
|
*/
|
||||||
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body, boolean secure, boolean endSession) {
|
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body, boolean secure, boolean endSession) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.body = Optional.fromNullable(body);
|
this.body = Optional.fromNullable(body);
|
||||||
@ -55,18 +89,30 @@ public class TextSecureMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The message timestamp.
|
||||||
|
*/
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The message attachments (if any).
|
||||||
|
*/
|
||||||
public Optional<List<TextSecureAttachment>> getAttachments() {
|
public Optional<List<TextSecureAttachment>> getAttachments() {
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The message body (if any).
|
||||||
|
*/
|
||||||
public Optional<String> getBody() {
|
public Optional<String> getBody() {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The message group info (if any).
|
||||||
|
*/
|
||||||
public Optional<TextSecureGroup> getGroupInfo() {
|
public Optional<TextSecureGroup> getGroupInfo() {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.api.push;
|
package org.whispersystems.textsecure.api.push;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a contact's registration state.
|
||||||
|
*/
|
||||||
public class ContactTokenDetails {
|
public class ContactTokenDetails {
|
||||||
|
|
||||||
private String token;
|
private String token;
|
||||||
@ -25,14 +28,23 @@ public class ContactTokenDetails {
|
|||||||
|
|
||||||
public ContactTokenDetails() {}
|
public ContactTokenDetails() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The "anonymized" token (truncated hash) that's transmitted to the server.
|
||||||
|
*/
|
||||||
public String getToken() {
|
public String getToken() {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The federated server this contact is registered with, or null if on your server.
|
||||||
|
*/
|
||||||
public String getRelay() {
|
public String getRelay() {
|
||||||
return relay;
|
return relay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether this contact supports receiving encrypted SMS.
|
||||||
|
*/
|
||||||
public boolean isSupportsSms() {
|
public boolean isSupportsSms() {
|
||||||
return supportsSms;
|
return supportsSms;
|
||||||
}
|
}
|
||||||
@ -41,6 +53,9 @@ public class ContactTokenDetails {
|
|||||||
this.number = number;
|
this.number = number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return This contact's username (e164 formatted number).
|
||||||
|
*/
|
||||||
public String getNumber() {
|
public String getNumber() {
|
||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecure.api.push;
|
package org.whispersystems.textsecure.api.push;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing a message destination or origin.
|
||||||
|
*/
|
||||||
public class PushAddress {
|
public class PushAddress {
|
||||||
|
|
||||||
public static final int DEFAULT_DEVICE_ID = 1;
|
public static final int DEFAULT_DEVICE_ID = 1;
|
||||||
@ -24,6 +27,13 @@ public class PushAddress {
|
|||||||
private final String e164number;
|
private final String e164number;
|
||||||
private final String relay;
|
private final String relay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a PushAddress.
|
||||||
|
*
|
||||||
|
* @param recipientId The axolotl recipient ID of this destination.
|
||||||
|
* @param e164number The TextSecure username of this destination (eg e164 representation of a phone number).
|
||||||
|
* @param relay The TextSecure federated server this user is registered with (if not your own server).
|
||||||
|
*/
|
||||||
public PushAddress(long recipientId, String e164number, String relay) {
|
public PushAddress(long recipientId, String e164number, String relay) {
|
||||||
this.recipientId = recipientId;
|
this.recipientId = recipientId;
|
||||||
this.e164number = e164number;
|
this.e164number = e164number;
|
||||||
|
@ -18,6 +18,10 @@ package org.whispersystems.textsecure.api.push;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that represents a Java {@link java.security.KeyStore} and
|
||||||
|
* its associated password.
|
||||||
|
*/
|
||||||
public interface TrustStore {
|
public interface TrustStore {
|
||||||
public InputStream getKeyStoreInputStream();
|
public InputStream getKeyStoreInputStream();
|
||||||
public String getKeyStorePassword();
|
public String getKeyStorePassword();
|
||||||
|
@ -50,6 +50,13 @@ public class Util {
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] trim(byte[] input, int length) {
|
||||||
|
byte[] result = new byte[length];
|
||||||
|
System.arraycopy(input, 0, result, 0, result.length);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isEmpty(String value) {
|
public static boolean isEmpty(String value) {
|
||||||
return value == null || value.trim().length() == 0;
|
return value == null || value.trim().length() == 0;
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
|||||||
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null,
|
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null,
|
||||||
null, null, true, true));
|
null, null, true, true));
|
||||||
} else {
|
} else {
|
||||||
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null,
|
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), message.getBody().getBody()));
|
||||||
message.getBody().getBody()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -45,7 +45,6 @@ import org.whispersystems.jobqueue.JobManager;
|
|||||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
||||||
import org.whispersystems.textsecure.api.push.ContactTokenDetails;
|
import org.whispersystems.textsecure.api.push.ContactTokenDetails;
|
||||||
import org.thoughtcrime.securesms.util.DirectoryUtil;
|
|
||||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -283,8 +282,7 @@ public class MessageSender {
|
|||||||
} catch (NotInDirectoryException e) {
|
} catch (NotInDirectoryException e) {
|
||||||
try {
|
try {
|
||||||
TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context);
|
TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context);
|
||||||
String contactToken = DirectoryUtil.getDirectoryServerToken(destination);
|
Optional<ContactTokenDetails> registeredUser = accountManager.getContact(destination);
|
||||||
Optional<ContactTokenDetails> registeredUser = accountManager.getContact(contactToken);
|
|
||||||
|
|
||||||
if (!registeredUser.isPresent()) {
|
if (!registeredUser.isPresent()) {
|
||||||
registeredUser = Optional.of(new ContactTokenDetails());
|
registeredUser = Optional.of(new ContactTokenDetails());
|
||||||
|
@ -15,7 +15,6 @@ import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class DirectoryHelper {
|
public class DirectoryHelper {
|
||||||
@ -67,13 +66,12 @@ public class DirectoryHelper {
|
|||||||
{
|
{
|
||||||
TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
|
TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
|
||||||
Set<String> eligibleContactNumbers = directory.getPushEligibleContactNumbers(localNumber);
|
Set<String> eligibleContactNumbers = directory.getPushEligibleContactNumbers(localNumber);
|
||||||
Map<String, String> tokenMap = DirectoryUtil.getDirectoryServerTokenMap(eligibleContactNumbers);
|
List<ContactTokenDetails> activeTokens = accountManager.getContacts(eligibleContactNumbers);
|
||||||
List<ContactTokenDetails> activeTokens = accountManager.getContacts(tokenMap.keySet());
|
|
||||||
|
|
||||||
if (activeTokens != null) {
|
if (activeTokens != null) {
|
||||||
for (ContactTokenDetails activeToken : activeTokens) {
|
for (ContactTokenDetails activeToken : activeTokens) {
|
||||||
eligibleContactNumbers.remove(tokenMap.get(activeToken.getToken()));
|
eligibleContactNumbers.remove(activeToken.getNumber());
|
||||||
activeToken.setNumber(tokenMap.get(activeToken.getToken()));
|
activeToken.setNumber(activeToken.getNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
directory.setNumbers(activeTokens, eligibleContactNumbers);
|
directory.setNumbers(activeTokens, eligibleContactNumbers);
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2014 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU 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 General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.util;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DirectoryUtil {
|
|
||||||
|
|
||||||
public static String getDirectoryServerToken(String e164number) {
|
|
||||||
try {
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
|
||||||
byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10);
|
|
||||||
return Base64.encodeBytesWithoutPadding(token);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a mapping of directory server tokens to their requested number.
|
|
||||||
* @param e164numbers
|
|
||||||
* @return map with token as key, E164 number as value
|
|
||||||
*/
|
|
||||||
public static Map<String, String> getDirectoryServerTokenMap(Collection<String> e164numbers) {
|
|
||||||
final Map<String,String> tokenMap = new HashMap<String,String>(e164numbers.size());
|
|
||||||
for (String number : e164numbers) {
|
|
||||||
tokenMap.put(getDirectoryServerToken(number), number);
|
|
||||||
}
|
|
||||||
return tokenMap;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user