libtextsecure javadoc and minor API refactoring

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-02-27 15:35:18 -08:00
parent fcde642563
commit 004f050741
18 changed files with 452 additions and 73 deletions

View File

@ -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<String> 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<PreKeyRecord> 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<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
{
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 {
@ -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<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;
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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> 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<PushAddress> recipients, TextSecureMessage message)
throws IOException, EncapsulatedExceptions
{

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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<TextSecureAttachment> 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<String> members,
TextSecureAttachment avatar)

View File

@ -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<TextSecureAttachment> 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<TextSecureAttachment> 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<TextSecureAttachment> 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<List<TextSecureAttachment>> getAttachments() {
return attachments;
}
/**
* @return The message body (if any).
*/
public Optional<String> getBody() {
return body;
}
/**
* @return The message group info (if any).
*/
public Optional<TextSecureGroup> getGroupInfo() {
return group;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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<ContactTokenDetails> registeredUser = accountManager.getContact(contactToken);
Optional<ContactTokenDetails> registeredUser = accountManager.getContact(destination);
if (!registeredUser.isPresent()) {
registeredUser = Optional.of(new ContactTokenDetails());

View File

@ -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<String> eligibleContactNumbers = directory.getPushEligibleContactNumbers(localNumber);
Map<String, String> tokenMap = DirectoryUtil.getDirectoryServerTokenMap(eligibleContactNumbers);
List<ContactTokenDetails> activeTokens = accountManager.getContacts(tokenMap.keySet());
List<ContactTokenDetails> 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);

View File

@ -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;
}
}