diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java index 4d2afb37ef..3326520222 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureAccountManager.java @@ -30,19 +30,40 @@ import org.whispersystems.textsecure.api.push.SignedPreKeyEntity; import org.whispersystems.textsecure.api.push.TrustStore; import org.whispersystems.textsecure.internal.crypto.ProvisioningCipher; 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.Util; 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.Map; import java.util.Set; 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 { private final PushServiceSocket pushServiceSocket; 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, String user, String password) { @@ -50,6 +71,12 @@ public class TextSecureAccountManager { 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 gcmRegistrationId) throws IOException { if (gcmRegistrationId.isPresent()) { 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 { 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 { 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, boolean supportsSms, int axolotlRegistrationId) throws IOException @@ -74,6 +129,17 @@ public class TextSecureAccountManager { 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, SignedPreKeyRecord signedPreKey, List oneTimePreKeys) throws IOException @@ -81,26 +147,68 @@ public class TextSecureAccountManager { 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 { 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 { this.pushServiceSocket.setCurrentSignedPreKey(signedPreKey); } + /** + * @return The server's view of the client's current signed prekey. + * @throws IOException + */ public SignedPreKeyEntity getSignedPreKey() throws IOException { return this.pushServiceSocket.getCurrentSignedPreKey(); } - public Optional 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 getContact(String e164number) throws IOException { + String contactToken = createDirectoryServerToken(e164number); + ContactTokenDetails contactTokenDetails = this.pushServiceSocket.getContactTokenDetails(contactToken); + + if (contactTokenDetails != null) { + contactTokenDetails.setNumber(e164number); + } + + return Optional.fromNullable(contactTokenDetails); } - public List getContacts(Set contactTokens) + /** + * 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 getContacts(Set e164numbers) throws IOException { - return this.pushServiceSocket.retrieveDirectory(contactTokens); + Map contactTokensMap = createDirectoryServerTokenMap(e164numbers); + List activeTokens = this.pushServiceSocket.retrieveDirectory(contactTokensMap.keySet()); + + for (ContactTokenDetails activeToken : activeTokens) { + activeToken.setNumber(contactTokensMap.get(activeToken.getToken())); + } + + return activeTokens; } public String getNewDeviceVerificationCode() throws IOException { @@ -125,4 +233,24 @@ public class TextSecureAccountManager { 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 createDirectoryServerTokenMap(Collection e164numbers) { + Map tokenMap = new HashMap<>(e164numbers.size()); + + for (String number : e164numbers) { + tokenMap.put(createDirectoryServerToken(number), number); + } + + return tokenMap; + } + } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java index 6036f0dee5..1550c7dbe6 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessagePipe.java @@ -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.WebSocketResponseMessage; +/** + * A TextSecureMessagePipe represents a dedicated connection + * to the TextSecure server, which the server can push messages + * down. + */ public class TextSecureMessagePipe { private final WebSocketConnection websocket; private final CredentialsProvider credentialsProvider; - public TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) { + TextSecureMessagePipe(WebSocketConnection websocket, CredentialsProvider credentialsProvider) { this.websocket = websocket; this.credentialsProvider = credentialsProvider; 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) throws InvalidVersionException, IOException, TimeoutException { 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) throws TimeoutException, IOException, InvalidVersionException { @@ -51,6 +86,9 @@ public class TextSecureMessagePipe { } } + /** + * Close this connection to the server. + */ public void shutdown() { 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 void onMessage(TextSecureEnvelope envelope); } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java index a7e30be72a..ecbd8eba92 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageReceiver.java @@ -17,25 +17,23 @@ package org.whispersystems.textsecure.api; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream; 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.util.CredentialsProvider; import org.whispersystems.textsecure.internal.push.PushServiceSocket; import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider; import org.whispersystems.textsecure.internal.websocket.WebSocketConnection; -import org.whispersystems.textsecure.internal.websocket.WebSocketProtos; import java.io.File; import java.io.IOException; 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 { private final PushServiceSocket socket; @@ -43,12 +41,30 @@ public class TextSecureMessageReceiver { private final String url; 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, String user, String password, String 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) { this.url = url; this.trustStore = trustStore; @@ -56,6 +72,17 @@ public class TextSecureMessageReceiver { 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) throws IOException, InvalidMessageException { @@ -63,6 +90,13 @@ public class TextSecureMessageReceiver { 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() { WebSocketConnection webSocket = new WebSocketConnection(url, trustStore, credentialsProvider); return new TextSecureMessagePipe(webSocket, credentialsProvider); diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java index 540adab208..1116d584c4 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/TextSecureMessageSender.java @@ -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.GroupContext; +/** + * The main interface for sending TextSecure messages. + * + * @author Moxie Marlinspike + */ public class TextSecureMessageSender { private static final String TAG = TextSecureMessageSender.class.getSimpleName(); @@ -70,6 +75,18 @@ public class TextSecureMessageSender { private final PushAddress syncAddress; private final Optional 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, String user, String password, long userId, AxolotlStore store, @@ -81,10 +98,25 @@ public class TextSecureMessageSender { 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 { 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) 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 recipients, TextSecureMessage message) throws IOException, EncapsulatedExceptions { diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java index af2be1dc32..46376a4f2f 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/crypto/TextSecureCipher.java @@ -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.GroupContext.Type.DELIVER; +/** + * This is used to decrypt received {@link org.whispersystems.textsecure.api.messages.TextSecureEnvelope}s. + * + * @author Moxie Marlinspike + */ public class TextSecureCipher { private final SessionCipher sessionCipher; @@ -57,6 +62,20 @@ public class TextSecureCipher { 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) throws InvalidVersionException, InvalidMessageException, InvalidKeyException, DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java index 455d1e81ac..a86e6199d2 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentPointer.java @@ -18,6 +18,13 @@ package org.whispersystems.textsecure.api.messages; 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 { private final long id; diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java index 8bf9eb52e1..2f57a1bcc8 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureAttachmentStream.java @@ -18,6 +18,9 @@ package org.whispersystems.textsecure.api.messages; import java.io.InputStream; +/** + * Represents a local TextSecureAttachment to be sent. + */ public class TextSecureAttachmentStream extends TextSecureAttachment { private final InputStream inputStream; diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java index 09876d00fc..630b7807d9 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureEnvelope.java @@ -39,6 +39,14 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; 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 { private static final String TAG = TextSecureEnvelope.class.getSimpleName(); @@ -56,12 +64,29 @@ public class TextSecureEnvelope { 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) throws IOException, InvalidVersionException { 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) throws InvalidVersionException, IOException { @@ -89,42 +114,72 @@ public class TextSecureEnvelope { .build(); } + /** + * @return The envelope's sender. + */ public String getSource() { return signal.getSource(); } + /** + * @return The envelope's sender device ID. + */ public int getSourceDevice() { return signal.getSourceDevice(); } + /** + * @return The envelope content type. + */ public int getType() { return signal.getType().getNumber(); } + /** + * @return The federated server this envelope came from. + */ public String getRelay() { return signal.getRelay(); } + /** + * @return The timestamp this envelope was sent. + */ public long getTimestamp() { return signal.getTimestamp(); } + /** + * @return The envelope's containing message. + */ public byte[] getMessage() { return signal.getMessage().toByteArray(); } + /** + * @return true if the containing message is a {@link org.whispersystems.libaxolotl.protocol.WhisperMessage} + */ public boolean isWhisperMessage() { 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() { return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE; } + /** + * @return true if the containing message is plaintext. + */ public boolean isPlaintext() { return signal.getType().getNumber() == IncomingPushMessageSignal.Type.PLAINTEXT_VALUE; } + /** + * @return true if the containing message is a delivery receipt. + */ public boolean isReceipt() { return signal.getType().getNumber() == IncomingPushMessageSignal.Type.RECEIPT_VALUE; } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureGroup.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureGroup.java index 8a3d492966..83d6a755d2 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureGroup.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureGroup.java @@ -20,6 +20,19 @@ import org.whispersystems.libaxolotl.util.guava.Optional; 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 enum Type { @@ -36,10 +49,22 @@ public class TextSecureGroup { private final Optional avatar; + /** + * Construct a DELIVER group context. + * @param groupId + */ public TextSecureGroup(byte[] groupId) { 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, List members, TextSecureAttachment avatar) diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java index 93580fa843..a70d073cfa 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/messages/TextSecureMessage.java @@ -20,6 +20,9 @@ import org.whispersystems.libaxolotl.util.guava.Optional; import java.util.List; +/** + * Represents a decrypted text secure message. + */ public class TextSecureMessage { private final long timestamp; @@ -29,18 +32,49 @@ public class TextSecureMessage { private final boolean secure; 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) { 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 attachments, String 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 attachments, String body) { 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 attachments, String body, boolean secure, boolean endSession) { this.timestamp = timestamp; this.body = Optional.fromNullable(body); @@ -55,18 +89,30 @@ public class TextSecureMessage { } } + /** + * @return The message timestamp. + */ public long getTimestamp() { return timestamp; } + /** + * @return The message attachments (if any). + */ public Optional> getAttachments() { return attachments; } + /** + * @return The message body (if any). + */ public Optional getBody() { return body; } + /** + * @return The message group info (if any). + */ public Optional getGroupInfo() { return group; } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java index 93e4539541..3012492446 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/ContactTokenDetails.java @@ -16,6 +16,9 @@ */ package org.whispersystems.textsecure.api.push; +/** + * A class that represents a contact's registration state. + */ public class ContactTokenDetails { private String token; @@ -25,14 +28,23 @@ public class ContactTokenDetails { public ContactTokenDetails() {} + /** + * @return The "anonymized" token (truncated hash) that's transmitted to the server. + */ public String getToken() { return token; } + /** + * @return The federated server this contact is registered with, or null if on your server. + */ public String getRelay() { return relay; } + /** + * @return Whether this contact supports receiving encrypted SMS. + */ public boolean isSupportsSms() { return supportsSms; } @@ -41,6 +53,9 @@ public class ContactTokenDetails { this.number = number; } + /** + * @return This contact's username (e164 formatted number). + */ public String getNumber() { return number; } diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/PushAddress.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/PushAddress.java index f33d4a5710..7072fc8c65 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/PushAddress.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/PushAddress.java @@ -16,6 +16,9 @@ */ package org.whispersystems.textsecure.api.push; +/** + * A class representing a message destination or origin. + */ public class PushAddress { public static final int DEFAULT_DEVICE_ID = 1; @@ -24,6 +27,13 @@ public class PushAddress { private final String e164number; 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) { this.recipientId = recipientId; this.e164number = e164number; diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/TrustStore.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/TrustStore.java index b03ac60ad2..e62e4f1a8a 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/TrustStore.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/api/push/TrustStore.java @@ -18,6 +18,10 @@ package org.whispersystems.textsecure.api.push; import java.io.InputStream; +/** + * A class that represents a Java {@link java.security.KeyStore} and + * its associated password. + */ public interface TrustStore { public InputStream getKeyStoreInputStream(); public String getKeyStorePassword(); diff --git a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java index 51e89c9a8f..1147f1da7a 100644 --- a/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java +++ b/libtextsecure/src/main/java/org/whispersystems/textsecure/internal/util/Util.java @@ -50,6 +50,13 @@ public class Util { 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) { return value == null || value.trim().length() == 0; } diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 2092f0271a..dad6e5f501 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -118,8 +118,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null, null, null, true, true)); } else { - messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null, - message.getBody().getBody())); + messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), message.getBody().getBody())); } return true; diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 5269ace9c6..258a5c6c1a 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -45,7 +45,6 @@ import org.whispersystems.jobqueue.JobManager; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.TextSecureAccountManager; import org.whispersystems.textsecure.api.push.ContactTokenDetails; -import org.thoughtcrime.securesms.util.DirectoryUtil; import org.whispersystems.textsecure.api.util.InvalidNumberException; import java.io.IOException; @@ -283,8 +282,7 @@ public class MessageSender { } catch (NotInDirectoryException e) { try { TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context); - String contactToken = DirectoryUtil.getDirectoryServerToken(destination); - Optional registeredUser = accountManager.getContact(contactToken); + Optional registeredUser = accountManager.getContact(destination); if (!registeredUser.isPresent()) { registeredUser = Optional.of(new ContactTokenDetails()); diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java index 55436d08ef..be3f66d966 100644 --- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java +++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java @@ -15,7 +15,6 @@ import org.whispersystems.textsecure.api.util.InvalidNumberException; import java.io.IOException; import java.util.List; -import java.util.Map; import java.util.Set; public class DirectoryHelper { @@ -67,13 +66,12 @@ public class DirectoryHelper { { TextSecureDirectory directory = TextSecureDirectory.getInstance(context); Set eligibleContactNumbers = directory.getPushEligibleContactNumbers(localNumber); - Map tokenMap = DirectoryUtil.getDirectoryServerTokenMap(eligibleContactNumbers); - List activeTokens = accountManager.getContacts(tokenMap.keySet()); + List activeTokens = accountManager.getContacts(eligibleContactNumbers); if (activeTokens != null) { for (ContactTokenDetails activeToken : activeTokens) { - eligibleContactNumbers.remove(tokenMap.get(activeToken.getToken())); - activeToken.setNumber(tokenMap.get(activeToken.getToken())); + eligibleContactNumbers.remove(activeToken.getNumber()); + activeToken.setNumber(activeToken.getNumber()); } directory.setNumbers(activeTokens, eligibleContactNumbers); diff --git a/src/org/thoughtcrime/securesms/util/DirectoryUtil.java b/src/org/thoughtcrime/securesms/util/DirectoryUtil.java deleted file mode 100644 index dae144669c..0000000000 --- a/src/org/thoughtcrime/securesms/util/DirectoryUtil.java +++ /dev/null @@ -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 . - */ -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 getDirectoryServerTokenMap(Collection e164numbers) { - final Map tokenMap = new HashMap(e164numbers.size()); - for (String number : e164numbers) { - tokenMap.put(getDirectoryServerToken(number), number); - } - return tokenMap; - } -}