Add last resort key and signaling key.

This commit is contained in:
Moxie Marlinspike 2013-08-28 15:35:30 -07:00
parent 45e380a5bb
commit 68ec0a3727
10 changed files with 110 additions and 30 deletions

View File

@ -40,6 +40,24 @@ public class PreKeyUtil {
return records; return records;
} }
public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) {
if (PreKeyRecord.hasRecord(context, Medium.MAX_VALUE)) {
try {
return new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE);
} catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e);
PreKeyRecord.delete(context, Medium.MAX_VALUE);
}
}
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
record.save();
return record;
}
public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) { public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>(); List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
File directory = getPreKeysDirectory(context); File directory = getPreKeysDirectory(context);
@ -49,7 +67,9 @@ public class PreKeyUtil {
for (String keyRecordId : keyRecordIds) { for (String keyRecordId : keyRecordIds) {
try { try {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId))); if (Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
}
} catch (InvalidKeyIdException e) { } catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e); Log.w("PreKeyUtil", e);
new File(getPreKeysDirectory(context), keyRecordId).delete(); new File(getPreKeysDirectory(context), keyRecordId).delete();

View File

@ -4,10 +4,12 @@ import java.util.List;
public class PreKeyList { public class PreKeyList {
private PreKeyEntity lastResortKey;
private List<PreKeyEntity> keys; private List<PreKeyEntity> keys;
public PreKeyList(List<PreKeyEntity> keys) { public PreKeyList(PreKeyEntity lastResortKey, List<PreKeyEntity> keys) {
this.keys = keys; this.keys = keys;
this.lastResortKey = lastResortKey;
} }
public List<PreKeyEntity> getKeys() { public List<PreKeyEntity> getKeys() {
@ -17,4 +19,8 @@ public class PreKeyList {
public static String toJson(PreKeyList entity) { public static String toJson(PreKeyList entity) {
return PreKeyEntity.getBuilder().create().toJson(entity); return PreKeyEntity.getBuilder().create().toJson(entity);
} }
public PreKeyEntity getLastResortKey() {
return lastResortKey;
}
} }

View File

@ -65,8 +65,9 @@ public class PushServiceSocket {
makeRequest(String.format(path, localNumber), "POST", null); makeRequest(String.format(path, localNumber), "POST", null);
} }
public void verifyAccount(String verificationCode) throws IOException { public void verifyAccount(String verificationCode, String signalingKey) throws IOException {
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", null); SignalingKey signalingKeyEntity = new SignalingKey(signalingKey);
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", new Gson().toJson(signalingKeyEntity));
} }
public void registerGcmId(String gcmRegistrationId) throws IOException { public void registerGcmId(String gcmRegistrationId) throws IOException {
@ -109,7 +110,9 @@ public class PushServiceSocket {
throw new IOException("Got send failure: " + response.getFailure().get(0)); throw new IOException("Got send failure: " + response.getFailure().get(0));
} }
public void registerPreKeys(IdentityKey identityKey, List<PreKeyRecord> records) public void registerPreKeys(IdentityKey identityKey,
PreKeyRecord lastResortKey,
List<PreKeyRecord> records)
throws IOException throws IOException
{ {
List<PreKeyEntity> entities = new LinkedList<PreKeyEntity>(); List<PreKeyEntity> entities = new LinkedList<PreKeyEntity>();
@ -121,7 +124,11 @@ public class PushServiceSocket {
entities.add(entity); entities.add(entity);
} }
makeRequest(String.format(PREKEY_PATH, ""), "PUT", PreKeyList.toJson(new PreKeyList(entities))); PreKeyEntity lastResortEntity = new PreKeyEntity(lastResortKey.getId(),
lastResortKey.getKeyPair().getPublicKey(),
identityKey);
makeRequest(String.format(PREKEY_PATH, ""), "PUT", PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
} }
public PreKeyEntity getPreKey(String number) throws IOException { public PreKeyEntity getPreKey(String number) throws IOException {

View File

@ -0,0 +1,16 @@
package org.whispersystems.textsecure.push;
public class SignalingKey {
private String signalingKey;
public SignalingKey(String signalingKey) {
this.signalingKey = signalingKey;
}
public SignalingKey() {}
public String getSignalingKey() {
return signalingKey;
}
}

View File

@ -24,6 +24,7 @@ import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Medium;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -65,7 +66,8 @@ public class LocalKeyRecord extends Record {
Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId); Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId);
if (keyId == localNextKeyPair.getId()) { if (keyId == localNextKeyPair.getId()) {
this.localCurrentKeyPair = this.localNextKeyPair; this.localCurrentKeyPair = this.localNextKeyPair;
this.localNextKeyPair = new KeyPair(this.localNextKeyPair.getId()+1, KeyUtil.generateKeyPair(), masterSecret); this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE,
KeyUtil.generateKeyPair(), masterSecret);
} }
} }

View File

@ -22,6 +22,7 @@ import android.util.Log;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Medium;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -62,7 +63,7 @@ public class RemoteKeyRecord extends Record {
public void updateCurrentRemoteKey(PublicKey remoteKey) { public void updateCurrentRemoteKey(PublicKey remoteKey) {
Log.w("RemoteKeyRecord", "Updating current remote key: " + remoteKey.getId()); Log.w("RemoteKeyRecord", "Updating current remote key: " + remoteKey.getId());
if (remoteKey.getId() > remoteKeyCurrent.getId()) { if (isWrappingGreaterThan(remoteKey.getId(), remoteKeyCurrent.getId())) {
this.remoteKeyLast = this.remoteKeyCurrent; this.remoteKeyLast = this.remoteKeyCurrent;
this.remoteKeyCurrent = remoteKey; this.remoteKeyCurrent = remoteKey;
} }
@ -112,6 +113,20 @@ public class RemoteKeyRecord extends Record {
} }
} }
private boolean isWrappingGreaterThan(int receivedValue, int currentValue) {
if (receivedValue > currentValue) {
return true;
}
if (receivedValue == currentValue) {
return false;
}
int gap = (receivedValue - currentValue) + Medium.MAX_VALUE;
return (gap >= 0) && (gap < 5);
}
private void loadData() { private void loadData() {
Log.w("RemoteKeyRecord", "Loading remote key record for recipient: " + this.address); Log.w("RemoteKeyRecord", "Loading remote key record for recipient: " + this.address);
synchronized (FILE_LOCK) { synchronized (FILE_LOCK) {

View File

@ -1,5 +1,5 @@
package org.whispersystems.textsecure.util; package org.whispersystems.textsecure.util;
public class Medium { public class Medium {
public static int MAX_VALUE = 0xFFF; public static int MAX_VALUE = 0xFFFFFF;
} }

View File

@ -432,14 +432,16 @@ public class RegistrationProgressActivity extends SherlockActivity {
private final String e164number; private final String e164number;
private final String password; private final String password;
private final String signalingKey;
private final Context context; private final Context context;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
public VerifyClickListener(String e164number, String password) { public VerifyClickListener(String e164number, String password) {
this.e164number = e164number; this.e164number = e164number;
this.password = password; this.password = password;
this.context = RegistrationProgressActivity.this; this.signalingKey = Util.getSecret(52);
this.context = RegistrationProgressActivity.this;
} }
@Override @Override
@ -473,6 +475,7 @@ public class RegistrationProgressActivity extends SherlockActivity {
intent.setAction(RegistrationService.VOICE_REGISTER_ACTION); intent.setAction(RegistrationService.VOICE_REGISTER_ACTION);
intent.putExtra("e164number", e164number); intent.putExtra("e164number", e164number);
intent.putExtra("password", password); intent.putExtra("password", password);
intent.putExtra("signaling_key", signalingKey);
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
startService(intent); startService(intent);
break; break;
@ -495,7 +498,7 @@ public class RegistrationProgressActivity extends SherlockActivity {
protected Integer doInBackground(Void... params) { protected Integer doInBackground(Void... params) {
try { try {
PushServiceSocket socket = new PushServiceSocket(context, e164number, password); PushServiceSocket socket = new PushServiceSocket(context, e164number, password);
socket.verifyAccount(code); socket.verifyAccount(code, signalingKey);
return SUCCESS; return SUCCESS;
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("RegistrationProgressActivity", e); Log.w("RegistrationProgressActivity", e);
@ -521,9 +524,9 @@ public class RegistrationProgressActivity extends SherlockActivity {
private final Context context; private final Context context;
public CallClickListener(String e164number) { public CallClickListener(String e164number) {
this.e164number = e164number; this.e164number = e164number;
this.password = Util.getSecret(18); this.password = Util.getSecret(18);
this.context = RegistrationProgressActivity.this; this.context = RegistrationProgressActivity.this;
} }
@Override @Override

View File

@ -153,6 +153,7 @@ public class RegistrationService extends Service {
public void run() { public void run() {
if (PreKeyUtil.getPreKeys(RegistrationService.this, masterSecret).size() < PreKeyUtil.BATCH_SIZE) { if (PreKeyUtil.getPreKeys(RegistrationService.this, masterSecret).size() < PreKeyUtil.BATCH_SIZE) {
PreKeyUtil.generatePreKeys(RegistrationService.this, masterSecret); PreKeyUtil.generatePreKeys(RegistrationService.this, masterSecret);
PreKeyUtil.generateLastResortKey(RegistrationService.this, masterSecret);
} }
synchronized (GENERATING_PREKEYS_SEMAPHOR) { synchronized (GENERATING_PREKEYS_SEMAPHOR) {
@ -188,6 +189,7 @@ public class RegistrationService extends Service {
String number = intent.getStringExtra("e164number"); String number = intent.getStringExtra("e164number");
String password = intent.getStringExtra("password" ); String password = intent.getStringExtra("password" );
String signalingKey = intent.getStringExtra("signaling_key");
MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
try { try {
@ -198,7 +200,7 @@ public class RegistrationService extends Service {
handleCommonRegistration(masterSecret, socket, number); handleCommonRegistration(masterSecret, socket, number);
markAsVerified(number, password); markAsVerified(number, password, signalingKey);
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number)); setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
broadcastComplete(true); broadcastComplete(true);
@ -226,7 +228,9 @@ public class RegistrationService extends Service {
MasterSecret masterSecret = intent.getParcelableExtra("master_secret"); MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
try { try {
String password = Util.getSecret(18); String password = Util.getSecret(18);
String signalingKey = Util.getSecret(52);
initializeChallengeListener(); initializeChallengeListener();
initializeGcmRegistrationListener(); initializeGcmRegistrationListener();
initializePreKeyGenerator(masterSecret); initializePreKeyGenerator(masterSecret);
@ -237,10 +241,10 @@ public class RegistrationService extends Service {
setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number)); setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number));
String challenge = waitForChallenge(); String challenge = waitForChallenge();
socket.verifyAccount(challenge); socket.verifyAccount(challenge, signalingKey);
handleCommonRegistration(masterSecret, socket, number); handleCommonRegistration(masterSecret, socket, number);
markAsVerified(number, password); markAsVerified(number, password, signalingKey);
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number)); setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
broadcastComplete(true); broadcastComplete(true);
@ -272,7 +276,8 @@ public class RegistrationService extends Service {
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this); IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this);
List<PreKeyRecord> records = waitForPreKeys(masterSecret); List<PreKeyRecord> records = waitForPreKeys(masterSecret);
socket.registerPreKeys(identityKey, records); PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
socket.registerPreKeys(identityKey, lastResort, records);
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID); GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID);
@ -280,7 +285,10 @@ public class RegistrationService extends Service {
socket.registerGcmId(gcmRegistrationId); socket.registerGcmId(gcmRegistrationId);
Pair<DirectoryDescriptor, File> directory = socket.retrieveDirectory(); Pair<DirectoryDescriptor, File> directory = socket.retrieveDirectory();
NumberFilter.getInstance(this).update(directory.first, directory.second);
if (directory != null) {
NumberFilter.getInstance(this).update(directory.first, directory.second);
}
} }
private synchronized String waitForChallenge() throws AccountVerificationTimeoutException { private synchronized String waitForChallenge() throws AccountVerificationTimeoutException {
@ -347,11 +355,12 @@ public class RegistrationService extends Service {
} }
} }
private void markAsVerified(String number, String password) { private void markAsVerified(String number, String password, String signalingKey) {
TextSecurePreferences.setVerifying(this, false); TextSecurePreferences.setVerifying(this, false);
TextSecurePreferences.setPushRegistered(this, true); TextSecurePreferences.setPushRegistered(this, true);
TextSecurePreferences.setLocalNumber(this, number); TextSecurePreferences.setLocalNumber(this, number);
TextSecurePreferences.setPushServerPassword(this, password); TextSecurePreferences.setPushServerPassword(this, password);
TextSecurePreferences.setSignalingKey(this, signalingKey);
} }
private void setState(RegistrationState state) { private void setState(RegistrationState state) {
@ -432,9 +441,9 @@ public class RegistrationService extends Service {
} }
public RegistrationState(int state, String number, String password) { public RegistrationState(int state, String number, String password) {
this.state = state; this.state = state;
this.number = number; this.number = number;
this.password = password; this.password = password;
} }
} }
} }

View File

@ -4,9 +4,6 @@ import android.content.Context;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.RoutingActivity;
public class TextSecurePreferences { public class TextSecurePreferences {
public static final String IDENTITY_PREF = "pref_choose_identity"; public static final String IDENTITY_PREF = "pref_choose_identity";
@ -40,6 +37,7 @@ public class TextSecurePreferences {
private static final String REGISTERED_GCM_PREF = "pref_gcm_registered"; private static final String REGISTERED_GCM_PREF = "pref_gcm_registered";
private static final String GCM_PASSWORD_PREF = "pref_gcm_password"; private static final String GCM_PASSWORD_PREF = "pref_gcm_password";
private static final String PROMPTED_PUSH_REGISTRATION_PREF = "pref_prompted_push_registration"; private static final String PROMPTED_PUSH_REGISTRATION_PREF = "pref_prompted_push_registration";
private static final String SIGNALING_KEY_PREF = "pref_signaling_key";
public static String getLocalNumber(Context context) { public static String getLocalNumber(Context context) {
return getStringPreference(context, LOCAL_NUMBER_PREF, "No Stored Number"); return getStringPreference(context, LOCAL_NUMBER_PREF, "No Stored Number");
@ -57,6 +55,10 @@ public class TextSecurePreferences {
setStringPreference(context, GCM_PASSWORD_PREF, password); setStringPreference(context, GCM_PASSWORD_PREF, password);
} }
public static void setSignalingKey(Context context, String signalingKey) {
setStringPreference(context, SIGNALING_KEY_PREF, signalingKey);
}
public static boolean isEnterImeKeyEnabled(Context context) { public static boolean isEnterImeKeyEnabled(Context context) {
return getBooleanPreference(context, ENTER_PRESENT_PREF, false); return getBooleanPreference(context, ENTER_PRESENT_PREF, false);
} }