Break core ratchet out into libaxolotol.
1) Break the core cryptography functions out into libaxolotol. 2) The objective for this code is a Java library that isn't dependent on any Android functions. However, while the code has been separated from any Android functionality, it is still an 'android library project' because of the JNI.pull/1/head
parent
fe3d91c40c
commit
d902c12941
@ -0,0 +1,2 @@
|
||||
/build
|
||||
/obj
|
@ -0,0 +1,34 @@
|
||||
apply plugin: 'android-library'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.1.0'
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
|
||||
all:
|
||||
protoc --java_out=../src/main/java/ WhisperTextProtocol.proto
|
@ -1,6 +1,6 @@
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.textsecure.crypto.protocol";
|
||||
option java_package = "org.whispersystems.libaxolotl.protocol";
|
||||
option java_outer_classname = "WhisperProtos";
|
||||
|
||||
message WhisperMessage {
|
@ -0,0 +1,321 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class InMemorySessionState implements SessionState {
|
||||
|
||||
private Map<ECPublicKey, InMemoryChain> receiverChains = new HashMap<>();
|
||||
|
||||
private boolean needsRefresh;
|
||||
private int sessionVersion;
|
||||
private IdentityKey remoteIdentityKey;
|
||||
private IdentityKey localIdentityKey;
|
||||
private int previousCounter;
|
||||
private RootKey rootKey;
|
||||
private ECKeyPair senderEphemeral;
|
||||
private ChainKey senderChainKey;
|
||||
private int pendingPreKeyid;
|
||||
private ECPublicKey pendingPreKey;
|
||||
private int remoteRegistrationId;
|
||||
private int localRegistrationId;
|
||||
|
||||
public InMemorySessionState() {}
|
||||
|
||||
public InMemorySessionState(SessionState sessionState) {
|
||||
try {
|
||||
this.needsRefresh = sessionState.getNeedsRefresh();
|
||||
this.sessionVersion = sessionState.getSessionVersion();
|
||||
this.remoteIdentityKey = new IdentityKey(sessionState.getRemoteIdentityKey().serialize(), 0);
|
||||
this.localIdentityKey = new IdentityKey(sessionState.getLocalIdentityKey().serialize(), 0);
|
||||
this.previousCounter = sessionState.getPreviousCounter();
|
||||
this.rootKey = new RootKey(sessionState.getRootKey().getKeyBytes());
|
||||
this.senderEphemeral = sessionState.getSenderEphemeralPair();
|
||||
this.senderChainKey = new ChainKey(sessionState.getSenderChainKey().getKey(),
|
||||
sessionState.getSenderChainKey().getIndex());
|
||||
this.pendingPreKeyid = sessionState.getPendingPreKey().first();
|
||||
this.pendingPreKey = sessionState.getPendingPreKey().second();
|
||||
this.remoteRegistrationId = sessionState.getRemoteRegistrationId();
|
||||
this.localRegistrationId = sessionState.getLocalRegistrationId();
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeedsRefresh(boolean needsRefresh) {
|
||||
this.needsRefresh = needsRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNeedsRefresh() {
|
||||
return needsRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionVersion(int version) {
|
||||
this.sessionVersion = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionVersion() {
|
||||
return sessionVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoteIdentityKey(IdentityKey identityKey) {
|
||||
this.remoteIdentityKey = identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalIdentityKey(IdentityKey identityKey) {
|
||||
this.localIdentityKey = identityKey;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getRemoteIdentityKey() {
|
||||
return remoteIdentityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getLocalIdentityKey() {
|
||||
return localIdentityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousCounter() {
|
||||
return previousCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreviousCounter(int previousCounter) {
|
||||
this.previousCounter = previousCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootKey getRootKey() {
|
||||
return rootKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootKey(RootKey rootKey) {
|
||||
this.rootKey = rootKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECPublicKey getSenderEphemeral() {
|
||||
return senderEphemeral.getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getSenderEphemeralPair() {
|
||||
return senderEphemeral;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
|
||||
return receiverChains.containsKey(senderEphemeral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSenderChain() {
|
||||
return senderChainKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
return new ChainKey(chain.chainKey, chain.index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
|
||||
InMemoryChain chain = new InMemoryChain();
|
||||
chain.chainKey = chainKey.getKey();
|
||||
chain.index = chainKey.getIndex();
|
||||
|
||||
receiverChains.put(senderEphemeral, chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
|
||||
this.senderEphemeral = senderEphemeralPair;
|
||||
this.senderChainKey = chainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChainKey getSenderChainKey() {
|
||||
return senderChainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSenderChainKey(ChainKey nextChainKey) {
|
||||
this.senderChainKey = nextChainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
|
||||
if (chain == null) return false;
|
||||
|
||||
for (InMemoryChain.InMemoryMessageKey messageKey : chain.messageKeys) {
|
||||
if (messageKey.index == counter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
MessageKeys results = null;
|
||||
|
||||
if (chain == null) return null;
|
||||
|
||||
Iterator<InMemoryChain.InMemoryMessageKey> iterator = chain.messageKeys.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
InMemoryChain.InMemoryMessageKey messageKey = iterator.next();
|
||||
|
||||
if (messageKey.index == counter) {
|
||||
results = new MessageKeys(new SecretKeySpec(messageKey.cipherKey, "AES"),
|
||||
new SecretKeySpec(messageKey.macKey, "HmacSHA256"),
|
||||
messageKey.index);
|
||||
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
InMemoryChain.InMemoryMessageKey key = new InMemoryChain.InMemoryMessageKey();
|
||||
|
||||
key.cipherKey = messageKeys.getCipherKey().getEncoded();
|
||||
key.macKey = messageKeys.getMacKey().getEncoded();
|
||||
key.index = messageKeys.getCounter();
|
||||
|
||||
chain.messageKeys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
chain.chainKey = chainKey.getKey();
|
||||
chain.index = chainKey.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingKeyExchange(int sequence, ECKeyPair ourBaseKey, ECKeyPair ourEphemeralKey, IdentityKeyPair ourIdentityKey) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingKeyExchangeSequence() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPendingKeyExchange() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
|
||||
this.pendingPreKeyid = preKeyId;
|
||||
this.pendingPreKey = baseKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPendingPreKey() {
|
||||
return this.pendingPreKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Integer, ECPublicKey> getPendingPreKey() {
|
||||
return new Pair<>(pendingPreKeyid, pendingPreKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPendingPreKey() {
|
||||
this.pendingPreKey = null;
|
||||
this.pendingPreKeyid = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoteRegistrationId(int registrationId) {
|
||||
this.remoteRegistrationId = registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteRegistrationId() {
|
||||
return remoteRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalRegistrationId(int registrationId) {
|
||||
this.localRegistrationId = registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static class InMemoryChain {
|
||||
byte[] chainKey;
|
||||
int index;
|
||||
List<InMemoryMessageKey> messageKeys = new LinkedList<>();
|
||||
|
||||
public static class InMemoryMessageKey {
|
||||
public InMemoryMessageKey(){}
|
||||
int index;
|
||||
byte[] cipherKey;
|
||||
byte[] macKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.SessionStore;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class InMemorySessionStore implements SessionStore {
|
||||
|
||||
private SessionState currentSessionState;
|
||||
private List<SessionState> previousSessionStates;
|
||||
|
||||
private SessionState checkedOutSessionState;
|
||||
private List<SessionState> checkedOutPreviousSessionStates;
|
||||
|
||||
public InMemorySessionStore(SessionState sessionState) {
|
||||
this.currentSessionState = sessionState;
|
||||
this.previousSessionStates = new LinkedList<>();
|
||||
this.checkedOutPreviousSessionStates = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionState getSessionState() {
|
||||
checkedOutSessionState = new InMemorySessionState(currentSessionState);
|
||||
return checkedOutSessionState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SessionState> getPreviousSessionStates() {
|
||||
checkedOutPreviousSessionStates = new LinkedList<>();
|
||||
for (SessionState state : previousSessionStates) {
|
||||
checkedOutPreviousSessionStates.add(new InMemorySessionState(state));
|
||||
}
|
||||
|
||||
return checkedOutPreviousSessionStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
this.currentSessionState = this.checkedOutSessionState;
|
||||
this.previousSessionStates = this.checkedOutPreviousSessionStates;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.SessionStore;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SessionCipherTest extends AndroidTestCase {
|
||||
|
||||
public void testBasicSession()
|
||||
throws InvalidKeyException, DuplicateMessageException,
|
||||
LegacyMessageException, InvalidMessageException
|
||||
{
|
||||
SessionState aliceSessionState = new InMemorySessionState();
|
||||
SessionState bobSessionState = new InMemorySessionState();
|
||||
|
||||
initializeSessions(aliceSessionState, bobSessionState);
|
||||
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore(aliceSessionState);
|
||||
SessionStore bobSessionStore = new InMemorySessionStore(bobSessionState);
|
||||
|
||||
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore);
|
||||
SessionCipher bobCipher = new SessionCipher(bobSessionStore);
|
||||
|
||||
byte[] alicePlaintext = "This is a plaintext message.".getBytes();
|
||||
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);
|
||||
byte[] bobPlaintext = bobCipher.decrypt(message.serialize());
|
||||
|
||||
assertTrue(Arrays.equals(alicePlaintext, bobPlaintext));
|
||||
|
||||
byte[] bobReply = "This is a message from Bob.".getBytes();
|
||||
CiphertextMessage reply = bobCipher.encrypt(bobReply);
|
||||
byte[] receivedReply = aliceCipher.decrypt(reply.serialize());
|
||||
|
||||
assertTrue(Arrays.equals(bobReply, receivedReply));
|
||||
}
|
||||
|
||||
|
||||
private void initializeSessions(SessionState aliceSessionState, SessionState bobSessionState)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
ECKeyPair aliceIdentityKeyPair = Curve.generateKeyPair(false);
|
||||
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(new IdentityKey(aliceIdentityKeyPair.getPublicKey()),
|
||||
aliceIdentityKeyPair.getPrivateKey());
|
||||
ECKeyPair aliceBaseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair aliceEphemeralKey = Curve.generateKeyPair(true);
|
||||
|
||||
ECKeyPair bobIdentityKeyPair = Curve.generateKeyPair(false);
|
||||
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(new IdentityKey(bobIdentityKeyPair.getPublicKey()),
|
||||
bobIdentityKeyPair.getPrivateKey());
|
||||
ECKeyPair bobBaseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair bobEphemeralKey = bobBaseKey;
|
||||
|
||||
|
||||
RatchetingSession.initializeSession(aliceSessionState, aliceBaseKey, bobBaseKey.getPublicKey(),
|
||||
aliceEphemeralKey, bobEphemeralKey.getPublicKey(),
|
||||
aliceIdentityKey, bobIdentityKey.getPublicKey());
|
||||
|
||||
RatchetingSession.initializeSession(bobSessionState, bobBaseKey, aliceBaseKey.getPublicKey(),
|
||||
bobEphemeralKey, aliceEphemeralKey.getPublicKey(),
|
||||
bobIdentityKey, aliceIdentityKey.getPublicKey());
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package org.whispersystems.test.ecc;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class Curve25519Test extends AndroidTestCase {
|
||||
|
||||
public void testAgreement() throws InvalidKeyException {
|
||||
|
||||
byte[] alicePublic = {(byte) 0x05, (byte) 0x1b, (byte) 0xb7, (byte) 0x59, (byte) 0x66,
|
||||
(byte) 0xf2, (byte) 0xe9, (byte) 0x3a, (byte) 0x36, (byte) 0x91,
|
||||
(byte) 0xdf, (byte) 0xff, (byte) 0x94, (byte) 0x2b, (byte) 0xb2,
|
||||
(byte) 0xa4, (byte) 0x66, (byte) 0xa1, (byte) 0xc0, (byte) 0x8b,
|
||||
(byte) 0x8d, (byte) 0x78, (byte) 0xca, (byte) 0x3f, (byte) 0x4d,
|
||||
(byte) 0x6d, (byte) 0xf8, (byte) 0xb8, (byte) 0xbf, (byte) 0xa2,
|
||||
(byte) 0xe4, (byte) 0xee, (byte) 0x28};
|
||||
|
||||
byte[] alicePrivate = {(byte) 0xc8, (byte) 0x06, (byte) 0x43, (byte) 0x9d, (byte) 0xc9,
|
||||
(byte) 0xd2, (byte) 0xc4, (byte) 0x76, (byte) 0xff, (byte) 0xed,
|
||||
(byte) 0x8f, (byte) 0x25, (byte) 0x80, (byte) 0xc0, (byte) 0x88,
|
||||
(byte) 0x8d, (byte) 0x58, (byte) 0xab, (byte) 0x40, (byte) 0x6b,
|
||||
(byte) 0xf7, (byte) 0xae, (byte) 0x36, (byte) 0x98, (byte) 0x87,
|
||||
(byte) 0x90, (byte) 0x21, (byte) 0xb9, (byte) 0x6b, (byte) 0xb4,
|
||||
(byte) 0xbf, (byte) 0x59};
|
||||
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x65, (byte) 0x36, (byte) 0x14, (byte) 0x99,
|
||||
(byte) 0x3d, (byte) 0x2b, (byte) 0x15, (byte) 0xee, (byte) 0x9e,
|
||||
(byte) 0x5f, (byte) 0xd3, (byte) 0xd8, (byte) 0x6c, (byte) 0xe7,
|
||||
(byte) 0x19, (byte) 0xef, (byte) 0x4e, (byte) 0xc1, (byte) 0xda,
|
||||
(byte) 0xae, (byte) 0x18, (byte) 0x86, (byte) 0xa8, (byte) 0x7b,
|
||||
(byte) 0x3f, (byte) 0x5f, (byte) 0xa9, (byte) 0x56, (byte) 0x5a,
|
||||
(byte) 0x27, (byte) 0xa2, (byte) 0x2f};
|
||||
|
||||
byte[] bobPrivate = {(byte) 0xb0, (byte) 0x3b, (byte) 0x34, (byte) 0xc3, (byte) 0x3a,
|
||||
(byte) 0x1c, (byte) 0x44, (byte) 0xf2, (byte) 0x25, (byte) 0xb6,
|
||||
(byte) 0x62, (byte) 0xd2, (byte) 0xbf, (byte) 0x48, (byte) 0x59,
|
||||
(byte) 0xb8, (byte) 0x13, (byte) 0x54, (byte) 0x11, (byte) 0xfa,
|
||||
(byte) 0x7b, (byte) 0x03, (byte) 0x86, (byte) 0xd4, (byte) 0x5f,
|
||||
(byte) 0xb7, (byte) 0x5d, (byte) 0xc5, (byte) 0xb9, (byte) 0x1b,
|
||||
(byte) 0x44, (byte) 0x66};
|
||||
|
||||
byte[] shared = {(byte) 0x32, (byte) 0x5f, (byte) 0x23, (byte) 0x93, (byte) 0x28,
|
||||
(byte) 0x94, (byte) 0x1c, (byte) 0xed, (byte) 0x6e, (byte) 0x67,
|
||||
(byte) 0x3b, (byte) 0x86, (byte) 0xba, (byte) 0x41, (byte) 0x01,
|
||||
(byte) 0x74, (byte) 0x48, (byte) 0xe9, (byte) 0x9b, (byte) 0x64,
|
||||
(byte) 0x9a, (byte) 0x9c, (byte) 0x38, (byte) 0x06, (byte) 0xc1,
|
||||
(byte) 0xdd, (byte) 0x7c, (byte) 0xa4, (byte) 0xc4, (byte) 0x77,
|
||||
(byte) 0xe6, (byte) 0x29};
|
||||
|
||||
ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0);
|
||||
ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
|
||||
|
||||
ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPrivateKey bobPrivateKey = Curve.decodePrivatePoint(bobPrivate);
|
||||
|
||||
byte[] sharedOne = Curve.calculateAgreement(alicePublicKey, bobPrivateKey);
|
||||
byte[] sharedTwo = Curve.calculateAgreement(bobPublicKey, alicePrivateKey);
|
||||
|
||||
assertTrue(Arrays.equals(sharedOne, shared));
|
||||
assertTrue(Arrays.equals(sharedTwo, shared));
|
||||
}
|
||||
|
||||
public void testRandomAgreements() throws InvalidKeyException {
|
||||
for (int i=0;i<50;i++) {
|
||||
ECKeyPair alice = Curve.generateKeyPair(false);
|
||||
ECKeyPair bob = Curve.generateKeyPair(false);
|
||||
|
||||
byte[] sharedAlice = Curve.calculateAgreement(bob.getPublicKey(), alice.getPrivateKey());
|
||||
byte[] sharedBob = Curve.calculateAgreement(alice.getPublicKey(), bob.getPrivateKey());
|
||||
|
||||
assertTrue(Arrays.equals(sharedAlice, sharedBob));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.whispersystems.test.kdf;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.kdf.DerivedSecrets;
|
||||
import org.whispersystems.libaxolotl.kdf.HKDF;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HKDFTest extends AndroidTestCase {
|
||||
|
||||
public void testVector() {
|
||||
byte[] ikm = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b};
|
||||
|
||||
byte[] salt = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x0c};
|
||||
|
||||
byte[] info = {(byte)0xf0, (byte)0xf1, (byte)0xf2, (byte)0xf3, (byte)0xf4,
|
||||
(byte)0xf5, (byte)0xf6, (byte)0xf7, (byte)0xf8, (byte)0xf9};
|
||||
|
||||
byte[] expectedOutputOne = {(byte)0x6e, (byte)0xc2, (byte)0x55, (byte)0x6d, (byte)0x5d,
|
||||
(byte)0x7b, (byte)0x1d, (byte)0x81, (byte)0xde, (byte)0xe4,
|
||||
(byte)0x22, (byte)0x2a, (byte)0xd7, (byte)0x48, (byte)0x36,
|
||||
(byte)0x95, (byte)0xdd, (byte)0xc9, (byte)0x8f, (byte)0x4f,
|
||||
(byte)0x5f, (byte)0xab, (byte)0xc0, (byte)0xe0, (byte)0x20,
|
||||
(byte)0x5d, (byte)0xc2, (byte)0xef, (byte)0x87, (byte)0x52,
|
||||
(byte)0xd4, (byte)0x1e};
|
||||
|
||||
byte[] expectedOutputTwo = {(byte)0x04, (byte)0xe2, (byte)0xe2, (byte)0x11, (byte)0x01,
|
||||
(byte)0xc6, (byte)0x8f, (byte)0xf0, (byte)0x93, (byte)0x94,
|
||||
(byte)0xb8, (byte)0xad, (byte)0x0b, (byte)0xdc, (byte)0xb9,
|
||||
(byte)0x60, (byte)0x9c, (byte)0xd4, (byte)0xee, (byte)0x82,
|
||||
(byte)0xac, (byte)0x13, (byte)0x19, (byte)0x9b, (byte)0x4a,
|
||||
(byte)0xa9, (byte)0xfd, (byte)0xa8, (byte)0x99, (byte)0xda,
|
||||
(byte)0xeb, (byte)0xec};
|
||||
|
||||
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(ikm, salt, info);
|
||||
|
||||
assertTrue(Arrays.equals(derivedSecrets.getCipherKey().getEncoded(), expectedOutputOne));
|
||||
assertTrue(Arrays.equals(derivedSecrets.getMacKey().getEncoded(), expectedOutputTwo));
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package org.whispersystems.test.ratchet;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChainKeyTest extends AndroidTestCase {
|
||||
|
||||
public void testChainKeyDerivation() throws NoSuchAlgorithmException {
|
||||
|
||||
byte[] seed = {(byte) 0x8a, (byte) 0xb7, (byte) 0x2d, (byte) 0x6f, (byte) 0x4c,
|
||||
(byte) 0xc5, (byte) 0xac, (byte) 0x0d, (byte) 0x38, (byte) 0x7e,
|
||||
(byte) 0xaf, (byte) 0x46, (byte) 0x33, (byte) 0x78, (byte) 0xdd,
|
||||
(byte) 0xb2, (byte) 0x8e, (byte) 0xdd, (byte) 0x07, (byte) 0x38,
|
||||
(byte) 0x5b, (byte) 0x1c, (byte) 0xb0, (byte) 0x12, (byte) 0x50,
|
||||
(byte) 0xc7, (byte) 0x15, (byte) 0x98, (byte) 0x2e, (byte) 0x7a,
|
||||
(byte) 0xd4, (byte) 0x8f};
|
||||
|
||||
byte[] messageKey = {(byte) 0x02, (byte) 0xa9, (byte) 0xaa, (byte) 0x6c, (byte) 0x7d,
|
||||
(byte) 0xbd, (byte) 0x64, (byte) 0xf9, (byte) 0xd3, (byte) 0xaa,
|
||||
(byte) 0x92, (byte) 0xf9, (byte) 0x2a, (byte) 0x27, (byte) 0x7b,
|
||||
(byte) 0xf5, (byte) 0x46, (byte) 0x09, (byte) 0xda, (byte) 0xdf,
|
||||
(byte) 0x0b, (byte) 0x00, (byte) 0x82, (byte) 0x8a, (byte) 0xcf,
|
||||
(byte) 0xc6, (byte) 0x1e, (byte) 0x3c, (byte) 0x72, (byte) 0x4b,
|
||||
(byte) 0x84, (byte) 0xa7};
|
||||
|
||||
byte[] macKey = {(byte) 0xbf, (byte) 0xbe, (byte) 0x5e, (byte) 0xfb, (byte) 0x60,
|
||||
(byte) 0x30, (byte) 0x30, (byte) 0x52, (byte) 0x67, (byte) 0x42,
|
||||
(byte) 0xe3, (byte) 0xee, (byte) 0x89, (byte) 0xc7, (byte) 0x02,
|
||||
(byte) 0x4e, (byte) 0x88, (byte) 0x4e, (byte) 0x44, (byte) 0x0f,
|
||||
(byte) 0x1f, (byte) 0xf3, (byte) 0x76, (byte) 0xbb, (byte) 0x23,
|
||||
(byte) 0x17, (byte) 0xb2, (byte) 0xd6, (byte) 0x4d, (byte) 0xeb,
|
||||
(byte) 0x7c, (byte) 0x83};
|
||||
|
||||
byte[] nextChainKey = {(byte) 0x28, (byte) 0xe8, (byte) 0xf8, (byte) 0xfe, (byte) 0xe5,
|
||||
(byte) 0x4b, (byte) 0x80, (byte) 0x1e, (byte) 0xef, (byte) 0x7c,
|
||||
(byte) 0x5c, (byte) 0xfb, (byte) 0x2f, (byte) 0x17, (byte) 0xf3,
|
||||
(byte) 0x2c, (byte) 0x7b, (byte) 0x33, (byte) 0x44, (byte) 0x85,
|
||||
(byte) 0xbb, (byte) 0xb7, (byte) 0x0f, (byte) 0xac, (byte) 0x6e,
|
||||
(byte) 0xc1, (byte) 0x03, (byte) 0x42, (byte) 0xa2, (byte) 0x46,
|
||||
(byte) 0xd1, (byte) 0x5d};
|
||||
|
||||
ChainKey chainKey = new ChainKey(seed, 0);
|
||||
|
||||
assertTrue(Arrays.equals(chainKey.getKey(), seed));
|
||||
assertTrue(Arrays.equals(chainKey.getMessageKeys().getCipherKey().getEncoded(), messageKey));
|
||||
assertTrue(Arrays.equals(chainKey.getMessageKeys().getMacKey().getEncoded(), macKey));
|
||||
assertTrue(Arrays.equals(chainKey.getNextChainKey().getKey(), nextChainKey));
|
||||
assertTrue(chainKey.getIndex() == 0);
|
||||
assertTrue(chainKey.getMessageKeys().getCounter() == 0);
|
||||
assertTrue(chainKey.getNextChainKey().getIndex() == 1);
|
||||
assertTrue(chainKey.getNextChainKey().getMessageKeys().getCounter() == 1);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package org.whispersystems.test.ratchet;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.test.InMemorySessionState;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RatchetingSessionTest extends AndroidTestCase {
|
||||
|
||||
public void testRatchetingSessionAsBob() throws InvalidKeyException {
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97,
|
||||
(byte) 0x76, (byte) 0xb8, (byte) 0x77, (byte) 0x02,
|
||||
(byte) 0x05, (byte) 0x74, (byte) 0x5a, (byte) 0x3a,
|
||||
(byte) 0x6e, (byte) 0x24, (byte) 0xf5, (byte) 0x79,
|
||||
(byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
|
||||
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05,
|
||||
(byte) 0x92, (byte) 0x8e, (byte) 0xbb, (byte) 0xad,
|
||||
(byte) 0xc9, (byte) 0xc0, (byte) 0x5a, (byte) 0xd4,
|
||||
(byte) 0x58};
|
||||
|
||||
byte[] bobPrivate = {(byte) 0xa1, (byte) 0xca, (byte) 0xb4, (byte) 0x8f,
|
||||
(byte) 0x7c, (byte) 0x89, (byte) 0x3f, (byte) 0xaf,
|
||||
(byte) 0xa9, (byte) 0x88, (byte) 0x0a, (byte) 0x28,
|
||||
(byte) 0xc3, (byte) 0xb4, (byte) 0x99, (byte) 0x9d,
|
||||
(byte) 0x28, (byte) 0xd6, (byte) 0x32, (byte) 0x95,
|
||||
(byte) 0x62, (byte) 0xd2, (byte) 0x7a, (byte) 0x4e,
|
||||
(byte) 0xa4, (byte) 0xe2, (byte) 0x2e, (byte) 0x9f,
|
||||
(byte) 0xf1, (byte) 0xbd, (byte) 0xd6, (byte) 0x5a};
|
||||
|
||||
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38,
|
||||
(byte) 0x74, (byte) 0xf6, (byte) 0x96, (byte) 0x69,
|
||||
(byte) 0x56, (byte) 0xc2, (byte) 0xdd, (byte) 0x47,
|
||||
(byte) 0x3f, (byte) 0x8f, (byte) 0xa1, (byte) 0x5a,
|
||||
(byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
|
||||
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34,
|
||||
(byte) 0x16, (byte) 0x92, (byte) 0x32, (byte) 0x4c,
|
||||
(byte) 0xef, (byte) 0xb1, (byte) 0xc5, (byte) 0xe6,
|
||||
(byte) 0x26};
|
||||
|
||||
byte[] bobIdentityPrivate = {(byte) 0x48, (byte) 0x75, (byte) 0xcc, (byte) 0x69,
|
||||
(byte) 0xdd, (byte) 0xf8, (byte) 0xea, (byte) 0x07,
|
||||
(byte) 0x19, (byte) 0xec, (byte) 0x94, (byte) 0x7d,
|
||||
(byte) 0x61, (byte) 0x08, (byte) 0x11, (byte) 0x35,
|
||||
(byte) 0x86, (byte) 0x8d, (byte) 0x5f, (byte) 0xd8,
|
||||
(byte) 0x01, (byte) 0xf0, (byte) 0x2c, (byte) 0x02,
|
||||
(byte) 0x25, (byte) 0xe5, (byte) 0x16, (byte) 0xdf,
|
||||
(byte) 0x21, (byte) 0x56, (byte) 0x60, (byte) 0x5e};
|
||||
|
||||
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f,
|
||||
(byte) 0xb1, (byte) 0xa9, (byte) 0x86, (byte) 0x2c,
|
||||
(byte) 0x3a, (byte) 0xf6, (byte) 0xbe, (byte) 0xac,
|
||||
(byte) 0xa8, (byte) 0x92, (byte) 0x02, (byte) 0x77,
|
||||
(byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
|
||||
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7,
|
||||
(byte) 0xc9, (byte) 0x06, (byte) 0xae, (byte) 0xb3,
|
||||
(byte) 0x5e, (byte) 0x03, (byte) 0xcf, (byte) 0x89,
|
||||
(byte) 0x50};
|
||||
|
||||
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d,
|
||||
(byte) 0x1f, (byte) 0x52, (byte) 0x02, (byte) 0x83,
|
||||
(byte) 0xef, (byte) 0xcc, (byte) 0x55, (byte) 0xfc,
|
||||
(byte) 0xa5, (byte) 0xe6, (byte) 0x70, (byte) 0x75,
|
||||
(byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
|
||||
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51,
|
||||
(byte) 0xaf, (byte) 0x76, (byte) 0xdf, (byte) 0x18,
|
||||
(byte) 0xc5, (byte) 0x1d, (byte) 0x29, (byte) 0xd3,
|
||||
(byte) 0x4b};
|
||||
|
||||
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45,
|
||||
(byte) 0x56, (byte) 0x60, (byte) 0xad, (byte) 0xa6,
|
||||
(byte) 0x5b, (byte) 0x40, (byte) 0x10, (byte) 0x07,
|
||||
(byte) 0xf6, (byte) 0x15, (byte) 0xe6, (byte) 0x54,
|
||||
(byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
|
||||
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6,
|
||||
(byte) 0x87, (byte) 0x51, (byte) 0x49, (byte) 0xbc,
|
||||
(byte) 0xee, (byte) 0xfc, (byte) 0xb4, (byte) 0x2b,
|
||||
(byte) 0x4a};
|
||||
|
||||
byte[] senderChain = {(byte)0xd2, (byte)0x2f, (byte)0xd5, (byte)0x6d, (byte)0x3f,
|
||||
(byte)0xec, (byte)0x81, (byte)0x9c, (byte)0xf4, (byte)0xc3,
|
||||
(byte)0xd5, (byte)0x0c, (byte)0x56, (byte)0xed, (byte)0xfb,
|
||||
(byte)0x1c, (byte)0x28, (byte)0x0a, (byte)0x1b, (byte)0x31,
|
||||
(byte)0x96, (byte)0x45, (byte)0x37, (byte)0xf1, (byte)0xd1,
|
||||
(byte)0x61, (byte)0xe1, (byte)0xc9, (byte)0x31, (byte)0x48,
|
||||
(byte)0xe3, (byte)0x6b};
|
||||
|
||||
IdentityKey bobIdentityKeyPublic = new IdentityKey(bobIdentityPublic, 0);
|
||||
ECPrivateKey bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate);
|
||||
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate);
|
||||
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPrivateKey bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate);
|
||||
ECKeyPair bobEphemeralKey = new ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey);
|
||||
ECKeyPair bobBaseKey = bobEphemeralKey;
|
||||
|
||||
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
|
||||
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
|
||||
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
|
||||
|
||||
SessionState session = new InMemorySessionState();
|
||||
|
||||
RatchetingSession.initializeSession(session, bobBaseKey, aliceBasePublicKey,
|
||||
bobEphemeralKey, aliceEphemeralPublicKey,
|
||||
bobIdentityKey, aliceIdentityPublicKey);
|
||||
|
||||
assertTrue(session.getLocalIdentityKey().equals(bobIdentityKey.getPublicKey()));
|
||||
assertTrue(session.getRemoteIdentityKey().equals(aliceIdentityPublicKey));
|
||||
assertTrue(Arrays.equals(session.getSenderChainKey().getKey(), senderChain));
|
||||
}
|
||||
|
||||
public void testRatchetingSessionAsAlice() throws InvalidKeyException {
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, (byte) 0x76,
|
||||
(byte) 0xb8, (byte) 0x77, (byte) 0x02, (byte) 0x05, (byte) 0x74,
|
||||
(byte) 0x5a, (byte) 0x3a, (byte) 0x6e, (byte) 0x24, (byte) 0xf5,
|
||||
(byte) 0x79, (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
|
||||
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, (byte) 0x92,
|
||||
(byte) 0x8e, (byte) 0xbb, (byte) 0xad, (byte) 0xc9, (byte) 0xc0,
|
||||
(byte) 0x5a, (byte) 0xd4, (byte) 0x58};
|
||||
|
||||
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, (byte) 0x74,
|
||||
(byte) 0xf6, (byte) 0x96, (byte) 0x69, (byte) 0x56, (byte) 0xc2,
|
||||
(byte) 0xdd, (byte) 0x47, (byte) 0x3f, (byte) 0x8f, (byte) 0xa1,
|
||||
(byte) 0x5a, (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
|
||||
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, (byte) 0x16,
|
||||
(byte) 0x92, (byte) 0x32, (byte) 0x4c, (byte) 0xef, (byte) 0xb1,
|
||||
(byte) 0xc5, (byte) 0xe6, (byte) 0x26};
|
||||
|
||||
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, (byte) 0xb1,
|
||||
(byte) 0xa9, (byte) 0x86, (byte) 0x2c, (byte) 0x3a, (byte) 0xf6,
|
||||
(byte) 0xbe, (byte) 0xac, (byte) 0xa8, (byte) 0x92, (byte) 0x02,
|
||||
(byte) 0x77, (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
|
||||
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, (byte) 0xc9,
|
||||
(byte) 0x06, (byte) 0xae, (byte) 0xb3, (byte) 0x5e, (byte) 0x03,
|
||||
(byte) 0xcf, (byte) 0x89, (byte) 0x50};
|
||||
|
||||
byte[] aliceBasePrivate = {(byte) 0x11, (byte) 0xae, (byte) 0x7c, (byte) 0x64, (byte) 0xd1,
|
||||
(byte) 0xe6, (byte) 0x1c, (byte) 0xd5, (byte) 0x96, (byte) 0xb7,
|
||||
(byte) 0x6a, (byte) 0x0d, (byte) 0xb5, (byte) 0x01, (byte) 0x26,
|
||||
(byte) 0x73, (byte) 0x39, (byte) 0x1c, (byte) 0xae, (byte) 0x66,
|
||||
(byte) 0xed, (byte) 0xbf, (byte) 0xcf, (byte) 0x07, (byte) 0x3b,
|
||||
(byte) 0x4d, (byte) 0xa8, (byte) 0x05, (byte) 0x16, (byte) 0xa4,
|
||||
(byte) 0x74, (byte) 0x49};
|
||||
|
||||
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, (byte) 0x1f,
|
||||
(byte) 0x52, (byte) 0x02, (byte) 0x83, (byte) 0xef, (byte) 0xcc,
|
||||
(byte) 0x55, (byte) 0xfc, (byte) 0xa5, (byte) 0xe6, (byte) 0x70,
|
||||
(byte) 0x75, (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
|
||||
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, (byte) 0xaf,
|
||||
(byte) 0x76, (byte) 0xdf, (byte) 0x18, (byte) 0xc5, (byte) 0x1d,
|
||||
(byte) 0x29, (byte) 0xd3, (byte) 0x4b};
|
||||
|
||||
byte[] aliceEphemeralPrivate = {(byte) 0xd1, (byte) 0xba, (byte) 0x38, (byte) 0xce, (byte) 0xa9,
|
||||
(byte) 0x17, (byte) 0x43, (byte) 0xd3, (byte) 0x39, (byte) 0x39,
|
||||
(byte) 0xc3, (byte) 0x3c, (byte) 0x84, (byte) 0x98, (byte) 0x65,
|
||||
(byte) 0x09, (byte) 0x28, (byte) 0x01, (byte) 0x61, (byte) 0xb8,
|
||||
(byte) 0xb6, (byte) 0x0f, (byte) 0xc7, (byte) 0x87, (byte) 0x0c,
|
||||
(byte) 0x59, (byte) 0x9c, (byte) 0x1d, (byte) 0x46, (byte) 0x20,
|
||||
(byte) 0x12, (byte) 0x48};
|
||||
|
||||
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, (byte) 0x56,
|
||||
(byte) 0x60, (byte) 0xad, (byte) 0xa6, (byte) 0x5b, (byte) 0x40,
|
||||
(byte) 0x10, (byte) 0x07, (byte) 0xf6, (byte) 0x15, (byte) 0xe6,
|
||||
(byte) 0x54, (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
|
||||
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, (byte) 0x87,
|
||||
(byte) 0x51, (byte) 0x49, (byte) 0xbc, (byte) 0xee, (byte) 0xfc,
|
||||
(byte) 0xb4, (byte) 0x2b, (byte) 0x4a};
|
||||
|
||||
byte[] aliceIdentityPrivate = {(byte) 0x90, (byte) 0x40, (byte) 0xf0, (byte) 0xd4, (byte) 0xe0,
|
||||
(byte) 0x9c, (byte) 0xf3, (byte) 0x8f, (byte) 0x6d, (byte) 0xc7,
|
||||
(byte) 0xc1, (byte) 0x37, (byte) 0x79, (byte) 0xc9, (byte) 0x08,
|
||||
(byte) 0xc0, (byte) 0x15, (byte) 0xa1, (byte) 0xda, (byte) 0x4f,
|
||||
(byte) 0xa7, (byte) 0x87, (byte) 0x37, (byte) 0xa0, (byte) 0x80,
|
||||
(byte) 0xeb, (byte) 0x0a, (byte) 0x6f, (byte) 0x4f, (byte) 0x5f,
|
||||
(byte) 0x8f, (byte) 0x58};
|
||||
|
||||
byte[] receiverChain = {(byte) 0xd2, (byte) 0x2f, (byte) 0xd5, (byte) 0x6d, (byte) 0x3f,
|
||||
(byte) 0xec, (byte) 0x81, (byte) 0x9c, (byte) 0xf4, (byte) 0xc3,
|
||||
(byte) 0xd5, (byte) 0x0c, (byte) 0x56, (byte) 0xed, (byte) 0xfb,
|
||||
(byte) 0x1c, (byte) 0x28, (byte) 0x0a, (byte) 0x1b, (byte) 0x31,
|
||||
(byte) 0x96, (byte) 0x45, (byte) 0x37, (byte) 0xf1, (byte) 0xd1,
|
||||
(byte) 0x61, (byte) 0xe1, (byte) 0xc9, (byte) 0x31, (byte) 0x48,
|
||||
(byte) 0xe3, (byte) 0x6b};
|
||||
|
||||
IdentityKey bobIdentityKey = new IdentityKey(bobIdentityPublic, 0);
|
||||
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPublicKey bobBasePublicKey = bobEphemeralPublicKey;
|
||||
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
|
||||
ECPrivateKey aliceBasePrivateKey = Curve.decodePrivatePoint(aliceBasePrivate);
|
||||
ECKeyPair aliceBaseKey = new ECKeyPair(aliceBasePublicKey, aliceBasePrivateKey);
|
||||
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
|
||||
ECPrivateKey aliceEphemeralPrivateKey = Curve.decodePrivatePoint(aliceEphemeralPrivate);
|
||||
ECKeyPair aliceEphemeralKey = new ECKeyPair(aliceEphemeralPublicKey, aliceEphemeralPrivateKey);
|
||||
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
|
||||
ECPrivateKey aliceIdentityPrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate);
|
||||
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(aliceIdentityPublicKey, aliceIdentityPrivateKey);
|
||||
|
||||
SessionState session = new InMemorySessionState();
|
||||
|
||||
RatchetingSession.initializeSession(session, aliceBaseKey, bobBasePublicKey,
|
||||
aliceEphemeralKey, bobEphemeralPublicKey,
|
||||
aliceIdentityKey, bobIdentityKey);
|
||||
|
||||
assertTrue(session.getLocalIdentityKey().equals(aliceIdentityKey.getPublicKey()));
|
||||
assertTrue(session.getRemoteIdentityKey().equals(bobIdentityKey));
|
||||
assertTrue(Arrays.equals(session.getReceiverChainKey(bobEphemeralPublicKey).getKey(),
|
||||
receiverChain));
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
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.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RootKeyTest extends AndroidTestCase {
|
||||
|
||||
public void testRootKeyDerivation() throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
byte[] rootKeySeed = {(byte) 0x7b, (byte) 0xa6, (byte) 0xde, (byte) 0xbc, (byte) 0x2b,
|
||||
(byte) 0xc1, (byte) 0xbb, (byte) 0xf9, (byte) 0x1a, (byte) 0xbb,
|
||||
(byte) 0xc1, (byte) 0x36, (byte) 0x74, (byte) 0x04, (byte) 0x17,
|
||||
(byte) 0x6c, (byte) 0xa6, (byte) 0x23, (byte) 0x09, (byte) 0x5b,
|
||||
(byte) 0x7e, (byte) 0xc6, (byte) 0x6b, (byte) 0x45, (byte) 0xf6,
|
||||
(byte) 0x02, (byte) 0xd9, (byte) 0x35, (byte) 0x38, (byte) 0x94,
|
||||
(byte) 0x2d, (byte) 0xcc};
|
||||
|
||||
byte[] alicePublic = {(byte) 0x05, (byte) 0xee, (byte) 0x4f, (byte) 0xa6, (byte) 0xcd,
|
||||
(byte) 0xc0, (byte) 0x30, (byte) 0xdf, (byte) 0x49, (byte) 0xec,
|
||||
(byte) 0xd0, (byte) 0xba, (byte) 0x6c, (byte) 0xfc, (byte) 0xff,
|
||||
(byte) 0xb2, (byte) 0x33, (byte) 0xd3, (byte) 0x65, (byte) 0xa2,
|
||||
(byte) 0x7f, (byte) 0xad, (byte) 0xbe, (byte) 0xff, (byte) 0x77,
|
||||
(byte) 0xe9, (byte) 0x63, (byte) 0xfc, (byte) 0xb1, (byte) 0x62,
|
||||
(byte) 0x22, (byte) 0xe1, (byte) 0x3a};
|
||||
|
||||
byte[] alicePrivate = {(byte) 0x21, (byte) 0x68, (byte) 0x22, (byte) 0xec, (byte) 0x67,
|
||||
(byte) 0xeb, (byte) 0x38, (byte) 0x04, (byte) 0x9e, (byte) 0xba,
|
||||
(byte) 0xe7, (byte) 0xb9, (byte) 0x39, (byte) 0xba, (byte) 0xea,
|
||||
(byte) 0xeb, (byte) 0xb1, (byte) 0x51, (byte) 0xbb, (byte) 0xb3,
|
||||
(byte) 0x2d, (byte) 0xb8, (byte) 0x0f, (byte) 0xd3, (byte) 0x89,
|
||||
(byte) 0x24, (byte) 0x5a, (byte) 0xc3, (byte) 0x7a, (byte) 0x94,
|
||||
(byte) 0x8e, (byte) 0x50};
|
||||
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0xab, (byte) 0xb8, (byte) 0xeb, (byte) 0x29,
|
||||
(byte) 0xcc, (byte) 0x80, (byte) 0xb4, (byte) 0x71, (byte) 0x09,
|
||||
(byte) 0xa2, (byte) 0x26, (byte) 0x5a, (byte) 0xbe, (byte) 0x97,
|
||||
(byte) 0x98, (byte) 0x48, (byte) 0x54, (byte) 0x06, (byte) 0xe3,
|
||||
(byte) 0x2d, (byte) 0xa2, (byte) 0x68, (byte) 0x93, (byte) 0x4a,
|
||||
(byte) 0x95, (byte) 0x55, (byte) 0xe8, (byte) 0x47, (byte) 0x57,
|
||||
(byte) 0x70, (byte) 0x8a, (byte) 0x30};
|
||||
|
||||
byte[] nextRoot = {(byte) 0xb1, (byte) 0x14, (byte) 0xf5, (byte) 0xde, (byte) 0x28,
|
||||
(byte) 0x01, (byte) 0x19, (byte) 0x85, (byte) 0xe6, (byte) 0xeb,
|
||||
(byte) 0xa2, (byte) 0x5d, (byte) 0x50, (byte) 0xe7, (byte) 0xec,
|
||||
(byte) 0x41, (byte) 0xa9, (byte) 0xb0, (byte) 0x2f, (byte) 0x56,
|
||||
(byte) 0x93, (byte) 0xc5, (byte) 0xc7, (byte) 0x88, (byte) 0xa6,
|
||||
(byte) 0x3a, (byte) 0x06, (byte) 0xd2, (byte) 0x12, (byte) 0xa2,
|
||||
(byte) 0xf7, (byte) 0x31};
|
||||
|
||||
byte[] nextChain = {(byte) 0x9d, (byte) 0x7d, (byte) 0x24, (byte) 0x69, (byte) 0xbc,
|
||||
(byte) 0x9a, (byte) 0xe5, (byte) 0x3e, (byte) 0xe9, (byte) 0x80,
|
||||
(byte) 0x5a, (byte) 0xa3, (byte) 0x26, (byte) 0x4d, (byte) 0x24,
|
||||
(byte) 0x99, (byte) 0xa3, (byte) 0xac, (byte) 0xe8, (byte) 0x0f,
|
||||
(byte) 0x4c, (byte) 0xca, (byte) 0xe2, (byte) 0xda, (byte) 0x13,
|
||||
(byte) 0x43, (byte) 0x0c, (byte) 0x5c, (byte) 0x55, (byte) 0xb5,
|
||||
(byte) 0xca, (byte) 0x5f};
|
||||
|
||||
ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0);
|
||||
ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
|
||||
ECKeyPair aliceKeyPair = new ECKeyPair(alicePublicKey, alicePrivateKey);
|
||||
|
||||
ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
RootKey rootKey = new RootKey(rootKeySeed);
|
||||
|
||||
Pair<RootKey, ChainKey> rootKeyChainKeyPair = rootKey.createChain(bobPublicKey, aliceKeyPair);
|
||||
RootKey nextRootKey = rootKeyChainKeyPair.first();
|
||||
ChainKey nextChainKey = rootKeyChainKeyPair.second();
|
||||
|
||||
assertTrue(Arrays.equals(rootKey.getKeyBytes(), rootKeySeed));
|
||||
assertTrue(Arrays.equals(nextRootKey.getKeyBytes(), nextRoot));
|
||||
assertTrue(Arrays.equals(nextChainKey.getKey(), nextChain));
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.whispersystems.libaxolotl"
|
||||
android:versionCode="1"
|
||||
android:versionName="0.1">
|
||||
<application />
|
||||
</manifest>
|
@ -1,4 +1,4 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
public class DuplicateMessageException extends Exception {
|
||||
public DuplicateMessageException(String s) {
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.util.Hex;
|
||||
|
||||
/**
|
||||
* A class for representing an identity key.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class IdentityKey {
|
||||
|
||||
private final ECPublicKey publicKey;
|
||||
|
||||
public IdentityKey(ECPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset);
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return Hex.toString(publicKey.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof IdentityKey)) return false;
|
||||
|
||||
return publicKey.equals(((IdentityKey) other).getPublicKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return publicKey.hashCode();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
public class LegacyMessageException extends Exception {
|
||||
public LegacyMessageException(String s) {
|
@ -0,0 +1,54 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
public interface SessionState {
|
||||
public void setNeedsRefresh(boolean needsRefresh);
|
||||
public boolean getNeedsRefresh();
|
||||
public void setSessionVersion(int version);
|
||||
public int getSessionVersion();
|
||||
public void setRemoteIdentityKey(IdentityKey identityKey);
|
||||
public void setLocalIdentityKey(IdentityKey identityKey);
|
||||
public IdentityKey getRemoteIdentityKey();
|
||||
public IdentityKey getLocalIdentityKey();
|
||||
public int getPreviousCounter();
|
||||
public void setPreviousCounter(int previousCounter);
|
||||
public RootKey getRootKey();
|
||||
public void setRootKey(RootKey rootKey);
|
||||
public ECPublicKey getSenderEphemeral();
|
||||
public ECKeyPair getSenderEphemeralPair();
|
||||
public boolean hasReceiverChain(ECPublicKey senderEphemeral);
|
||||
public boolean hasSenderChain();
|
||||
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral);
|
||||
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey);
|
||||
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey);
|
||||
public ChainKey getSenderChainKey();
|
||||
public void setSenderChainKey(ChainKey nextChainKey);
|
||||
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter);
|
||||
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter);
|
||||
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys);
|
||||
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey);
|
||||
public void setPendingKeyExchange(int sequence,
|
||||
ECKeyPair ourBaseKey,
|
||||
ECKeyPair ourEphemeralKey,
|
||||
IdentityKeyPair ourIdentityKey);
|
||||
public int getPendingKeyExchangeSequence();
|
||||
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException;
|
||||
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException;
|
||||
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException;
|
||||
public boolean hasPendingKeyExchange();
|
||||
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey);
|
||||
public boolean hasPendingPreKey();
|
||||
public Pair<Integer, ECPublicKey> getPendingPreKey();
|
||||
public void clearPendingPreKey();
|
||||
public void setRemoteRegistrationId(int registrationId);
|
||||
public int getRemoteRegistrationId();
|
||||
public void setLocalRegistrationId(int registrationId);
|
||||
public int getLocalRegistrationId();
|
||||
public byte[] serialize();
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SessionStore {
|
||||
|
||||
public SessionState getSessionState();
|
||||
public List<SessionState> getPreviousSessionStates();
|
||||
public void save();
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.protocol;
|
||||
|
||||
public interface CiphertextMessage {
|
||||
|
||||
public static final int UNSUPPORTED_VERSION = 1;
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static final int WHISPER_TYPE = 2;
|
||||
public static final int PREKEY_TYPE = 3;
|
||||
|
||||
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
|
||||
public byte[] serialize();
|
||||
public int getType();
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.ratchet;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class MessageKeys {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final int counter;
|
||||
|
||||
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.ratchet;
|
||||
|
||||
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.util.Pair;
|
||||
|
||||
public class RootKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
public RootKey(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
HKDF kdf = new HKDF();
|
||||
byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey());
|
||||
DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes());
|
||||
RootKey newRootKey = new RootKey(keys.getCipherKey().getEncoded());
|
||||
ChainKey newChainKey = new ChainKey(keys.getMacKey().getEncoded(), 0);
|
||||
|
||||
return new Pair<>(newRootKey, newChainKey);
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class ByteUtil {
|
||||
|
||||
public static byte[] combine(byte[]... elements) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
for (byte[] element : elements) {
|
||||
baos.write(element);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
|
||||
byte[][] parts = new byte[2][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength)
|
||||
throws ParseException
|
||||
{
|
||||
if (input == null || firstLength < 0 || secondLength < 0 || thirdLength < 0 ||
|
||||
input.length < firstLength + secondLength + thirdLength)
|
||||
{
|
||||
throw new ParseException("Input too small: " + (input == null ? null : Hex.toString(input)), 0);
|
||||
}
|
||||
|
||||
byte[][] parts = new byte[3][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
parts[2] = new byte[thirdLength];
|
||||
System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static byte[] trim(byte[] input, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(input, 0, result, 0, result.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte intsToByteHighAndLow(int highValue, int lowValue) {
|
||||
return (byte)((highValue << 4 | lowValue) & 0xFF);
|
||||
}
|
||||
|
||||
public static int highBitsToInt(byte value) {
|
||||
return (value & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
public static int lowBitsToInt(byte value) {
|
||||
return (value & 0xF);
|
||||
}
|
||||
|
||||
public static int highBitsToMedium(int value) {
|
||||
return (value >> 12);
|
||||
}
|
||||
|
||||
public static int lowBitsToMedium(int value) {
|
||||
return (value & 0xFFF);
|
||||
}
|
||||
|
||||
public static byte[] shortToByteArray(int value) {
|
||||
byte[] bytes = new byte[2];
|
||||
shortToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset+1] = (byte)value;
|
||||
bytes[offset] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static byte[] mediumToByteArray(int value) {
|
||||
byte[] bytes = new byte[3];
|
||||
mediumToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 2] = (byte)value;
|
||||
bytes[offset + 1] = (byte)(value >> 8);
|
||||
bytes[offset] = (byte)(value >> 16);
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static byte[] intToByteArray(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
intToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
bytes[offset+2] = (byte)(value >> 16);
|
||||
bytes[offset+3] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte[] longToByteArray(long l) {
|
||||
byte[] bytes = new byte[8];
|
||||
longToByteArray(bytes, 0, l);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 7] = (byte)value;
|
||||
bytes[offset + 6] = (byte)(value >> 8);
|
||||
bytes[offset + 5] = (byte)(value >> 16);
|
||||
bytes[offset + 4] = (byte)(value >> 24);
|
||||
bytes[offset + 3] = (byte)(value >> 32);
|
||||
bytes[offset + 2] = (byte)(value >> 40);
|
||||
bytes[offset + 1] = (byte)(value >> 48);
|
||||
bytes[offset] = (byte)(value >> 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset + 0] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes) {
|
||||
return byteArrayToShort(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff);
|
||||
}
|
||||
|
||||
// The SSL patented 3-byte Value.
|
||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset + 2] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes) {
|
||||
return byteArrayToInt(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 24 |
|
||||
(bytes[offset + 1] & 0xff) << 16 |
|
||||
(bytes[offset + 2] & 0xff) << 8 |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToIntLittleEndian(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset + 3] & 0xff) << 24 |
|
||||
(bytes[offset + 2] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset] & 0xff);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
return byteArrayToLong(bytes, 0);
|
||||
}
|
||||
|
||||
public static long byteArray4ToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset + 0] & 0xffL) << 24) |
|
||||
((bytes[offset + 1] & 0xffL) << 16) |
|
||||
((bytes[offset + 2] & 0xffL) << 8) |
|
||||
((bytes[offset + 3] & 0xffL));
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset] & 0xffL) << 56) |
|
||||
((bytes[offset + 1] & 0xffL) << 48) |
|
||||
((bytes[offset + 2] & 0xffL) << 40) |
|
||||
((bytes[offset + 3] & 0xffL) << 32) |
|
||||
((bytes[offset + 4] & 0xffL) << 24) |
|
||||
((bytes[offset + 5] & 0xffL) << 16) |
|
||||
((bytes[offset + 6] & 0xffL) << 8) |
|
||||
((bytes[offset + 7] & 0xffL));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Utility for generating hex dumps.
|
||||
*/
|
||||
public class Hex {
|
||||
|
||||
private final static char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
buf.append("(byte)0x");
|
||||
appendHexChar(buf, bytes[offset + i]);
|
||||
buf.append(", ");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i=0;i<bytes.length;i++) {
|
||||
appendHexChar(buf, bytes[i]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static byte[] fromStringCondensed(String encoded) throws IOException {
|
||||
final char[] data = encoded.toCharArray();
|
||||
final int len = data.length;
|
||||
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new IOException("Odd number of characters.");
|
||||
}
|
||||
|
||||
final byte[] out = new byte[len >> 1];
|
||||
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = Character.digit(data[j], 16) << 4;
|
||||
j++;
|
||||
f = f | Character.digit(data[j], 16);
|
||||
j++;
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void appendHexChar(StringBuffer buf, int b) {
|
||||
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[b & 0xf]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
public class Pair<T1, T2> {
|
||||
private final T1 v1;
|
||||
private final T2 v2;
|
||||
|
||||
public Pair(T1 v1, T2 v2) {
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
public T1 first(){
|
||||
return v1;
|
||||
}
|
||||
|
||||
public T2 second(){
|
||||
return v2;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Pair &&
|
||||
equal(((Pair) o).first(), first()) &&
|
||||
equal(((Pair) o).second(), second());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return first().hashCode() ^ second().hashCode();
|
||||
}
|
||||
|
||||
private boolean equal(Object first, Object second) {
|
||||
if (first == null && second == null) return true;
|
||||
if (first == null || second == null) return false;
|
||||
return first.equals(second);
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2013 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
/**
|
||||
* A class for representing an identity key.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class IdentityKey implements Parcelable, SerializableKey {
|
||||
|
||||
public static final Parcelable.Creator<IdentityKey> CREATOR = new Parcelable.Creator<IdentityKey>() {
|
||||
public IdentityKey createFromParcel(Parcel in) {
|
||||
try {
|
||||
return new IdentityKey(in);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKey[] newArray(int size) {
|
||||
return new IdentityKey[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static final int NIST_SIZE = 1 + ECPublicKey.KEY_SIZE;
|
||||
|
||||
private ECPublicKey publicKey;
|
||||
|
||||
public IdentityKey(ECPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey(Parcel in) throws InvalidKeyException {
|
||||
int length = in.readInt();
|
||||
byte[] serialized = new byte[length];
|
||||
|
||||
in.readByteArray(serialized);
|
||||
initializeFromSerialized(serialized, 0);
|
||||
}
|
||||
|
||||
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
initializeFromSerialized(bytes, offset);
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
if ((bytes[offset] & 0xff) == 1) {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset +1);
|
||||
} else {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return Hex.toString(publicKey.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof IdentityKey)) return false;
|
||||
|
||||
return publicKey.equals(((IdentityKey) other).getPublicKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return publicKey.hashCode();
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
byte[] serialized = this.serialize();
|
||||
dest.writeInt(serialized.length);
|
||||
dest.writeByteArray(serialized);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
|
||||
public class IdentityKeyParcelable implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<IdentityKeyParcelable> CREATOR = new Parcelable.Creator<IdentityKeyParcelable>() {
|
||||
public IdentityKeyParcelable createFromParcel(Parcel in) {
|
||||
try {
|
||||
return new IdentityKeyParcelable(in);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKeyParcelable[] newArray(int size) {
|
||||
return new IdentityKeyParcelable[size];
|
||||
}
|
||||
};
|
||||
|
||||
private final IdentityKey identityKey;
|
||||
|
||||
public IdentityKeyParcelable(IdentityKey identityKey) {
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
public IdentityKeyParcelable(Parcel in) throws InvalidKeyException {
|
||||
int serializedLength = in.readInt();
|
||||
byte[] serialized = new byte[serializedLength];
|
||||
|
||||
in.readByteArray(serialized);
|
||||
this.identityKey = new IdentityKey(serialized, 0);
|
||||
}
|
||||
|
||||
public IdentityKey get() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(identityKey.serialize().length);
|
||||
dest.writeByteArray(identityKey.serialize());
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class InvalidVersionException extends Exception {
|
||||
|
||||
public InvalidVersionException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (C) 2013 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
|
||||
public class SessionCipherFactory {
|
||||
|
||||
public static SessionCipher getInstance(Context context,
|
||||
MasterSecret masterSecret,
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
SessionRecordV2 record = new SessionRecordV2(context, masterSecret, recipient);
|
||||
return new SessionCipher(record);
|
||||
} else {
|
||||
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class NKDF {
|
||||
|
||||
public static final int LEGACY_CIPHER_KEY_LENGTH = 16;
|
||||
public static final int LEGACY_MAC_KEY_LENGTH = 20;
|
||||
|
||||
public DerivedSecrets deriveSecrets(byte[] sharedSecret, boolean isLowEnd)
|
||||
{
|
||||
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
|
||||
SecretKeySpec macKey = deriveMacSecret(cipherKey);
|
||||
|
||||
return new DerivedSecrets(cipherKey, macKey);
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, byte[] sharedSecret) {
|
||||
byte[] derivedBytes = deriveBytes(sharedSecret, LEGACY_CIPHER_KEY_LENGTH * 2);
|
||||
byte[] cipherSecret = new byte[LEGACY_CIPHER_KEY_LENGTH];
|
||||
|
||||
if (isLowEnd) {
|
||||
System.arraycopy(derivedBytes, LEGACY_CIPHER_KEY_LENGTH, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
|
||||
} else {
|
||||
System.arraycopy(derivedBytes, 0, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
|
||||
}
|
||||
|
||||
return new SecretKeySpec(cipherSecret, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] secret = md.digest(key.getEncoded());
|
||||
|
||||
return new SecretKeySpec(secret, "HmacSHA1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("SHA-1 Not Supported!",e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] deriveBytes(byte[] seed, int bytesNeeded) {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("NKDF", e);
|
||||
throw new IllegalArgumentException("SHA-256 Not Supported!");
|
||||
}
|
||||
|
||||
int rounds = bytesNeeded / md.getDigestLength();
|
||||
|
||||
for (int i=1;i<=rounds;i++) {
|
||||
byte[] roundBytes = Conversions.intToByteArray(i);
|
||||
md.update(roundBytes);
|
||||
md.update(seed);
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
public interface CiphertextMessage {
|
||||
|
||||
public static final int UNSUPPORTED_VERSION = 1;
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static final int WHISPER_TYPE = 2;
|
||||
public static final int PREKEY_TYPE = 3;
|
||||
|
||||
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
|
||||
public byte[] serialize();
|
||||
public int getType();
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class MessageKeys {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final int counter;
|
||||
|
||||
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RootKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
public RootKey(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
HKDF kdf = new HKDF();
|
||||
byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey());
|
||||
DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes());
|
||||
RootKey newRootKey = new RootKey(keys.getCipherKey().getEncoded());
|
||||
ChainKey newChainKey = new ChainKey(keys.getMacKey().getEncoded(), 0);
|
||||
|
||||
return new Pair<RootKey, ChainKey>(newRootKey, newChainKey);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.storage;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.kdf.NKDF;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Represents the currently negotiated session key for a given
|
||||
* local key id and remote key id. This is stored encrypted on
|
||||
* disk.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class SessionKey {
|
||||
|
||||
private int mode;
|
||||
private int localKeyId;
|
||||
private int remoteKeyId;
|
||||
private SecretKeySpec cipherKey;
|
||||
private SecretKeySpec macKey;
|
||||
private MasterCipher masterCipher;
|
||||
|
||||
public SessionKey(int mode, int localKeyId, int remoteKeyId,
|
||||
SecretKeySpec cipherKey, SecretKeySpec macKey,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
this.mode = mode;
|
||||
this.localKeyId = localKeyId;
|
||||
this.remoteKeyId = remoteKeyId;
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
}
|
||||
|
||||
public SessionKey(byte[] bytes, MasterSecret masterSecret) throws InvalidMessageException {
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
deserialize(bytes);
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
byte[] localKeyIdBytes = Conversions.mediumToByteArray(localKeyId);
|
||||
byte[] remoteKeyIdBytes = Conversions.mediumToByteArray(remoteKeyId);
|
||||
byte[] cipherKeyBytes = cipherKey.getEncoded();
|
||||
byte[] macKeyBytes = macKey.getEncoded();
|
||||
byte[] modeBytes = {(byte)mode};
|
||||
byte[] combined = Util.combine(localKeyIdBytes, remoteKeyIdBytes,
|
||||
cipherKeyBytes, macKeyBytes, modeBytes);
|
||||
|
||||
return masterCipher.encryptBytes(combined);
|
||||
}
|
||||
|
||||
private void deserialize(byte[] bytes) throws InvalidMessageException {
|
||||
byte[] decrypted = masterCipher.decryptBytes(bytes);
|
||||
|
||||
this.localKeyId = Conversions.byteArrayToMedium(decrypted, 0);
|
||||
this.remoteKeyId = Conversions.byteArrayToMedium(decrypted, 3);
|
||||
|
||||
byte[] keyBytes = new byte[NKDF.LEGACY_CIPHER_KEY_LENGTH];
|
||||
System.arraycopy(decrypted, 6, keyBytes, 0, keyBytes.length);
|
||||
|
||||
byte[] macBytes = new byte[NKDF.LEGACY_MAC_KEY_LENGTH];
|
||||
System.arraycopy(decrypted, 6 + keyBytes.length, macBytes, 0, macBytes.length);
|
||||
|
||||
if (decrypted.length < 6 + NKDF.LEGACY_CIPHER_KEY_LENGTH + NKDF.LEGACY_MAC_KEY_LENGTH + 1) {
|
||||
throw new InvalidMessageException("No mode included");
|
||||
}
|
||||
|
||||
this.mode = decrypted[6 + keyBytes.length + macBytes.length];
|
||||
|
||||
this.cipherKey = new SecretKeySpec(keyBytes, "AES");
|
||||
this.macKey = new SecretKeySpec(macBytes, "HmacSHA1");
|
||||
}
|
||||
|
||||
public int getLocalKeyId() {
|
||||
return this.localKeyId;
|
||||
}
|
||||
|
||||
public int getRemoteKeyId() {
|
||||
return this.remoteKeyId;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return this.cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return this.macKey;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package org.whispersystems.textsecure.storage;
|
||||
package org.whispersystems.textsecure.storage.legacy;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipient;
|
||||
import org.whispersystems.textsecure.storage.Record;
|
||||
|
||||
/**
|
||||
* A disk record representing a current session.
|
||||
*
|
@ -1 +1 @@
|
||||
include ':library'
|
||||
include ':library', ':libaxolotl'
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue