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.
This commit is contained in:
Moxie Marlinspike
2014-04-21 08:40:19 -07:00
parent fe3d91c40c
commit d902c12941
115 changed files with 2246 additions and 1017 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
package org.whispersystems.libaxolotl;
public class DuplicateMessageException extends Exception {
public DuplicateMessageException(String s) {
super(s);
}
}

View File

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

View File

@@ -0,0 +1,43 @@
/**
* 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.libaxolotl;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
/**
* Holder for public and private identity key pair.
*
* @author Moxie Marlinspike
*/
public class IdentityKeyPair {
private final IdentityKey publicKey;
private final ECPrivateKey privateKey;
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public IdentityKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -0,0 +1,34 @@
/**
* 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;
public class InvalidKeyException extends Exception {
public InvalidKeyException() {}
public InvalidKeyException(String detailMessage) {
super(detailMessage);
}
public InvalidKeyException(Throwable throwable) {
super(throwable);
}
public InvalidKeyException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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;
public class InvalidMacException extends Exception {
public InvalidMacException(String detailMessage) {
super(detailMessage);
}
public InvalidMacException(Throwable throwable) {
super(throwable);
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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 java.util.List;
public class InvalidMessageException extends Exception {
public InvalidMessageException() {}
public InvalidMessageException(String detailMessage) {
super(detailMessage);
}
public InvalidMessageException(Throwable throwable) {
super(throwable);
}
public InvalidMessageException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public InvalidMessageException(String detailMessage, List<Exception> exceptions) {
super(detailMessage, exceptions.get(0));
}
}

View File

@@ -0,0 +1,23 @@
/**
* 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;
public class InvalidVersionException extends Exception {
public InvalidVersionException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,7 @@
package org.whispersystems.libaxolotl;
public class LegacyMessageException extends Exception {
public LegacyMessageException(String s) {
super(s);
}
}

View File

@@ -0,0 +1,245 @@
/**
* 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.libaxolotl;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.Pair;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final SessionStore sessionStore;
public SessionCipher(SessionStore sessionStore) {
this.sessionStore = sessionStore;
}
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionState sessionState = sessionStore.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first(),
pendingPreKey.second(),
sessionState.getLocalIdentityKey(),
(WhisperMessage) ciphertextMessage);
}
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionStore.save();
return ciphertextMessage;
}
}
public byte[] decrypt(byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
synchronized (SESSION_LOCK) {
SessionState sessionState = sessionStore.getSessionState();
List<SessionState> previousStates = sessionStore.getPreviousSessionStates();
List<Exception> exceptions = new LinkedList<Exception>();
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionStore.save();
return plaintext;
} catch (InvalidMessageException e) {
exceptions.add(e);
}
for (SessionState previousState : previousStates) {
try {
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionStore.save();
return plaintext;
} catch (InvalidMessageException e) {
exceptions.add(e);
}
}
throw new InvalidMessageException("No valid sessions.", exceptions);
}
}
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionState.clearPendingPreKey();
return plaintext;
}
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
return sessionStore.getSessionState().getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionState.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> senderChain = receiverChain.first().createChain(theirEphemeral, ourNewEphemeral);
sessionState.setRootKey(senderChain.first());
sessionState.addReceiverChain(theirEphemeral, receiverChain.second());
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionState.setSenderChain(ourNewEphemeral, senderChain.second());
return receiverChain.second();
}
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException, DuplicateMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionState.removeMessageKeys(theirEphemeral, counter);
} else {
throw new DuplicateMessageException("Received message with old counter: " +
chainKey.getIndex() + " , " + counter);
}
}
if (chainKey.getIndex() - counter > 2000) {
throw new InvalidMessageException("Over 2000 messages into the future!");
}
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys();
}
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] ivBytes = new byte[16];
ByteUtil.intToByteArray(ivBytes, 0, counter);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(mode, key, iv);
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (java.security.InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
/**
* 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.libaxolotl.ecc;
import org.whispersystems.libaxolotl.InvalidKeyException;
public class Curve {
public static final int DJB_TYPE = 0x05;
public static ECKeyPair generateKeyPair(boolean ephemeral) {
return Curve25519.generateKeyPair(ephemeral);
}
public static ECPublicKey decodePoint(byte[] bytes, int offset)
throws InvalidKeyException
{
int type = bytes[offset];
if (type == DJB_TYPE) {
return Curve25519.decodePoint(bytes, offset);
} else {
throw new InvalidKeyException("Unknown key type: " + type);
}
}
public static ECPrivateKey decodePrivatePoint(byte[] bytes) {
return new DjbECPrivateKey(bytes);
}
public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
throws InvalidKeyException
{
if (publicKey.getType() != privateKey.getType()) {
throw new InvalidKeyException("Public and private keys must be of the same type!");
}
if (publicKey.getType() == DJB_TYPE) {
return Curve25519.calculateAgreement(publicKey, privateKey);
} else {
throw new InvalidKeyException("Unknown type: " + publicKey.getType());
}
}
}

View File

@@ -0,0 +1,75 @@
/**
* 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.libaxolotl.ecc;
import org.whispersystems.libaxolotl.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Curve25519 {
static {
System.loadLibrary("curve25519");
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static final SecureRandom random;
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
private static native byte[] generatePublicKey(byte[] privateKey);
private static native byte[] generatePrivateKey(byte[] random, boolean ephemeral);
public static ECKeyPair generateKeyPair(boolean ephemeral) {
byte[] privateKey = generatePrivateKey(ephemeral);
byte[] publicKey = generatePublicKey(privateKey);
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
}
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
return calculateAgreement(((DjbECPrivateKey)privateKey).getPrivateKey(),
((DjbECPublicKey)publicKey).getPublicKey());
}
static ECPublicKey decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
int type = encoded[offset] & 0xFF;
byte[] keyBytes = new byte[32];
System.arraycopy(encoded, offset+1, keyBytes, 0, keyBytes.length);
if (type != Curve.DJB_TYPE) {
throw new InvalidKeyException("Bad key type: " + type);
}
return new DjbECPublicKey(keyBytes);
}
private static byte[] generatePrivateKey(boolean ephemeral) {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return generatePrivateKey(privateKey, ephemeral);
}
}

View File

@@ -0,0 +1,41 @@
/**
* 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.libaxolotl.ecc;
public class DjbECPrivateKey implements ECPrivateKey {
private final byte[] privateKey;
DjbECPrivateKey(byte[] privateKey) {
this.privateKey = privateKey;
}
@Override
public byte[] serialize() {
return privateKey;
}
@Override
public int getType() {
return Curve.DJB_TYPE;
}
public byte[] getPrivateKey() {
return privateKey;
}
}

View File

@@ -0,0 +1,66 @@
/**
* 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.libaxolotl.ecc;
import org.whispersystems.libaxolotl.util.ByteUtil;
import java.math.BigInteger;
import java.util.Arrays;
public class DjbECPublicKey implements ECPublicKey {
private final byte[] publicKey;
DjbECPublicKey(byte[] publicKey) {
this.publicKey = publicKey;
}
@Override
public byte[] serialize() {
byte[] type = {Curve.DJB_TYPE};
return ByteUtil.combine(type, publicKey);
}
@Override
public int getType() {
return Curve.DJB_TYPE;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof DjbECPublicKey)) return false;
DjbECPublicKey that = (DjbECPublicKey)other;
return Arrays.equals(this.publicKey, that.publicKey);
}
@Override
public int hashCode() {
return Arrays.hashCode(publicKey);
}
@Override
public int compareTo(ECPublicKey another) {
return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey));
}
public byte[] getPublicKey() {
return publicKey;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.libaxolotl.ecc;
public class ECKeyPair {
private final ECPublicKey publicKey;
private final ECPrivateKey privateKey;
public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public ECPublicKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -0,0 +1,23 @@
/**
* 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.libaxolotl.ecc;
public interface ECPrivateKey {
public byte[] serialize();
public int getType();
}

View File

@@ -0,0 +1,27 @@
/**
* 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.libaxolotl.ecc;
public interface ECPublicKey extends Comparable<ECPublicKey> {
public static final int KEY_SIZE = 33;
public byte[] serialize();
public int getType();
}

View File

@@ -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.libaxolotl.kdf;
import javax.crypto.spec.SecretKeySpec;
public class DerivedSecrets {
private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey;
public DerivedSecrets(SecretKeySpec cipherKey, SecretKeySpec macKey) {
this.cipherKey = cipherKey;
this.macKey = macKey;
}
public SecretKeySpec getCipherKey() {
return cipherKey;
}
public SecretKeySpec getMacKey() {
return macKey;
}
}

View File

@@ -0,0 +1,106 @@
/**
* 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.libaxolotl.kdf;
import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HKDF {
private static final int HASH_OUTPUT_SIZE = 32;
private static final int KEY_MATERIAL_SIZE = 64;
private static final int CIPHER_KEYS_OFFSET = 0;
private static final int MAC_KEYS_OFFSET = 32;
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] info) {
byte[] salt = new byte[HASH_OUTPUT_SIZE];
return deriveSecrets(inputKeyMaterial, salt, info);
}
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info) {
byte[] prk = extract(salt, inputKeyMaterial);
byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE);
SecretKeySpec cipherKey = deriveCipherKey(okm);
SecretKeySpec macKey = deriveMacKey(okm);
return new DerivedSecrets(cipherKey, macKey);
}
private SecretKeySpec deriveCipherKey(byte[] okm) {
byte[] cipherKey = new byte[32];
System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length);
return new SecretKeySpec(cipherKey, "AES");
}
private SecretKeySpec deriveMacKey(byte[] okm) {
byte[] macKey = new byte[32];
System.arraycopy(okm, MAC_KEYS_OFFSET, macKey, 0, macKey.length);
return new SecretKeySpec(macKey, "HmacSHA256");
}
private byte[] extract(byte[] salt, byte[] inputKeyMaterial) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(salt, "HmacSHA256"));
return mac.doFinal(inputKeyMaterial);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private byte[] expand(byte[] prk, byte[] info, int outputSize) {
try {
int iterations = (int)Math.ceil((double)outputSize/(double)HASH_OUTPUT_SIZE);
byte[] mixin = new byte[0];
ByteArrayOutputStream results = new ByteArrayOutputStream();
for (int i=0;i<iterations;i++) {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
mac.update(mixin);
if (info != null) {
mac.update(info);
}
mac.update((byte)i);
byte[] stepResult = mac.doFinal();
results.write(stepResult, 0, stepResult.length);
mixin = stepResult;
}
return results.toByteArray();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

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

View File

@@ -0,0 +1,131 @@
/**
* 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;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.util.ByteUtil;
public class PreKeyWhisperMessage implements CiphertextMessage {
private final int version;
private final int registrationId;
private final int preKeyId;
private final ECPublicKey baseKey;
private final IdentityKey identityKey;
private final WhisperMessage message;
private final byte[] serialized;
public PreKeyWhisperMessage(byte[] serialized)
throws InvalidMessageException, InvalidVersionException
{
try {
this.version = ByteUtil.highBitsToInt(serialized[0]);
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
}
WhisperProtos.PreKeyWhisperMessage preKeyWhisperMessage
= WhisperProtos.PreKeyWhisperMessage.parseFrom(ByteString.copyFrom(serialized, 1,
serialized.length-1));
if (!preKeyWhisperMessage.hasPreKeyId() ||
!preKeyWhisperMessage.hasBaseKey() ||
!preKeyWhisperMessage.hasIdentityKey() ||
!preKeyWhisperMessage.hasMessage())
{
throw new InvalidMessageException("Incomplete message.");
}
this.serialized = serialized;
this.registrationId = preKeyWhisperMessage.getRegistrationId();
this.preKeyId = preKeyWhisperMessage.getPreKeyId();
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
this.message = new WhisperMessage(preKeyWhisperMessage.getMessage().toByteArray());
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
} catch (LegacyMessageException e) {
throw new InvalidMessageException(e);
}
}
public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey,
IdentityKey identityKey, WhisperMessage message)
{
this.version = CiphertextMessage.CURRENT_VERSION;
this.registrationId = registrationId;
this.preKeyId = preKeyId;
this.baseKey = baseKey;
this.identityKey = identityKey;
this.message = message;
byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, this.version)};
byte[] messageBytes = WhisperProtos.PreKeyWhisperMessage.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.setMessage(ByteString.copyFrom(message.serialize()))
.setRegistrationId(registrationId)
.build().toByteArray();
this.serialized = ByteUtil.combine(versionBytes, messageBytes);
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public int getRegistrationId() {
return registrationId;
}
public int getPreKeyId() {
return preKeyId;
}
public ECPublicKey getBaseKey() {
return baseKey;
}
public WhisperMessage getWhisperMessage() {
return message;
}
@Override
public byte[] serialize() {
return serialized;
}
@Override
public int getType() {
return CiphertextMessage.PREKEY_TYPE;
}
}

View File

@@ -0,0 +1,156 @@
/**
* 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;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.util.ByteUtil;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class WhisperMessage implements CiphertextMessage {
private static final int MAC_LENGTH = 8;
private final ECPublicKey senderEphemeral;
private final int counter;
private final int previousCounter;
private final byte[] ciphertext;
private final byte[] serialized;
public WhisperMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
try {
byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
byte version = messageParts[0][0];
byte[] message = messageParts[1];
byte[] mac = messageParts[2];
if (ByteUtil.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version));
}
if (ByteUtil.highBitsToInt(version) != CURRENT_VERSION) {
throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version));
}
WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
if (!whisperMessage.hasCiphertext() ||
!whisperMessage.hasCounter() ||
!whisperMessage.hasEphemeralKey())
{
throw new InvalidMessageException("Incomplete message.");
}
this.serialized = serialized;
this.senderEphemeral = Curve.decodePoint(whisperMessage.getEphemeralKey().toByteArray(), 0);
this.counter = whisperMessage.getCounter();
this.previousCounter = whisperMessage.getPreviousCounter();
this.ciphertext = whisperMessage.getCiphertext().toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
} catch (ParseException e) {
throw new InvalidMessageException(e);
}
}
public WhisperMessage(SecretKeySpec macKey, ECPublicKey senderEphemeral,
int counter, int previousCounter, byte[] ciphertext)
{
byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
byte[] message = WhisperProtos.WhisperMessage.newBuilder()
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
.setCounter(counter)
.setPreviousCounter(previousCounter)
.setCiphertext(ByteString.copyFrom(ciphertext))
.build().toByteArray();
byte[] mac = getMac(macKey, ByteUtil.combine(version, message));
this.serialized = ByteUtil.combine(version, message, mac);
this.senderEphemeral = senderEphemeral;
this.counter = counter;
this.previousCounter = previousCounter;
this.ciphertext = ciphertext;
}
public ECPublicKey getSenderEphemeral() {
return senderEphemeral;
}
public int getCounter() {
return counter;
}
public byte[] getBody() {
return ciphertext;
}
public void verifyMac(SecretKeySpec macKey)
throws InvalidMessageException
{
byte[][] parts = ByteUtil.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH);
byte[] ourMac = getMac(macKey, parts[0]);
byte[] theirMac = parts[1];
if (!MessageDigest.isEqual(ourMac, theirMac)) {
throw new InvalidMessageException("Bad Mac!");
}
}
private byte[] getMac(SecretKeySpec macKey, byte[] serialized) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
byte[] fullMac = mac.doFinal(serialized);
return ByteUtil.trim(fullMac, MAC_LENGTH);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (java.security.InvalidKeyException e) {
throw new AssertionError(e);
}
}
@Override
public byte[] serialize() {
return serialized;
}
@Override
public int getType() {
return CiphertextMessage.WHISPER_TYPE;
}
public static boolean isLegacy(byte[] message) {
return message != null && message.length >= 1 &&
ByteUtil.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION;
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.kdf.DerivedSecrets;
import org.whispersystems.libaxolotl.kdf.HKDF;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class ChainKey {
private static final byte[] MESSAGE_KEY_SEED = {0x01};
private static final byte[] CHAIN_KEY_SEED = {0x02};
private final byte[] key;
private final int index;
public ChainKey(byte[] key, int index) {
this.key = key;
this.index = index;
}
public byte[] getKey() {
return key;
}
public int getIndex() {
return index;
}
public ChainKey getNextChainKey() {
byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED);
return new ChainKey(nextKey, index + 1);
}
public MessageKeys getMessageKeys() {
HKDF kdf = new HKDF();
byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED);
DerivedSecrets keyMaterial = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes());
return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index);
}
private byte[] getBaseMaterial(byte[] seed) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(seed);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

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

View File

@@ -0,0 +1,139 @@
/**
* 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 android.util.Log;
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.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.Hex;
import org.whispersystems.libaxolotl.util.Pair;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class RatchetingSession {
public static void initializeSession(SessionState sessionState,
ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
} else {
initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
ourEphemeralKey, ourIdentityKey, theirIdentityKey);
}
}
private static void initializeSessionAsAlice(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first().createChain(theirEphemeralKey, sendingKey);
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second());
sessionState.setSenderChain(sendingKey, sendingChain.second());
sessionState.setRootKey(sendingChain.first());
}
private static void initializeSessionAsBob(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
ourIdentityKey, theirIdentityKey);
sessionState.setSenderChain(ourEphemeralKey, sendingChain.second());
sessionState.setRootKey(sendingChain.first());
}
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,
ECKeyPair ourEphemeral, ECPublicKey theirEphemeral,
IdentityKeyPair ourIdentity, IdentityKey theirIdentity)
throws InvalidKeyException
{
try {
ByteArrayOutputStream secrets = new ByteArrayOutputStream();
if (isAlice) {
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
} else {
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
}
secrets.write(Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()));
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(secrets.toByteArray(),
"WhisperText".getBytes());
return new Pair<RootKey, ChainKey>(new RootKey(derivedSecrets.getCipherKey().getEncoded()),
new ChainKey(derivedSecrets.getMacKey().getEncoded(), 0));
} catch (IOException e) {
throw new AssertionError(e);
}
}
private static boolean isAlice(ECPublicKey ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey ourEphemeralKey, ECPublicKey theirEphemeralKey)
{
if (ourEphemeralKey.equals(ourBaseKey)) {
return false;
}
if (theirEphemeralKey.equals(theirBaseKey)) {
return true;
}
return isLowEnd(ourBaseKey, theirBaseKey);
}
private static boolean isLowEnd(ECPublicKey ourKey, ECPublicKey theirKey) {
return ourKey.compareTo(theirKey) < 0;
}
}

View File

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

View File

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

View File

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

View File

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