Add V3 support for KeyExchangeMessage case.

1) V3 KeyExchangeMessages can now contain signatures and
   verification tags.
This commit is contained in:
Moxie Marlinspike 2014-07-08 19:54:07 -07:00
parent 77ff9cece8
commit 64b40df15b
5 changed files with 310 additions and 101 deletions

View File

@ -25,4 +25,6 @@ message KeyExchangeMessage {
optional bytes baseKey = 2;
optional bytes ephemeralKey = 3;
optional bytes identityKey = 4;
optional bytes baseKeySignature = 5;
optional bytes verification = 6;
}

View File

@ -501,7 +501,7 @@ public class SessionBuilderTest extends AndroidTestCase {
}
public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException {
public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException, InvalidVersionException {
SessionStore aliceSessionStore = new InMemorySessionStore();
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
DeviceKeyStore aliceDeviceKeyStore = new InMemoryDeviceKeyStore();
@ -521,12 +521,15 @@ public class SessionBuilderTest extends AndroidTestCase {
ALICE_RECIPIENT_ID, 1);
KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process();
KeyExchangeMessage bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
assertTrue(bobKeyExchangeMessage != null);
assertTrue(aliceKeyExchangeMessage != null);
KeyExchangeMessage response = aliceSessionBuilder.process(bobKeyExchangeMessage);
byte[] aliceKeyExchangeMessageBytes = aliceKeyExchangeMessage.serialize();
KeyExchangeMessage bobKeyExchangeMessage = bobSessionBuilder.process(new KeyExchangeMessage(aliceKeyExchangeMessageBytes));
assertTrue(bobKeyExchangeMessage != null);
byte[] bobKeyExchangeMessageBytes = bobKeyExchangeMessage.serialize();
KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes));
assertTrue(response == null);
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1));

View File

@ -5,10 +5,10 @@ import android.util.Log;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.DeviceKeyRecord;
import org.whispersystems.libaxolotl.state.DeviceKeyStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
@ -149,7 +149,6 @@ public class SessionBuilder {
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setAliceBaseKey(theirBaseKey.serialize());
sessionRecord.getSessionState().setSessionVersion(message.getMessageVersion());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
@ -197,7 +196,6 @@ public class SessionBuilder {
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setSessionVersion(message.getMessageVersion());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
@ -277,31 +275,71 @@ public class SessionBuilder {
}
KeyExchangeMessage responseMessage = null;
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
Log.w(TAG, "Received key exchange with sequence: " + message.getSequence());
if (message.isInitiate()) responseMessage = processInitiate(message);
else processResponse(message);
if (message.isInitiate()) {
Log.w(TAG, "KeyExchange is an initiate.");
responseMessage = processInitiate(sessionRecord, message);
return responseMessage;
}
if (message.isResponse()) {
private KeyExchangeMessage processInitiate(KeyExchangeMessage message) throws InvalidKeyException {
ECKeyPair ourBaseKey, ourEphemeralKey;
IdentityKeyPair ourIdentityKey;
int flags = KeyExchangeMessage.RESPONSE_FLAG;
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
if (message.getVersion() >= 3 &&
!Curve.verifySignature(message.getIdentityKey().getPublicKey(),
message.getBaseKey().serialize(),
message.getBaseKeySignature()))
{
throw new InvalidKeyException("Bad signature!");
}
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w(TAG, "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = identityKeyStore.getIdentityKeyPair();
} else {
Log.w(TAG, "We already have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
}
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourBaseKey, message.getBaseKey(),
ourIdentityKey, message.getIdentityKey());
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
identityKeyStore.saveIdentity(recipientId, message.getIdentityKey());
return new KeyExchangeMessage(sessionRecord.getSessionState().getSessionVersion(),
message.getSequence(), flags, ourBaseKey.getPublicKey(), null,
ourEphemeralKey.getPublicKey(), ourIdentityKey.getPublicKey(),
sessionRecord.getSessionState().getVerification());
}
private void processResponse(KeyExchangeMessage message)
throws StaleKeyExchangeException, InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
SessionState sessionState = sessionRecord.getSessionState();
boolean hasPendingKeyExchange = sessionState.hasPendingKeyExchange();
boolean isSimultaneousInitiateResponse = message.isResponseForSimultaneousInitiate();
if ((!hasPendingKeyExchange || sessionState.getPendingKeyExchangeSequence() != message.getSequence()) &&
!isSimultaneousInitiateResponse)
{
throw new StaleKeyExchangeException();
}
}
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
Log.w("KeyExchangeProcessor", "No matching sequence for response. " +
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
return responseMessage;
if (!hasPendingKeyExchange || sessionState.getPendingKeyExchangeSequence() != message.getSequence()) {
Log.w(TAG, "No matching sequence for response. Is simultaneous initiate response: " + isSimultaneousInitiateResponse);
if (!isSimultaneousInitiateResponse) throw new StaleKeyExchangeException();
else return;
}
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
@ -311,51 +349,22 @@ public class SessionBuilder {
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
2,
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
null, null,
ourBaseKey, message.getBaseKey(),
ourIdentityKey, message.getIdentityKey());
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
if (sessionRecord.getSessionState().getSessionVersion() >= 3 &&
!MessageDigest.isEqual(message.getVerificationTag(),
sessionRecord.getSessionState().getVerification()))
{
throw new InvalidKeyException("Verification tag doesn't match!");
}
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
identityKeyStore.saveIdentity(recipientId, message.getIdentityKey());
return responseMessage;
}
private KeyExchangeMessage processInitiate(SessionRecord sessionRecord, KeyExchangeMessage message)
throws InvalidKeyException
{
ECKeyPair ourBaseKey, ourEphemeralKey;
IdentityKeyPair ourIdentityKey;
int flags = KeyExchangeMessage.RESPONSE_FLAG;
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w(TAG, "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = identityKeyStore.getIdentityKeyPair();
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
} else {
Log.w(TAG, "We already have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
}
return new KeyExchangeMessage(message.getSequence(),
flags, ourBaseKey.getPublicKey(),
ourEphemeralKey.getPublicKey(),
ourIdentityKey.getPublicKey());
}
/**
@ -374,10 +383,10 @@ public class SessionBuilder {
sessionRecord.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
return new KeyExchangeMessage(sequence, flags,
baseKey.getPublicKey(),
return new KeyExchangeMessage(2, sequence, flags,
baseKey.getPublicKey(), null,
ephemeralKey.getPublicKey(),
identityKey.getPublicKey());
identityKey.getPublicKey(), null);
}

View File

@ -14,6 +14,8 @@ import org.whispersystems.libaxolotl.util.ByteUtil;
import java.io.IOException;
import static org.whispersystems.libaxolotl.protocol.WhisperProtos.KeyExchangeMessage.Builder;
public class KeyExchangeMessage {
public static final int INITIATE_FLAG = 0x01;
@ -26,31 +28,43 @@ public class KeyExchangeMessage {
private final int flags;
private final ECPublicKey baseKey;
private final byte[] baseKeySignature;
private final ECPublicKey ephemeralKey;
private final IdentityKey identityKey;
private final byte[] verificationTag;
private final byte[] serialized;
public KeyExchangeMessage(int sequence, int flags,
ECPublicKey baseKey, ECPublicKey ephemeralKey,
IdentityKey identityKey)
public KeyExchangeMessage(int messageVersion, int sequence, int flags,
ECPublicKey baseKey, byte[] baseKeySignature,
ECPublicKey ephemeralKey,
IdentityKey identityKey, byte[] verificationTag)
{
this.supportedVersion = 2;
this.version = 2;
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
this.version = messageVersion;
this.sequence = sequence;
this.flags = flags;
this.baseKey = baseKey;
this.baseKeySignature = baseKeySignature;
this.ephemeralKey = ephemeralKey;
this.identityKey = identityKey;
this.verificationTag = verificationTag;
byte[] version = {ByteUtil.intsToByteHighAndLow(this.version, this.supportedVersion)};
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
Builder builder = WhisperProtos.KeyExchangeMessage.newBuilder()
.setId((sequence << 5) | flags)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.build().toByteArray();
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()));
this.serialized = ByteUtil.combine(version, message);
if (messageVersion >= 3 && baseKeySignature != null) {
builder.setBaseKeySignature(ByteString.copyFrom(baseKeySignature));
}
if (messageVersion >=3 && verificationTag != null) {
builder.setVerification(ByteString.copyFrom(verificationTag));
}
this.serialized = ByteUtil.combine(version, builder.build().toByteArray());
}
public KeyExchangeMessage(byte[] serialized)
@ -72,7 +86,9 @@ public class KeyExchangeMessage {
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
if (!message.hasId() || !message.hasBaseKey() ||
!message.hasEphemeralKey() || !message.hasIdentityKey())
!message.hasEphemeralKey() || !message.hasIdentityKey() ||
(this.version >=3 && (((message.getId() & 0x1f) & INITIATE_FLAG) != 0) && !message.hasBaseKeySignature()) ||
(this.version >=3 && (((message.getId() & 0x1f) & RESPONSE_FLAG) != 0) && !message.hasVerification()))
{
throw new InvalidMessageException("Some required fields missing!");
}
@ -81,6 +97,8 @@ public class KeyExchangeMessage {
this.flags = message.getId() & 0x1f;
this.serialized = serialized;
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
this.baseKeySignature = message.getBaseKeySignature().toByteArray();
this.verificationTag = message.getVerification().toByteArray();
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
} catch (InvalidKeyException | IOException e) {
@ -96,6 +114,14 @@ public class KeyExchangeMessage {
return baseKey;
}
public byte[] getBaseKeySignature() {
return baseKeySignature;
}
public byte[] getVerificationTag() {
return verificationTag;
}
public ECPublicKey getEphemeralKey() {
return ephemeralKey;
}

View File

@ -1621,6 +1621,26 @@ public final class WhisperProtos {
* <code>optional bytes identityKey = 4;</code>
*/
com.google.protobuf.ByteString getIdentityKey();
// optional bytes baseKeySignature = 5;
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
boolean hasBaseKeySignature();
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
com.google.protobuf.ByteString getBaseKeySignature();
// optional bytes verification = 6;
/**
* <code>optional bytes verification = 6;</code>
*/
boolean hasVerification();
/**
* <code>optional bytes verification = 6;</code>
*/
com.google.protobuf.ByteString getVerification();
}
/**
* Protobuf type {@code textsecure.KeyExchangeMessage}
@ -1693,6 +1713,16 @@ public final class WhisperProtos {
identityKey_ = input.readBytes();
break;
}
case 42: {
bitField0_ |= 0x00000010;
baseKeySignature_ = input.readBytes();
break;
}
case 50: {
bitField0_ |= 0x00000020;
verification_ = input.readBytes();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -1797,11 +1827,45 @@ public final class WhisperProtos {
return identityKey_;
}
// optional bytes baseKeySignature = 5;
public static final int BASEKEYSIGNATURE_FIELD_NUMBER = 5;
private com.google.protobuf.ByteString baseKeySignature_;
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public boolean hasBaseKeySignature() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public com.google.protobuf.ByteString getBaseKeySignature() {
return baseKeySignature_;
}
// optional bytes verification = 6;
public static final int VERIFICATION_FIELD_NUMBER = 6;
private com.google.protobuf.ByteString verification_;
/**
* <code>optional bytes verification = 6;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00000020) == 0x00000020);
}
/**
* <code>optional bytes verification = 6;</code>
*/
public com.google.protobuf.ByteString getVerification() {
return verification_;
}
private void initFields() {
id_ = 0;
baseKey_ = com.google.protobuf.ByteString.EMPTY;
ephemeralKey_ = com.google.protobuf.ByteString.EMPTY;
identityKey_ = com.google.protobuf.ByteString.EMPTY;
baseKeySignature_ = com.google.protobuf.ByteString.EMPTY;
verification_ = com.google.protobuf.ByteString.EMPTY;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -1827,6 +1891,12 @@ public final class WhisperProtos {
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeBytes(4, identityKey_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
output.writeBytes(5, baseKeySignature_);
}
if (((bitField0_ & 0x00000020) == 0x00000020)) {
output.writeBytes(6, verification_);
}
getUnknownFields().writeTo(output);
}
@ -1852,6 +1922,14 @@ public final class WhisperProtos {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(4, identityKey_);
}
if (((bitField0_ & 0x00000010) == 0x00000010)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(5, baseKeySignature_);
}
if (((bitField0_ & 0x00000020) == 0x00000020)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(6, verification_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -1976,6 +2054,10 @@ public final class WhisperProtos {
bitField0_ = (bitField0_ & ~0x00000004);
identityKey_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000008);
baseKeySignature_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000010);
verification_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000020);
return this;
}
@ -2020,6 +2102,14 @@ public final class WhisperProtos {
to_bitField0_ |= 0x00000008;
}
result.identityKey_ = identityKey_;
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000010;
}
result.baseKeySignature_ = baseKeySignature_;
if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
to_bitField0_ |= 0x00000020;
}
result.verification_ = verification_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -2048,6 +2138,12 @@ public final class WhisperProtos {
if (other.hasIdentityKey()) {
setIdentityKey(other.getIdentityKey());
}
if (other.hasBaseKeySignature()) {
setBaseKeySignature(other.getBaseKeySignature());
}
if (other.hasVerification()) {
setVerification(other.getVerification());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -2216,6 +2312,78 @@ public final class WhisperProtos {
return this;
}
// optional bytes baseKeySignature = 5;
private com.google.protobuf.ByteString baseKeySignature_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public boolean hasBaseKeySignature() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public com.google.protobuf.ByteString getBaseKeySignature() {
return baseKeySignature_;
}
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public Builder setBaseKeySignature(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000010;
baseKeySignature_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes baseKeySignature = 5;</code>
*/
public Builder clearBaseKeySignature() {
bitField0_ = (bitField0_ & ~0x00000010);
baseKeySignature_ = getDefaultInstance().getBaseKeySignature();
onChanged();
return this;
}
// optional bytes verification = 6;
private com.google.protobuf.ByteString verification_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes verification = 6;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00000020) == 0x00000020);
}
/**
* <code>optional bytes verification = 6;</code>
*/
public com.google.protobuf.ByteString getVerification() {
return verification_;
}
/**
* <code>optional bytes verification = 6;</code>
*/
public Builder setVerification(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000020;
verification_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes verification = 6;</code>
*/
public Builder clearVerification() {
bitField0_ = (bitField0_ & ~0x00000020);
verification_ = getDefaultInstance().getVerification();
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:textsecure.KeyExchangeMessage)
}
@ -2258,11 +2426,12 @@ public final class WhisperProtos {
"essage\022\026\n\016registrationId\030\005 \001(\r\022\020\n\010preKey" +
"Id\030\001 \001(\r\022\023\n\013deviceKeyId\030\006 \001(\r\022\017\n\007baseKey" +
"\030\002 \001(\014\022\023\n\013identityKey\030\003 \001(\014\022\024\n\014verificat" +
"ion\030\007 \001(\014\022\017\n\007message\030\004 \001(\014\"\\\n\022KeyExchang" +
"eMessage\022\n\n\002id\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\022\024\n" +
"\014ephemeralKey\030\003 \001(\014\022\023\n\013identityKey\030\004 \001(\014",
"B7\n&org.whispersystems.libaxolotl.protoc" +
"olB\rWhisperProtos"
"ion\030\007 \001(\014\022\017\n\007message\030\004 \001(\014\"\214\001\n\022KeyExchan" +
"geMessage\022\n\n\002id\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\022\024" +
"\n\014ephemeralKey\030\003 \001(\014\022\023\n\013identityKey\030\004 \001(",
"\014\022\030\n\020baseKeySignature\030\005 \001(\014\022\024\n\014verificat" +
"ion\030\006 \001(\014B7\n&org.whispersystems.libaxolo" +
"tl.protocolB\rWhisperProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -2286,7 +2455,7 @@ public final class WhisperProtos {
internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_KeyExchangeMessage_descriptor,
new java.lang.String[] { "Id", "BaseKey", "EphemeralKey", "IdentityKey", });
new java.lang.String[] { "Id", "BaseKey", "EphemeralKey", "IdentityKey", "BaseKeySignature", "Verification", });
return null;
}
};