From 2d083208cce72dab7741a1ba57b500ebbbb846dc Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 1 Oct 2013 16:26:04 -0700 Subject: [PATCH] Handle negative directory case and unlisted contacts. --- .../textsecure/directory/Directory.java | 60 +++++++++++-------- .../directory/NotInDirectoryException.java | 4 ++ .../textsecure/push/NotFoundException.java | 9 +++ .../textsecure/push/PushServiceSocket.java | 18 +++++- .../service/RegistrationService.java | 4 +- .../transport/UniversalTransport.java | 38 +++++++++++- 6 files changed, 101 insertions(+), 32 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/directory/NotInDirectoryException.java create mode 100644 library/src/org/whispersystems/textsecure/push/NotFoundException.java diff --git a/library/src/org/whispersystems/textsecure/directory/Directory.java b/library/src/org/whispersystems/textsecure/directory/Directory.java index f812e35445..c2e32e8c06 100644 --- a/library/src/org/whispersystems/textsecure/directory/Directory.java +++ b/library/src/org/whispersystems/textsecure/directory/Directory.java @@ -15,8 +15,8 @@ import org.whispersystems.textsecure.util.Util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -53,20 +53,7 @@ public class Directory { this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); } - public boolean containsNumbers(List e164numbers) { - if (e164numbers == null || e164numbers.isEmpty()) { - return false; - } - - for (String e164number : e164numbers) { - if (!containsNumber(e164number)) - return false; - } - - return true; - } - - public boolean containsNumber(String e164number) { + public boolean isActiveNumber(String e164number) throws NotInDirectoryException { if (e164number == null || e164number.length() == 0) { return false; } @@ -82,29 +69,33 @@ public class Directory { if (cursor != null && cursor.moveToFirst()) { return cursor.getInt(0) == 1; + } else { + throw new NotInDirectoryException(); } - return false; } finally { if (cursor != null) cursor.close(); } } - public void setActiveTokens(List tokens) { + public void setToken(String token, boolean active) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(TOKEN, token); + values.put(REGISTERED, active ? 1 : 0); + values.put(TIMESTAMP, System.currentTimeMillis()); + db.replace(TABLE_NAME, null, values); + } + + public void setTokens(List activeTokens, Collection inactiveTokens) { long timestamp = System.currentTimeMillis(); SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.beginTransaction(); try { - ContentValues clear = new ContentValues(); - clear.put(REGISTERED, 0); - clear.put(TIMESTAMP, timestamp); - db.update(TABLE_NAME, clear, null, null); - - - for (String token : tokens) { - Log.w("Directory", "Adding token: " + token); + for (String token : activeTokens) { + Log.w("Directory", "Adding active token: " + token); ContentValues values = new ContentValues(); values.put(TOKEN, token); values.put(REGISTERED, 1); @@ -112,6 +103,14 @@ public class Directory { db.replace(TABLE_NAME, null, values); } + for (String token : inactiveTokens) { + ContentValues values = new ContentValues(); + values.put(TOKEN, token); + values.put(REGISTERED, 0); + values.put(TIMESTAMP, timestamp); + db.replace(TABLE_NAME, null, values); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -135,6 +134,15 @@ public class Directory { } } + cursor.close(); + + cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {TOKEN}, + null, null, null, null, null); + + while (cursor.moveToNext()) { + results.add(cursor.getString(0)); + } + return results; } finally { if (cursor != null) @@ -142,7 +150,7 @@ public class Directory { } } - private String getToken(String e164number) { + public String getToken(String e164number) { try { MessageDigest digest = MessageDigest.getInstance("SHA1"); byte[] token = Util.trim(digest.digest(e164number.getBytes()), 10); diff --git a/library/src/org/whispersystems/textsecure/directory/NotInDirectoryException.java b/library/src/org/whispersystems/textsecure/directory/NotInDirectoryException.java new file mode 100644 index 0000000000..45299ee870 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/directory/NotInDirectoryException.java @@ -0,0 +1,4 @@ +package org.whispersystems.textsecure.directory; + +public class NotInDirectoryException extends Throwable { +} diff --git a/library/src/org/whispersystems/textsecure/push/NotFoundException.java b/library/src/org/whispersystems/textsecure/push/NotFoundException.java new file mode 100644 index 0000000000..b27d791e39 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/NotFoundException.java @@ -0,0 +1,9 @@ +package org.whispersystems.textsecure.push; + +import java.io.IOException; + +public class NotFoundException extends IOException { + public NotFoundException(String s) { + super(s); + } +} diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 22070d871e..6f878ef9f1 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -42,7 +42,8 @@ public class PushServiceSocket { private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String PREKEY_PATH = "/v1/keys/%s"; - private static final String DIRECTORY_PATH = "/v1/directory/"; + private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens"; + private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s"; private static final String MESSAGE_PATH = "/v1/messages/"; private static final String ATTACHMENT_PATH = "/v1/attachments/%s"; @@ -172,7 +173,7 @@ public class PushServiceSocket { public List retrieveDirectory(Set contactTokens) { try { ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens)); - String response = makeRequest(DIRECTORY_PATH, "PUT", new Gson().toJson(contactTokenList)); + String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList)); ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class); return activeTokens.getContacts(); @@ -182,6 +183,15 @@ public class PushServiceSocket { } } + public boolean isRegisteredUser(String contactToken) throws IOException { + try { + makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null); + return true; + } catch (NotFoundException nfe) { + return false; + } + } + private void downloadExternalFile(String url, File localDestination) throws IOException { @@ -284,6 +294,10 @@ public class PushServiceSocket { throw new AuthorizationFailedException("Authorization failed!"); } + if (connection.getResponseCode() == 404) { + throw new NotFoundException("Not found"); + } + if (connection.getResponseCode() != 200) { throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage()); } diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index eeaf11f54b..8f5e138dd7 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -286,8 +286,8 @@ public class RegistrationService extends Service { List activeTokens = socket.retrieveDirectory(contactTokens); if (activeTokens != null) { - Directory.getInstance(this).setActiveTokens(activeTokens); -// NumberFilter.getInstance(this).update(directory.first, directory.second); + contactTokens.removeAll(activeTokens); + Directory.getInstance(this).setTokens(activeTokens, contactTokens); } } diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java index 3e4ee69e49..1f16214339 100644 --- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java +++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java @@ -26,6 +26,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.directory.Directory; +import org.whispersystems.textsecure.directory.NotInDirectoryException; +import org.whispersystems.textsecure.push.PushServiceSocket; import java.io.IOException; import java.util.LinkedList; @@ -57,7 +59,7 @@ public class UniversalTransport { Recipient recipient = message.getIndividualRecipient(); String number = Util.canonicalizeNumber(context, recipient.getNumber()); - if (Directory.getInstance(context).containsNumber(number)) { + if (isPushTransport(number)) { try { Log.w("UniversalTransport", "Delivering with GCM..."); pushTransport.deliver(message); @@ -78,7 +80,7 @@ public class UniversalTransport { List destinations = getMediaDestinations(mediaMessage); - if (Directory.getInstance(context).containsNumbers(destinations)) { + if (isPushTransport(destinations)) { try { Log.w("UniversalTransport", "Delivering media message with GCM..."); pushTransport.deliver(mediaMessage, destinations); @@ -117,4 +119,36 @@ public class UniversalTransport { return destinations; } + private boolean isPushTransport(String destination) { + Directory directory = Directory.getInstance(context); + + try { + return directory.isActiveNumber(destination); + } catch (NotInDirectoryException e) { + try { + String localNumber = TextSecurePreferences.getLocalNumber(context); + String pushPassword = TextSecurePreferences.getPushServerPassword(context); + String contactToken = directory.getToken(destination); + PushServiceSocket socket = new PushServiceSocket(context, localNumber, pushPassword); + boolean registeredUser = socket.isRegisteredUser(contactToken); + + directory.setToken(contactToken, registeredUser); + + return registeredUser; + } catch (IOException e1) { + Log.w("UniversalTransport", e1); + return false; + } + } + } + + private boolean isPushTransport(List destinations) { + for (String destination : destinations) { + if (!isPushTransport(destination)) { + return false; + } + } + + return true; + } }