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;
}
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) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
File directory = getPreKeysDirectory(context);
@ -49,7 +67,9 @@ public class PreKeyUtil {
for (String keyRecordId : keyRecordIds) {
try {
if (Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
}
} catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e);
new File(getPreKeysDirectory(context), keyRecordId).delete();

View File

@ -4,10 +4,12 @@ import java.util.List;
public class PreKeyList {
private PreKeyEntity lastResortKey;
private List<PreKeyEntity> keys;
public PreKeyList(List<PreKeyEntity> keys) {
public PreKeyList(PreKeyEntity lastResortKey, List<PreKeyEntity> keys) {
this.keys = keys;
this.lastResortKey = lastResortKey;
}
public List<PreKeyEntity> getKeys() {
@ -17,4 +19,8 @@ public class PreKeyList {
public static String toJson(PreKeyList 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);
}
public void verifyAccount(String verificationCode) throws IOException {
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", null);
public void verifyAccount(String verificationCode, String signalingKey) throws IOException {
SignalingKey signalingKeyEntity = new SignalingKey(signalingKey);
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", new Gson().toJson(signalingKeyEntity));
}
public void registerGcmId(String gcmRegistrationId) throws IOException {
@ -109,7 +110,9 @@ public class PushServiceSocket {
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
{
List<PreKeyEntity> entities = new LinkedList<PreKeyEntity>();
@ -121,7 +124,11 @@ public class PushServiceSocket {
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 {

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.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Medium;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -65,7 +66,8 @@ public class LocalKeyRecord extends Record {
Log.w("LocalKeyRecord", "Remote client acknowledges receiving key id: " + keyId);
if (keyId == localNextKeyPair.getId()) {
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.PublicKey;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Medium;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -62,7 +63,7 @@ public class RemoteKeyRecord extends Record {
public void updateCurrentRemoteKey(PublicKey remoteKey) {
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.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() {
Log.w("RemoteKeyRecord", "Loading remote key record for recipient: " + this.address);
synchronized (FILE_LOCK) {

View File

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

View File

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

View File

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

View File

@ -4,9 +4,6 @@ import android.content.Context;
import android.preference.PreferenceManager;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.RoutingActivity;
public class TextSecurePreferences {
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 GCM_PASSWORD_PREF = "pref_gcm_password";
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) {
return getStringPreference(context, LOCAL_NUMBER_PREF, "No Stored Number");
@ -57,6 +55,10 @@ public class TextSecurePreferences {
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) {
return getBooleanPreference(context, ENTER_PRESENT_PREF, false);
}