From d1969412fb7e82e6a34292bd8c047499bb858271 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 19 Aug 2013 10:07:07 -0700 Subject: [PATCH] Move PreKey ids to be Mediums, generate in circular buffer. --- .../textsecure/crypto/PreKeyPublic.java | 4 + .../textsecure/crypto/PreKeyUtil.java | 73 ++++++++++++------- .../textsecure/crypto/PublicKey.java | 5 ++ .../textsecure/push/PreKeyEntity.java | 6 +- .../textsecure/storage/PreKeyRecord.java | 8 +- .../textsecure/util/Medium.java | 5 ++ .../whispersystems/textsecure/util/Util.java | 8 ++ .../securesms/ReceiveKeyActivity.java | 2 +- .../securesms/crypto/DecryptingQueue.java | 1 + .../crypto/KeyExchangeInitiator.java | 1 + .../crypto/KeyExchangeProcessor.java | 32 +++++++- .../{ => protocol}/KeyExchangeMessage.java | 8 +- .../securesms/service/SmsReceiver.java | 2 +- .../securesms/transport/PushTransport.java | 41 ++++++++++- 14 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/util/Medium.java rename src/org/thoughtcrime/securesms/crypto/{ => protocol}/KeyExchangeMessage.java (95%) diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java index 6053dbc9b0..e4f4e436c5 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyPublic.java @@ -18,4 +18,8 @@ public class PreKeyPublic { return KeyUtil.encodePoint(publicKey.getQ()); } + public ECPublicKeyParameters getPublicKey() { + return publicKey; + } + } diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java index c13b468f88..408ce9a669 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java @@ -3,16 +3,17 @@ package org.whispersystems.textsecure.crypto; import android.content.Context; import android.util.Log; -import com.google.protobuf.ByteString; -import org.whispersystems.textsecure.encoded.PreKeyProtos.PreKeyEntity; -import org.whispersystems.textsecure.push.PreKeyList; +import com.google.thoughtcrimegson.Gson; import org.whispersystems.textsecure.storage.InvalidKeyIdException; 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.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.Arrays; import java.util.Comparator; import java.util.LinkedList; @@ -24,17 +25,18 @@ public class PreKeyUtil { public static List generatePreKeys(Context context, MasterSecret masterSecret) { List records = new LinkedList(); - long preKeyIdOffset = getNextPreKeyId(context); + int preKeyIdOffset = getNextPreKeyId(context); for (int i=0;i nextPreKeyId) - nextPreKeyId = Long.parseLong(keyRecordId); + private static int getNextPreKeyId(Context context) { + try { + File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME); + + if (nextFile.exists()) { + return Util.getSecureRandom().nextInt(Medium.MAX_VALUE); + } else { + InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile)); + PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class); + reader.close(); + return index.nextPreKeyId; } - - if (nextPreKeyId == 0) - nextPreKeyId = SecureRandom.getInstance("SHA1PRNG").nextInt(Integer.MAX_VALUE/2); - - return nextPreKeyId; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); + } 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; + } + } + } diff --git a/library/src/org/whispersystems/textsecure/crypto/PublicKey.java b/library/src/org/whispersystems/textsecure/crypto/PublicKey.java index be9dd0339f..4756042ce4 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PublicKey.java +++ b/library/src/org/whispersystems/textsecure/crypto/PublicKey.java @@ -43,6 +43,11 @@ public class PublicKey { this.id = id; } + public PublicKey(int preKeyId, PreKeyPublic publicKey) { + this.id = preKeyId; + this.publicKey = publicKey.getPublicKey(); + } + public PublicKey(byte[] bytes, int offset) throws InvalidKeyException { Log.w("PublicKey", "PublicKey Length: " + (bytes.length - offset)); if ((bytes.length - offset) < KEY_SIZE) diff --git a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java index a75c5e4aff..cd7e61c2ea 100644 --- a/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java +++ b/library/src/org/whispersystems/textsecure/push/PreKeyEntity.java @@ -18,17 +18,17 @@ import java.lang.reflect.Type; public class PreKeyEntity { - private long keyId; + private int keyId; private PreKeyPublic publicKey; private IdentityKey identityKey; - public PreKeyEntity(long keyId, PreKeyPublic publicKey, IdentityKey identityKey) { + public PreKeyEntity(int keyId, PreKeyPublic publicKey, IdentityKey identityKey) { this.keyId = keyId; this.publicKey = publicKey; this.identityKey = identityKey; } - public long getKeyId() { + public int getKeyId() { return keyId; } diff --git a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java index f084c2dc59..dd67468eee 100644 --- a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java @@ -21,9 +21,9 @@ public class PreKeyRecord extends Record { private final MasterSecret masterSecret; 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 { super(context, PREKEY_DIRECTORY, id+""); @@ -35,7 +35,7 @@ public class PreKeyRecord extends Record { } public PreKeyRecord(Context context, MasterSecret masterSecret, - long id, PreKeyPair keyPair) + int id, PreKeyPair keyPair) { super(context, PREKEY_DIRECTORY, id+""); this.id = id; @@ -43,7 +43,7 @@ public class PreKeyRecord extends Record { this.masterSecret = masterSecret; } - public long getId() { + public int getId() { return id; } diff --git a/library/src/org/whispersystems/textsecure/util/Medium.java b/library/src/org/whispersystems/textsecure/util/Medium.java new file mode 100644 index 0000000000..acaa20086e --- /dev/null +++ b/library/src/org/whispersystems/textsecure/util/Medium.java @@ -0,0 +1,5 @@ +package org.whispersystems.textsecure.util; + +public class Medium { + public static int MAX_VALUE = 0xFFF; +} diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index e7e678c1dc..379c250000 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -119,4 +119,12 @@ public class Util { return result.toString(); } + + public static SecureRandom getSecureRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } } diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 435907a4cd..1da034570e 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -32,7 +32,7 @@ import android.widget.TextView; import org.whispersystems.textsecure.crypto.InvalidKeyException; 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.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 17f38fa3c3..d64c776e14 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -20,6 +20,7 @@ import android.content.Context; import android.database.Cursor; import android.util.Log; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index 66d034d692..e3a3d13537 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -22,6 +22,7 @@ import android.content.DialogInterface; import android.util.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index dcd0513d3d..82b39ab16b 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -20,9 +20,13 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.KeyUtil; 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.RemoteKeyRecord; import org.whispersystems.textsecure.storage.SessionRecord; @@ -65,8 +69,12 @@ public class KeyExchangeProcessor { return false; } + return isTrusted(message.getIdentityKey()); + } + + public boolean isTrusted(IdentityKey identityKey) { return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, - message.getIdentityKey()); + identityKey); } public boolean hasInitiatedSession() { @@ -86,7 +94,25 @@ public class KeyExchangeProcessor { (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()); message.getPublicKey().setId(initiateKeyId); @@ -123,8 +149,6 @@ public class KeyExchangeProcessor { intent.putExtra("thread_id", threadId); intent.setPackage(context.getPackageName()); context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - - return true; } } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java similarity index 95% rename from src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java rename to src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index 2cf225bd0e..c08312e4d0 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -14,11 +14,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.thoughtcrime.securesms.crypto; +package org.thoughtcrime.securesms.crypto.protocol; import android.content.Context; 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.InvalidKeyException; import org.whispersystems.textsecure.crypto.MasterSecret; @@ -58,9 +60,9 @@ public class KeyExchangeMessage { private final int messageVersion; private final int supportedVersion; - private final PublicKey publicKey; + private final PublicKey publicKey; private final String serialized; - private IdentityKey identityKey; + private IdentityKey identityKey; public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index 9fb53684b8..efe8a7604b 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -24,7 +24,7 @@ import android.util.Pair; import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.whispersystems.textsecure.crypto.InvalidKeyException; 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.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index fc3155ec74..5c2c44cd40 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -3,14 +3,20 @@ package org.thoughtcrime.securesms.transport; import android.content.Context; 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.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.util.TextSecurePreferences; 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.PushServiceSocket; import org.whispersystems.textsecure.push.RateLimitException; +import org.whispersystems.textsecure.storage.SessionRecord; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.IOException; @@ -37,10 +43,22 @@ public class PushTransport extends BaseTransport { String password = TextSecurePreferences.getPushServerPassword(context); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); - String recipientNumber = message.getIndividualRecipient().getNumber(); - String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipientNumber, + Recipient recipient = message.getIndividualRecipient(); + String plaintext = message.getBody().getBody(); + String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), 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()); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); @@ -82,4 +100,23 @@ public class PushTransport extends BaseTransport { 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()); + } + } + }