Resign ourselves to a less sophisticated contact intersection method.

This commit is contained in:
Moxie Marlinspike 2013-09-30 13:24:42 -07:00
parent 073b1f69e3
commit 75cca3add1
7 changed files with 219 additions and 22 deletions

View File

@ -67,7 +67,7 @@ public class PreKeyUtil {
for (String keyRecordId : keyRecordIds) { for (String keyRecordId : keyRecordIds) {
try { try {
if (Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) { if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId))); records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
} }
} catch (InvalidKeyIdException e) { } catch (InvalidKeyIdException e) {

View File

@ -0,0 +1,175 @@
package org.whispersystems.textsecure.directory;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.util.Log;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import org.whispersystems.textsecure.util.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class Directory {
private static final String DATABASE_NAME = "whisper_directory.db";
private static final int DATABASE_VERSION = 1;
private static final String TABLE_NAME = "directory";
private static final String ID = "_id";
private static final String TOKEN = "token";
private static final String REGISTERED = "registered";
private static final String SUPPORTS_SMS = "supports_sms";
private static final String TIMESTAMP = "timestamp";
private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY, " +
TOKEN + " TEXT UNIQUE, " + REGISTERED + " INTEGER, " +
SUPPORTS_SMS + " INTEGER, " + TIMESTAMP + " INTEGER);";
private static Directory instance;
public synchronized static Directory getInstance(Context context) {
if (instance == null) {
instance = new Directory(context);
}
return instance;
}
private final DatabaseHelper databaseHelper;
private final Context context;
private Directory(Context context) {
this.context = context;
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public boolean containsNumbers(List<String> e164numbers) {
if (e164numbers == null || e164numbers.isEmpty()) {
return false;
}
for (String e164number : e164numbers) {
if (!containsNumber(e164number))
return false;
}
return true;
}
public boolean containsNumber(String e164number) {
if (e164number == null || e164number.length() == 0) {
return false;
}
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String token = getToken(e164number);
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME,
new String[] {REGISTERED}, TOKEN + " = ?",
new String[] {token}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0) == 1;
}
return false;
} finally {
if (cursor != null)
cursor.close();
}
}
public void setActiveTokens(List<String> tokens) {
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);
ContentValues values = new ContentValues();
values.put(TOKEN, token);
values.put(REGISTERED, 1);
values.put(TIMESTAMP, timestamp);
db.replace(TABLE_NAME, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
public Set<String> getPushEligibleContactTokens(String localNumber) {
Uri uri = Phone.CONTENT_URI;
Set<String> results = new HashSet<String>();
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, new String[] {Phone.NUMBER}, null, null, null);
while (cursor.moveToNext()) {
String rawNumber = cursor.getString(0);
if (rawNumber != null) {
String e164Number = PhoneNumberFormatter.formatNumber(rawNumber, localNumber);
results.add(getToken(e164Number));
}
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
private String getToken(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 class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory,
int version)
{
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View File

@ -0,0 +1,18 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class ContactTokenList {
private List<String> contacts;
public ContactTokenList(List<String> contacts) {
this.contacts = contacts;
}
public ContactTokenList() {}
public List<String> getContacts() {
return contacts;
}
}

View File

@ -8,7 +8,6 @@ import com.google.thoughtcrimegson.Gson;
import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.R;
import org.whispersystems.textsecure.Release; import org.whispersystems.textsecure.Release;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.directory.DirectoryDescriptor;
import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
@ -33,6 +32,7 @@ import java.security.cert.CertificateException;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set;
public class PushServiceSocket { public class PushServiceSocket {
@ -169,16 +169,13 @@ public class PushServiceSocket {
return attachment; return attachment;
} }
public Pair<DirectoryDescriptor, File> retrieveDirectory() { public List<String> retrieveDirectory(Set<String> contactTokens) {
try { try {
DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null), ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens));
DirectoryDescriptor.class); String response = makeRequest(DIRECTORY_PATH, "PUT", new Gson().toJson(contactTokenList));
ContactTokenList activeTokens = new Gson().fromJson(response, ContactTokenList.class);
File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir()); return activeTokens.getContacts();
downloadExternalFile(directoryDescriptor.getUrl(), directoryData);
return new Pair<DirectoryDescriptor, File>(directoryDescriptor, directoryData);
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("PushServiceSocket", ioe); Log.w("PushServiceSocket", ioe);
return null; return null;

View File

@ -73,6 +73,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;
} }

View File

@ -9,7 +9,6 @@ import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.google.android.gcm.GCMRegistrar; import com.google.android.gcm.GCMRegistrar;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -20,15 +19,14 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PreKeyUtil; import org.whispersystems.textsecure.crypto.PreKeyUtil;
import org.whispersystems.textsecure.directory.DirectoryDescriptor; import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NumberFilter;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -282,12 +280,14 @@ public class RegistrationService extends Service {
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID); GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID);
String gcmRegistrationId = waitForGcmRegistrationId(); String gcmRegistrationId = waitForGcmRegistrationId();
socket.registerGcmId(gcmRegistrationId); socket.registerGcmId(gcmRegistrationId);
Pair<DirectoryDescriptor, File> directory = socket.retrieveDirectory();
if (directory != null) { Set<String> contactTokens = Directory.getInstance(this).getPushEligibleContactTokens(number);
NumberFilter.getInstance(this).update(directory.first, directory.second); List<String> activeTokens = socket.retrieveDirectory(contactTokens);
if (activeTokens != null) {
Directory.getInstance(this).setActiveTokens(activeTokens);
// NumberFilter.getInstance(this).update(directory.first, directory.second);
} }
} }

View File

@ -20,12 +20,12 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.directory.NumberFilter; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.directory.Directory;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
@ -57,7 +57,7 @@ public class UniversalTransport {
Recipient recipient = message.getIndividualRecipient(); Recipient recipient = message.getIndividualRecipient();
String number = Util.canonicalizeNumber(context, recipient.getNumber()); String number = Util.canonicalizeNumber(context, recipient.getNumber());
if (NumberFilter.getInstance(context).containsNumber(number)) { if (Directory.getInstance(context).containsNumber(number)) {
try { try {
Log.w("UniversalTransport", "Delivering with GCM..."); Log.w("UniversalTransport", "Delivering with GCM...");
pushTransport.deliver(message); pushTransport.deliver(message);
@ -78,7 +78,7 @@ public class UniversalTransport {
List<String> destinations = getMediaDestinations(mediaMessage); List<String> destinations = getMediaDestinations(mediaMessage);
if (NumberFilter.getInstance(context).containsNumbers(destinations)) { if (Directory.getInstance(context).containsNumbers(destinations)) {
try { try {
Log.w("UniversalTransport", "Delivering media message with GCM..."); Log.w("UniversalTransport", "Delivering media message with GCM...");
pushTransport.deliver(mediaMessage, destinations); pushTransport.deliver(mediaMessage, destinations);