Move PreKey ids to be Mediums, generate in circular buffer.

This commit is contained in:
Moxie Marlinspike 2013-08-19 10:07:07 -07:00
parent edb89ee3e9
commit d1969412fb
14 changed files with 153 additions and 43 deletions

View File

@ -18,4 +18,8 @@ public class PreKeyPublic {
return KeyUtil.encodePoint(publicKey.getQ()); return KeyUtil.encodePoint(publicKey.getQ());
} }
public ECPublicKeyParameters getPublicKey() {
return publicKey;
}
} }

View File

@ -3,16 +3,17 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import com.google.protobuf.ByteString; import com.google.thoughtcrimegson.Gson;
import org.whispersystems.textsecure.encoded.PreKeyProtos.PreKeyEntity;
import org.whispersystems.textsecure.push.PreKeyList;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Medium;
import org.whispersystems.textsecure.util.Util;
import java.io.File; import java.io.File;
import java.security.NoSuchAlgorithmException; import java.io.FileInputStream;
import java.security.SecureRandom; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedList; import java.util.LinkedList;
@ -24,17 +25,18 @@ public class PreKeyUtil {
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) { public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>(); List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
long preKeyIdOffset = getNextPreKeyId(context); int preKeyIdOffset = getNextPreKeyId(context);
for (int i=0;i<BATCH_SIZE;i++) { for (int i=0;i<BATCH_SIZE;i++) {
Log.w("PreKeyUtil", "Generating PreKey: " + (preKeyIdOffset + i)); int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair()); PreKeyPair keyPair = new PreKeyPair(masterSecret, KeyUtil.generateKeyPair());
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyIdOffset + i, keyPair); PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
record.save(); record.save();
records.add(record); records.add(record);
} }
setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
return records; return records;
} }
@ -47,7 +49,7 @@ public class PreKeyUtil {
for (String keyRecordId : keyRecordIds) { for (String keyRecordId : keyRecordIds) {
try { try {
records.add(new PreKeyRecord(context, masterSecret, Long.parseLong(keyRecordId))); 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();
@ -69,23 +71,32 @@ public class PreKeyUtil {
} }
} }
private static long getNextPreKeyId(Context context) { private static void setNextPreKeyId(Context context, int id) {
try { try {
File directory = getPreKeysDirectory(context); File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
String[] keyRecordIds = directory.list(); FileOutputStream fout = new FileOutputStream(nextFile);
long nextPreKeyId = 0; fout.write(new Gson().toJson(new PreKeyIndex(id)).getBytes());
fout.close();
for (String keyRecordId : keyRecordIds) { } catch (IOException e) {
if (Long.parseLong(keyRecordId) > nextPreKeyId) Log.w("PreKeyUtil", e);
nextPreKeyId = Long.parseLong(keyRecordId); }
} }
if (nextPreKeyId == 0) private static int getNextPreKeyId(Context context) {
nextPreKeyId = SecureRandom.getInstance("SHA1PRNG").nextInt(Integer.MAX_VALUE/2); try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
return nextPreKeyId; if (nextFile.exists()) {
} catch (NoSuchAlgorithmException e) { return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
throw new AssertionError(e); } else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class);
reader.close();
return index.nextPreKeyId;
}
} catch (IOException e) {
Log.w("PreKeyUtil", e);
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} }
} }
@ -114,4 +125,16 @@ public class PreKeyUtil {
} }
} }
private static class PreKeyIndex {
public static final String FILE_NAME = "index.dat";
private int nextPreKeyId;
public PreKeyIndex() {}
public PreKeyIndex(int nextPreKeyId) {
this.nextPreKeyId = nextPreKeyId;
}
}
} }

View File

@ -43,6 +43,11 @@ public class PublicKey {
this.id = id; this.id = id;
} }
public PublicKey(int preKeyId, PreKeyPublic publicKey) {
this.id = preKeyId;
this.publicKey = publicKey.getPublicKey();
}
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException { public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset)); Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset));
if ((bytes.length - offset) < KEY_SIZE) if ((bytes.length - offset) < KEY_SIZE)

View File

@ -18,17 +18,17 @@ import java.lang.reflect.Type;
public class PreKeyEntity { public class PreKeyEntity {
private long keyId; private int keyId;
private PreKeyPublic publicKey; private PreKeyPublic publicKey;
private IdentityKey identityKey; private IdentityKey identityKey;
public PreKeyEntity(long keyId, PreKeyPublic publicKey, IdentityKey identityKey) { public PreKeyEntity(int keyId, PreKeyPublic publicKey, IdentityKey identityKey) {
this.keyId = keyId; this.keyId = keyId;
this.publicKey = publicKey; this.publicKey = publicKey;
this.identityKey = identityKey; this.identityKey = identityKey;
} }
public long getKeyId() { public int getKeyId() {
return keyId; return keyId;
} }

View File

@ -21,9 +21,9 @@ public class PreKeyRecord extends Record {
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private PreKeyPair keyPair; private PreKeyPair keyPair;
private long id; private int id;
public PreKeyRecord(Context context, MasterSecret masterSecret, long id) public PreKeyRecord(Context context, MasterSecret masterSecret, int id)
throws InvalidKeyIdException throws InvalidKeyIdException
{ {
super(context, PREKEY_DIRECTORY, id+""); super(context, PREKEY_DIRECTORY, id+"");
@ -35,7 +35,7 @@ public class PreKeyRecord extends Record {
} }
public PreKeyRecord(Context context, MasterSecret masterSecret, public PreKeyRecord(Context context, MasterSecret masterSecret,
long id, PreKeyPair keyPair) int id, PreKeyPair keyPair)
{ {
super(context, PREKEY_DIRECTORY, id+""); super(context, PREKEY_DIRECTORY, id+"");
this.id = id; this.id = id;
@ -43,7 +43,7 @@ public class PreKeyRecord extends Record {
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
} }
public long getId() { public int getId() {
return id; return id;
} }

View File

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

View File

@ -119,4 +119,12 @@ public class Util {
return result.toString(); return result.toString();
} }
public static SecureRandom getSecureRandom() {
try {
return SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
} }

View File

@ -32,7 +32,7 @@ import android.widget.TextView;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.InvalidVersionException; import org.thoughtcrime.securesms.crypto.InvalidVersionException;
import org.thoughtcrime.securesms.crypto.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;

View File

@ -22,6 +22,7 @@ import android.content.DialogInterface;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;

View File

@ -20,9 +20,13 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecord; import org.whispersystems.textsecure.storage.SessionRecord;
@ -65,8 +69,12 @@ public class KeyExchangeProcessor {
return false; return false;
} }
return isTrusted(message.getIdentityKey());
}
public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
message.getIdentityKey()); identityKey);
} }
public boolean hasInitiatedSession() { public boolean hasInitiatedSession() {
@ -86,7 +94,25 @@ public class KeyExchangeProcessor {
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); (localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
} }
public boolean processKeyExchangeMessage(KeyExchangeMessage message, long threadId) { public void processKeyExchangeMessage(PreKeyEntity message) {
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
remoteKeyRecord.setLastRemoteKey(remoteKey);
remoteKeyRecord.save();
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(Message.SUPPORTED_VERSION);
sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
}
public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId) {
int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId()); int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId());
message.getPublicKey().setId(initiateKeyId); message.getPublicKey().setId(initiateKeyId);
@ -123,8 +149,6 @@ public class KeyExchangeProcessor {
intent.putExtra("thread_id", threadId); intent.putExtra("thread_id", threadId);
intent.setPackage(context.getPackageName()); intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
return true;
} }
} }

View File

@ -14,11 +14,13 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.crypto; package org.thoughtcrime.securesms.crypto.protocol;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;

View File

@ -24,7 +24,7 @@ import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.InvalidVersionException; import org.thoughtcrime.securesms.crypto.InvalidVersionException;
import org.thoughtcrime.securesms.crypto.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.MasterSecretUtil;

View File

@ -3,14 +3,20 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException; import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.storage.SessionRecord;
import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import java.io.IOException; import java.io.IOException;
@ -37,10 +43,22 @@ public class PushTransport extends BaseTransport {
String password = TextSecurePreferences.getPushServerPassword(context); String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
String recipientNumber = message.getIndividualRecipient().getNumber(); Recipient recipient = message.getIndividualRecipient();
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipientNumber, String plaintext = message.getBody().getBody();
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
localNumber); localNumber);
// if (SessionRecord.hasSession(context, recipient)) {
// byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
// } else {
// byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
// recipientCanonicalNumber,
// plaintext);
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
// }
socket.sendMessage(recipientCanonicalNumber, message.getBody().getBody()); socket.sendMessage(recipientCanonicalNumber, message.getBody().getBody());
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
@ -82,4 +100,23 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) throws IOException {
PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
processor.processKeyExchangeMessage(preKey);
synchronized (SessionCipher.CIPHER_LOCK) {
SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
return sessionCipher.encryptMessage(plaintext.getBytes());
}
}
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) {
synchronized (SessionCipher.CIPHER_LOCK) {
SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
return sessionCipher.encryptMessage(plaintext.getBytes());
}
}
} }