From 77ff9cece8df363a1bbda7fc7c810012750fe372 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 7 Jul 2014 12:37:01 -0700 Subject: [PATCH] Add a 'verification tag' to incoming PreKeyWhisperMessage bundles. --- .../protobuf/LocalStorageProtocol.proto | 1 + libaxolotl/protobuf/WhisperTextProtocol.proto | 1 + .../test/SessionBuilderTest.java | 76 +++++++- .../test/ratchet/VerifyKeyTest.java | 84 +++++++++ .../libaxolotl/SessionBuilder.java | 14 +- .../libaxolotl/SessionCipher.java | 1 + ...ecrets.java => DerivedMessageSecrets.java} | 7 +- .../libaxolotl/kdf/DerivedRootSecrets.java | 26 +++ .../protocol/PreKeyWhisperMessage.java | 20 +- .../libaxolotl/protocol/WhisperProtos.java | 115 ++++++++++-- .../libaxolotl/ratchet/ChainKey.java | 8 +- .../libaxolotl/ratchet/RatchetingSession.java | 103 ++++++++--- .../libaxolotl/ratchet/RootKey.java | 12 +- .../libaxolotl/ratchet/VerifyKey.java | 50 +++++ .../libaxolotl/state/SessionState.java | 11 ++ .../libaxolotl/state/StorageProtos.java | 171 +++++++++++++----- 16 files changed, 589 insertions(+), 111 deletions(-) create mode 100644 libaxolotl/src/androidTest/java/org/whispersystems/test/ratchet/VerifyKeyTest.java rename libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/{DerivedSecrets.java => DerivedMessageSecrets.java} (92%) create mode 100644 libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedRootSecrets.java create mode 100644 libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/VerifyKey.java diff --git a/libaxolotl/protobuf/LocalStorageProtocol.proto b/libaxolotl/protobuf/LocalStorageProtocol.proto index 15e710114d..fab8f2ef73 100644 --- a/libaxolotl/protobuf/LocalStorageProtocol.proto +++ b/libaxolotl/protobuf/LocalStorageProtocol.proto @@ -58,6 +58,7 @@ message SessionStructure { optional bool needsRefresh = 12; optional bytes aliceBaseKey = 13; + optional bytes verification = 14; } message RecordStructure { diff --git a/libaxolotl/protobuf/WhisperTextProtocol.proto b/libaxolotl/protobuf/WhisperTextProtocol.proto index 23ca2091d9..cc278ea748 100644 --- a/libaxolotl/protobuf/WhisperTextProtocol.proto +++ b/libaxolotl/protobuf/WhisperTextProtocol.proto @@ -16,6 +16,7 @@ message PreKeyWhisperMessage { optional uint32 deviceKeyId = 6; optional bytes baseKey = 2; optional bytes identityKey = 3; + optional bytes verification = 7; optional bytes message = 4; // WhisperMessage } diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java index 3024181d43..cb00031667 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java @@ -357,7 +357,6 @@ public class SessionBuilderTest extends AndroidTestCase { } - public void testRepeatBundleMessageV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException { SessionStore aliceSessionStore = new InMemorySessionStore(); DeviceKeyStore aliceDeviceKeyStore = new InMemoryDeviceKeyStore(); @@ -426,6 +425,81 @@ public class SessionBuilderTest extends AndroidTestCase { } + public void testBadVerificationTagV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException { + SessionStore aliceSessionStore = new InMemorySessionStore(); + DeviceKeyStore aliceDeviceKeyStore = new InMemoryDeviceKeyStore(); + PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); + IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, + aliceDeviceKeyStore, + aliceIdentityKeyStore, + BOB_RECIPIENT_ID, 1); + + SessionStore bobSessionStore = new InMemorySessionStore(); + PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); + DeviceKeyStore bobDeviceKeyStore = new InMemoryDeviceKeyStore(); + IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore, + bobDeviceKeyStore, + bobIdentityKeyStore, + ALICE_RECIPIENT_ID, 1); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(true); + ECKeyPair bobDeviceKeyPair = Curve.generateKeyPair(true); + byte[] bobDeviceKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + bobDeviceKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobDeviceKeyPair.getPublicKey(), bobDeviceKeySignature, + bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + + bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobDeviceKeyStore.storeDeviceKey(22, new DeviceKeyRecord(22, System.currentTimeMillis(), bobDeviceKeyPair, bobDeviceKeySignature)); + + aliceSessionBuilder.process(bobPreKey); + + String originalMessage = "L'homme est condamné à être libre"; + SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, BOB_RECIPIENT_ID, 1); + CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); + + PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize()); + + for (int i=0;i= 0 ? preKeyStore.loadPreKey(preKeyId) : null; - DeviceKeyRecord deviceKeyRecord = deviceKeyStore.loadDeviceKey(deviceKeyId); - ECKeyPair ourPreKey = preKeyRecord != null ? preKeyRecord.getKeyPair() : null; - ECPublicKey theirPreKey = theirBaseKey; - ECKeyPair ourBaseKey = deviceKeyRecord.getKeyPair(); + ECKeyPair ourBaseKey = deviceKeyStore.loadDeviceKey(deviceKeyId).getKeyPair(); ECKeyPair ourEphemeralKey = ourBaseKey; + ECKeyPair ourPreKey = preKeyId < 0 ? ourBaseKey : preKeyStore.loadPreKey(preKeyId).getKeyPair(); + ECPublicKey theirPreKey = theirBaseKey; IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair(); boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey(); @@ -142,6 +142,10 @@ public class SessionBuilder { ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey); + if (!MessageDigest.isEqual(sessionRecord.getSessionState().getVerification(), message.getVerification())) { + throw new InvalidKeyException("Verification secret mismatch!"); + } + sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(theirBaseKey.serialize()); diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java index 7389d9bad0..cb69a5d55f 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java @@ -106,6 +106,7 @@ public class SessionCipher { localRegistrationId, pendingPreKeyId, pendingDeviceKeyId, pendingBaseKey, sessionState.getLocalIdentityKey(), + sessionState.getVerification(), (WhisperMessage) ciphertextMessage); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedSecrets.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java similarity index 92% rename from libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedSecrets.java rename to libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java index 56d24cd460..f9d8a91918 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedSecrets.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2013 Open Whisper Systems + * Copyright (C) 2014 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ package org.whispersystems.libaxolotl.kdf; import javax.crypto.spec.SecretKeySpec; -public class DerivedSecrets { +public class DerivedMessageSecrets { public static final int SIZE = 64; private static final int CIPHER_KEYS_OFFSET = 0; @@ -28,10 +28,11 @@ public class DerivedSecrets { private final SecretKeySpec cipherKey; private final SecretKeySpec macKey; - public DerivedSecrets(byte[] okm) { + public DerivedMessageSecrets(byte[] okm) { this.cipherKey = deriveCipherKey(okm); this.macKey = deriveMacKey(okm); } + private SecretKeySpec deriveCipherKey(byte[] okm) { byte[] cipherKey = new byte[32]; System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length); diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedRootSecrets.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedRootSecrets.java new file mode 100644 index 0000000000..82cc541d83 --- /dev/null +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedRootSecrets.java @@ -0,0 +1,26 @@ +package org.whispersystems.libaxolotl.kdf; + +import org.whispersystems.libaxolotl.util.ByteUtil; + +public class DerivedRootSecrets { + + public static final int SIZE = 64; + + private final byte[] rootKey; + private final byte[] chainKey; + + public DerivedRootSecrets(byte[] okm) { + byte[][] keys = ByteUtil.split(okm, 32, 32); + this.rootKey = keys[0]; + this.chainKey = keys[1]; + } + + public byte[] getRootKey() { + return rootKey; + } + + public byte[] getChainKey() { + return chainKey; + } + +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/PreKeyWhisperMessage.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/PreKeyWhisperMessage.java index f7f90346da..ac44935e95 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/PreKeyWhisperMessage.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/PreKeyWhisperMessage.java @@ -37,6 +37,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { private final int deviceKeyId; private final ECPublicKey baseKey; private final IdentityKey identityKey; + private final byte[] verification; private final WhisperMessage message; private final byte[] serialized; @@ -54,10 +55,11 @@ public class PreKeyWhisperMessage implements CiphertextMessage { = WhisperProtos.PreKeyWhisperMessage.parseFrom(ByteString.copyFrom(serialized, 1, serialized.length-1)); - if ((version == 2 && !preKeyWhisperMessage.hasPreKeyId()) || - (version == 3 && !preKeyWhisperMessage.hasDeviceKeyId()) || - !preKeyWhisperMessage.hasBaseKey() || - !preKeyWhisperMessage.hasIdentityKey() || + if ((version == 2 && !preKeyWhisperMessage.hasPreKeyId()) || + (version == 3 && !preKeyWhisperMessage.hasDeviceKeyId()) || + (version == 3 && !preKeyWhisperMessage.hasVerification()) || + !preKeyWhisperMessage.hasBaseKey() || + !preKeyWhisperMessage.hasIdentityKey() || !preKeyWhisperMessage.hasMessage()) { throw new InvalidMessageException("Incomplete message."); @@ -69,6 +71,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { this.deviceKeyId = preKeyWhisperMessage.hasDeviceKeyId() ? preKeyWhisperMessage.getDeviceKeyId() : -1; this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); + this.verification = preKeyWhisperMessage.getVerification().toByteArray(); this.message = new WhisperMessage(preKeyWhisperMessage.getMessage().toByteArray()); } catch (InvalidProtocolBufferException | InvalidKeyException | LegacyMessageException e) { throw new InvalidMessageException(e); @@ -76,7 +79,8 @@ public class PreKeyWhisperMessage implements CiphertextMessage { } public PreKeyWhisperMessage(int messageVersion, int registrationId, int preKeyId, int deviceKeyId, - ECPublicKey baseKey, IdentityKey identityKey, WhisperMessage message) + ECPublicKey baseKey, IdentityKey identityKey, byte[] verification, + WhisperMessage message) { this.version = messageVersion; this.registrationId = registrationId; @@ -84,6 +88,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { this.deviceKeyId = deviceKeyId; this.baseKey = baseKey; this.identityKey = identityKey; + this.verification = verification; this.message = message; byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(this.version, CURRENT_VERSION)}; @@ -92,6 +97,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage { .setDeviceKeyId(deviceKeyId) .setBaseKey(ByteString.copyFrom(baseKey.serialize())) .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) + .setVerification(ByteString.copyFrom(verification)) .setMessage(ByteString.copyFrom(message.serialize())) .setRegistrationId(registrationId) .build().toByteArray(); @@ -123,6 +129,10 @@ public class PreKeyWhisperMessage implements CiphertextMessage { return baseKey; } + public byte[] getVerification() { + return verification; + } + public WhisperMessage getWhisperMessage() { return message; } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperProtos.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperProtos.java index bf06328b74..6765827afc 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperProtos.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperProtos.java @@ -706,6 +706,16 @@ public final class WhisperProtos { */ com.google.protobuf.ByteString getIdentityKey(); + // optional bytes verification = 7; + /** + * optional bytes verification = 7; + */ + boolean hasVerification(); + /** + * optional bytes verification = 7; + */ + com.google.protobuf.ByteString getVerification(); + // optional bytes message = 4; /** * optional bytes message = 4; @@ -791,7 +801,7 @@ public final class WhisperProtos { break; } case 34: { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; message_ = input.readBytes(); break; } @@ -805,6 +815,11 @@ public final class WhisperProtos { deviceKeyId_ = input.readUInt32(); break; } + case 58: { + bitField0_ |= 0x00000020; + verification_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -925,6 +940,22 @@ public final class WhisperProtos { return identityKey_; } + // optional bytes verification = 7; + public static final int VERIFICATION_FIELD_NUMBER = 7; + private com.google.protobuf.ByteString verification_; + /** + * optional bytes verification = 7; + */ + public boolean hasVerification() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes verification = 7; + */ + public com.google.protobuf.ByteString getVerification() { + return verification_; + } + // optional bytes message = 4; public static final int MESSAGE_FIELD_NUMBER = 4; private com.google.protobuf.ByteString message_; @@ -936,7 +967,7 @@ public final class WhisperProtos { * */ public boolean hasMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional bytes message = 4; @@ -955,6 +986,7 @@ public final class WhisperProtos { deviceKeyId_ = 0; baseKey_ = com.google.protobuf.ByteString.EMPTY; identityKey_ = com.google.protobuf.ByteString.EMPTY; + verification_ = com.google.protobuf.ByteString.EMPTY; message_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; @@ -978,7 +1010,7 @@ public final class WhisperProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeBytes(3, identityKey_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeBytes(4, message_); } if (((bitField0_ & 0x00000001) == 0x00000001)) { @@ -987,6 +1019,9 @@ public final class WhisperProtos { if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeUInt32(6, deviceKeyId_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(7, verification_); + } getUnknownFields().writeTo(output); } @@ -1008,7 +1043,7 @@ public final class WhisperProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, identityKey_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(4, message_); } @@ -1020,6 +1055,10 @@ public final class WhisperProtos { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(6, deviceKeyId_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(7, verification_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -1146,8 +1185,10 @@ public final class WhisperProtos { bitField0_ = (bitField0_ & ~0x00000008); identityKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000010); - message_ = com.google.protobuf.ByteString.EMPTY; + verification_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000020); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000040); return this; } @@ -1199,6 +1240,10 @@ public final class WhisperProtos { if (((from_bitField0_ & 0x00000020) == 0x00000020)) { to_bitField0_ |= 0x00000020; } + result.verification_ = verification_; + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000040; + } result.message_ = message_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -1231,6 +1276,9 @@ public final class WhisperProtos { if (other.hasIdentityKey()) { setIdentityKey(other.getIdentityKey()); } + if (other.hasVerification()) { + setVerification(other.getVerification()); + } if (other.hasMessage()) { setMessage(other.getMessage()); } @@ -1432,6 +1480,42 @@ public final class WhisperProtos { return this; } + // optional bytes verification = 7; + private com.google.protobuf.ByteString verification_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes verification = 7; + */ + public boolean hasVerification() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes verification = 7; + */ + public com.google.protobuf.ByteString getVerification() { + return verification_; + } + /** + * optional bytes verification = 7; + */ + public Builder setVerification(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + verification_ = value; + onChanged(); + return this; + } + /** + * optional bytes verification = 7; + */ + public Builder clearVerification() { + bitField0_ = (bitField0_ & ~0x00000020); + verification_ = getDefaultInstance().getVerification(); + onChanged(); + return this; + } + // optional bytes message = 4; private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; /** @@ -1442,7 +1526,7 @@ public final class WhisperProtos { * */ public boolean hasMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional bytes message = 4; @@ -1465,7 +1549,7 @@ public final class WhisperProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; message_ = value; onChanged(); return this; @@ -1478,7 +1562,7 @@ public final class WhisperProtos { * */ public Builder clearMessage() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000040); message_ = getDefaultInstance().getMessage(); onChanged(); return this; @@ -2170,14 +2254,15 @@ public final class WhisperProtos { "\n\031WhisperTextProtocol.proto\022\ntextsecure\"" + "d\n\016WhisperMessage\022\024\n\014ephemeralKey\030\001 \001(\014\022" + "\017\n\007counter\030\002 \001(\r\022\027\n\017previousCounter\030\003 \001(" + - "\r\022\022\n\nciphertext\030\004 \001(\014\"\214\001\n\024PreKeyWhisperM" + + "\r\022\022\n\nciphertext\030\004 \001(\014\"\242\001\n\024PreKeyWhisperM" + "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\017\n\007message\030\004" + - " \001(\014\"\\\n\022KeyExchangeMessage\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(\014B7\n&org.whispersystems", - ".libaxolotl.protocolB\rWhisperProtos" + "\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" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -2195,7 +2280,7 @@ public final class WhisperProtos { internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PreKeyWhisperMessage_descriptor, - new java.lang.String[] { "RegistrationId", "PreKeyId", "DeviceKeyId", "BaseKey", "IdentityKey", "Message", }); + new java.lang.String[] { "RegistrationId", "PreKeyId", "DeviceKeyId", "BaseKey", "IdentityKey", "Verification", "Message", }); internal_static_textsecure_KeyExchangeMessage_descriptor = getDescriptor().getMessageTypes().get(2); internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable = new diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java index 863d34c945..ebc38eda8b 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java @@ -17,7 +17,7 @@ package org.whispersystems.libaxolotl.ratchet; -import org.whispersystems.libaxolotl.kdf.DerivedSecrets; +import org.whispersystems.libaxolotl.kdf.DerivedMessageSecrets; import org.whispersystems.libaxolotl.kdf.HKDF; import java.security.InvalidKeyException; @@ -55,9 +55,9 @@ public class ChainKey { } public MessageKeys getMessageKeys() { - byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED); - byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedSecrets.SIZE); - DerivedSecrets keyMaterial = new DerivedSecrets(keyMaterialBytes); + byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED); + byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE); + DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes); return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java index e284a76972..a314791783 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java @@ -22,13 +22,14 @@ import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPublicKey; -import org.whispersystems.libaxolotl.kdf.DerivedSecrets; import org.whispersystems.libaxolotl.kdf.HKDF; import org.whispersystems.libaxolotl.state.SessionState; +import org.whispersystems.libaxolotl.util.ByteUtil; import org.whispersystems.libaxolotl.util.Pair; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.text.ParseException; import java.util.Arrays; public class RatchetingSession { @@ -68,16 +69,26 @@ public class RatchetingSession { sessionState.setRemoteIdentityKey(theirIdentityKey); sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey()); - ECKeyPair sendingKey = Curve.generateKeyPair(true); - Pair receivingChain = calculate4DHE(true, sessionVersion, - ourBaseKey, theirBaseKey, - ourPreKey, theirPreKey, - ourIdentityKey, theirIdentityKey); - Pair sendingChain = receivingChain.first().createChain(theirEphemeralKey, sendingKey); + ECKeyPair sendingKey = Curve.generateKeyPair(true); + DHEResult result = calculate4DHE(true, sessionVersion, ourBaseKey, theirBaseKey, + ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey); - sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second()); + Pair sendingChain = result.getRootKey().createChain(theirEphemeralKey, sendingKey); + + sessionState.addReceiverChain(theirEphemeralKey, result.getChainKey()); sessionState.setSenderChain(sendingKey, sendingChain.second()); sessionState.setRootKey(sendingChain.first()); + + if (sessionVersion >= 3) { + VerifyKey verifyKey = result.getVerifyKey(); + byte[] verificationTag = verifyKey.generateVerification(ourBaseKey.getPublicKey(), + ourPreKey.getPublicKey(), + ourIdentityKey.getPublicKey().getPublicKey(), + theirBaseKey, theirPreKey, + theirIdentityKey.getPublicKey()); + + sessionState.setVerification(verificationTag); + } } private static void initializeSessionAsBob(SessionState sessionState, @@ -92,19 +103,28 @@ public class RatchetingSession { sessionState.setRemoteIdentityKey(theirIdentityKey); sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey()); - Pair sendingChain = calculate4DHE(false, sessionVersion, - ourBaseKey, theirBaseKey, - ourPreKey, theirPreKey, - ourIdentityKey, theirIdentityKey); + DHEResult result = calculate4DHE(false, sessionVersion, ourBaseKey, theirBaseKey, + ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey); - sessionState.setSenderChain(ourEphemeralKey, sendingChain.second()); - sessionState.setRootKey(sendingChain.first()); + sessionState.setSenderChain(ourEphemeralKey, result.getChainKey()); + sessionState.setRootKey(result.getRootKey()); + + if (sessionVersion >= 3) { + VerifyKey verifyKey = result.getVerifyKey(); + byte[] verificationTag = verifyKey.generateVerification(theirBaseKey, theirPreKey, + theirIdentityKey.getPublicKey(), + ourBaseKey.getPublicKey(), + ourPreKey.getPublicKey(), + ourIdentityKey.getPublicKey().getPublicKey()); + + sessionState.setVerification(verificationTag); + } } - private static Pair calculate4DHE(boolean isAlice, int sessionVersion, - ECKeyPair ourEphemeral, ECPublicKey theirEphemeral, - ECKeyPair ourPreKey, ECPublicKey theirPreKey, - IdentityKeyPair ourIdentity, IdentityKey theirIdentity) + private static DHEResult calculate4DHE(boolean isAlice, int sessionVersion, + ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, + ECKeyPair ourPreKey, ECPublicKey theirPreKey, + IdentityKeyPair ourIdentity, IdentityKey theirIdentity) throws InvalidKeyException { try { @@ -118,25 +138,27 @@ public class RatchetingSession { } if (isAlice) { - secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey())); - secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirBaseKey, ourIdentity.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourBaseKey.getPrivateKey())); } else { - secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey())); - secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourBaseKey.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirBaseKey, ourIdentity.getPrivateKey())); } - secrets.write(Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirBaseKey, ourBaseKey.getPrivateKey())); - if (sessionVersion >= 3 && ourPreKey != null && theirPreKey != null) { + if (sessionVersion >= 3) { secrets.write(Curve.calculateAgreement(theirPreKey, ourPreKey.getPrivateKey())); } - byte[] derivedSecretBytes = kdf.deriveSecrets(secrets.toByteArray(), "WhisperText".getBytes(), DerivedSecrets.SIZE); - DerivedSecrets derivedSecrets = new DerivedSecrets(derivedSecretBytes); + byte[] derivedSecretBytes = kdf.deriveSecrets(secrets.toByteArray(), "WhisperText".getBytes(), 96); + byte[][] derivedSecrets = ByteUtil.split(derivedSecretBytes, 32, 32, 32); - return new Pair<>(new RootKey(kdf, derivedSecrets.getCipherKey().getEncoded()), - new ChainKey(kdf, derivedSecrets.getMacKey().getEncoded(), 0)); - } catch (IOException e) { + return new DHEResult(new RootKey(kdf, derivedSecrets[0]), + new ChainKey(kdf, derivedSecrets[1], 0), + new VerifyKey(derivedSecrets[2])); + + } catch (IOException | ParseException e) { throw new AssertionError(e); } } @@ -159,5 +181,28 @@ public class RatchetingSession { return ourKey.compareTo(theirKey) < 0; } + private static class DHEResult { + private final RootKey rootKey; + private final ChainKey chainKey; + private final VerifyKey verifyKey; + + private DHEResult(RootKey rootKey, ChainKey chainKey, VerifyKey verifyKey) { + this.rootKey = rootKey; + this.chainKey = chainKey; + this.verifyKey = verifyKey; + } + + public RootKey getRootKey() { + return rootKey; + } + + public ChainKey getChainKey() { + return chainKey; + } + + public VerifyKey getVerifyKey() { + return verifyKey; + } + } } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RootKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RootKey.java index 9782508777..638acfb361 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RootKey.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/RootKey.java @@ -20,7 +20,7 @@ import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPublicKey; -import org.whispersystems.libaxolotl.kdf.DerivedSecrets; +import org.whispersystems.libaxolotl.kdf.DerivedRootSecrets; import org.whispersystems.libaxolotl.kdf.HKDF; import org.whispersystems.libaxolotl.util.ByteUtil; import org.whispersystems.libaxolotl.util.Pair; @@ -42,12 +42,12 @@ public class RootKey { public Pair createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral) throws InvalidKeyException { - byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()); - byte[] keyBytes = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes(), 64); - byte[][] keys = ByteUtil.split(keyBytes, 32, 32); + byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()); + byte[] derivedSecretBytes = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes(), DerivedRootSecrets.SIZE); + DerivedRootSecrets derivedSecrets = new DerivedRootSecrets(derivedSecretBytes); - RootKey newRootKey = new RootKey(kdf, keys[0]); - ChainKey newChainKey = new ChainKey(kdf, keys[1], 0); + RootKey newRootKey = new RootKey(kdf, derivedSecrets.getRootKey()); + ChainKey newChainKey = new ChainKey(kdf, derivedSecrets.getChainKey(), 0); return new Pair<>(newRootKey, newChainKey); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/VerifyKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/VerifyKey.java new file mode 100644 index 0000000000..8f9c2e2d6d --- /dev/null +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/VerifyKey.java @@ -0,0 +1,50 @@ +package org.whispersystems.libaxolotl.ratchet; + +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.util.ByteUtil; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class VerifyKey { + + private static final byte[] VERIFICATION_INFO = "TextSecure Verification Tag".getBytes(); + + private final byte[] key; + + public VerifyKey(byte[] key) { + this.key = key; + } + + public byte[] getKey() { + return key; + } + + public byte[] generateVerification(ECPublicKey aliceBaseKey, ECPublicKey alicePreKey, ECPublicKey aliceIdentityKey, + ECPublicKey bobBaseKey, ECPublicKey bobPreKey, ECPublicKey bobIdentityKey) + { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + + mac.update(VERIFICATION_INFO); + mac.update(aliceBaseKey.serialize()); + mac.update(alicePreKey.serialize()); + mac.update(aliceIdentityKey.serialize()); + mac.update(bobBaseKey.serialize()); + + if (bobPreKey != null) { + mac.update(bobPreKey.serialize()); + } + + mac.update(bobIdentityKey.serialize()); + + return ByteUtil.trim(mac.doFinal(), 8); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new AssertionError(e); + } + } +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java index 7a190f0043..6ef34ae273 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java @@ -32,6 +32,7 @@ import org.whispersystems.libaxolotl.kdf.HKDF; import org.whispersystems.libaxolotl.ratchet.ChainKey; import org.whispersystems.libaxolotl.ratchet.MessageKeys; import org.whispersystems.libaxolotl.ratchet.RootKey; +import org.whispersystems.libaxolotl.ratchet.VerifyKey; import org.whispersystems.libaxolotl.util.Pair; import org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure.Chain; import org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure.PendingKeyExchange; @@ -61,6 +62,16 @@ public class SessionState { return sessionStructure; } + public byte[] getVerification() { + return this.sessionStructure.getVerification().toByteArray(); + } + + public void setVerification(byte[] verification) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setVerification(ByteString.copyFrom(verification)) + .build(); + } + public byte[] getAliceBaseKey() { return this.sessionStructure.getAliceBaseKey().toByteArray(); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java index 17fc45703d..ba07598860 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java @@ -167,6 +167,16 @@ public final class StorageProtos { * optional bytes aliceBaseKey = 13; */ com.google.protobuf.ByteString getAliceBaseKey(); + + // optional bytes verification = 14; + /** + * optional bytes verification = 14; + */ + boolean hasVerification(); + /** + * optional bytes verification = 14; + */ + com.google.protobuf.ByteString getVerification(); } /** * Protobuf type {@code textsecure.SessionStructure} @@ -311,6 +321,11 @@ public final class StorageProtos { aliceBaseKey_ = input.readBytes(); break; } + case 114: { + bitField0_ |= 0x00001000; + verification_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -3347,13 +3362,13 @@ public final class StorageProtos { */ int getPreKeyId(); - // optional uint32 deviceKeyId = 3; + // optional int32 deviceKeyId = 3; /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ boolean hasDeviceKeyId(); /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ int getDeviceKeyId(); @@ -3430,7 +3445,7 @@ public final class StorageProtos { } case 24: { bitField0_ |= 0x00000002; - deviceKeyId_ = input.readUInt32(); + deviceKeyId_ = input.readInt32(); break; } } @@ -3489,17 +3504,17 @@ public final class StorageProtos { return preKeyId_; } - // optional uint32 deviceKeyId = 3; + // optional int32 deviceKeyId = 3; public static final int DEVICEKEYID_FIELD_NUMBER = 3; private int deviceKeyId_; /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public boolean hasDeviceKeyId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public int getDeviceKeyId() { return deviceKeyId_; @@ -3545,7 +3560,7 @@ public final class StorageProtos { output.writeBytes(2, baseKey_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(3, deviceKeyId_); + output.writeInt32(3, deviceKeyId_); } getUnknownFields().writeTo(output); } @@ -3566,7 +3581,7 @@ public final class StorageProtos { } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(3, deviceKeyId_); + .computeInt32Size(3, deviceKeyId_); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -3815,22 +3830,22 @@ public final class StorageProtos { return this; } - // optional uint32 deviceKeyId = 3; + // optional int32 deviceKeyId = 3; private int deviceKeyId_ ; /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public boolean hasDeviceKeyId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public int getDeviceKeyId() { return deviceKeyId_; } /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public Builder setDeviceKeyId(int value) { bitField0_ |= 0x00000002; @@ -3839,7 +3854,7 @@ public final class StorageProtos { return this; } /** - * optional uint32 deviceKeyId = 3; + * optional int32 deviceKeyId = 3; */ public Builder clearDeviceKeyId() { bitField0_ = (bitField0_ & ~0x00000002); @@ -4142,6 +4157,22 @@ public final class StorageProtos { return aliceBaseKey_; } + // optional bytes verification = 14; + public static final int VERIFICATION_FIELD_NUMBER = 14; + private com.google.protobuf.ByteString verification_; + /** + * optional bytes verification = 14; + */ + public boolean hasVerification() { + return ((bitField0_ & 0x00001000) == 0x00001000); + } + /** + * optional bytes verification = 14; + */ + public com.google.protobuf.ByteString getVerification() { + return verification_; + } + private void initFields() { sessionVersion_ = 0; localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; @@ -4156,6 +4187,7 @@ public final class StorageProtos { localRegistrationId_ = 0; needsRefresh_ = false; aliceBaseKey_ = com.google.protobuf.ByteString.EMPTY; + verification_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -4208,6 +4240,9 @@ public final class StorageProtos { if (((bitField0_ & 0x00000800) == 0x00000800)) { output.writeBytes(13, aliceBaseKey_); } + if (((bitField0_ & 0x00001000) == 0x00001000)) { + output.writeBytes(14, verification_); + } getUnknownFields().writeTo(output); } @@ -4269,6 +4304,10 @@ public final class StorageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(13, aliceBaseKey_); } + if (((bitField0_ & 0x00001000) == 0x00001000)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(14, verification_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -4431,6 +4470,8 @@ public final class StorageProtos { bitField0_ = (bitField0_ & ~0x00000800); aliceBaseKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00001000); + verification_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00002000); return this; } @@ -4528,6 +4569,10 @@ public final class StorageProtos { to_bitField0_ |= 0x00000800; } result.aliceBaseKey_ = aliceBaseKey_; + if (((from_bitField0_ & 0x00002000) == 0x00002000)) { + to_bitField0_ |= 0x00001000; + } + result.verification_ = verification_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -4606,6 +4651,9 @@ public final class StorageProtos { if (other.hasAliceBaseKey()) { setAliceBaseKey(other.getAliceBaseKey()); } + if (other.hasVerification()) { + setVerification(other.getVerification()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -5533,6 +5581,42 @@ public final class StorageProtos { return this; } + // optional bytes verification = 14; + private com.google.protobuf.ByteString verification_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes verification = 14; + */ + public boolean hasVerification() { + return ((bitField0_ & 0x00002000) == 0x00002000); + } + /** + * optional bytes verification = 14; + */ + public com.google.protobuf.ByteString getVerification() { + return verification_; + } + /** + * optional bytes verification = 14; + */ + public Builder setVerification(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00002000; + verification_ = value; + onChanged(); + return this; + } + /** + * optional bytes verification = 14; + */ + public Builder clearVerification() { + bitField0_ = (bitField0_ & ~0x00002000); + verification_ = getDefaultInstance().getVerification(); + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure) } @@ -8249,7 +8333,7 @@ public final class StorageProtos { static { java.lang.String[] descriptorData = { "\n\032LocalStorageProtocol.proto\022\ntextsecure" + - "\"\306\010\n\020SessionStructure\022\026\n\016sessionVersion\030" + + "\"\334\010\n\020SessionStructure\022\026\n\016sessionVersion\030" + "\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" + "\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + @@ -8261,33 +8345,34 @@ public final class StorageProtos { "\001(\0132*.textsecure.SessionStructure.Pendin" + "gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" + "\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" + - "sh\030\014 \001(\010\022\024\n\014aliceBaseKey\030\r \001(\014\032\253\002\n\005Chain" + - "\022\027\n\017senderEphemeral\030\001 \001(\014\022\036\n\026senderEphem" + - "eralPrivate\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.te" + - "xtsecure.SessionStructure.Chain.ChainKey" + - "\022B\n\013messageKeys\030\004 \003(\0132-.textsecure.Sessi" + - "onStructure.Chain.MessageKey\032&\n\010ChainKey" + - "\022\r\n\005index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageK", - "ey\022\r\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006" + - "macKey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010s" + - "equence\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023l" + - "ocalBaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemer" + - "alKey\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030" + - "\005 \001(\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027local" + - "IdentityKeyPrivate\030\010 \001(\014\032G\n\rPendingPreKe" + - "y\022\020\n\010preKeyId\030\001 \001(\r\022\023\n\013deviceKeyId\030\003 \001(\r" + - "\022\017\n\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n\016" + - "currentSession\030\001 \001(\0132\034.textsecure.Sessio", - "nStructure\0226\n\020previousSessions\030\002 \003(\0132\034.t" + - "extsecure.SessionStructure\"J\n\025PreKeyReco" + - "rdStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001" + - "(\014\022\022\n\nprivateKey\030\003 \001(\014\"s\n\030DeviceKeyRecor" + - "dStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001(" + - "\014\022\022\n\nprivateKey\030\003 \001(\014\022\021\n\tsignature\030\004 \001(\014" + - "\022\021\n\ttimestamp\030\005 \001(\006\"A\n\030IdentityKeyPairSt" + - "ructure\022\021\n\tpublicKey\030\001 \001(\014\022\022\n\nprivateKey" + - "\030\002 \001(\014B4\n#org.whispersystems.libaxolotl." + - "stateB\rStorageProtos" + "sh\030\014 \001(\010\022\024\n\014aliceBaseKey\030\r \001(\014\022\024\n\014verifi" + + "cation\030\016 \001(\014\032\253\002\n\005Chain\022\027\n\017senderEphemera" + + "l\030\001 \001(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022" + + "=\n\010chainKey\030\003 \001(\0132+.textsecure.SessionSt" + + "ructure.Chain.ChainKey\022B\n\013messageKeys\030\004 " + + "\003(\0132-.textsecure.SessionStructure.Chain." + + "MessageKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n", + "\003key\030\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022" + + "\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022P" + + "endingKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014l" + + "ocalBaseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate" + + "\030\003 \001(\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030loc" + + "alEphemeralKeyPrivate\030\005 \001(\014\022\030\n\020localIden" + + "tityKey\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate" + + "\030\010 \001(\014\032G\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(" + + "\r\022\023\n\013deviceKeyId\030\003 \001(\005\022\017\n\007baseKey\030\002 \001(\014\"" + + "\177\n\017RecordStructure\0224\n\016currentSession\030\001 \001", + "(\0132\034.textsecure.SessionStructure\0226\n\020prev" + + "iousSessions\030\002 \003(\0132\034.textsecure.SessionS" + + "tructure\"J\n\025PreKeyRecordStructure\022\n\n\002id\030" + + "\001 \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003" + + " \001(\014\"s\n\030DeviceKeyRecordStructure\022\n\n\002id\030\001" + + " \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003 " + + "\001(\014\022\021\n\tsignature\030\004 \001(\014\022\021\n\ttimestamp\030\005 \001(" + + "\006\"A\n\030IdentityKeyPairStructure\022\021\n\tpublicK" + + "ey\030\001 \001(\014\022\022\n\nprivateKey\030\002 \001(\014B4\n#org.whis" + + "persystems.libaxolotl.stateB\rStorageProt", + "os" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -8299,7 +8384,7 @@ public final class StorageProtos { internal_static_textsecure_SessionStructure_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_SessionStructure_descriptor, - new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", "NeedsRefresh", "AliceBaseKey", }); + new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", "NeedsRefresh", "AliceBaseKey", "Verification", }); internal_static_textsecure_SessionStructure_Chain_descriptor = internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(0); internal_static_textsecure_SessionStructure_Chain_fieldAccessorTable = new