mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-01 14:13:22 +01:00
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:
@@ -0,0 +1,321 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class InMemorySessionState implements SessionState {
|
||||
|
||||
private Map<ECPublicKey, InMemoryChain> receiverChains = new HashMap<>();
|
||||
|
||||
private boolean needsRefresh;
|
||||
private int sessionVersion;
|
||||
private IdentityKey remoteIdentityKey;
|
||||
private IdentityKey localIdentityKey;
|
||||
private int previousCounter;
|
||||
private RootKey rootKey;
|
||||
private ECKeyPair senderEphemeral;
|
||||
private ChainKey senderChainKey;
|
||||
private int pendingPreKeyid;
|
||||
private ECPublicKey pendingPreKey;
|
||||
private int remoteRegistrationId;
|
||||
private int localRegistrationId;
|
||||
|
||||
public InMemorySessionState() {}
|
||||
|
||||
public InMemorySessionState(SessionState sessionState) {
|
||||
try {
|
||||
this.needsRefresh = sessionState.getNeedsRefresh();
|
||||
this.sessionVersion = sessionState.getSessionVersion();
|
||||
this.remoteIdentityKey = new IdentityKey(sessionState.getRemoteIdentityKey().serialize(), 0);
|
||||
this.localIdentityKey = new IdentityKey(sessionState.getLocalIdentityKey().serialize(), 0);
|
||||
this.previousCounter = sessionState.getPreviousCounter();
|
||||
this.rootKey = new RootKey(sessionState.getRootKey().getKeyBytes());
|
||||
this.senderEphemeral = sessionState.getSenderEphemeralPair();
|
||||
this.senderChainKey = new ChainKey(sessionState.getSenderChainKey().getKey(),
|
||||
sessionState.getSenderChainKey().getIndex());
|
||||
this.pendingPreKeyid = sessionState.getPendingPreKey().first();
|
||||
this.pendingPreKey = sessionState.getPendingPreKey().second();
|
||||
this.remoteRegistrationId = sessionState.getRemoteRegistrationId();
|
||||
this.localRegistrationId = sessionState.getLocalRegistrationId();
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeedsRefresh(boolean needsRefresh) {
|
||||
this.needsRefresh = needsRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNeedsRefresh() {
|
||||
return needsRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionVersion(int version) {
|
||||
this.sessionVersion = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionVersion() {
|
||||
return sessionVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoteIdentityKey(IdentityKey identityKey) {
|
||||
this.remoteIdentityKey = identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalIdentityKey(IdentityKey identityKey) {
|
||||
this.localIdentityKey = identityKey;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getRemoteIdentityKey() {
|
||||
return remoteIdentityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getLocalIdentityKey() {
|
||||
return localIdentityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousCounter() {
|
||||
return previousCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreviousCounter(int previousCounter) {
|
||||
this.previousCounter = previousCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootKey getRootKey() {
|
||||
return rootKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootKey(RootKey rootKey) {
|
||||
this.rootKey = rootKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECPublicKey getSenderEphemeral() {
|
||||
return senderEphemeral.getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getSenderEphemeralPair() {
|
||||
return senderEphemeral;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
|
||||
return receiverChains.containsKey(senderEphemeral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSenderChain() {
|
||||
return senderChainKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
return new ChainKey(chain.chainKey, chain.index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
|
||||
InMemoryChain chain = new InMemoryChain();
|
||||
chain.chainKey = chainKey.getKey();
|
||||
chain.index = chainKey.getIndex();
|
||||
|
||||
receiverChains.put(senderEphemeral, chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
|
||||
this.senderEphemeral = senderEphemeralPair;
|
||||
this.senderChainKey = chainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChainKey getSenderChainKey() {
|
||||
return senderChainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSenderChainKey(ChainKey nextChainKey) {
|
||||
this.senderChainKey = nextChainKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
|
||||
if (chain == null) return false;
|
||||
|
||||
for (InMemoryChain.InMemoryMessageKey messageKey : chain.messageKeys) {
|
||||
if (messageKey.index == counter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
MessageKeys results = null;
|
||||
|
||||
if (chain == null) return null;
|
||||
|
||||
Iterator<InMemoryChain.InMemoryMessageKey> iterator = chain.messageKeys.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
InMemoryChain.InMemoryMessageKey messageKey = iterator.next();
|
||||
|
||||
if (messageKey.index == counter) {
|
||||
results = new MessageKeys(new SecretKeySpec(messageKey.cipherKey, "AES"),
|
||||
new SecretKeySpec(messageKey.macKey, "HmacSHA256"),
|
||||
messageKey.index);
|
||||
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
InMemoryChain.InMemoryMessageKey key = new InMemoryChain.InMemoryMessageKey();
|
||||
|
||||
key.cipherKey = messageKeys.getCipherKey().getEncoded();
|
||||
key.macKey = messageKeys.getMacKey().getEncoded();
|
||||
key.index = messageKeys.getCounter();
|
||||
|
||||
chain.messageKeys.add(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
|
||||
InMemoryChain chain = receiverChains.get(senderEphemeral);
|
||||
chain.chainKey = chainKey.getKey();
|
||||
chain.index = chainKey.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingKeyExchange(int sequence, ECKeyPair ourBaseKey, ECKeyPair ourEphemeralKey, IdentityKeyPair ourIdentityKey) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingKeyExchangeSequence() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPendingKeyExchange() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
|
||||
this.pendingPreKeyid = preKeyId;
|
||||
this.pendingPreKey = baseKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPendingPreKey() {
|
||||
return this.pendingPreKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Integer, ECPublicKey> getPendingPreKey() {
|
||||
return new Pair<>(pendingPreKeyid, pendingPreKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPendingPreKey() {
|
||||
this.pendingPreKey = null;
|
||||
this.pendingPreKeyid = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoteRegistrationId(int registrationId) {
|
||||
this.remoteRegistrationId = registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteRegistrationId() {
|
||||
return remoteRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocalRegistrationId(int registrationId) {
|
||||
this.localRegistrationId = registrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return localRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static class InMemoryChain {
|
||||
byte[] chainKey;
|
||||
int index;
|
||||
List<InMemoryMessageKey> messageKeys = new LinkedList<>();
|
||||
|
||||
public static class InMemoryMessageKey {
|
||||
public InMemoryMessageKey(){}
|
||||
int index;
|
||||
byte[] cipherKey;
|
||||
byte[] macKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.SessionStore;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class InMemorySessionStore implements SessionStore {
|
||||
|
||||
private SessionState currentSessionState;
|
||||
private List<SessionState> previousSessionStates;
|
||||
|
||||
private SessionState checkedOutSessionState;
|
||||
private List<SessionState> checkedOutPreviousSessionStates;
|
||||
|
||||
public InMemorySessionStore(SessionState sessionState) {
|
||||
this.currentSessionState = sessionState;
|
||||
this.previousSessionStates = new LinkedList<>();
|
||||
this.checkedOutPreviousSessionStates = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionState getSessionState() {
|
||||
checkedOutSessionState = new InMemorySessionState(currentSessionState);
|
||||
return checkedOutSessionState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SessionState> getPreviousSessionStates() {
|
||||
checkedOutPreviousSessionStates = new LinkedList<>();
|
||||
for (SessionState state : previousSessionStates) {
|
||||
checkedOutPreviousSessionStates.add(new InMemorySessionState(state));
|
||||
}
|
||||
|
||||
return checkedOutPreviousSessionStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
this.currentSessionState = this.checkedOutSessionState;
|
||||
this.previousSessionStates = this.checkedOutPreviousSessionStates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.SessionCipher;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.SessionStore;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SessionCipherTest extends AndroidTestCase {
|
||||
|
||||
public void testBasicSession()
|
||||
throws InvalidKeyException, DuplicateMessageException,
|
||||
LegacyMessageException, InvalidMessageException
|
||||
{
|
||||
SessionState aliceSessionState = new InMemorySessionState();
|
||||
SessionState bobSessionState = new InMemorySessionState();
|
||||
|
||||
initializeSessions(aliceSessionState, bobSessionState);
|
||||
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore(aliceSessionState);
|
||||
SessionStore bobSessionStore = new InMemorySessionStore(bobSessionState);
|
||||
|
||||
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore);
|
||||
SessionCipher bobCipher = new SessionCipher(bobSessionStore);
|
||||
|
||||
byte[] alicePlaintext = "This is a plaintext message.".getBytes();
|
||||
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);
|
||||
byte[] bobPlaintext = bobCipher.decrypt(message.serialize());
|
||||
|
||||
assertTrue(Arrays.equals(alicePlaintext, bobPlaintext));
|
||||
|
||||
byte[] bobReply = "This is a message from Bob.".getBytes();
|
||||
CiphertextMessage reply = bobCipher.encrypt(bobReply);
|
||||
byte[] receivedReply = aliceCipher.decrypt(reply.serialize());
|
||||
|
||||
assertTrue(Arrays.equals(bobReply, receivedReply));
|
||||
}
|
||||
|
||||
|
||||
private void initializeSessions(SessionState aliceSessionState, SessionState bobSessionState)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
ECKeyPair aliceIdentityKeyPair = Curve.generateKeyPair(false);
|
||||
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(new IdentityKey(aliceIdentityKeyPair.getPublicKey()),
|
||||
aliceIdentityKeyPair.getPrivateKey());
|
||||
ECKeyPair aliceBaseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair aliceEphemeralKey = Curve.generateKeyPair(true);
|
||||
|
||||
ECKeyPair bobIdentityKeyPair = Curve.generateKeyPair(false);
|
||||
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(new IdentityKey(bobIdentityKeyPair.getPublicKey()),
|
||||
bobIdentityKeyPair.getPrivateKey());
|
||||
ECKeyPair bobBaseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair bobEphemeralKey = bobBaseKey;
|
||||
|
||||
|
||||
RatchetingSession.initializeSession(aliceSessionState, aliceBaseKey, bobBaseKey.getPublicKey(),
|
||||
aliceEphemeralKey, bobEphemeralKey.getPublicKey(),
|
||||
aliceIdentityKey, bobIdentityKey.getPublicKey());
|
||||
|
||||
RatchetingSession.initializeSession(bobSessionState, bobBaseKey, aliceBaseKey.getPublicKey(),
|
||||
bobEphemeralKey, aliceEphemeralKey.getPublicKey(),
|
||||
bobIdentityKey, aliceIdentityKey.getPublicKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.whispersystems.test.ecc;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class Curve25519Test extends AndroidTestCase {
|
||||
|
||||
public void testAgreement() throws InvalidKeyException {
|
||||
|
||||
byte[] alicePublic = {(byte) 0x05, (byte) 0x1b, (byte) 0xb7, (byte) 0x59, (byte) 0x66,
|
||||
(byte) 0xf2, (byte) 0xe9, (byte) 0x3a, (byte) 0x36, (byte) 0x91,
|
||||
(byte) 0xdf, (byte) 0xff, (byte) 0x94, (byte) 0x2b, (byte) 0xb2,
|
||||
(byte) 0xa4, (byte) 0x66, (byte) 0xa1, (byte) 0xc0, (byte) 0x8b,
|
||||
(byte) 0x8d, (byte) 0x78, (byte) 0xca, (byte) 0x3f, (byte) 0x4d,
|
||||
(byte) 0x6d, (byte) 0xf8, (byte) 0xb8, (byte) 0xbf, (byte) 0xa2,
|
||||
(byte) 0xe4, (byte) 0xee, (byte) 0x28};
|
||||
|
||||
byte[] alicePrivate = {(byte) 0xc8, (byte) 0x06, (byte) 0x43, (byte) 0x9d, (byte) 0xc9,
|
||||
(byte) 0xd2, (byte) 0xc4, (byte) 0x76, (byte) 0xff, (byte) 0xed,
|
||||
(byte) 0x8f, (byte) 0x25, (byte) 0x80, (byte) 0xc0, (byte) 0x88,
|
||||
(byte) 0x8d, (byte) 0x58, (byte) 0xab, (byte) 0x40, (byte) 0x6b,
|
||||
(byte) 0xf7, (byte) 0xae, (byte) 0x36, (byte) 0x98, (byte) 0x87,
|
||||
(byte) 0x90, (byte) 0x21, (byte) 0xb9, (byte) 0x6b, (byte) 0xb4,
|
||||
(byte) 0xbf, (byte) 0x59};
|
||||
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x65, (byte) 0x36, (byte) 0x14, (byte) 0x99,
|
||||
(byte) 0x3d, (byte) 0x2b, (byte) 0x15, (byte) 0xee, (byte) 0x9e,
|
||||
(byte) 0x5f, (byte) 0xd3, (byte) 0xd8, (byte) 0x6c, (byte) 0xe7,
|
||||
(byte) 0x19, (byte) 0xef, (byte) 0x4e, (byte) 0xc1, (byte) 0xda,
|
||||
(byte) 0xae, (byte) 0x18, (byte) 0x86, (byte) 0xa8, (byte) 0x7b,
|
||||
(byte) 0x3f, (byte) 0x5f, (byte) 0xa9, (byte) 0x56, (byte) 0x5a,
|
||||
(byte) 0x27, (byte) 0xa2, (byte) 0x2f};
|
||||
|
||||
byte[] bobPrivate = {(byte) 0xb0, (byte) 0x3b, (byte) 0x34, (byte) 0xc3, (byte) 0x3a,
|
||||
(byte) 0x1c, (byte) 0x44, (byte) 0xf2, (byte) 0x25, (byte) 0xb6,
|
||||
(byte) 0x62, (byte) 0xd2, (byte) 0xbf, (byte) 0x48, (byte) 0x59,
|
||||
(byte) 0xb8, (byte) 0x13, (byte) 0x54, (byte) 0x11, (byte) 0xfa,
|
||||
(byte) 0x7b, (byte) 0x03, (byte) 0x86, (byte) 0xd4, (byte) 0x5f,
|
||||
(byte) 0xb7, (byte) 0x5d, (byte) 0xc5, (byte) 0xb9, (byte) 0x1b,
|
||||
(byte) 0x44, (byte) 0x66};
|
||||
|
||||
byte[] shared = {(byte) 0x32, (byte) 0x5f, (byte) 0x23, (byte) 0x93, (byte) 0x28,
|
||||
(byte) 0x94, (byte) 0x1c, (byte) 0xed, (byte) 0x6e, (byte) 0x67,
|
||||
(byte) 0x3b, (byte) 0x86, (byte) 0xba, (byte) 0x41, (byte) 0x01,
|
||||
(byte) 0x74, (byte) 0x48, (byte) 0xe9, (byte) 0x9b, (byte) 0x64,
|
||||
(byte) 0x9a, (byte) 0x9c, (byte) 0x38, (byte) 0x06, (byte) 0xc1,
|
||||
(byte) 0xdd, (byte) 0x7c, (byte) 0xa4, (byte) 0xc4, (byte) 0x77,
|
||||
(byte) 0xe6, (byte) 0x29};
|
||||
|
||||
ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0);
|
||||
ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
|
||||
|
||||
ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPrivateKey bobPrivateKey = Curve.decodePrivatePoint(bobPrivate);
|
||||
|
||||
byte[] sharedOne = Curve.calculateAgreement(alicePublicKey, bobPrivateKey);
|
||||
byte[] sharedTwo = Curve.calculateAgreement(bobPublicKey, alicePrivateKey);
|
||||
|
||||
assertTrue(Arrays.equals(sharedOne, shared));
|
||||
assertTrue(Arrays.equals(sharedTwo, shared));
|
||||
}
|
||||
|
||||
public void testRandomAgreements() throws InvalidKeyException {
|
||||
for (int i=0;i<50;i++) {
|
||||
ECKeyPair alice = Curve.generateKeyPair(false);
|
||||
ECKeyPair bob = Curve.generateKeyPair(false);
|
||||
|
||||
byte[] sharedAlice = Curve.calculateAgreement(bob.getPublicKey(), alice.getPrivateKey());
|
||||
byte[] sharedBob = Curve.calculateAgreement(alice.getPublicKey(), bob.getPrivateKey());
|
||||
|
||||
assertTrue(Arrays.equals(sharedAlice, sharedBob));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.whispersystems.test.kdf;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.kdf.DerivedSecrets;
|
||||
import org.whispersystems.libaxolotl.kdf.HKDF;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HKDFTest extends AndroidTestCase {
|
||||
|
||||
public void testVector() {
|
||||
byte[] ikm = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
|
||||
0x0b, 0x0b};
|
||||
|
||||
byte[] salt = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x0c};
|
||||
|
||||
byte[] info = {(byte)0xf0, (byte)0xf1, (byte)0xf2, (byte)0xf3, (byte)0xf4,
|
||||
(byte)0xf5, (byte)0xf6, (byte)0xf7, (byte)0xf8, (byte)0xf9};
|
||||
|
||||
byte[] expectedOutputOne = {(byte)0x6e, (byte)0xc2, (byte)0x55, (byte)0x6d, (byte)0x5d,
|
||||
(byte)0x7b, (byte)0x1d, (byte)0x81, (byte)0xde, (byte)0xe4,
|
||||
(byte)0x22, (byte)0x2a, (byte)0xd7, (byte)0x48, (byte)0x36,
|
||||
(byte)0x95, (byte)0xdd, (byte)0xc9, (byte)0x8f, (byte)0x4f,
|
||||
(byte)0x5f, (byte)0xab, (byte)0xc0, (byte)0xe0, (byte)0x20,
|
||||
(byte)0x5d, (byte)0xc2, (byte)0xef, (byte)0x87, (byte)0x52,
|
||||
(byte)0xd4, (byte)0x1e};
|
||||
|
||||
byte[] expectedOutputTwo = {(byte)0x04, (byte)0xe2, (byte)0xe2, (byte)0x11, (byte)0x01,
|
||||
(byte)0xc6, (byte)0x8f, (byte)0xf0, (byte)0x93, (byte)0x94,
|
||||
(byte)0xb8, (byte)0xad, (byte)0x0b, (byte)0xdc, (byte)0xb9,
|
||||
(byte)0x60, (byte)0x9c, (byte)0xd4, (byte)0xee, (byte)0x82,
|
||||
(byte)0xac, (byte)0x13, (byte)0x19, (byte)0x9b, (byte)0x4a,
|
||||
(byte)0xa9, (byte)0xfd, (byte)0xa8, (byte)0x99, (byte)0xda,
|
||||
(byte)0xeb, (byte)0xec};
|
||||
|
||||
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(ikm, salt, info);
|
||||
|
||||
assertTrue(Arrays.equals(derivedSecrets.getCipherKey().getEncoded(), expectedOutputOne));
|
||||
assertTrue(Arrays.equals(derivedSecrets.getMacKey().getEncoded(), expectedOutputTwo));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.whispersystems.test.ratchet;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChainKeyTest extends AndroidTestCase {
|
||||
|
||||
public void testChainKeyDerivation() throws NoSuchAlgorithmException {
|
||||
|
||||
byte[] seed = {(byte) 0x8a, (byte) 0xb7, (byte) 0x2d, (byte) 0x6f, (byte) 0x4c,
|
||||
(byte) 0xc5, (byte) 0xac, (byte) 0x0d, (byte) 0x38, (byte) 0x7e,
|
||||
(byte) 0xaf, (byte) 0x46, (byte) 0x33, (byte) 0x78, (byte) 0xdd,
|
||||
(byte) 0xb2, (byte) 0x8e, (byte) 0xdd, (byte) 0x07, (byte) 0x38,
|
||||
(byte) 0x5b, (byte) 0x1c, (byte) 0xb0, (byte) 0x12, (byte) 0x50,
|
||||
(byte) 0xc7, (byte) 0x15, (byte) 0x98, (byte) 0x2e, (byte) 0x7a,
|
||||
(byte) 0xd4, (byte) 0x8f};
|
||||
|
||||
byte[] messageKey = {(byte) 0x02, (byte) 0xa9, (byte) 0xaa, (byte) 0x6c, (byte) 0x7d,
|
||||
(byte) 0xbd, (byte) 0x64, (byte) 0xf9, (byte) 0xd3, (byte) 0xaa,
|
||||
(byte) 0x92, (byte) 0xf9, (byte) 0x2a, (byte) 0x27, (byte) 0x7b,
|
||||
(byte) 0xf5, (byte) 0x46, (byte) 0x09, (byte) 0xda, (byte) 0xdf,
|
||||
(byte) 0x0b, (byte) 0x00, (byte) 0x82, (byte) 0x8a, (byte) 0xcf,
|
||||
(byte) 0xc6, (byte) 0x1e, (byte) 0x3c, (byte) 0x72, (byte) 0x4b,
|
||||
(byte) 0x84, (byte) 0xa7};
|
||||
|
||||
byte[] macKey = {(byte) 0xbf, (byte) 0xbe, (byte) 0x5e, (byte) 0xfb, (byte) 0x60,
|
||||
(byte) 0x30, (byte) 0x30, (byte) 0x52, (byte) 0x67, (byte) 0x42,
|
||||
(byte) 0xe3, (byte) 0xee, (byte) 0x89, (byte) 0xc7, (byte) 0x02,
|
||||
(byte) 0x4e, (byte) 0x88, (byte) 0x4e, (byte) 0x44, (byte) 0x0f,
|
||||
(byte) 0x1f, (byte) 0xf3, (byte) 0x76, (byte) 0xbb, (byte) 0x23,
|
||||
(byte) 0x17, (byte) 0xb2, (byte) 0xd6, (byte) 0x4d, (byte) 0xeb,
|
||||
(byte) 0x7c, (byte) 0x83};
|
||||
|
||||
byte[] nextChainKey = {(byte) 0x28, (byte) 0xe8, (byte) 0xf8, (byte) 0xfe, (byte) 0xe5,
|
||||
(byte) 0x4b, (byte) 0x80, (byte) 0x1e, (byte) 0xef, (byte) 0x7c,
|
||||
(byte) 0x5c, (byte) 0xfb, (byte) 0x2f, (byte) 0x17, (byte) 0xf3,
|
||||
(byte) 0x2c, (byte) 0x7b, (byte) 0x33, (byte) 0x44, (byte) 0x85,
|
||||
(byte) 0xbb, (byte) 0xb7, (byte) 0x0f, (byte) 0xac, (byte) 0x6e,
|
||||
(byte) 0xc1, (byte) 0x03, (byte) 0x42, (byte) 0xa2, (byte) 0x46,
|
||||
(byte) 0xd1, (byte) 0x5d};
|
||||
|
||||
ChainKey chainKey = new ChainKey(seed, 0);
|
||||
|
||||
assertTrue(Arrays.equals(chainKey.getKey(), seed));
|
||||
assertTrue(Arrays.equals(chainKey.getMessageKeys().getCipherKey().getEncoded(), messageKey));
|
||||
assertTrue(Arrays.equals(chainKey.getMessageKeys().getMacKey().getEncoded(), macKey));
|
||||
assertTrue(Arrays.equals(chainKey.getNextChainKey().getKey(), nextChainKey));
|
||||
assertTrue(chainKey.getIndex() == 0);
|
||||
assertTrue(chainKey.getMessageKeys().getCounter() == 0);
|
||||
assertTrue(chainKey.getNextChainKey().getIndex() == 1);
|
||||
assertTrue(chainKey.getNextChainKey().getMessageKeys().getCounter() == 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package org.whispersystems.test.ratchet;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
import org.whispersystems.test.InMemorySessionState;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.SessionState;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RatchetingSessionTest extends AndroidTestCase {
|
||||
|
||||
public void testRatchetingSessionAsBob() throws InvalidKeyException {
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97,
|
||||
(byte) 0x76, (byte) 0xb8, (byte) 0x77, (byte) 0x02,
|
||||
(byte) 0x05, (byte) 0x74, (byte) 0x5a, (byte) 0x3a,
|
||||
(byte) 0x6e, (byte) 0x24, (byte) 0xf5, (byte) 0x79,
|
||||
(byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
|
||||
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05,
|
||||
(byte) 0x92, (byte) 0x8e, (byte) 0xbb, (byte) 0xad,
|
||||
(byte) 0xc9, (byte) 0xc0, (byte) 0x5a, (byte) 0xd4,
|
||||
(byte) 0x58};
|
||||
|
||||
byte[] bobPrivate = {(byte) 0xa1, (byte) 0xca, (byte) 0xb4, (byte) 0x8f,
|
||||
(byte) 0x7c, (byte) 0x89, (byte) 0x3f, (byte) 0xaf,
|
||||
(byte) 0xa9, (byte) 0x88, (byte) 0x0a, (byte) 0x28,
|
||||
(byte) 0xc3, (byte) 0xb4, (byte) 0x99, (byte) 0x9d,
|
||||
(byte) 0x28, (byte) 0xd6, (byte) 0x32, (byte) 0x95,
|
||||
(byte) 0x62, (byte) 0xd2, (byte) 0x7a, (byte) 0x4e,
|
||||
(byte) 0xa4, (byte) 0xe2, (byte) 0x2e, (byte) 0x9f,
|
||||
(byte) 0xf1, (byte) 0xbd, (byte) 0xd6, (byte) 0x5a};
|
||||
|
||||
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38,
|
||||
(byte) 0x74, (byte) 0xf6, (byte) 0x96, (byte) 0x69,
|
||||
(byte) 0x56, (byte) 0xc2, (byte) 0xdd, (byte) 0x47,
|
||||
(byte) 0x3f, (byte) 0x8f, (byte) 0xa1, (byte) 0x5a,
|
||||
(byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
|
||||
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34,
|
||||
(byte) 0x16, (byte) 0x92, (byte) 0x32, (byte) 0x4c,
|
||||
(byte) 0xef, (byte) 0xb1, (byte) 0xc5, (byte) 0xe6,
|
||||
(byte) 0x26};
|
||||
|
||||
byte[] bobIdentityPrivate = {(byte) 0x48, (byte) 0x75, (byte) 0xcc, (byte) 0x69,
|
||||
(byte) 0xdd, (byte) 0xf8, (byte) 0xea, (byte) 0x07,
|
||||
(byte) 0x19, (byte) 0xec, (byte) 0x94, (byte) 0x7d,
|
||||
(byte) 0x61, (byte) 0x08, (byte) 0x11, (byte) 0x35,
|
||||
(byte) 0x86, (byte) 0x8d, (byte) 0x5f, (byte) 0xd8,
|
||||
(byte) 0x01, (byte) 0xf0, (byte) 0x2c, (byte) 0x02,
|
||||
(byte) 0x25, (byte) 0xe5, (byte) 0x16, (byte) 0xdf,
|
||||
(byte) 0x21, (byte) 0x56, (byte) 0x60, (byte) 0x5e};
|
||||
|
||||
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f,
|
||||
(byte) 0xb1, (byte) 0xa9, (byte) 0x86, (byte) 0x2c,
|
||||
(byte) 0x3a, (byte) 0xf6, (byte) 0xbe, (byte) 0xac,
|
||||
(byte) 0xa8, (byte) 0x92, (byte) 0x02, (byte) 0x77,
|
||||
(byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
|
||||
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7,
|
||||
(byte) 0xc9, (byte) 0x06, (byte) 0xae, (byte) 0xb3,
|
||||
(byte) 0x5e, (byte) 0x03, (byte) 0xcf, (byte) 0x89,
|
||||
(byte) 0x50};
|
||||
|
||||
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d,
|
||||
(byte) 0x1f, (byte) 0x52, (byte) 0x02, (byte) 0x83,
|
||||
(byte) 0xef, (byte) 0xcc, (byte) 0x55, (byte) 0xfc,
|
||||
(byte) 0xa5, (byte) 0xe6, (byte) 0x70, (byte) 0x75,
|
||||
(byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
|
||||
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51,
|
||||
(byte) 0xaf, (byte) 0x76, (byte) 0xdf, (byte) 0x18,
|
||||
(byte) 0xc5, (byte) 0x1d, (byte) 0x29, (byte) 0xd3,
|
||||
(byte) 0x4b};
|
||||
|
||||
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45,
|
||||
(byte) 0x56, (byte) 0x60, (byte) 0xad, (byte) 0xa6,
|
||||
(byte) 0x5b, (byte) 0x40, (byte) 0x10, (byte) 0x07,
|
||||
(byte) 0xf6, (byte) 0x15, (byte) 0xe6, (byte) 0x54,
|
||||
(byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
|
||||
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6,
|
||||
(byte) 0x87, (byte) 0x51, (byte) 0x49, (byte) 0xbc,
|
||||
(byte) 0xee, (byte) 0xfc, (byte) 0xb4, (byte) 0x2b,
|
||||
(byte) 0x4a};
|
||||
|
||||
byte[] senderChain = {(byte)0xd2, (byte)0x2f, (byte)0xd5, (byte)0x6d, (byte)0x3f,
|
||||
(byte)0xec, (byte)0x81, (byte)0x9c, (byte)0xf4, (byte)0xc3,
|
||||
(byte)0xd5, (byte)0x0c, (byte)0x56, (byte)0xed, (byte)0xfb,
|
||||
(byte)0x1c, (byte)0x28, (byte)0x0a, (byte)0x1b, (byte)0x31,
|
||||
(byte)0x96, (byte)0x45, (byte)0x37, (byte)0xf1, (byte)0xd1,
|
||||
(byte)0x61, (byte)0xe1, (byte)0xc9, (byte)0x31, (byte)0x48,
|
||||
(byte)0xe3, (byte)0x6b};
|
||||
|
||||
IdentityKey bobIdentityKeyPublic = new IdentityKey(bobIdentityPublic, 0);
|
||||
ECPrivateKey bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate);
|
||||
IdentityKeyPair bobIdentityKey = new IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate);
|
||||
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPrivateKey bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate);
|
||||
ECKeyPair bobEphemeralKey = new ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey);
|
||||
ECKeyPair bobBaseKey = bobEphemeralKey;
|
||||
|
||||
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
|
||||
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
|
||||
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
|
||||
|
||||
SessionState session = new InMemorySessionState();
|
||||
|
||||
RatchetingSession.initializeSession(session, bobBaseKey, aliceBasePublicKey,
|
||||
bobEphemeralKey, aliceEphemeralPublicKey,
|
||||
bobIdentityKey, aliceIdentityPublicKey);
|
||||
|
||||
assertTrue(session.getLocalIdentityKey().equals(bobIdentityKey.getPublicKey()));
|
||||
assertTrue(session.getRemoteIdentityKey().equals(aliceIdentityPublicKey));
|
||||
assertTrue(Arrays.equals(session.getSenderChainKey().getKey(), senderChain));
|
||||
}
|
||||
|
||||
public void testRatchetingSessionAsAlice() throws InvalidKeyException {
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0x2c, (byte) 0xb4, (byte) 0x97, (byte) 0x76,
|
||||
(byte) 0xb8, (byte) 0x77, (byte) 0x02, (byte) 0x05, (byte) 0x74,
|
||||
(byte) 0x5a, (byte) 0x3a, (byte) 0x6e, (byte) 0x24, (byte) 0xf5,
|
||||
(byte) 0x79, (byte) 0xcd, (byte) 0xb4, (byte) 0xba, (byte) 0x7a,
|
||||
(byte) 0x89, (byte) 0x04, (byte) 0x10, (byte) 0x05, (byte) 0x92,
|
||||
(byte) 0x8e, (byte) 0xbb, (byte) 0xad, (byte) 0xc9, (byte) 0xc0,
|
||||
(byte) 0x5a, (byte) 0xd4, (byte) 0x58};
|
||||
|
||||
byte[] bobIdentityPublic = {(byte) 0x05, (byte) 0xf1, (byte) 0xf4, (byte) 0x38, (byte) 0x74,
|
||||
(byte) 0xf6, (byte) 0x96, (byte) 0x69, (byte) 0x56, (byte) 0xc2,
|
||||
(byte) 0xdd, (byte) 0x47, (byte) 0x3f, (byte) 0x8f, (byte) 0xa1,
|
||||
(byte) 0x5a, (byte) 0xde, (byte) 0xb7, (byte) 0x1d, (byte) 0x1c,
|
||||
(byte) 0xb9, (byte) 0x91, (byte) 0xb2, (byte) 0x34, (byte) 0x16,
|
||||
(byte) 0x92, (byte) 0x32, (byte) 0x4c, (byte) 0xef, (byte) 0xb1,
|
||||
(byte) 0xc5, (byte) 0xe6, (byte) 0x26};
|
||||
|
||||
byte[] aliceBasePublic = {(byte) 0x05, (byte) 0x47, (byte) 0x2d, (byte) 0x1f, (byte) 0xb1,
|
||||
(byte) 0xa9, (byte) 0x86, (byte) 0x2c, (byte) 0x3a, (byte) 0xf6,
|
||||
(byte) 0xbe, (byte) 0xac, (byte) 0xa8, (byte) 0x92, (byte) 0x02,
|
||||
(byte) 0x77, (byte) 0xe2, (byte) 0xb2, (byte) 0x6f, (byte) 0x4a,
|
||||
(byte) 0x79, (byte) 0x21, (byte) 0x3e, (byte) 0xc7, (byte) 0xc9,
|
||||
(byte) 0x06, (byte) 0xae, (byte) 0xb3, (byte) 0x5e, (byte) 0x03,
|
||||
(byte) 0xcf, (byte) 0x89, (byte) 0x50};
|
||||
|
||||
byte[] aliceBasePrivate = {(byte) 0x11, (byte) 0xae, (byte) 0x7c, (byte) 0x64, (byte) 0xd1,
|
||||
(byte) 0xe6, (byte) 0x1c, (byte) 0xd5, (byte) 0x96, (byte) 0xb7,
|
||||
(byte) 0x6a, (byte) 0x0d, (byte) 0xb5, (byte) 0x01, (byte) 0x26,
|
||||
(byte) 0x73, (byte) 0x39, (byte) 0x1c, (byte) 0xae, (byte) 0x66,
|
||||
(byte) 0xed, (byte) 0xbf, (byte) 0xcf, (byte) 0x07, (byte) 0x3b,
|
||||
(byte) 0x4d, (byte) 0xa8, (byte) 0x05, (byte) 0x16, (byte) 0xa4,
|
||||
(byte) 0x74, (byte) 0x49};
|
||||
|
||||
byte[] aliceEphemeralPublic = {(byte) 0x05, (byte) 0x6c, (byte) 0x3e, (byte) 0x0d, (byte) 0x1f,
|
||||
(byte) 0x52, (byte) 0x02, (byte) 0x83, (byte) 0xef, (byte) 0xcc,
|
||||
(byte) 0x55, (byte) 0xfc, (byte) 0xa5, (byte) 0xe6, (byte) 0x70,
|
||||
(byte) 0x75, (byte) 0xb9, (byte) 0x04, (byte) 0x00, (byte) 0x7f,
|
||||
(byte) 0x18, (byte) 0x81, (byte) 0xd1, (byte) 0x51, (byte) 0xaf,
|
||||
(byte) 0x76, (byte) 0xdf, (byte) 0x18, (byte) 0xc5, (byte) 0x1d,
|
||||
(byte) 0x29, (byte) 0xd3, (byte) 0x4b};
|
||||
|
||||
byte[] aliceEphemeralPrivate = {(byte) 0xd1, (byte) 0xba, (byte) 0x38, (byte) 0xce, (byte) 0xa9,
|
||||
(byte) 0x17, (byte) 0x43, (byte) 0xd3, (byte) 0x39, (byte) 0x39,
|
||||
(byte) 0xc3, (byte) 0x3c, (byte) 0x84, (byte) 0x98, (byte) 0x65,
|
||||
(byte) 0x09, (byte) 0x28, (byte) 0x01, (byte) 0x61, (byte) 0xb8,
|
||||
(byte) 0xb6, (byte) 0x0f, (byte) 0xc7, (byte) 0x87, (byte) 0x0c,
|
||||
(byte) 0x59, (byte) 0x9c, (byte) 0x1d, (byte) 0x46, (byte) 0x20,
|
||||
(byte) 0x12, (byte) 0x48};
|
||||
|
||||
byte[] aliceIdentityPublic = {(byte) 0x05, (byte) 0xb4, (byte) 0xa8, (byte) 0x45, (byte) 0x56,
|
||||
(byte) 0x60, (byte) 0xad, (byte) 0xa6, (byte) 0x5b, (byte) 0x40,
|
||||
(byte) 0x10, (byte) 0x07, (byte) 0xf6, (byte) 0x15, (byte) 0xe6,
|
||||
(byte) 0x54, (byte) 0x04, (byte) 0x17, (byte) 0x46, (byte) 0x43,
|
||||
(byte) 0x2e, (byte) 0x33, (byte) 0x39, (byte) 0xc6, (byte) 0x87,
|
||||
(byte) 0x51, (byte) 0x49, (byte) 0xbc, (byte) 0xee, (byte) 0xfc,
|
||||
(byte) 0xb4, (byte) 0x2b, (byte) 0x4a};
|
||||
|
||||
byte[] aliceIdentityPrivate = {(byte) 0x90, (byte) 0x40, (byte) 0xf0, (byte) 0xd4, (byte) 0xe0,
|
||||
(byte) 0x9c, (byte) 0xf3, (byte) 0x8f, (byte) 0x6d, (byte) 0xc7,
|
||||
(byte) 0xc1, (byte) 0x37, (byte) 0x79, (byte) 0xc9, (byte) 0x08,
|
||||
(byte) 0xc0, (byte) 0x15, (byte) 0xa1, (byte) 0xda, (byte) 0x4f,
|
||||
(byte) 0xa7, (byte) 0x87, (byte) 0x37, (byte) 0xa0, (byte) 0x80,
|
||||
(byte) 0xeb, (byte) 0x0a, (byte) 0x6f, (byte) 0x4f, (byte) 0x5f,
|
||||
(byte) 0x8f, (byte) 0x58};
|
||||
|
||||
byte[] receiverChain = {(byte) 0xd2, (byte) 0x2f, (byte) 0xd5, (byte) 0x6d, (byte) 0x3f,
|
||||
(byte) 0xec, (byte) 0x81, (byte) 0x9c, (byte) 0xf4, (byte) 0xc3,
|
||||
(byte) 0xd5, (byte) 0x0c, (byte) 0x56, (byte) 0xed, (byte) 0xfb,
|
||||
(byte) 0x1c, (byte) 0x28, (byte) 0x0a, (byte) 0x1b, (byte) 0x31,
|
||||
(byte) 0x96, (byte) 0x45, (byte) 0x37, (byte) 0xf1, (byte) 0xd1,
|
||||
(byte) 0x61, (byte) 0xe1, (byte) 0xc9, (byte) 0x31, (byte) 0x48,
|
||||
(byte) 0xe3, (byte) 0x6b};
|
||||
|
||||
IdentityKey bobIdentityKey = new IdentityKey(bobIdentityPublic, 0);
|
||||
ECPublicKey bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
ECPublicKey bobBasePublicKey = bobEphemeralPublicKey;
|
||||
ECPublicKey aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0);
|
||||
ECPrivateKey aliceBasePrivateKey = Curve.decodePrivatePoint(aliceBasePrivate);
|
||||
ECKeyPair aliceBaseKey = new ECKeyPair(aliceBasePublicKey, aliceBasePrivateKey);
|
||||
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
|
||||
ECPrivateKey aliceEphemeralPrivateKey = Curve.decodePrivatePoint(aliceEphemeralPrivate);
|
||||
ECKeyPair aliceEphemeralKey = new ECKeyPair(aliceEphemeralPublicKey, aliceEphemeralPrivateKey);
|
||||
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
|
||||
ECPrivateKey aliceIdentityPrivateKey = Curve.decodePrivatePoint(aliceIdentityPrivate);
|
||||
IdentityKeyPair aliceIdentityKey = new IdentityKeyPair(aliceIdentityPublicKey, aliceIdentityPrivateKey);
|
||||
|
||||
SessionState session = new InMemorySessionState();
|
||||
|
||||
RatchetingSession.initializeSession(session, aliceBaseKey, bobBasePublicKey,
|
||||
aliceEphemeralKey, bobEphemeralPublicKey,
|
||||
aliceIdentityKey, bobIdentityKey);
|
||||
|
||||
assertTrue(session.getLocalIdentityKey().equals(aliceIdentityKey.getPublicKey()));
|
||||
assertTrue(session.getRemoteIdentityKey().equals(bobIdentityKey));
|
||||
assertTrue(Arrays.equals(session.getReceiverChainKey(bobEphemeralPublicKey).getKey(),
|
||||
receiverChain));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.whispersystems.test.ratchet;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RootKeyTest extends AndroidTestCase {
|
||||
|
||||
public void testRootKeyDerivation() throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
byte[] rootKeySeed = {(byte) 0x7b, (byte) 0xa6, (byte) 0xde, (byte) 0xbc, (byte) 0x2b,
|
||||
(byte) 0xc1, (byte) 0xbb, (byte) 0xf9, (byte) 0x1a, (byte) 0xbb,
|
||||
(byte) 0xc1, (byte) 0x36, (byte) 0x74, (byte) 0x04, (byte) 0x17,
|
||||
(byte) 0x6c, (byte) 0xa6, (byte) 0x23, (byte) 0x09, (byte) 0x5b,
|
||||
(byte) 0x7e, (byte) 0xc6, (byte) 0x6b, (byte) 0x45, (byte) 0xf6,
|
||||
(byte) 0x02, (byte) 0xd9, (byte) 0x35, (byte) 0x38, (byte) 0x94,
|
||||
(byte) 0x2d, (byte) 0xcc};
|
||||
|
||||
byte[] alicePublic = {(byte) 0x05, (byte) 0xee, (byte) 0x4f, (byte) 0xa6, (byte) 0xcd,
|
||||
(byte) 0xc0, (byte) 0x30, (byte) 0xdf, (byte) 0x49, (byte) 0xec,
|
||||
(byte) 0xd0, (byte) 0xba, (byte) 0x6c, (byte) 0xfc, (byte) 0xff,
|
||||
(byte) 0xb2, (byte) 0x33, (byte) 0xd3, (byte) 0x65, (byte) 0xa2,
|
||||
(byte) 0x7f, (byte) 0xad, (byte) 0xbe, (byte) 0xff, (byte) 0x77,
|
||||
(byte) 0xe9, (byte) 0x63, (byte) 0xfc, (byte) 0xb1, (byte) 0x62,
|
||||
(byte) 0x22, (byte) 0xe1, (byte) 0x3a};
|
||||
|
||||
byte[] alicePrivate = {(byte) 0x21, (byte) 0x68, (byte) 0x22, (byte) 0xec, (byte) 0x67,
|
||||
(byte) 0xeb, (byte) 0x38, (byte) 0x04, (byte) 0x9e, (byte) 0xba,
|
||||
(byte) 0xe7, (byte) 0xb9, (byte) 0x39, (byte) 0xba, (byte) 0xea,
|
||||
(byte) 0xeb, (byte) 0xb1, (byte) 0x51, (byte) 0xbb, (byte) 0xb3,
|
||||
(byte) 0x2d, (byte) 0xb8, (byte) 0x0f, (byte) 0xd3, (byte) 0x89,
|
||||
(byte) 0x24, (byte) 0x5a, (byte) 0xc3, (byte) 0x7a, (byte) 0x94,
|
||||
(byte) 0x8e, (byte) 0x50};
|
||||
|
||||
byte[] bobPublic = {(byte) 0x05, (byte) 0xab, (byte) 0xb8, (byte) 0xeb, (byte) 0x29,
|
||||
(byte) 0xcc, (byte) 0x80, (byte) 0xb4, (byte) 0x71, (byte) 0x09,
|
||||
(byte) 0xa2, (byte) 0x26, (byte) 0x5a, (byte) 0xbe, (byte) 0x97,
|
||||
(byte) 0x98, (byte) 0x48, (byte) 0x54, (byte) 0x06, (byte) 0xe3,
|
||||
(byte) 0x2d, (byte) 0xa2, (byte) 0x68, (byte) 0x93, (byte) 0x4a,
|
||||
(byte) 0x95, (byte) 0x55, (byte) 0xe8, (byte) 0x47, (byte) 0x57,
|
||||
(byte) 0x70, (byte) 0x8a, (byte) 0x30};
|
||||
|
||||
byte[] nextRoot = {(byte) 0xb1, (byte) 0x14, (byte) 0xf5, (byte) 0xde, (byte) 0x28,
|
||||
(byte) 0x01, (byte) 0x19, (byte) 0x85, (byte) 0xe6, (byte) 0xeb,
|
||||
(byte) 0xa2, (byte) 0x5d, (byte) 0x50, (byte) 0xe7, (byte) 0xec,
|
||||
(byte) 0x41, (byte) 0xa9, (byte) 0xb0, (byte) 0x2f, (byte) 0x56,
|
||||
(byte) 0x93, (byte) 0xc5, (byte) 0xc7, (byte) 0x88, (byte) 0xa6,
|
||||
(byte) 0x3a, (byte) 0x06, (byte) 0xd2, (byte) 0x12, (byte) 0xa2,
|
||||
(byte) 0xf7, (byte) 0x31};
|
||||
|
||||
byte[] nextChain = {(byte) 0x9d, (byte) 0x7d, (byte) 0x24, (byte) 0x69, (byte) 0xbc,
|
||||
(byte) 0x9a, (byte) 0xe5, (byte) 0x3e, (byte) 0xe9, (byte) 0x80,
|
||||
(byte) 0x5a, (byte) 0xa3, (byte) 0x26, (byte) 0x4d, (byte) 0x24,
|
||||
(byte) 0x99, (byte) 0xa3, (byte) 0xac, (byte) 0xe8, (byte) 0x0f,
|
||||
(byte) 0x4c, (byte) 0xca, (byte) 0xe2, (byte) 0xda, (byte) 0x13,
|
||||
(byte) 0x43, (byte) 0x0c, (byte) 0x5c, (byte) 0x55, (byte) 0xb5,
|
||||
(byte) 0xca, (byte) 0x5f};
|
||||
|
||||
ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0);
|
||||
ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate);
|
||||
ECKeyPair aliceKeyPair = new ECKeyPair(alicePublicKey, alicePrivateKey);
|
||||
|
||||
ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0);
|
||||
RootKey rootKey = new RootKey(rootKeySeed);
|
||||
|
||||
Pair<RootKey, ChainKey> rootKeyChainKeyPair = rootKey.createChain(bobPublicKey, aliceKeyPair);
|
||||
RootKey nextRootKey = rootKeyChainKeyPair.first();
|
||||
ChainKey nextChainKey = rootKeyChainKeyPair.second();
|
||||
|
||||
assertTrue(Arrays.equals(rootKey.getKeyBytes(), rootKeySeed));
|
||||
assertTrue(Arrays.equals(nextRootKey.getKeyBytes(), nextRoot));
|
||||
assertTrue(Arrays.equals(nextChainKey.getKey(), nextChain));
|
||||
}
|
||||
}
|
||||
7
libaxolotl/src/main/AndroidManifest.xml
Normal file
7
libaxolotl/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
public class DuplicateMessageException extends Exception {
|
||||
public DuplicateMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.util.Hex;
|
||||
|
||||
/**
|
||||
* A class for representing an identity key.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class IdentityKey {
|
||||
|
||||
private final ECPublicKey publicKey;
|
||||
|
||||
public IdentityKey(ECPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset);
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return Hex.toString(publicKey.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof IdentityKey)) return false;
|
||||
|
||||
return publicKey.equals(((IdentityKey) other).getPublicKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return publicKey.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
public class LegacyMessageException extends Exception {
|
||||
public LegacyMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
public interface SessionState {
|
||||
public void setNeedsRefresh(boolean needsRefresh);
|
||||
public boolean getNeedsRefresh();
|
||||
public void setSessionVersion(int version);
|
||||
public int getSessionVersion();
|
||||
public void setRemoteIdentityKey(IdentityKey identityKey);
|
||||
public void setLocalIdentityKey(IdentityKey identityKey);
|
||||
public IdentityKey getRemoteIdentityKey();
|
||||
public IdentityKey getLocalIdentityKey();
|
||||
public int getPreviousCounter();
|
||||
public void setPreviousCounter(int previousCounter);
|
||||
public RootKey getRootKey();
|
||||
public void setRootKey(RootKey rootKey);
|
||||
public ECPublicKey getSenderEphemeral();
|
||||
public ECKeyPair getSenderEphemeralPair();
|
||||
public boolean hasReceiverChain(ECPublicKey senderEphemeral);
|
||||
public boolean hasSenderChain();
|
||||
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral);
|
||||
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey);
|
||||
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey);
|
||||
public ChainKey getSenderChainKey();
|
||||
public void setSenderChainKey(ChainKey nextChainKey);
|
||||
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter);
|
||||
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter);
|
||||
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys);
|
||||
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey);
|
||||
public void setPendingKeyExchange(int sequence,
|
||||
ECKeyPair ourBaseKey,
|
||||
ECKeyPair ourEphemeralKey,
|
||||
IdentityKeyPair ourIdentityKey);
|
||||
public int getPendingKeyExchangeSequence();
|
||||
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException;
|
||||
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException;
|
||||
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException;
|
||||
public boolean hasPendingKeyExchange();
|
||||
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey);
|
||||
public boolean hasPendingPreKey();
|
||||
public Pair<Integer, ECPublicKey> getPendingPreKey();
|
||||
public void clearPendingPreKey();
|
||||
public void setRemoteRegistrationId(int registrationId);
|
||||
public int getRemoteRegistrationId();
|
||||
public void setLocalRegistrationId(int registrationId);
|
||||
public int getLocalRegistrationId();
|
||||
public byte[] serialize();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.whispersystems.libaxolotl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SessionStore {
|
||||
|
||||
public SessionState getSessionState();
|
||||
public List<SessionState> getPreviousSessionStates();
|
||||
public void save();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.protocol;
|
||||
|
||||
public interface CiphertextMessage {
|
||||
|
||||
public static final int UNSUPPORTED_VERSION = 1;
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static final int WHISPER_TYPE = 2;
|
||||
public static final int PREKEY_TYPE = 3;
|
||||
|
||||
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
|
||||
public byte[] serialize();
|
||||
public int getType();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.ratchet;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class MessageKeys {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final int counter;
|
||||
|
||||
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.ratchet;
|
||||
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.kdf.DerivedSecrets;
|
||||
import org.whispersystems.libaxolotl.kdf.HKDF;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
public class RootKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
public RootKey(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
HKDF kdf = new HKDF();
|
||||
byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey());
|
||||
DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes());
|
||||
RootKey newRootKey = new RootKey(keys.getCipherKey().getEncoded());
|
||||
ChainKey newChainKey = new ChainKey(keys.getMacKey().getEncoded(), 0);
|
||||
|
||||
return new Pair<>(newRootKey, newChainKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class ByteUtil {
|
||||
|
||||
public static byte[] combine(byte[]... elements) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
for (byte[] element : elements) {
|
||||
baos.write(element);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
|
||||
byte[][] parts = new byte[2][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength)
|
||||
throws ParseException
|
||||
{
|
||||
if (input == null || firstLength < 0 || secondLength < 0 || thirdLength < 0 ||
|
||||
input.length < firstLength + secondLength + thirdLength)
|
||||
{
|
||||
throw new ParseException("Input too small: " + (input == null ? null : Hex.toString(input)), 0);
|
||||
}
|
||||
|
||||
byte[][] parts = new byte[3][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
parts[2] = new byte[thirdLength];
|
||||
System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static byte[] trim(byte[] input, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(input, 0, result, 0, result.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte intsToByteHighAndLow(int highValue, int lowValue) {
|
||||
return (byte)((highValue << 4 | lowValue) & 0xFF);
|
||||
}
|
||||
|
||||
public static int highBitsToInt(byte value) {
|
||||
return (value & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
public static int lowBitsToInt(byte value) {
|
||||
return (value & 0xF);
|
||||
}
|
||||
|
||||
public static int highBitsToMedium(int value) {
|
||||
return (value >> 12);
|
||||
}
|
||||
|
||||
public static int lowBitsToMedium(int value) {
|
||||
return (value & 0xFFF);
|
||||
}
|
||||
|
||||
public static byte[] shortToByteArray(int value) {
|
||||
byte[] bytes = new byte[2];
|
||||
shortToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset+1] = (byte)value;
|
||||
bytes[offset] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static byte[] mediumToByteArray(int value) {
|
||||
byte[] bytes = new byte[3];
|
||||
mediumToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 2] = (byte)value;
|
||||
bytes[offset + 1] = (byte)(value >> 8);
|
||||
bytes[offset] = (byte)(value >> 16);
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static byte[] intToByteArray(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
intToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
bytes[offset+2] = (byte)(value >> 16);
|
||||
bytes[offset+3] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte[] longToByteArray(long l) {
|
||||
byte[] bytes = new byte[8];
|
||||
longToByteArray(bytes, 0, l);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 7] = (byte)value;
|
||||
bytes[offset + 6] = (byte)(value >> 8);
|
||||
bytes[offset + 5] = (byte)(value >> 16);
|
||||
bytes[offset + 4] = (byte)(value >> 24);
|
||||
bytes[offset + 3] = (byte)(value >> 32);
|
||||
bytes[offset + 2] = (byte)(value >> 40);
|
||||
bytes[offset + 1] = (byte)(value >> 48);
|
||||
bytes[offset] = (byte)(value >> 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset + 0] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes) {
|
||||
return byteArrayToShort(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff);
|
||||
}
|
||||
|
||||
// The SSL patented 3-byte Value.
|
||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset + 2] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes) {
|
||||
return byteArrayToInt(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 24 |
|
||||
(bytes[offset + 1] & 0xff) << 16 |
|
||||
(bytes[offset + 2] & 0xff) << 8 |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToIntLittleEndian(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset + 3] & 0xff) << 24 |
|
||||
(bytes[offset + 2] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset] & 0xff);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
return byteArrayToLong(bytes, 0);
|
||||
}
|
||||
|
||||
public static long byteArray4ToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset + 0] & 0xffL) << 24) |
|
||||
((bytes[offset + 1] & 0xffL) << 16) |
|
||||
((bytes[offset + 2] & 0xffL) << 8) |
|
||||
((bytes[offset + 3] & 0xffL));
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset] & 0xffL) << 56) |
|
||||
((bytes[offset + 1] & 0xffL) << 48) |
|
||||
((bytes[offset + 2] & 0xffL) << 40) |
|
||||
((bytes[offset + 3] & 0xffL) << 32) |
|
||||
((bytes[offset + 4] & 0xffL) << 24) |
|
||||
((bytes[offset + 5] & 0xffL) << 16) |
|
||||
((bytes[offset + 6] & 0xffL) << 8) |
|
||||
((bytes[offset + 7] & 0xffL));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Utility for generating hex dumps.
|
||||
*/
|
||||
public class Hex {
|
||||
|
||||
private final static char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
buf.append("(byte)0x");
|
||||
appendHexChar(buf, bytes[offset + i]);
|
||||
buf.append(", ");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i=0;i<bytes.length;i++) {
|
||||
appendHexChar(buf, bytes[i]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static byte[] fromStringCondensed(String encoded) throws IOException {
|
||||
final char[] data = encoded.toCharArray();
|
||||
final int len = data.length;
|
||||
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new IOException("Odd number of characters.");
|
||||
}
|
||||
|
||||
final byte[] out = new byte[len >> 1];
|
||||
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = Character.digit(data[j], 16) << 4;
|
||||
j++;
|
||||
f = f | Character.digit(data[j], 16);
|
||||
j++;
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void appendHexChar(StringBuffer buf, int b) {
|
||||
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[b & 0xf]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.libaxolotl.util;
|
||||
|
||||
public class Pair<T1, T2> {
|
||||
private final T1 v1;
|
||||
private final T2 v2;
|
||||
|
||||
public Pair(T1 v1, T2 v2) {
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
public T1 first(){
|
||||
return v1;
|
||||
}
|
||||
|
||||
public T2 second(){
|
||||
return v2;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Pair &&
|
||||
equal(((Pair) o).first(), first()) &&
|
||||
equal(((Pair) o).second(), second());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return first().hashCode() ^ second().hashCode();
|
||||
}
|
||||
|
||||
private boolean equal(Object first, Object second) {
|
||||
if (first == null && second == null) return true;
|
||||
if (first == null || second == null) return false;
|
||||
return first.equals(second);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user