diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ce18289bf6..e7a4f93789 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -502,7 +502,12 @@
-
+
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 09fbd009fa..87b721196f 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -34,7 +34,9 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvi
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
+import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
+import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.dependencies.DependencyInjector;
@@ -75,6 +77,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
initializeExpiringMessageManager();
initializeGcmCheck();
initializeSignedPreKeyCheck();
+ initializePeriodicTasks();
}
@Override
@@ -150,4 +153,9 @@ public class ApplicationContext extends Application implements DependencyInjecto
this.expiringMessageManager = new ExpiringMessageManager(this);
}
+ private void initializePeriodicTasks() {
+ RotateSignedPreKeyListener.schedule(this);
+ DirectoryRefreshListener.schedule(this);
+ }
+
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java
index 7ec9d40409..ce3ec32514 100644
--- a/src/org/thoughtcrime/securesms/ConversationListActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java
@@ -73,7 +73,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
initializeContactUpdatesReceiver();
- DirectoryRefreshListener.schedule(this);
RatingManager.showRatingDialogIfNecessary(this);
}
diff --git a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
index 7333bfbfa9..84981a90f9 100644
--- a/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/PreKeyUtil.java
@@ -35,6 +35,7 @@ import org.whispersystems.libsignal.state.PreKeyStore;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.libsignal.util.Medium;
+import org.whispersystems.libsignal.util.guava.Optional;
import java.io.File;
import java.io.FileInputStream;
@@ -46,7 +47,9 @@ import java.util.List;
public class PreKeyUtil {
- public static final int BATCH_SIZE = 100;
+ private static final String TAG = PreKeyUtil.class.getName();
+
+ private static final int BATCH_SIZE = 100;
public static List generatePreKeys(Context context) {
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
@@ -66,7 +69,7 @@ public class PreKeyUtil {
return records;
}
- public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair)
+ public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active)
{
try {
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
@@ -78,6 +81,10 @@ public class PreKeyUtil {
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
+ if (active) {
+ setActiveSignedPreKeyId(context, signedPreKeyId);
+ }
+
return record;
} catch (InvalidKeyException e) {
throw new AssertionError(e);
@@ -104,7 +111,7 @@ public class PreKeyUtil {
return record;
}
- private static void setNextPreKeyId(Context context, int id) {
+ private static synchronized void setNextPreKeyId(Context context, int id) {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
FileOutputStream fout = new FileOutputStream(nextFile);
@@ -115,18 +122,36 @@ public class PreKeyUtil {
}
}
- private static void setNextSignedPreKeyId(Context context, int id) {
+ private static synchronized void setNextSignedPreKeyId(Context context, int id) {
try {
- File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
- FileOutputStream fout = new FileOutputStream(nextFile);
- fout.write(JsonUtils.toJson(new SignedPreKeyIndex(id)).getBytes());
- fout.close();
+ SignedPreKeyIndex index = getSignedPreKeyIndex(context).or(new SignedPreKeyIndex());
+ index.nextSignedPreKeyId = id;
+
+ setSignedPreKeyIndex(context, index);
} catch (IOException e) {
- Log.w("PreKeyUtil", e);
+ Log.w(TAG, e);
}
}
- private static int getNextPreKeyId(Context context) {
+ public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
+ try {
+ SignedPreKeyIndex index = getSignedPreKeyIndex(context).or(new SignedPreKeyIndex());
+ index.activeSignedPreKeyId = id;
+
+ setSignedPreKeyIndex(context, index);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public static synchronized int getActiveSignedPreKeyId(Context context) {
+ Optional index = getSignedPreKeyIndex(context);
+
+ if (index.isPresent()) return index.get().activeSignedPreKeyId;
+ else return -1;
+ }
+
+ private static synchronized int getNextPreKeyId(Context context) {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
@@ -144,7 +169,7 @@ public class PreKeyUtil {
}
}
- private static int getNextSignedPreKeyId(Context context) {
+ private static synchronized int getNextSignedPreKeyId(Context context) {
try {
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
@@ -162,6 +187,32 @@ public class PreKeyUtil {
}
}
+ private static synchronized Optional getSignedPreKeyIndex(Context context) {
+ File indexFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
+
+ if (!indexFile.exists()) {
+ return Optional.absent();
+ }
+
+ try {
+ InputStreamReader reader = new InputStreamReader(new FileInputStream(indexFile));
+ SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class);
+ reader.close();
+
+ return Optional.of(index);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return Optional.absent();
+ }
+ }
+
+ private static synchronized void setSignedPreKeyIndex(Context context, SignedPreKeyIndex index) throws IOException {
+ File indexFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
+ FileOutputStream fout = new FileOutputStream(indexFile);
+ fout.write(JsonUtils.toJson(index).getBytes());
+ fout.close();
+ }
+
private static File getPreKeysDirectory(Context context) {
return getKeysDirectory(context, TextSecurePreKeyStore.PREKEY_DIRECTORY);
}
@@ -198,11 +249,11 @@ public class PreKeyUtil {
@JsonProperty
private int nextSignedPreKeyId;
+ @JsonProperty
+ private int activeSignedPreKeyId = -1;
+
public SignedPreKeyIndex() {}
- public SignedPreKeyIndex(int nextSignedPreKeyId) {
- this.nextSignedPreKeyId = nextSignedPreKeyId;
- }
}
diff --git a/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java
index 68c727caa5..3e27dd6019 100644
--- a/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java
+++ b/src/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java
@@ -79,7 +79,9 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
for (File signedPreKeyFile : directory.listFiles()) {
try {
- results.add(new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile)));
+ if (!"index.dat".equals(signedPreKeyFile.getName())) {
+ results.add(new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile)));
+ }
} catch (IOException | InvalidMessageException e) {
Log.w(TAG, e);
}
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index 3002d78b8f..bce24e0c29 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
+import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.push.SecurityEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
@@ -56,7 +57,8 @@ import dagger.Provides;
GcmRefreshJob.class,
RequestGroupInfoJob.class,
PushGroupUpdateJob.class,
- AvatarDownloadJob.class})
+ AvatarDownloadJob.class,
+ RotateSignedPreKeyJob.class})
public class SignalCommunicationModule {
private final Context context;
diff --git a/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java b/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java
index 22d597ccd1..8641b8a074 100644
--- a/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/CleanPreKeysJob.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.whispersystems.jobqueue.JobParameters;
@@ -11,7 +12,6 @@ import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
-import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
@@ -30,7 +30,7 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
private static final String TAG = CleanPreKeysJob.class.getSimpleName();
- private static final int ARCHIVE_AGE_DAYS = 15;
+ private static final long ARCHIVE_AGE = TimeUnit.DAYS.toMillis(7);
@Inject transient SignalServiceAccountManager accountManager;
@Inject transient SignedPreKeyStoreFactory signedPreKeyStoreFactory;
@@ -51,17 +51,20 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
@Override
public void onRun(MasterSecret masterSecret) throws IOException {
try {
- SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create();
- SignedPreKeyEntity currentSignedPreKey = accountManager.getSignedPreKey();
+ Log.w(TAG, "Cleaning prekeys...");
- if (currentSignedPreKey == null) return;
+ int activeSignedPreKeyId = PreKeyUtil.getActiveSignedPreKeyId(context);
+ SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create();
- SignedPreKeyRecord currentRecord = signedPreKeyStore.loadSignedPreKey(currentSignedPreKey.getKeyId());
+ if (activeSignedPreKeyId < 0) return;
+
+ SignedPreKeyRecord currentRecord = signedPreKeyStore.loadSignedPreKey(activeSignedPreKeyId);
List allRecords = signedPreKeyStore.loadSignedPreKeys();
LinkedList oldRecords = removeRecordFrom(currentRecord, allRecords);
Collections.sort(oldRecords, new SignedPreKeySorter());
+ Log.w(TAG, "Active signed prekey: " + activeSignedPreKeyId);
Log.w(TAG, "Old signed prekey record count: " + oldRecords.size());
boolean foundAgedRecord = false;
@@ -69,7 +72,7 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
for (SignedPreKeyRecord oldRecord : oldRecords) {
long archiveDuration = System.currentTimeMillis() - oldRecord.getTimestamp();
- if (archiveDuration >= TimeUnit.DAYS.toMillis(ARCHIVE_AGE_DAYS)) {
+ if (archiveDuration >= ARCHIVE_AGE) {
if (!foundAgedRecord) {
foundAgedRecord = true;
} else {
diff --git a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java
index 6bc0aeee72..78639cb22c 100644
--- a/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java
@@ -53,7 +53,7 @@ public class CreateSignedPreKeyJob extends MasterSecretJob implements Injectable
}
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
- SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair);
+ SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair, true);
accountManager.setSignedPreKey(signedPreKeyRecord);
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
index 4eaf156fbf..b4fe814013 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java
@@ -70,12 +70,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
-// DatabaseFactory.getMmsDatabase(context)
-// .markAsSending(messageId);
}
@Override
- public void onSend(MasterSecret masterSecret)
+ public void onPushSend(MasterSecret masterSecret)
throws MmsException, IOException, NoSuchMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
@@ -84,8 +82,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
try {
deliver(masterSecret, message, filterRecipientId);
-// database.markAsPush(messageId);
-// database.markAsSecure(messageId);
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
@@ -114,10 +110,8 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
}
database.addFailures(messageId, failures);
-// database.markAsPush(messageId);
if (e.getNetworkExceptions().isEmpty() && e.getUntrustedIdentityExceptions().isEmpty()) {
-// database.markAsSecure(messageId);
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
} else {
diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
index d2bdf7c68d..4964547827 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java
@@ -53,13 +53,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
-// MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
-// mmsDatabase.markAsSending(messageId);
-// mmsDatabase.markAsPush(messageId);
+
}
@Override
- public void onSend(MasterSecret masterSecret)
+ public void onPushSend(MasterSecret masterSecret)
throws RetryLaterException, MmsException, NoSuchMessageException,
UndeliverableMessageException
{
@@ -69,8 +67,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
try {
deliver(masterSecret, message);
-// database.markAsPush(messageId);
-// database.markAsSecure(messageId);
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
@@ -91,7 +87,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey());
database.markAsSentFailed(messageId);
-// database.markAsPush(messageId);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
index ca23d0046e..679c8c8b9d 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.util.Log;
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.TextSecureExpiredException;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -12,6 +14,7 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@@ -48,6 +51,19 @@ public abstract class PushSendJob extends SendJob {
return builder.create();
}
+ @Override
+ protected final void onSend(MasterSecret masterSecret) throws Exception {
+ if (TextSecurePreferences.getSignedPreKeyFailureCount(context) > 5) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RotateSignedPreKeyJob(context));
+
+ throw new TextSecureExpiredException("Too many signed prekey rotation failures");
+ }
+
+ onPushSend(masterSecret);
+ }
+
protected SignalServiceAddress getPushAddress(String number) throws InvalidNumberException {
String e164number = Util.canonicalizeNumber(context, number);
String relay = TextSecureDirectory.getInstance(context).getRelay(e164number);
@@ -93,4 +109,6 @@ public abstract class PushSendJob extends SendJob {
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
}
}
+
+ protected abstract void onPushSend(MasterSecret masterSecret) throws Exception;
}
diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index a875698a04..52bce00ddf 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -49,7 +49,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
public void onAdded() {}
@Override
- public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
+ public void onPushSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
diff --git a/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java b/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java
index 8c088d3c4a..af72ddb7dc 100644
--- a/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java
@@ -60,12 +60,13 @@ public class RefreshPreKeysJob extends MasterSecretJob implements InjectableType
List preKeyRecords = PreKeyUtil.generatePreKeys(context);
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
- SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey);
+ SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
Log.w(TAG, "Registering new prekeys...");
accountManager.setPreKeys(identityKey.getPublicKey(), lastResortKeyRecord, signedPreKeyRecord, preKeyRecords);
+ PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
ApplicationContext.getInstance(context)
diff --git a/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java b/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java
new file mode 100644
index 0000000000..050bc0240c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java
@@ -0,0 +1,69 @@
+package org.thoughtcrime.securesms.jobs;
+
+
+import android.content.Context;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.crypto.PreKeyUtil;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.jobqueue.JobParameters;
+import org.whispersystems.jobqueue.requirements.NetworkRequirement;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.state.SignedPreKeyRecord;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
+
+import javax.inject.Inject;
+
+public class RotateSignedPreKeyJob extends MasterSecretJob implements InjectableType {
+
+ private static final String TAG = RotateSignedPreKeyJob.class.getName();
+
+ @Inject transient SignalServiceAccountManager accountManager;
+
+ public RotateSignedPreKeyJob(Context context) {
+ super(context, JobParameters.newBuilder()
+ .withRequirement(new NetworkRequirement(context))
+ .withRequirement(new MasterSecretRequirement(context))
+ .withRetryCount(5)
+ .create());
+ }
+
+ @Override
+ public void onAdded() {
+
+ }
+
+ @Override
+ public void onRun(MasterSecret masterSecret) throws Exception {
+ Log.w(TAG, "Rotating signed prekey...");
+
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
+ SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey, false);
+
+ accountManager.setSignedPreKey(signedPreKeyRecord);
+
+ PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
+ TextSecurePreferences.setSignedPreKeyRegistered(context, true);
+ TextSecurePreferences.setSignedPreKeyFailureCount(context, 0);
+
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new CleanPreKeysJob(context));
+ }
+
+ @Override
+ public boolean onShouldRetryThrowable(Exception exception) {
+ return exception instanceof PushNetworkException;
+ }
+
+ @Override
+ public void onCanceled() {
+ TextSecurePreferences.setSignedPreKeyFailureCount(context, TextSecurePreferences.getSignedPreKeyFailureCount(context) + 1);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java b/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java
index 9e70ce8cd9..53300b07aa 100644
--- a/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java
+++ b/src/org/thoughtcrime/securesms/service/DirectoryRefreshListener.java
@@ -1,64 +1,39 @@
package org.thoughtcrime.securesms.service;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
-public class DirectoryRefreshListener extends BroadcastReceiver {
+import java.util.concurrent.TimeUnit;
- private static final String TAG = DirectoryRefreshListener.class.getSimpleName();
+public class DirectoryRefreshListener extends PersistentAlarmManagerListener {
- private static final String REFRESH_EVENT = "org.whispersystems.whisperpush.DIRECTORY_REFRESH";
- private static final String BOOT_EVENT = "android.intent.action.BOOT_COMPLETED";
-
- private static final long INTERVAL = 12 * 60 * 60 * 1000; // 12 hours.
+ private static final long INTERVAL = TimeUnit.HOURS.toMillis(12);
@Override
- public void onReceive(Context context, Intent intent) {
- if (REFRESH_EVENT.equals(intent.getAction())) handleRefreshAction(context);
- else if (BOOT_EVENT.equals(intent.getAction())) handleBootEvent(context);
+ protected long getNextScheduledExecutionTime(Context context) {
+ return TextSecurePreferences.getDirectoryRefreshTime(context);
}
- private void handleBootEvent(Context context) {
- schedule(context);
- }
+ @Override
+ protected long onAlarm(Context context, long scheduledTime) {
+ if (scheduledTime != 0 && TextSecurePreferences.isPushRegistered(context)) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new DirectoryRefreshJob(context));
+ }
- private void handleRefreshAction(Context context) {
- schedule(context);
+ long newTime = System.currentTimeMillis() + INTERVAL;
+ TextSecurePreferences.setDirectoryRefreshTime(context, newTime);
+
+ return newTime;
}
public static void schedule(Context context) {
- if (!TextSecurePreferences.isPushRegistered(context)) return;
-
- AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(DirectoryRefreshListener.REFRESH_EVENT);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
- long time = TextSecurePreferences.getDirectoryRefreshTime(context);
-
- if (time <= System.currentTimeMillis()) {
- if (time != 0) {
- ApplicationContext.getInstance(context)
- .getJobManager()
- .add(new DirectoryRefreshJob(context));
- }
-
- time = System.currentTimeMillis() + INTERVAL;
- }
-
- Log.w(TAG, "Scheduling for: " + time);
-
- alarmManager.cancel(pendingIntent);
- alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
-
- TextSecurePreferences.setDirectoryRefreshTime(context, time);
+ new DirectoryRefreshListener().onReceive(context, new Intent());
}
-
}
diff --git a/src/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/src/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java
new file mode 100644
index 0000000000..f592d9f988
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java
@@ -0,0 +1,34 @@
+package org.thoughtcrime.securesms.service;
+
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
+
+ private static final String TAG = PersistentAlarmManagerListener.class.getSimpleName();
+
+ protected abstract long getNextScheduledExecutionTime(Context context);
+ protected abstract long onAlarm(Context context, long scheduledTime);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ long scheduledTime = getNextScheduledExecutionTime(context);
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent alarmIntent = new Intent(context, getClass());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);
+
+ if (System.currentTimeMillis() >= scheduledTime) {
+ scheduledTime = onAlarm(context, scheduledTime);
+ }
+
+ Log.w(TAG, getClass() + " scheduling for: " + scheduledTime);
+
+ alarmManager.cancel(pendingIntent);
+ alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java
index bd5f282817..ec9f4c1514 100644
--- a/src/org/thoughtcrime/securesms/service/RegistrationService.java
+++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java
@@ -239,7 +239,7 @@ public class RegistrationService extends Service {
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this);
List records = PreKeyUtil.generatePreKeys(this);
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this);
- SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey);
+ SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey, true);
accountManager.setPreKeys(identityKey.getPublicKey(),lastResort, signedPreKey, records);
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
@@ -261,6 +261,7 @@ public class RegistrationService extends Service {
redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId));
DirectoryRefreshListener.schedule(this);
+ RotateSignedPreKeyListener.schedule(this);
}
private synchronized String waitForChallenge() throws AccountVerificationTimeoutException {
diff --git a/src/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java b/src/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java
new file mode 100644
index 0000000000..0eee795d62
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java
@@ -0,0 +1,39 @@
+package org.thoughtcrime.securesms.service;
+
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+
+import java.util.concurrent.TimeUnit;
+
+public class RotateSignedPreKeyListener extends PersistentAlarmManagerListener {
+
+ private static final long INTERVAL = TimeUnit.DAYS.toMillis(2);
+
+ @Override
+ protected long getNextScheduledExecutionTime(Context context) {
+ return TextSecurePreferences.getSignedPreKeyRotationTime(context);
+ }
+
+ @Override
+ protected long onAlarm(Context context, long scheduledTime) {
+ if (scheduledTime != 0 && TextSecurePreferences.isPushRegistered(context)) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new RotateSignedPreKeyJob(context));
+ }
+
+ long nextTime = System.currentTimeMillis() + INTERVAL;
+ TextSecurePreferences.setSignedPreKeyRotationTime(context, nextTime);
+
+ return nextTime;
+ }
+
+ public static void schedule(Context context) {
+ new RotateSignedPreKeyListener().onReceive(context, new Intent());
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index 2d985a2d35..0a4e27adc3 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -72,6 +72,7 @@ public class TextSecurePreferences {
private static final String PROMPTED_SHARE_PREF = "pref_prompted_share";
private static final String SIGNALING_KEY_PREF = "pref_signaling_key";
private static final String DIRECTORY_FRESH_TIME_PREF = "pref_directory_refresh_time";
+ private static final String SIGNED_PREKEY_ROTATION_TIME_PREF = "pref_signed_pre_key_rotation_time";
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String BLOCKING_IDENTITY_CHANGES_PREF = "pref_blocking_identity_changes";
@@ -84,6 +85,7 @@ public class TextSecurePreferences {
private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered";
private static final String RATING_LATER_PREF = "pref_rating_later";
private static final String RATING_ENABLED_PREF = "pref_rating_enabled";
+ private static final String SIGNED_PREKEY_FAILURE_COUNT_PREF = "pref_signed_prekey_failure_count";
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
@@ -122,6 +124,14 @@ public class TextSecurePreferences {
setBooleanPreference(context, BLOCKING_IDENTITY_CHANGES_PREF, value);
}
+ public static void setSignedPreKeyFailureCount(Context context, int value) {
+ setIntegerPrefrence(context, SIGNED_PREKEY_FAILURE_COUNT_PREF, value);
+ }
+
+ public static int getSignedPreKeyFailureCount(Context context) {
+ return getIntegerPreference(context, SIGNED_PREKEY_FAILURE_COUNT_PREF, 0);
+ }
+
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
}
@@ -214,6 +224,14 @@ public class TextSecurePreferences {
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
}
+ public static long getSignedPreKeyRotationTime(Context context) {
+ return getLongPreference(context, SIGNED_PREKEY_ROTATION_TIME_PREF, 0L);
+ }
+
+ public static void setSignedPreKeyRotationTime(Context context, long value) {
+ setLongPreference(context, SIGNED_PREKEY_ROTATION_TIME_PREF, value);
+ }
+
public static long getDirectoryRefreshTime(Context context) {
return getLongPreference(context, DIRECTORY_FRESH_TIME_PREF, 0L);
}