mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Add a 'verification tag' to incoming PreKeyWhisperMessage bundles.
This commit is contained in:
parent
6326ef73f3
commit
77ff9cece8
@ -58,6 +58,7 @@ message SessionStructure {
|
||||
|
||||
optional bool needsRefresh = 12;
|
||||
optional bytes aliceBaseKey = 13;
|
||||
optional bytes verification = 14;
|
||||
}
|
||||
|
||||
message RecordStructure {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
@ -106,6 +106,7 @@ public class SessionCipher {
|
||||
localRegistrationId, pendingPreKeyId,
|
||||
pendingDeviceKeyId, pendingBaseKey,
|
||||
sessionState.getLocalIdentityKey(),
|
||||
sessionState.getVerification(),
|
||||
(WhisperMessage) ciphertextMessage);
|
||||
}
|
||||
|
||||
|
@ -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);
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user