Start increasing frequency of signed prekey rotation

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-01-06 09:19:58 -08:00
parent 884d8b7f72
commit bb5dcb7131
19 changed files with 299 additions and 85 deletions

View File

@ -502,7 +502,12 @@
<receiver android:name=".service.DirectoryRefreshListener"> <receiver android:name=".service.DirectoryRefreshListener">
<intent-filter> <intent-filter>
<action android:name="org.whispersystems.whisperpush.DIRECTORY_REFRESH"/> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSignedPreKeyListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>

View File

@ -34,7 +34,9 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvi
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider; import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider; import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.dependencies.DependencyInjector; import org.whispersystems.jobqueue.dependencies.DependencyInjector;
@ -75,6 +77,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
initializeExpiringMessageManager(); initializeExpiringMessageManager();
initializeGcmCheck(); initializeGcmCheck();
initializeSignedPreKeyCheck(); initializeSignedPreKeyCheck();
initializePeriodicTasks();
} }
@Override @Override
@ -150,4 +153,9 @@ public class ApplicationContext extends Application implements DependencyInjecto
this.expiringMessageManager = new ExpiringMessageManager(this); this.expiringMessageManager = new ExpiringMessageManager(this);
} }
private void initializePeriodicTasks() {
RotateSignedPreKeyListener.schedule(this);
DirectoryRefreshListener.schedule(this);
}
} }

View File

@ -73,7 +73,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
initializeContactUpdatesReceiver(); initializeContactUpdatesReceiver();
DirectoryRefreshListener.schedule(this);
RatingManager.showRatingDialogIfNecessary(this); RatingManager.showRatingDialogIfNecessary(this);
} }

View File

@ -35,6 +35,7 @@ import org.whispersystems.libsignal.state.PreKeyStore;
import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore; import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.libsignal.util.Medium; import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -46,7 +47,9 @@ import java.util.List;
public class PreKeyUtil { 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<PreKeyRecord> generatePreKeys(Context context) { public static List<PreKeyRecord> generatePreKeys(Context context) {
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context); PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
@ -66,7 +69,7 @@ public class PreKeyUtil {
return records; return records;
} }
public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair) public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active)
{ {
try { try {
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context); SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
@ -78,6 +81,10 @@ public class PreKeyUtil {
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE); setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
if (active) {
setActiveSignedPreKeyId(context, signedPreKeyId);
}
return record; return record;
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new AssertionError(e); throw new AssertionError(e);
@ -104,7 +111,7 @@ public class PreKeyUtil {
return record; return record;
} }
private static void setNextPreKeyId(Context context, int id) { private static synchronized void setNextPreKeyId(Context context, int id) {
try { try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME); File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
FileOutputStream fout = new FileOutputStream(nextFile); 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 { try {
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME); SignedPreKeyIndex index = getSignedPreKeyIndex(context).or(new SignedPreKeyIndex());
FileOutputStream fout = new FileOutputStream(nextFile); index.nextSignedPreKeyId = id;
fout.write(JsonUtils.toJson(new SignedPreKeyIndex(id)).getBytes());
fout.close(); setSignedPreKeyIndex(context, index);
} catch (IOException e) { } 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<SignedPreKeyIndex> index = getSignedPreKeyIndex(context);
if (index.isPresent()) return index.get().activeSignedPreKeyId;
else return -1;
}
private static synchronized int getNextPreKeyId(Context context) {
try { try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME); 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 { try {
File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME); File nextFile = new File(getSignedPreKeysDirectory(context), SignedPreKeyIndex.FILE_NAME);
@ -162,6 +187,32 @@ public class PreKeyUtil {
} }
} }
private static synchronized Optional<SignedPreKeyIndex> 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) { private static File getPreKeysDirectory(Context context) {
return getKeysDirectory(context, TextSecurePreKeyStore.PREKEY_DIRECTORY); return getKeysDirectory(context, TextSecurePreKeyStore.PREKEY_DIRECTORY);
} }
@ -198,11 +249,11 @@ public class PreKeyUtil {
@JsonProperty @JsonProperty
private int nextSignedPreKeyId; private int nextSignedPreKeyId;
@JsonProperty
private int activeSignedPreKeyId = -1;
public SignedPreKeyIndex() {} public SignedPreKeyIndex() {}
public SignedPreKeyIndex(int nextSignedPreKeyId) {
this.nextSignedPreKeyId = nextSignedPreKeyId;
}
} }

View File

@ -79,7 +79,9 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
for (File signedPreKeyFile : directory.listFiles()) { for (File signedPreKeyFile : directory.listFiles()) {
try { try {
results.add(new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile))); if (!"index.dat".equals(signedPreKeyFile.getName())) {
results.add(new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile)));
}
} catch (IOException | InvalidMessageException e) { } catch (IOException | InvalidMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }

View File

@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob; import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SecurityEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.service.MessageRetrievalService;
@ -56,7 +57,8 @@ import dagger.Provides;
GcmRefreshJob.class, GcmRefreshJob.class,
RequestGroupInfoJob.class, RequestGroupInfoJob.class,
PushGroupUpdateJob.class, PushGroupUpdateJob.class,
AvatarDownloadJob.class}) AvatarDownloadJob.class,
RotateSignedPreKeyJob.class})
public class SignalCommunicationModule { public class SignalCommunicationModule {
private final Context context; private final Context context;

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.whispersystems.jobqueue.JobParameters; 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.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore; import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; 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.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; 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 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 SignalServiceAccountManager accountManager;
@Inject transient SignedPreKeyStoreFactory signedPreKeyStoreFactory; @Inject transient SignedPreKeyStoreFactory signedPreKeyStoreFactory;
@ -51,17 +51,20 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
@Override @Override
public void onRun(MasterSecret masterSecret) throws IOException { public void onRun(MasterSecret masterSecret) throws IOException {
try { try {
SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create(); Log.w(TAG, "Cleaning prekeys...");
SignedPreKeyEntity currentSignedPreKey = accountManager.getSignedPreKey();
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<SignedPreKeyRecord> allRecords = signedPreKeyStore.loadSignedPreKeys(); List<SignedPreKeyRecord> allRecords = signedPreKeyStore.loadSignedPreKeys();
LinkedList<SignedPreKeyRecord> oldRecords = removeRecordFrom(currentRecord, allRecords); LinkedList<SignedPreKeyRecord> oldRecords = removeRecordFrom(currentRecord, allRecords);
Collections.sort(oldRecords, new SignedPreKeySorter()); Collections.sort(oldRecords, new SignedPreKeySorter());
Log.w(TAG, "Active signed prekey: " + activeSignedPreKeyId);
Log.w(TAG, "Old signed prekey record count: " + oldRecords.size()); Log.w(TAG, "Old signed prekey record count: " + oldRecords.size());
boolean foundAgedRecord = false; boolean foundAgedRecord = false;
@ -69,7 +72,7 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
for (SignedPreKeyRecord oldRecord : oldRecords) { for (SignedPreKeyRecord oldRecord : oldRecords) {
long archiveDuration = System.currentTimeMillis() - oldRecord.getTimestamp(); long archiveDuration = System.currentTimeMillis() - oldRecord.getTimestamp();
if (archiveDuration >= TimeUnit.DAYS.toMillis(ARCHIVE_AGE_DAYS)) { if (archiveDuration >= ARCHIVE_AGE) {
if (!foundAgedRecord) { if (!foundAgedRecord) {
foundAgedRecord = true; foundAgedRecord = true;
} else { } else {

View File

@ -53,7 +53,7 @@ public class CreateSignedPreKeyJob extends MasterSecretJob implements Injectable
} }
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair); SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair, true);
accountManager.setSignedPreKey(signedPreKeyRecord); accountManager.setSignedPreKey(signedPreKeyRecord);
TextSecurePreferences.setSignedPreKeyRegistered(context, true); TextSecurePreferences.setSignedPreKeyRegistered(context, true);

View File

@ -70,12 +70,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override @Override
public void onAdded() { public void onAdded() {
// DatabaseFactory.getMmsDatabase(context)
// .markAsSending(messageId);
} }
@Override @Override
public void onSend(MasterSecret masterSecret) public void onPushSend(MasterSecret masterSecret)
throws MmsException, IOException, NoSuchMessageException throws MmsException, IOException, NoSuchMessageException
{ {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
@ -84,8 +82,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
try { try {
deliver(masterSecret, message, filterRecipientId); deliver(masterSecret, message, filterRecipientId);
// database.markAsPush(messageId);
// database.markAsSecure(messageId);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments()); markAttachmentsUploaded(messageId, message.getAttachments());
@ -114,10 +110,8 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
} }
database.addFailures(messageId, failures); database.addFailures(messageId, failures);
// database.markAsPush(messageId);
if (e.getNetworkExceptions().isEmpty() && e.getUntrustedIdentityExceptions().isEmpty()) { if (e.getNetworkExceptions().isEmpty() && e.getUntrustedIdentityExceptions().isEmpty()) {
// database.markAsSecure(messageId);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments()); markAttachmentsUploaded(messageId, message.getAttachments());
} else { } else {

View File

@ -53,13 +53,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
@Override @Override
public void onAdded() { public void onAdded() {
// MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
// mmsDatabase.markAsSending(messageId);
// mmsDatabase.markAsPush(messageId);
} }
@Override @Override
public void onSend(MasterSecret masterSecret) public void onPushSend(MasterSecret masterSecret)
throws RetryLaterException, MmsException, NoSuchMessageException, throws RetryLaterException, MmsException, NoSuchMessageException,
UndeliverableMessageException UndeliverableMessageException
{ {
@ -69,8 +67,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
try { try {
deliver(masterSecret, message); deliver(masterSecret, message);
// database.markAsPush(messageId);
// database.markAsSecure(messageId);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments()); markAttachmentsUploaded(messageId, message.getAttachments());
@ -91,7 +87,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey()); database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey());
database.markAsSentFailed(messageId); database.markAsSentFailed(messageId);
// database.markAsPush(messageId);
} }
} }

View File

@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.TextSecureExpiredException;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; 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.mms.PartAuthority;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -48,6 +51,19 @@ public abstract class PushSendJob extends SendJob {
return builder.create(); 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 { protected SignalServiceAddress getPushAddress(String number) throws InvalidNumberException {
String e164number = Util.canonicalizeNumber(context, number); String e164number = Util.canonicalizeNumber(context, number);
String relay = TextSecureDirectory.getInstance(context).getRelay(e164number); String relay = TextSecureDirectory.getInstance(context).getRelay(e164number);
@ -93,4 +109,6 @@ public abstract class PushSendJob extends SendJob {
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} }
} }
protected abstract void onPushSend(MasterSecret masterSecret) throws Exception;
} }

View File

@ -49,7 +49,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
public void onAdded() {} public void onAdded() {}
@Override @Override
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException { public void onPushSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsMessageRecord record = database.getMessage(masterSecret, messageId); SmsMessageRecord record = database.getMessage(masterSecret, messageId);

View File

@ -60,12 +60,13 @@ public class RefreshPreKeysJob extends MasterSecretJob implements InjectableType
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context); List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context);
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context); PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(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..."); Log.w(TAG, "Registering new prekeys...");
accountManager.setPreKeys(identityKey.getPublicKey(), lastResortKeyRecord, signedPreKeyRecord, preKeyRecords); accountManager.setPreKeys(identityKey.getPublicKey(), lastResortKeyRecord, signedPreKeyRecord, preKeyRecords);
PreKeyUtil.setActiveSignedPreKeyId(context, signedPreKeyRecord.getId());
TextSecurePreferences.setSignedPreKeyRegistered(context, true); TextSecurePreferences.setSignedPreKeyRegistered(context, true);
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)

View File

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

View File

@ -1,64 +1,39 @@
package org.thoughtcrime.securesms.service; package org.thoughtcrime.securesms.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences; 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 long INTERVAL = TimeUnit.HOURS.toMillis(12);
private static final String BOOT_EVENT = "android.intent.action.BOOT_COMPLETED";
private static final long INTERVAL = 12 * 60 * 60 * 1000; // 12 hours.
@Override @Override
public void onReceive(Context context, Intent intent) { protected long getNextScheduledExecutionTime(Context context) {
if (REFRESH_EVENT.equals(intent.getAction())) handleRefreshAction(context); return TextSecurePreferences.getDirectoryRefreshTime(context);
else if (BOOT_EVENT.equals(intent.getAction())) handleBootEvent(context);
} }
private void handleBootEvent(Context context) { @Override
schedule(context); 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) { long newTime = System.currentTimeMillis() + INTERVAL;
schedule(context); TextSecurePreferences.setDirectoryRefreshTime(context, newTime);
return newTime;
} }
public static void schedule(Context context) { public static void schedule(Context context) {
if (!TextSecurePreferences.isPushRegistered(context)) return; new DirectoryRefreshListener().onReceive(context, new Intent());
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);
} }
} }

View File

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

View File

@ -239,7 +239,7 @@ public class RegistrationService extends Service {
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this);
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(this); List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(this);
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(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); accountManager.setPreKeys(identityKey.getPublicKey(),lastResort, signedPreKey, records);
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
@ -261,6 +261,7 @@ public class RegistrationService extends Service {
redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId)); redPhoneAccountManager.createAccount(verificationToken, new RedPhoneAccountAttributes(signalingKey, gcmRegistrationId));
DirectoryRefreshListener.schedule(this); DirectoryRefreshListener.schedule(this);
RotateSignedPreKeyListener.schedule(this);
} }
private synchronized String waitForChallenge() throws AccountVerificationTimeoutException { private synchronized String waitForChallenge() throws AccountVerificationTimeoutException {

View File

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

View File

@ -72,6 +72,7 @@ public class TextSecurePreferences {
private static final String PROMPTED_SHARE_PREF = "pref_prompted_share"; private static final String PROMPTED_SHARE_PREF = "pref_prompted_share";
private static final String SIGNALING_KEY_PREF = "pref_signaling_key"; 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 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 IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String BLOCKING_IDENTITY_CHANGES_PREF = "pref_blocking_identity_changes"; 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 WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered";
private static final String RATING_LATER_PREF = "pref_rating_later"; private static final String RATING_LATER_PREF = "pref_rating_later";
private static final String RATING_ENABLED_PREF = "pref_rating_enabled"; 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 REPEAT_ALERTS_PREF = "pref_repeat_alerts";
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy"; public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
@ -122,6 +124,14 @@ public class TextSecurePreferences {
setBooleanPreference(context, BLOCKING_IDENTITY_CHANGES_PREF, value); 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) { public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
} }
@ -214,6 +224,14 @@ public class TextSecurePreferences {
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true); 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) { public static long getDirectoryRefreshTime(Context context) {
return getLongPreference(context, DIRECTORY_FRESH_TIME_PREF, 0L); return getLongPreference(context, DIRECTORY_FRESH_TIME_PREF, 0L);
} }