From 499de2d2bff53c302173c80c9b22b2b89e3728ba Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sat, 17 Aug 2013 19:06:04 -0700 Subject: [PATCH] Move prekey jsonifcation into the push code, add identity key. --- library/protobuf/PreKeyEntity.proto | 5 +- .../textsecure/crypto/PreKeyUtil.java | 16 --- .../textsecure/crypto/SessionCipher.java | 2 +- .../{ => crypto}/protocol/Message.java | 2 +- .../textsecure/encoded/PreKeyProtos.java | 122 +++++++++++++----- .../textsecure/push/PushServiceSocket.java | 35 +++-- .../textsecure/storage/PreKeyRecord.java | 5 + .../securesms/crypto/IdentityKeyUtil.java | 1 - .../securesms/crypto/KeyExchangeMessage.java | 2 +- .../crypto/KeyExchangeProcessor.java | 2 +- .../service/RegistrationService.java | 7 +- 11 files changed, 134 insertions(+), 65 deletions(-) rename library/src/org/whispersystems/textsecure/{ => crypto}/protocol/Message.java (99%) diff --git a/library/protobuf/PreKeyEntity.proto b/library/protobuf/PreKeyEntity.proto index eae28cc31f..490c1cc5ba 100644 --- a/library/protobuf/PreKeyEntity.proto +++ b/library/protobuf/PreKeyEntity.proto @@ -4,6 +4,7 @@ option java_package = "org.whispersystems.textsecure.encoded"; option java_outer_classname = "PreKeyProtos"; message PreKeyEntity { - optional uint64 id = 1; - optional bytes key = 2; + optional uint64 id = 1; + optional bytes public_key = 2; + optional bytes identity_key = 3; } diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java index fb8a8cf5fa..73904706a0 100644 --- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java +++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java @@ -65,22 +65,6 @@ public class PreKeyUtil { } } - public static PreKeyList toJson(List records) { - List encoded = new LinkedList(); - - for (PreKeyRecord record : records) { - PreKeyEntity entity = PreKeyEntity.newBuilder().setId(record.getId()) - .setKey(ByteString.copyFrom(KeyUtil.encodePoint(record.getKeyPair().getPublicKey().getQ()))) - .build(); - - String encodedEntity = Base64.encodeBytesWithoutPadding(entity.toByteArray()); - - encoded.add(encodedEntity); - } - - return new PreKeyList(encoded); - } - private static long getNextPreKeyId(Context context) { try { File directory = getPreKeysDirectory(context); diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index 773bbfc9a9..0a6cb5cc10 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -22,7 +22,7 @@ import android.util.Log; import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.agreement.ECDHBasicAgreement; import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.whispersystems.textsecure.protocol.Message; +import org.whispersystems.textsecure.crypto.protocol.Message; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.LocalKeyRecord; diff --git a/library/src/org/whispersystems/textsecure/protocol/Message.java b/library/src/org/whispersystems/textsecure/crypto/protocol/Message.java similarity index 99% rename from library/src/org/whispersystems/textsecure/protocol/Message.java rename to library/src/org/whispersystems/textsecure/crypto/protocol/Message.java index cd5c8ea73e..709d943e9c 100644 --- a/library/src/org/whispersystems/textsecure/protocol/Message.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/Message.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.whispersystems.textsecure.protocol; +package org.whispersystems.textsecure.crypto.protocol; import android.util.Log; diff --git a/library/src/org/whispersystems/textsecure/encoded/PreKeyProtos.java b/library/src/org/whispersystems/textsecure/encoded/PreKeyProtos.java index dd7a9cddce..14f9f5fab0 100644 --- a/library/src/org/whispersystems/textsecure/encoded/PreKeyProtos.java +++ b/library/src/org/whispersystems/textsecure/encoded/PreKeyProtos.java @@ -15,9 +15,13 @@ public final class PreKeyProtos { boolean hasId(); long getId(); - // optional bytes key = 2; - boolean hasKey(); - com.google.protobuf.ByteString getKey(); + // optional bytes public_key = 2; + boolean hasPublicKey(); + com.google.protobuf.ByteString getPublicKey(); + + // optional bytes identity_key = 3; + boolean hasIdentityKey(); + com.google.protobuf.ByteString getIdentityKey(); } public static final class PreKeyEntity extends com.google.protobuf.GeneratedMessage @@ -58,19 +62,30 @@ public final class PreKeyProtos { return id_; } - // optional bytes key = 2; - public static final int KEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString key_; - public boolean hasKey() { + // optional bytes public_key = 2; + public static final int PUBLIC_KEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString publicKey_; + public boolean hasPublicKey() { return ((bitField0_ & 0x00000002) == 0x00000002); } - public com.google.protobuf.ByteString getKey() { - return key_; + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + + // optional bytes identity_key = 3; + public static final int IDENTITY_KEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString identityKey_; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; } private void initFields() { id_ = 0L; - key_ = com.google.protobuf.ByteString.EMPTY; + publicKey_ = com.google.protobuf.ByteString.EMPTY; + identityKey_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -88,7 +103,10 @@ public final class PreKeyProtos { output.writeUInt64(1, id_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, key_); + output.writeBytes(2, publicKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, identityKey_); } getUnknownFields().writeTo(output); } @@ -105,7 +123,11 @@ public final class PreKeyProtos { } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, key_); + .computeBytesSize(2, publicKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, identityKey_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -233,8 +255,10 @@ public final class PreKeyProtos { super.clear(); id_ = 0L; bitField0_ = (bitField0_ & ~0x00000001); - key_ = com.google.protobuf.ByteString.EMPTY; + publicKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000002); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -280,7 +304,11 @@ public final class PreKeyProtos { if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } - result.key_ = key_; + result.publicKey_ = publicKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.identityKey_ = identityKey_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -300,8 +328,11 @@ public final class PreKeyProtos { if (other.hasId()) { setId(other.getId()); } - if (other.hasKey()) { - setKey(other.getKey()); + if (other.hasPublicKey()) { + setPublicKey(other.getPublicKey()); + } + if (other.hasIdentityKey()) { + setIdentityKey(other.getIdentityKey()); } this.mergeUnknownFields(other.getUnknownFields()); return this; @@ -341,7 +372,12 @@ public final class PreKeyProtos { } case 18: { bitField0_ |= 0x00000002; - key_ = input.readBytes(); + publicKey_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + identityKey_ = input.readBytes(); break; } } @@ -371,26 +407,50 @@ public final class PreKeyProtos { return this; } - // optional bytes key = 2; - private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; - public boolean hasKey() { + // optional bytes public_key = 2; + private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasPublicKey() { return ((bitField0_ & 0x00000002) == 0x00000002); } - public com.google.protobuf.ByteString getKey() { - return key_; + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; } - public Builder setKey(com.google.protobuf.ByteString value) { + public Builder setPublicKey(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000002; - key_ = value; + publicKey_ = value; onChanged(); return this; } - public Builder clearKey() { + public Builder clearPublicKey() { bitField0_ = (bitField0_ & ~0x00000002); - key_ = getDefaultInstance().getKey(); + publicKey_ = getDefaultInstance().getPublicKey(); + onChanged(); + return this; + } + + // optional bytes identity_key = 3; + private com.google.protobuf.ByteString identityKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + public Builder setIdentityKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + identityKey_ = value; + onChanged(); + return this; + } + public Builder clearIdentityKey() { + bitField0_ = (bitField0_ & ~0x00000004); + identityKey_ = getDefaultInstance().getIdentityKey(); onChanged(); return this; } @@ -420,10 +480,10 @@ public final class PreKeyProtos { descriptor; static { java.lang.String[] descriptorData = { - "\n\022PreKeyEntity.proto\022\ntextsecure\"\'\n\014PreK" + - "eyEntity\022\n\n\002id\030\001 \001(\004\022\013\n\003key\030\002 \001(\014B5\n%org" + - ".whispersystems.textsecure.encodedB\014PreK" + - "eyProtos" + "\n\022PreKeyEntity.proto\022\ntextsecure\"D\n\014PreK" + + "eyEntity\022\n\n\002id\030\001 \001(\004\022\022\n\npublic_key\030\002 \001(\014" + + "\022\024\n\014identity_key\030\003 \001(\014B5\n%org.whispersys" + + "tems.textsecure.encodedB\014PreKeyProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -435,7 +495,7 @@ public final class PreKeyProtos { internal_static_textsecure_PreKeyEntity_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PreKeyEntity_descriptor, - new java.lang.String[] { "Id", "Key", }, + new java.lang.String[] { "Id", "PublicKey", "IdentityKey", }, org.whispersystems.textsecure.encoded.PreKeyProtos.PreKeyEntity.class, org.whispersystems.textsecure.encoded.PreKeyProtos.PreKeyEntity.Builder.class); return null; diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 9ef057c834..bf8c08b3ca 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -1,15 +1,18 @@ package org.whispersystems.textsecure.push; import android.content.Context; -import android.util.Base64; import android.util.Log; import android.util.Pair; +import com.google.protobuf.ByteString; import com.google.thoughtcrimegson.Gson; import org.whispersystems.textsecure.R; import org.whispersystems.textsecure.Release; +import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.directory.DirectoryDescriptor; -import org.whispersystems.textsecure.directory.NumberFilter; +import org.whispersystems.textsecure.encoded.PreKeyProtos.PreKeyEntity; +import org.whispersystems.textsecure.storage.PreKeyRecord; +import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; import javax.net.ssl.HttpsURLConnection; @@ -31,7 +34,6 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.LinkedList; import java.util.List; -import java.util.zip.GZIPInputStream; public class PushServiceSocket { @@ -57,16 +59,16 @@ public class PushServiceSocket { this.trustManagerFactory = initializeTrustManagerFactory(context); } - public void createAccount(boolean voice) throws IOException, RateLimitException { + public void createAccount(boolean voice) throws IOException { String path = voice ? CREATE_ACCOUNT_VOICE_PATH : CREATE_ACCOUNT_SMS_PATH; makeRequest(String.format(path, localNumber), "POST", null); } - public void verifyAccount(String verificationCode) throws IOException, RateLimitException { + public void verifyAccount(String verificationCode) throws IOException { makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", null); } - public void registerGcmId(String gcmRegistrationId) throws IOException, RateLimitException { + public void registerGcmId(String gcmRegistrationId) throws IOException { GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId); makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration)); } @@ -106,8 +108,23 @@ public class PushServiceSocket { throw new IOException("Got send failure: " + response.getFailure().get(0)); } - public void registerPreKeys(PreKeyList keys) throws IOException { - makeRequest(PREKEY_PATH, "PUT", new Gson().toJson(keys)); + public void registerPreKeys(IdentityKey identityKey, List records) + throws IOException + { + List encoded = new LinkedList(); + + for (PreKeyRecord record : records) { + PreKeyEntity entity = PreKeyEntity.newBuilder().setId(record.getId()) + .setPublicKey(ByteString.copyFrom(record.getEncodedPublicKey())) + .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) + .build(); + + String encodedEntity = Base64.encodeBytesWithoutPadding(entity.toByteArray()); + + encoded.add(encodedEntity); + } + + makeRequest(PREKEY_PATH, "PUT", new Gson().toJson(new PreKeyList(encoded))); } @@ -317,7 +334,7 @@ public class PushServiceSocket { private String getAuthorizationHeader() { try { - return "Basic " + new String(Base64.encode((localNumber + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP)); + return "Basic " + Base64.encodeBytes((localNumber + ":" + password).getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); } diff --git a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java index f084c2dc59..ce2261cd22 100644 --- a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java @@ -4,6 +4,7 @@ import android.content.Context; import android.util.Log; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyPair; @@ -51,6 +52,10 @@ public class PreKeyRecord extends Record { return keyPair; } + public byte[] getEncodedPublicKey() { + return KeyUtil.encodePoint(keyPair.getPublicKey().getQ()); + } + public static boolean hasRecord(Context context, long id) { Log.w("PreKeyRecord", "Checking: " + id); return Record.hasRecord(context, PREKEY_DIRECTORY, id+""); diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index 7ccd1fc657..2c2ebde48c 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -22,7 +22,6 @@ import android.content.SharedPreferences.Editor; import android.util.Log; import org.spongycastle.asn1.ASN1Encoding; -import org.spongycastle.asn1.ASN1Object; import org.spongycastle.asn1.ASN1Primitive; import org.spongycastle.asn1.ASN1Sequence; import org.spongycastle.asn1.DERInteger; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java index 64a00c82cd..2cf225bd0e 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeMessage.java @@ -24,7 +24,7 @@ import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.protocol.Message; +import org.whispersystems.textsecure.crypto.protocol.Message; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 50787c78e8..dcd0513d3d 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -26,7 +26,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.SessionRecord; -import org.whispersystems.textsecure.protocol.Message; +import org.whispersystems.textsecure.crypto.protocol.Message; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java index eefa969396..355d380216 100644 --- a/src/org/thoughtcrime/securesms/service/RegistrationService.java +++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java @@ -13,6 +13,8 @@ import android.util.Pair; import com.google.android.gcm.GCMRegistrar; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PreKeyUtil; import org.whispersystems.textsecure.storage.PreKeyRecord; @@ -268,8 +270,9 @@ public class RegistrationService extends Service { throws GcmRegistrationTimeoutException, IOException { setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number)); - List records = waitForPreKeys(masterSecret); - socket.registerPreKeys(PreKeyUtil.toJson(records)); + IdentityKey identityKey = IdentityKeyUtil.getIdentityKey(this); + List records = waitForPreKeys(masterSecret); + socket.registerPreKeys(identityKey, records); setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number)); GCMRegistrar.register(this, GcmIntentService.GCM_SENDER_ID);