Add a 'verification tag' to incoming PreKeyWhisperMessage bundles.

This commit is contained in:
Moxie Marlinspike 2014-07-07 12:37:01 -07:00
parent 6326ef73f3
commit 77ff9cece8
16 changed files with 589 additions and 111 deletions

View File

@ -58,6 +58,7 @@ message SessionStructure {
optional bool needsRefresh = 12;
optional bytes aliceBaseKey = 13;
optional bytes verification = 14;
}
message RecordStructure {

View File

@ -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
}

View File

@ -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<incomingMessage.getVerification().length * 8;i++) {
byte[] modifiedVerification = new byte[incomingMessage.getVerification().length];
modifiedVerification[i / 8] ^= (0x01 << i % 8);
PreKeyWhisperMessage modifiedMessage = new PreKeyWhisperMessage(incomingMessage.getMessageVersion(),
incomingMessage.getRegistrationId(),
incomingMessage.getPreKeyId(),
incomingMessage.getDeviceKeyId(),
incomingMessage.getBaseKey(),
incomingMessage.getIdentityKey(),
modifiedVerification,
incomingMessage.getWhisperMessage());
try {
bobSessionBuilder.process(modifiedMessage);
throw new AssertionError("Modified verification tag passed!");
} catch (InvalidKeyException e) {
// good
}
}
PreKeyWhisperMessage unmodifiedMessage = new PreKeyWhisperMessage(incomingMessage.getMessageVersion(),
incomingMessage.getRegistrationId(),
incomingMessage.getPreKeyId(),
incomingMessage.getDeviceKeyId(),
incomingMessage.getBaseKey(),
incomingMessage.getIdentityKey(),
incomingMessage.getVerification(),
incomingMessage.getWhisperMessage());
bobSessionBuilder.process(unmodifiedMessage);
}
public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException {
SessionStore aliceSessionStore = new InMemorySessionStore();

View File

@ -0,0 +1,84 @@
package org.whispersystems.test.ratchet;
import android.test.AndroidTestCase;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.VerifyKey;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class VerifyKeyTest extends AndroidTestCase {
public void testVerify() throws NoSuchAlgorithmException, InvalidKeyException {
byte[] aliceBaseKeyBytes = {(byte) 0x05, (byte) 0x2d, (byte) 0x0c, (byte) 0xdd, (byte) 0xde,
(byte) 0xa8, (byte) 0x9f, (byte) 0x6a, (byte) 0x2c, (byte) 0xe0,
(byte) 0x21, (byte) 0xfa, (byte) 0x69, (byte) 0x39, (byte) 0x30,
(byte) 0x43, (byte) 0x28, (byte) 0xd0, (byte) 0xa3, (byte) 0x53,
(byte) 0xe0, (byte) 0x67, (byte) 0xb9, (byte) 0x11, (byte) 0xf5,
(byte) 0xa9, (byte) 0xbd, (byte) 0xa4, (byte) 0x7b, (byte) 0x29,
(byte) 0x41, (byte) 0x6e, (byte) 0x2b};
byte[] aliceIdentityKeyBytes = {(byte) 0x05, (byte) 0x9d, (byte) 0x86, (byte) 0xef, (byte) 0x77,
(byte) 0x7d, (byte) 0x71, (byte) 0x0c, (byte) 0xc2, (byte) 0xb1,
(byte) 0x4e, (byte) 0xd6, (byte) 0x15, (byte) 0x2e, (byte) 0x91,
(byte) 0xfb, (byte) 0x7f, (byte) 0xa2, (byte) 0x34, (byte) 0xe5,
(byte) 0x5b, (byte) 0x57, (byte) 0x2e, (byte) 0x52, (byte) 0xb8,
(byte) 0x5f, (byte) 0x84, (byte) 0xdb, (byte) 0x34, (byte) 0x16,
(byte) 0x69, (byte) 0xfd, (byte) 0x45};
byte[] bobBaseKeyBytes = {(byte) 0x05, (byte) 0xc0, (byte) 0xbd, (byte) 0x26, (byte) 0x62,
(byte) 0xf7, (byte) 0xea, (byte) 0xa8, (byte) 0x5a, (byte) 0x5e,
(byte) 0x43, (byte) 0x95, (byte) 0x34, (byte) 0x3a, (byte) 0xcf,
(byte) 0x66, (byte) 0x36, (byte) 0xec, (byte) 0x75, (byte) 0x54,
(byte) 0x7b, (byte) 0x96, (byte) 0x02, (byte) 0x6d, (byte) 0x8a,
(byte) 0x16, (byte) 0xb6, (byte) 0x39, (byte) 0x10, (byte) 0x36,
(byte) 0xf6, (byte) 0x9f, (byte) 0x39};
byte[] bobPreKeyBytes = {(byte) 0x05, (byte) 0xb8, (byte) 0x28, (byte) 0x04, (byte) 0xe6,
(byte) 0x46, (byte) 0xeb, (byte) 0x04, (byte) 0xaf, (byte) 0x54,
(byte) 0xeb, (byte) 0xea, (byte) 0xfa, (byte) 0x8e, (byte) 0x27,
(byte) 0xb1, (byte) 0xa7, (byte) 0xa8, (byte) 0x00, (byte) 0xef,
(byte) 0xcf, (byte) 0xd7, (byte) 0xe8, (byte) 0x9c, (byte) 0x92,
(byte) 0xfc, (byte) 0x51, (byte) 0x66, (byte) 0xb8, (byte) 0x70,
(byte) 0xee, (byte) 0x63, (byte) 0x74};
byte[] bobIdentityKeyBytes = {(byte) 0x05, (byte) 0x3a, (byte) 0x32, (byte) 0x3a, (byte) 0xda,
(byte) 0xe8, (byte) 0x46, (byte) 0x1b, (byte) 0x57, (byte) 0x8d,
(byte) 0x46, (byte) 0x70, (byte) 0x80, (byte) 0x0e, (byte) 0x06,
(byte) 0x76, (byte) 0x5a, (byte) 0xf1, (byte) 0x50, (byte) 0x51,
(byte) 0xd3, (byte) 0x74, (byte) 0xa0, (byte) 0x65, (byte) 0x85,
(byte) 0xea, (byte) 0x03, (byte) 0xff, (byte) 0x58, (byte) 0x7c,
(byte) 0x81, (byte) 0xa8, (byte) 0x04};
byte[] key = {(byte)0xfc, (byte)0x57, (byte)0x05, (byte)0xdc, (byte)0xe0,
(byte)0x34, (byte)0x4c, (byte)0x8f, (byte)0x1c, (byte)0xeb,
(byte)0x9b, (byte)0x05, (byte)0x7c, (byte)0xaa, (byte)0xb0,
(byte)0x08, (byte)0xf0, (byte)0xb7, (byte)0x26, (byte)0x73,
(byte)0x46, (byte)0xa4, (byte)0x00, (byte)0xa3, (byte)0x66,
(byte)0x79, (byte)0x00, (byte)0xef, (byte)0x1b, (byte)0x40,
(byte)0x0f, (byte)0xdc};
byte[] expectedTag = {(byte)0x68, (byte)0x64, (byte)0xbf, (byte)0xbf, (byte)0x82,
(byte)0x34, (byte)0x20, (byte)0xdc};
ECPublicKey aliceBaseKey = Curve.decodePoint(aliceBaseKeyBytes, 0);
ECPublicKey alicePreKey = aliceBaseKey;
ECPublicKey aliceIdentityKey = Curve.decodePoint(aliceIdentityKeyBytes, 0);
ECPublicKey bobBaseKey = Curve.decodePoint(bobBaseKeyBytes, 0);
ECPublicKey bobPreKey = Curve.decodePoint(bobPreKeyBytes, 0);
ECPublicKey bobIdentityKey = Curve.decodePoint(bobIdentityKeyBytes, 0);
VerifyKey verifyKey = new VerifyKey(key);
byte[] verification = verifyKey.generateVerification(aliceBaseKey, alicePreKey, aliceIdentityKey,
bobBaseKey, bobPreKey, bobIdentityKey);
assertTrue(MessageDigest.isEqual(verification, expectedTag));
}
}

View File

@ -20,6 +20,8 @@ import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.util.KeyHelper;
import org.whispersystems.libaxolotl.util.Medium;
import java.security.MessageDigest;
/**
* SessionBuilder is responsible for setting up encrypted sessions.
* Once a session has been established, {@link org.whispersystems.libaxolotl.SessionCipher}
@ -123,12 +125,10 @@ public class SessionBuilder {
if (!deviceKeyStore.containsDeviceKey(deviceKeyId))
throw new InvalidKeyIdException("No such device key: " + deviceKeyId);
PreKeyRecord preKeyRecord = preKeyId >= 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());

View File

@ -106,6 +106,7 @@ public class SessionCipher {
localRegistrationId, pendingPreKeyId,
pendingDeviceKeyId, pendingBaseKey,
sessionState.getLocalIdentityKey(),
sessionState.getVerification(),
(WhisperMessage) ciphertextMessage);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
@ -56,6 +57,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
if ((version == 2 && !preKeyWhisperMessage.hasPreKeyId()) ||
(version == 3 && !preKeyWhisperMessage.hasDeviceKeyId()) ||
(version == 3 && !preKeyWhisperMessage.hasVerification()) ||
!preKeyWhisperMessage.hasBaseKey() ||
!preKeyWhisperMessage.hasIdentityKey() ||
!preKeyWhisperMessage.hasMessage())
@ -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;
}

View File

@ -706,6 +706,16 @@ public final class WhisperProtos {
*/
com.google.protobuf.ByteString getIdentityKey();
// optional bytes verification = 7;
/**
* <code>optional bytes verification = 7;</code>
*/
boolean hasVerification();
/**
* <code>optional bytes verification = 7;</code>
*/
com.google.protobuf.ByteString getVerification();
// optional bytes message = 4;
/**
* <code>optional bytes message = 4;</code>
@ -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_;
/**
* <code>optional bytes verification = 7;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00000020) == 0x00000020);
}
/**
* <code>optional bytes verification = 7;</code>
*/
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 {
* </pre>
*/
public boolean hasMessage() {
return ((bitField0_ & 0x00000020) == 0x00000020);
return ((bitField0_ & 0x00000040) == 0x00000040);
}
/**
* <code>optional bytes message = 4;</code>
@ -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;
/**
* <code>optional bytes verification = 7;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00000020) == 0x00000020);
}
/**
* <code>optional bytes verification = 7;</code>
*/
public com.google.protobuf.ByteString getVerification() {
return verification_;
}
/**
* <code>optional bytes verification = 7;</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 = 7;</code>
*/
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 {
* </pre>
*/
public boolean hasMessage() {
return ((bitField0_ & 0x00000020) == 0x00000020);
return ((bitField0_ & 0x00000040) == 0x00000040);
}
/**
* <code>optional bytes message = 4;</code>
@ -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 {
* </pre>
*/
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

View File

@ -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;
@ -56,8 +56,8 @@ 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[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE);
DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes);
return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index);
}

View File

@ -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 {
@ -69,15 +70,25 @@ public class RatchetingSession {
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> receivingChain = calculate4DHE(true, sessionVersion,
ourBaseKey, theirBaseKey,
ourPreKey, theirPreKey,
ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first().createChain(theirEphemeralKey, sendingKey);
DHEResult result = calculate4DHE(true, sessionVersion, ourBaseKey, theirBaseKey,
ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey);
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second());
Pair<RootKey, ChainKey> 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,17 +103,26 @@ public class RatchetingSession {
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> 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<RootKey, ChainKey> calculate4DHE(boolean isAlice, int sessionVersion,
ECKeyPair ourEphemeral, ECPublicKey theirEphemeral,
private static DHEResult calculate4DHE(boolean isAlice, int sessionVersion,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourPreKey, ECPublicKey theirPreKey,
IdentityKeyPair ourIdentity, IdentityKey theirIdentity)
throws InvalidKeyException
@ -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;
}
}
}

View File

@ -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;
@ -43,11 +43,11 @@ public class RootKey {
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[] 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);
}

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -167,6 +167,16 @@ public final class StorageProtos {
* <code>optional bytes aliceBaseKey = 13;</code>
*/
com.google.protobuf.ByteString getAliceBaseKey();
// optional bytes verification = 14;
/**
* <code>optional bytes verification = 14;</code>
*/
boolean hasVerification();
/**
* <code>optional bytes verification = 14;</code>
*/
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;
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
boolean hasDeviceKeyId();
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
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_;
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
public boolean hasDeviceKeyId() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
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_ ;
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
public boolean hasDeviceKeyId() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
public int getDeviceKeyId() {
return deviceKeyId_;
}
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
public Builder setDeviceKeyId(int value) {
bitField0_ |= 0x00000002;
@ -3839,7 +3854,7 @@ public final class StorageProtos {
return this;
}
/**
* <code>optional uint32 deviceKeyId = 3;</code>
* <code>optional int32 deviceKeyId = 3;</code>
*/
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_;
/**
* <code>optional bytes verification = 14;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00001000) == 0x00001000);
}
/**
* <code>optional bytes verification = 14;</code>
*/
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;
/**
* <code>optional bytes verification = 14;</code>
*/
public boolean hasVerification() {
return ((bitField0_ & 0x00002000) == 0x00002000);
}
/**
* <code>optional bytes verification = 14;</code>
*/
public com.google.protobuf.ByteString getVerification() {
return verification_;
}
/**
* <code>optional bytes verification = 14;</code>
*/
public Builder setVerification(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00002000;
verification_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes verification = 14;</code>
*/
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