Generate SignedPreKey records, improve SignedPreKey cleanup.

This commit is contained in:
Moxie Marlinspike
2014-07-12 18:06:22 -07:00
parent 144f269059
commit 5f5ddd7c26
11 changed files with 287 additions and 66 deletions

View File

@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.GcmRegistrationService;
import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -17,9 +18,11 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_CONVERSATION_OR_LIST = 3;
private static final int STATE_UPGRADE_DATABASE = 4;
private static final int STATE_PROMPT_PUSH_REGISTRATION = 5;
private static final int STATE_CREATE_SIGNED_PREKEY = 6;
private MasterSecret masterSecret = null;
private boolean isVisible = false;
@@ -119,14 +122,13 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
final ConversationParameters parameters = getConversationParameters();
final Intent intent;
scheduleRefreshActions();
if (isShareAction()) {
intent = getShareIntent(parameters);
} else if (parameters.recipients != null) {
intent = getConversationIntent(parameters);
} else {
intent = getConversationListIntent();
if (isShareAction()) intent = getShareIntent(parameters);
else if (parameters.recipients != null) intent = getConversationIntent(parameters);
else intent = getConversationListIntent();
if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) {
PreKeyService.initiateCreateSigned(this, masterSecret);
}
startActivity(intent);

View File

@@ -10,26 +10,30 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PreKeyUtil;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.SignedPreKeyEntity;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
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 String TAG = PreKeyService.class.getSimpleName();
public static final String REFRESH_ACTION = "org.thoughtcrime.securesms.PreKeyService.REFRESH";
public static final String CLEAN_ACTION = "org.thoughtcrime.securesms.PreKeyService.CLEAN";
public static final String CREATE_SIGNED_ACTION = "org.thoughtcrime.securesms.PreKeyService.CREATE_SIGNED";
private static final int PREKEY_MINIMUM = 10;
@@ -42,11 +46,36 @@ public class PreKeyService extends Service {
context.startService(intent);
}
public static void initiateClean(Context context, MasterSecret masterSecret) {
Intent intent = new Intent(context, PreKeyService.class);
intent.setAction(PreKeyService.CLEAN_ACTION);
intent.putExtra("master_secret", masterSecret);
context.startService(intent);
}
public static void initiateCreateSigned(Context context, MasterSecret masterSecret) {
Intent intent = new Intent(context, PreKeyService.class);
intent.setAction(PreKeyService.CREATE_SIGNED_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));
if (intent == null) return START_NOT_STICKY;
if (intent.getAction() == null) return START_NOT_STICKY;
MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
if (masterSecret == null) {
Log.w(TAG, "No master secret!");
return START_NOT_STICKY;
}
switch (intent.getAction()) {
case REFRESH_ACTION: executor.execute(new RefreshTask(this, masterSecret)); break;
case CLEAN_ACTION: executor.execute(new CleanSignedPreKeysTask(this, masterSecret)); break;
case CREATE_SIGNED_ACTION: executor.execute(new CreateSignedPreKeyTask(this, masterSecret)); break;
}
return START_NOT_STICKY;
@@ -57,6 +86,98 @@ public class PreKeyService extends Service {
return null;
}
private static class CreateSignedPreKeyTask implements Runnable {
private final Context context;
private final MasterSecret masterSecret;
public CreateSignedPreKeyTask(Context context, MasterSecret masterSecret) {
this.context = context;
this.masterSecret = masterSecret;
}
@Override
public void run() {
if (TextSecurePreferences.isSignedPreKeyRegistered(context)) {
Log.w(TAG, "Signed prekey already registered...");
return;
}
try {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKeyPair);
PushServiceSocket socket = PushServiceSocketFactory.create(context);
socket.setCurrentSignedPreKey(signedPreKeyRecord);
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
static class CleanSignedPreKeysTask implements Runnable {
private final SignedPreKeyStore signedPreKeyStore;
private final PushServiceSocket socket;
public CleanSignedPreKeysTask(Context context, MasterSecret masterSecret) {
this(new TextSecurePreKeyStore(context, masterSecret), PushServiceSocketFactory.create(context));
}
public CleanSignedPreKeysTask(SignedPreKeyStore signedPreKeyStore, PushServiceSocket socket) {
this.signedPreKeyStore = signedPreKeyStore;
this.socket = socket;
}
public void run() {
try {
SignedPreKeyEntity currentSignedPreKey = socket.getCurrentSignedPreKey();
if (currentSignedPreKey == null) return;
SignedPreKeyRecord currentRecord = signedPreKeyStore.loadSignedPreKey(currentSignedPreKey.getKeyId());
List<SignedPreKeyRecord> allRecords = signedPreKeyStore.loadSignedPreKeys();
List<SignedPreKeyRecord> oldRecords = removeCurrentRecord(allRecords, currentRecord);
SignedPreKeyRecord[] oldRecordsArray = oldRecords.toArray(new SignedPreKeyRecord[0]);
Arrays.sort(oldRecordsArray, new SignedPreKeySorter());
Log.w(TAG, "Existing signed prekey record count: " + oldRecordsArray.length);
if (oldRecordsArray.length > 3) {
long oldTimestamp = System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000);
SignedPreKeyRecord[] deletionCandidates = Arrays.copyOf(oldRecordsArray, oldRecordsArray.length - 1);
for (SignedPreKeyRecord deletionCandidate : deletionCandidates) {
Log.w(TAG, "Old signed prekey record timestamp: " + deletionCandidate.getTimestamp());
if (deletionCandidate.getTimestamp() <= oldTimestamp) {
Log.w(TAG, "Remove signed prekey record: " + deletionCandidate.getId());
signedPreKeyStore.removeSignedPreKey(deletionCandidate.getId());
}
}
}
} catch (IOException | InvalidKeyIdException e) {
Log.w(TAG, e);
}
}
private List<SignedPreKeyRecord> removeCurrentRecord(List<SignedPreKeyRecord> records,
SignedPreKeyRecord currentRecord)
{
List<SignedPreKeyRecord> others = new LinkedList<>();
for (SignedPreKeyRecord record : records) {
if (record.getId() != currentRecord.getId()) {
others.add(record);
}
}
return others;
}
}
private static class RefreshTask implements Runnable {
private final Context context;
@@ -68,15 +189,13 @@ public class PreKeyService extends Service {
}
public void run() {
SignedPreKeyRecord signedPreKeyRecord = null;
try {
if (!TextSecurePreferences.isPushRegistered(context)) return;
PushServiceSocket socket = PushServiceSocketFactory.create(context);
int availableKeys = socket.getAvailablePreKeys();
if (availableKeys >= PREKEY_MINIMUM) {
if (availableKeys >= PREKEY_MINIMUM && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
Log.w(TAG, "Available keys sufficient: " + availableKeys);
return;
}
@@ -84,61 +203,28 @@ public class PreKeyService extends Service {
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret);
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey);
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey);
Log.w(TAG, "Registering new prekeys...");
socket.registerPreKeys(identityKey.getPublicKey(), lastResortKeyRecord,
signedPreKeyRecord, preKeyRecords);
removeOldSignedPreKeysIfNecessary(signedPreKeyRecord);
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
PreKeyService.initiateClean(context, masterSecret);
} catch (IOException e) {
Log.w(TAG, e);
if (signedPreKeyRecord != null) {
Log.w(TAG, "Remote store failed, removing generated device key: " + signedPreKeyRecord.getId());
new TextSecurePreKeyStore(context, masterSecret).removeSignedPreKey(signedPreKeyRecord.getId());
}
}
}
private void removeOldSignedPreKeysIfNecessary(SignedPreKeyRecord currentSignedPreKey) {
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
List<SignedPreKeyRecord> records = signedPreKeyStore.loadSignedPreKeys();
Iterator<SignedPreKeyRecord> iterator = records.iterator();
while (iterator.hasNext()) {
if (iterator.next().getId() == currentSignedPreKey.getId()) {
iterator.remove();
}
}
SignedPreKeyRecord[] recordsArray = (SignedPreKeyRecord[])records.toArray();
Arrays.sort(recordsArray, new Comparator<SignedPreKeyRecord>() {
@Override
public int compare(SignedPreKeyRecord lhs, SignedPreKeyRecord rhs) {
if (lhs.getTimestamp() < rhs.getTimestamp()) return -1;
else if (lhs.getTimestamp() > rhs.getTimestamp()) return 1;
else return 0;
}
});
Log.w(TAG, "Existing device key record count: " + recordsArray.length);
if (recordsArray.length > 3) {
long oldTimestamp = System.currentTimeMillis() - (14 * 24 * 60 * 60 * 1000);
SignedPreKeyRecord[] oldRecords = Arrays.copyOf(recordsArray, recordsArray.length - 1);
for (SignedPreKeyRecord oldRecord : oldRecords) {
Log.w(TAG, "Old device key record timestamp: " + oldRecord.getTimestamp());
if (oldRecord.getTimestamp() <= oldTimestamp) {
Log.w(TAG, "Remove device key record: " + oldRecord.getId());
signedPreKeyStore.removeSignedPreKey(oldRecord.getId());
}
}
}
}
}
private static class SignedPreKeySorter implements Comparator<SignedPreKeyRecord> {
@Override
public int compare(SignedPreKeyRecord lhs, SignedPreKeyRecord rhs) {
if (lhs.getTimestamp() < rhs.getTimestamp()) return -1;
else if (lhs.getTimestamp() > rhs.getTimestamp()) return 1;
else return 0;
}
}
}

View File

@@ -281,6 +281,7 @@ public class RegistrationService extends Service {
TextSecurePreferences.setLocalNumber(this, number);
TextSecurePreferences.setPushServerPassword(this, password);
TextSecurePreferences.setSignalingKey(this, signalingKey);
TextSecurePreferences.setSignedPreKeyRegistered(this, true);
}
private void setState(RegistrationState state) {

View File

@@ -51,6 +51,15 @@ public class TextSecurePreferences {
private static final String FALLBACK_SMS_ALLOWED_PREF = "pref_allow_sms_traffic_out";
private static final String FALLBACK_SMS_ASK_REQUIRED_PREF = "pref_sms_fallback_ask";
private static final String DIRECT_SMS_ALLOWED_PREF = "pref_sms_non_data_out";
private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
public static boolean isSignedPreKeyRegistered(Context context) {
return getBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, false);
}
public static void setSignedPreKeyRegistered(Context context, boolean value) {
setBooleanPreference(context, SIGNED_PREKEY_REGISTERED_PREF, value);
}
private static final String GCM_REGISTRATION_ID_PREF = "pref_gcm_registration_id";
private static final String GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version";