diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5e15d318c5..f4bf7b48ad 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -205,6 +205,7 @@ + getPreKeys(Context context, MasterSecret masterSecret) { - List records = new LinkedList(); - File directory = getPreKeysDirectory(context); - String[] keyRecordIds = directory.list(); - - Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator()); - - for (String keyRecordId : keyRecordIds) { - try { - if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) { - records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId))); - } - } catch (InvalidKeyIdException e) { - Log.w("PreKeyUtil", e); - new File(getPreKeysDirectory(context), keyRecordId).delete(); - } catch (NumberFormatException nfe) { - Log.w("PreKeyUtil", nfe); - new File(getPreKeysDirectory(context), keyRecordId).delete(); - } - } - - return records; - } - - public static void clearPreKeys(Context context) { - File directory = getPreKeysDirectory(context); - String[] keyRecords = directory.list(); - - for (String keyRecord : keyRecords) { - new File(directory, keyRecord).delete(); - } - } +// public static List getPreKeys(Context context, MasterSecret masterSecret) { +// List records = new LinkedList(); +// File directory = getPreKeysDirectory(context); +// String[] keyRecordIds = directory.list(); +// +// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator()); +// +// for (String keyRecordId : keyRecordIds) { +// try { +// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) { +// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId))); +// } +// } catch (InvalidKeyIdException e) { +// Log.w("PreKeyUtil", e); +// new File(getPreKeysDirectory(context), keyRecordId).delete(); +// } catch (NumberFormatException nfe) { +// Log.w("PreKeyUtil", nfe); +// new File(getPreKeysDirectory(context), keyRecordId).delete(); +// } +// } +// +// return records; +// } +// +// public static void clearPreKeys(Context context) { +// File directory = getPreKeysDirectory(context); +// String[] keyRecords = directory.list(); +// +// for (String keyRecord : keyRecords) { +// new File(directory, keyRecord).delete(); +// } +// } private static void setNextPreKeyId(Context context, int id) { try { @@ -126,7 +125,7 @@ public class PreKeyUtil { try { File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME); - if (nextFile.exists()) { + if (!nextFile.exists()) { return Util.getSecureRandom().nextInt(Medium.MAX_VALUE); } else { InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile)); diff --git a/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java b/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java new file mode 100644 index 0000000000..1a361e1fae --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/PreKeyStatus.java @@ -0,0 +1,12 @@ +package org.whispersystems.textsecure.push; + +public class PreKeyStatus { + + private int count; + + public PreKeyStatus() {} + + public int getCount() { + return count; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 360266d0c7..abae23f565 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -40,6 +40,7 @@ public class PushServiceSocket { private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s"; private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; + private static final String PREKEY_METADATA_PATH = "/v1/keys/"; private static final String PREKEY_PATH = "/v1/keys/%s"; private static final String PREKEY_DEVICE_PATH = "/v1/keys/%s/%s"; @@ -123,6 +124,13 @@ public class PushServiceSocket { PreKeyList.toJson(new PreKeyList(lastResortEntity, entities))); } + public int getAvailablePreKeys() throws IOException { + String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null); + PreKeyStatus preKeyStatus = new Gson().fromJson(responseText, PreKeyStatus.class); + + return preKeyStatus.getCount(); + } + public List getPreKeys(PushAddress destination) throws IOException { try { String deviceId = String.valueOf(destination.getDeviceId()); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index f1ade383cd..2bbf886a82 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.service.PreKeyService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -121,6 +122,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { PreKeyRecord.delete(context, preKeyId); } + PreKeyService.initiateRefresh(context, masterSecret); + DatabaseFactory.getIdentityDatabase(context) .saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey); } diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java new file mode 100644 index 0000000000..9296eb51de --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java @@ -0,0 +1,90 @@ +package org.thoughtcrime.securesms.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.push.PushServiceSocketFactory; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.PreKeyUtil; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.push.PushServiceSocket; +import org.whispersystems.textsecure.storage.PreKeyRecord; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class PreKeyService extends Service { + + private static final String TAG = PreKeyService.class.getSimpleName(); + public static final String REFRESH_ACTION = "org.thoughtcrime.securesms.PreKeyService.REFRESH"; + + private static final int PREKEY_MINIMUM = 10; + + private final Executor executor = Executors.newSingleThreadExecutor(); + + public static void initiateRefresh(Context context, MasterSecret masterSecret) { + Intent intent = new Intent(context, PreKeyService.class); + intent.setAction(PreKeyService.REFRESH_ACTION); + intent.putExtra("master_secret", masterSecret); + context.startService(intent); + } + + @Override + public int onStartCommand(Intent intent, int flats, int startId) { + if (REFRESH_ACTION.equals(intent.getAction())) { + MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); + executor.execute(new RefreshTask(this, masterSecret)); + } + + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private static class RefreshTask implements Runnable { + + private final Context context; + private final MasterSecret masterSecret; + + public RefreshTask(Context context, MasterSecret masterSecret) { + this.context = context.getApplicationContext(); + this.masterSecret = masterSecret; + } + + public void run() { + try { + if (!TextSecurePreferences.isPushRegistered(context)) return; + + PushServiceSocket socket = PushServiceSocketFactory.create(context); + int availableKeys = socket.getAvailablePreKeys(); + + if (availableKeys >= PREKEY_MINIMUM) { + Log.w(TAG, "Available keys sufficient: " + availableKeys); + return; + } + + List preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret); + PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret); + IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE); + + Log.w(TAG, "Registering new prekeys..."); + + socket.registerPreKeys(identityKey, lastResortKeyRecord, preKeyRecords); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + +} diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index e56def7f34..ad69f5f890 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -68,7 +68,6 @@ public class RegistrationService extends Service { public static final String GCM_REGISTRATION_ID = "GCMRegistrationId"; private static final long REGISTRATION_TIMEOUT_MILLIS = 120000; - private static final Object GENERATING_PREKEYS_SEMAPHOR = new Object(); private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final Binder binder = new RegistrationServiceBinder(); @@ -144,27 +143,6 @@ public class RegistrationService extends Service { registerReceiver(gcmRegistrationReceiver, filter); } - private void initializePreKeyGenerator(final MasterSecret masterSecret) { - synchronized (GENERATING_PREKEYS_SEMAPHOR) { - if (generatingPreKeys) return; - else generatingPreKeys = true; - } - - new Thread() { - public void run() { - if (PreKeyUtil.getPreKeys(RegistrationService.this, masterSecret).size() < PreKeyUtil.BATCH_SIZE) { - PreKeyUtil.generatePreKeys(RegistrationService.this, masterSecret); - PreKeyUtil.generateLastResortKey(RegistrationService.this, masterSecret); - } - - synchronized (GENERATING_PREKEYS_SEMAPHOR) { - generatingPreKeys = false; - GENERATING_PREKEYS_SEMAPHOR.notifyAll(); - } - } - }.start(); - } - private synchronized void shutdownChallengeListener() { if (challengeReceiver != null) { unregisterReceiver(challengeReceiver); @@ -195,7 +173,6 @@ public class RegistrationService extends Service { try { initializeGcmRegistrationListener(); - initializePreKeyGenerator(masterSecret); PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password); @@ -240,8 +217,6 @@ public class RegistrationService extends Service { initializeChallengeListener(); initializeGcmRegistrationListener(); - initializePreKeyGenerator(masterSecret); - setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number)); PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password); @@ -287,7 +262,7 @@ public class RegistrationService extends Service { { setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this, Curve.DJB_TYPE); - List records = waitForPreKeys(masterSecret); + List records = PreKeyUtil.generatePreKeys(this, masterSecret); PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret); socket.registerPreKeys(identityKey, lastResort, records); @@ -333,20 +308,6 @@ public class RegistrationService extends Service { return this.gcmRegistrationId; } - private List waitForPreKeys(MasterSecret masterSecret) { - synchronized (GENERATING_PREKEYS_SEMAPHOR) { - while (generatingPreKeys) { - try { - GENERATING_PREKEYS_SEMAPHOR.wait(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - } - } - - return PreKeyUtil.getPreKeys(this, masterSecret); - } - private synchronized void challengeReceived(String challenge) { this.challenge = challenge; notifyAll();