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

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

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 {
@ -68,16 +69,26 @@ public class RatchetingSession {
sessionState.setRemoteIdentityKey(theirIdentityKey);
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);
ECKeyPair sendingKey = Curve.generateKeyPair(true);
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,19 +103,28 @@ 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,
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;
}
}
}

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;
@ -42,12 +42,12 @@ public class RootKey {
public Pair<RootKey, ChainKey> 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);
}

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