diff --git a/library/src/org/whispersystems/textsecure/storage/InvalidKeyIdException.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/InvalidKeyIdException.java
similarity index 78%
rename from library/src/org/whispersystems/textsecure/storage/InvalidKeyIdException.java
rename to libaxolotl/src/main/java/org/whispersystems/libaxolotl/InvalidKeyIdException.java
index 920a633fa6..c7c6bc016c 100644
--- a/library/src/org/whispersystems/textsecure/storage/InvalidKeyIdException.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/InvalidKeyIdException.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2011 Whisper Systems
+ * Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,13 +14,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.whispersystems.textsecure.storage;
+package org.whispersystems.libaxolotl;
public class InvalidKeyIdException extends Exception {
-
- public InvalidKeyIdException() {
- }
-
public InvalidKeyIdException(String detailMessage) {
super(detailMessage);
}
@@ -28,9 +24,4 @@ public class InvalidKeyIdException extends Exception {
public InvalidKeyIdException(Throwable throwable) {
super(throwable);
}
-
- public InvalidKeyIdException(String detailMessage, Throwable throwable) {
- super(detailMessage, throwable);
- }
-
}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java
new file mode 100644
index 0000000000..7d171adb67
--- /dev/null
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java
@@ -0,0 +1,180 @@
+package org.whispersystems.libaxolotl;
+
+import android.util.Log;
+
+import org.whispersystems.libaxolotl.ecc.Curve;
+import org.whispersystems.libaxolotl.ecc.ECKeyPair;
+import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
+import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
+import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
+import org.whispersystems.libaxolotl.state.IdentityKeyStore;
+import org.whispersystems.libaxolotl.state.PreKey;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.PreKeyStore;
+import org.whispersystems.libaxolotl.state.SessionRecord;
+import org.whispersystems.libaxolotl.state.SessionStore;
+import org.whispersystems.libaxolotl.util.Medium;
+
+public class SessionBuilder {
+
+ private static final String TAG = SessionBuilder.class.getSimpleName();
+
+ private final SessionStore sessionStore;
+ private final PreKeyStore preKeyStore;
+ private final IdentityKeyStore identityKeyStore;
+ private final long recipientId;
+ private final int deviceId;
+
+ public SessionBuilder(SessionStore sessionStore,
+ PreKeyStore preKeyStore,
+ IdentityKeyStore identityKeyStore,
+ long recipientId, int deviceId)
+ {
+ this.sessionStore = sessionStore;
+ this.preKeyStore = preKeyStore;
+ this.identityKeyStore = identityKeyStore;
+ this.recipientId = recipientId;
+ this.deviceId = deviceId;
+ }
+
+ public void process(PreKeyWhisperMessage message)
+ throws InvalidKeyIdException, InvalidKeyException
+ {
+ int preKeyId = message.getPreKeyId();
+ ECPublicKey theirBaseKey = message.getBaseKey();
+ ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
+ IdentityKey theirIdentityKey = message.getIdentityKey();
+
+ Log.w(TAG, "Received pre-key with local key ID: " + preKeyId);
+
+ if (!preKeyStore.contains(preKeyId) &&
+ sessionStore.contains(recipientId, deviceId))
+ {
+ Log.w(TAG, "We've already processed the prekey part, letting bundled message fall through...");
+ return;
+ }
+
+ if (!preKeyStore.contains(preKeyId))
+ throw new InvalidKeyIdException("No such prekey: " + preKeyId);
+
+ SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
+ PreKeyRecord preKeyRecord = preKeyStore.load(preKeyId);
+ ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
+ ECKeyPair ourEphemeralKey = ourBaseKey;
+ IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair();
+ boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
+
+ if (!simultaneousInitiate) sessionRecord.reset();
+ else sessionRecord.archiveCurrentState();
+
+ RatchetingSession.initializeSession(sessionRecord.getSessionState(),
+ ourBaseKey, theirBaseKey,
+ ourEphemeralKey, theirEphemeralKey,
+ ourIdentityKey, theirIdentityKey);
+
+ sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
+ sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
+
+ if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
+
+ sessionStore.put(recipientId, deviceId, sessionRecord);
+
+ if (preKeyId != Medium.MAX_VALUE) {
+ preKeyStore.remove(preKeyId);
+ }
+
+ identityKeyStore.saveIdentity(recipientId, theirIdentityKey);
+ }
+
+ public void process(PreKey preKey) throws InvalidKeyException {
+ SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
+ ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
+ ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
+ ECPublicKey theirBaseKey = preKey.getPublicKey();
+ ECPublicKey theirEphemeralKey = theirBaseKey;
+ IdentityKey theirIdentityKey = preKey.getIdentityKey();
+ IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair();
+
+ if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
+ else sessionRecord.reset();
+
+ RatchetingSession.initializeSession(sessionRecord.getSessionState(),
+ ourBaseKey, theirBaseKey, ourEphemeralKey,
+ theirEphemeralKey, ourIdentityKey, theirIdentityKey);
+
+ sessionRecord.getSessionState().setPendingPreKey(preKey.getKeyId(), ourBaseKey.getPublicKey());
+ sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
+ sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId());
+
+ sessionStore.put(recipientId, deviceId, sessionRecord);
+
+ identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey());
+ }
+
+ public KeyExchangeMessage process(KeyExchangeMessage message) throws InvalidKeyException {
+ KeyExchangeMessage responseMessage = null;
+ SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
+
+ Log.w(TAG, "Received key exchange with sequence: " + message.getSequence());
+
+ if (message.isInitiate()) {
+ ECKeyPair ourBaseKey, ourEphemeralKey;
+ IdentityKeyPair ourIdentityKey;
+
+ int flags = KeyExchangeMessage.RESPONSE_FLAG;
+
+ Log.w(TAG, "KeyExchange is an initiate.");
+
+ if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
+ Log.w(TAG, "We don't have a pending initiate...");
+ ourBaseKey = Curve.generateKeyPair(true);
+ ourEphemeralKey = Curve.generateKeyPair(true);
+ ourIdentityKey = identityKeyStore.getIdentityKeyPair();
+
+ sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
+ ourEphemeralKey, ourIdentityKey);
+ } else {
+ Log.w(TAG, "We already have a pending initiate, responding as simultaneous initiate...");
+ ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
+ ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
+ ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
+ flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
+
+ sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
+ ourEphemeralKey, ourIdentityKey);
+ }
+
+ responseMessage = new KeyExchangeMessage(message.getSequence(),
+ flags, ourBaseKey.getPublicKey(),
+ ourEphemeralKey.getPublicKey(),
+ ourIdentityKey.getPublicKey());
+ }
+
+ if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
+ Log.w("KeyExchangeProcessor", "No matching sequence for response. " +
+ "Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
+ return responseMessage;
+ }
+
+ ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
+ ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
+ IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
+
+ sessionRecord.reset();
+
+ RatchetingSession.initializeSession(sessionRecord.getSessionState(),
+ ourBaseKey, message.getBaseKey(),
+ ourEphemeralKey, message.getEphemeralKey(),
+ ourIdentityKey, message.getIdentityKey());
+
+ sessionRecord.getSessionState().setSessionVersion(message.getVersion());
+ sessionStore.put(recipientId, deviceId, sessionRecord);
+
+ identityKeyStore.saveIdentity(recipientId, message.getIdentityKey());
+
+ return responseMessage;
+ }
+
+
+}
diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/KeyExchangeMessage.java
similarity index 72%
rename from src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java
rename to libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/KeyExchangeMessage.java
index c2a79cd840..02abceed10 100644
--- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/protocol/KeyExchangeMessage.java
@@ -1,7 +1,7 @@
-package org.thoughtcrime.securesms.crypto.protocol;
+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;
@@ -10,11 +10,7 @@ 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.protocol.CiphertextMessage;
-import org.whispersystems.libaxolotl.protocol.WhisperProtos;
-import org.whispersystems.textsecure.util.Base64;
-import org.whispersystems.textsecure.util.Conversions;
-import org.whispersystems.textsecure.util.Util;
+import org.whispersystems.libaxolotl.util.ByteUtil;
import java.io.IOException;
@@ -46,26 +42,24 @@ public class KeyExchangeMessage {
this.ephemeralKey = ephemeralKey;
this.identityKey = identityKey;
- byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
+ byte[] version = {ByteUtil.intsToByteHighAndLow(this.version, this.supportedVersion)};
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
- .setId((sequence << 5) | flags)
- .setBaseKey(ByteString.copyFrom(baseKey.serialize()))
- .setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
- .setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
- .build().toByteArray();
+ .setId((sequence << 5) | flags)
+ .setBaseKey(ByteString.copyFrom(baseKey.serialize()))
+ .setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
+ .setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
+ .build().toByteArray();
- this.serialized = Util.combine(version, message);
+ this.serialized = ByteUtil.combine(version, message);
}
- public KeyExchangeMessage(String serializedAndEncoded)
+ public KeyExchangeMessage(byte[] serialized)
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
{
try {
- byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
- byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
-
- this.version = Conversions.highBitsToInt(parts[0][0]);
- this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
+ byte[][] parts = ByteUtil.split(serialized, 1, serialized.length - 1);
+ this.version = ByteUtil.highBitsToInt(parts[0][0]);
+ this.supportedVersion = ByteUtil.lowBitsToInt(parts[0][0]);
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
@@ -138,7 +132,7 @@ public class KeyExchangeMessage {
return sequence;
}
- public String serialize() {
- return Base64.encodeBytesWithoutPadding(serialized);
+ public byte[] serialize() {
+ return serialized;
}
}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java
new file mode 100644
index 0000000000..091e4189be
--- /dev/null
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java
@@ -0,0 +1,12 @@
+package org.whispersystems.libaxolotl.state;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.IdentityKeyPair;
+
+public interface IdentityKeyStore {
+
+ public IdentityKeyPair getIdentityKeyPair();
+ public int getLocalRegistrationId();
+ public void saveIdentity(long recipientId, IdentityKey identityKey);
+
+}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java
new file mode 100644
index 0000000000..a4358eb2f2
--- /dev/null
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java
@@ -0,0 +1,12 @@
+package org.whispersystems.libaxolotl.state;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+
+public interface PreKey {
+ public int getDeviceId();
+ public int getKeyId();
+ public ECPublicKey getPublicKey();
+ public IdentityKey getIdentityKey();
+ public int getRegistrationId();
+}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java
new file mode 100644
index 0000000000..7d2e391185
--- /dev/null
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java
@@ -0,0 +1,9 @@
+package org.whispersystems.libaxolotl.state;
+
+import org.whispersystems.libaxolotl.ecc.ECKeyPair;
+
+public interface PreKeyRecord {
+ public int getId();
+ public ECKeyPair getKeyPair();
+ public byte[] serialize();
+}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java
new file mode 100644
index 0000000000..f1d778375e
--- /dev/null
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java
@@ -0,0 +1,12 @@
+package org.whispersystems.libaxolotl.state;
+
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+
+public interface PreKeyStore {
+
+ public PreKeyRecord load(int preKeyId) throws InvalidKeyIdException;
+ public void store(int preKeyId, PreKeyRecord record);
+ public boolean contains(int preKeyId);
+ public void remove(int preKeyId);
+
+}
diff --git a/library/src/org/whispersystems/textsecure/util/Medium.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Medium.java
similarity index 60%
rename from library/src/org/whispersystems/textsecure/util/Medium.java
rename to libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Medium.java
index 30defca019..d18b2d667d 100644
--- a/library/src/org/whispersystems/textsecure/util/Medium.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Medium.java
@@ -1,4 +1,4 @@
-package org.whispersystems.textsecure.util;
+package org.whispersystems.libaxolotl.util;
public class Medium {
public static int MAX_VALUE = 0xFFFFFF;
diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java
index 7574d4db7b..3e0304bd66 100644
--- a/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java
+++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyUtil.java
@@ -24,9 +24,12 @@ import com.google.thoughtcrimegson.Gson;
import org.whispersystems.libaxolotl.ecc.Curve25519;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
-import org.whispersystems.textsecure.storage.InvalidKeyIdException;
-import org.whispersystems.textsecure.storage.PreKeyRecord;
-import org.whispersystems.textsecure.util.Medium;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.PreKeyStore;
+import org.whispersystems.libaxolotl.util.Medium;
+import org.whispersystems.textsecure.storage.TextSecurePreKeyRecord;
+import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.util.Util;
import java.io.File;
@@ -43,15 +46,16 @@ public class PreKeyUtil {
public static final int BATCH_SIZE = 100;
public static List generatePreKeys(Context context, MasterSecret masterSecret) {
- List records = new LinkedList();
+ PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
+ List records = new LinkedList<>();
int preKeyIdOffset = getNextPreKeyId(context);
for (int i=0;i records)
throws IOException
{
- List entities = new LinkedList();
+ List entities = new LinkedList<>();
for (PreKeyRecord record : records) {
PreKeyEntity entity = new PreKeyEntity(record.getId(),
diff --git a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java
deleted file mode 100644
index edf52ce16a..0000000000
--- a/library/src/org/whispersystems/textsecure/storage/PreKeyRecord.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.whispersystems.textsecure.storage;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.google.protobuf.ByteString;
-
-import org.whispersystems.libaxolotl.InvalidKeyException;
-import org.whispersystems.libaxolotl.InvalidMessageException;
-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.textsecure.crypto.MasterCipher;
-import org.whispersystems.textsecure.crypto.MasterSecret;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-
-public class PreKeyRecord extends Record {
-
- private static final Object FILE_LOCK = new Object();
- private static final int CURRENT_VERSION_MARKER = 1;
-
- private final MasterSecret masterSecret;
- private StorageProtos.PreKeyRecordStructure structure;
-
- public PreKeyRecord(Context context, MasterSecret masterSecret, int id)
- throws InvalidKeyIdException
- {
- super(context, PREKEY_DIRECTORY, id+"");
-
- this.structure = StorageProtos.PreKeyRecordStructure.newBuilder().setId(id).build();
- this.masterSecret = masterSecret;
-
- loadData();
- }
-
- public PreKeyRecord(Context context, MasterSecret masterSecret,
- int id, ECKeyPair keyPair)
- {
- super(context, PREKEY_DIRECTORY, id+"");
- this.masterSecret = masterSecret;
- this.structure = StorageProtos.PreKeyRecordStructure.newBuilder()
- .setId(id)
- .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey()
- .serialize()))
- .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey()
- .serialize()))
- .build();
- }
-
- public int getId() {
- return this.structure.getId();
- }
-
- public ECKeyPair getKeyPair() {
- try {
- ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0);
- ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray());
-
- return new ECKeyPair(publicKey, privateKey);
- } catch (InvalidKeyException e) {
- throw new AssertionError(e);
- }
- }
-
- public static boolean hasRecord(Context context, long id) {
- Log.w("PreKeyRecord", "Checking: " + id);
- return Record.hasRecord(context, PREKEY_DIRECTORY, id+"");
- }
-
- public static void delete(Context context, long id) {
- Record.delete(context, PREKEY_DIRECTORY, id+"");
- }
-
- public void save() {
- synchronized (FILE_LOCK) {
- try {
- RandomAccessFile file = openRandomAccessFile();
- FileChannel out = file.getChannel();
- out.position(0);
-
- MasterCipher masterCipher = new MasterCipher(masterSecret);
-
- writeInteger(CURRENT_VERSION_MARKER, out);
- writeBlob(masterCipher.encryptBytes(structure.toByteArray()), out);
-
- out.force(true);
- out.truncate(out.position());
- out.close();
- file.close();
- } catch (IOException ioe) {
- Log.w("PreKeyRecord", ioe);
- }
- }
- }
-
- private void loadData() throws InvalidKeyIdException {
- synchronized (FILE_LOCK) {
- try {
- MasterCipher masterCipher = new MasterCipher(masterSecret);
- FileInputStream in = this.openInputStream();
- int recordVersion = readInteger(in);
-
- if (recordVersion != CURRENT_VERSION_MARKER) {
- Log.w("PreKeyRecord", "Invalid version: " + recordVersion);
- return;
- }
-
- this.structure =
- StorageProtos.PreKeyRecordStructure.parseFrom(masterCipher.decryptBytes(readBlob(in)));
-
- in.close();
- } catch (FileNotFoundException e) {
- Log.w("PreKeyRecord", e);
- throw new InvalidKeyIdException(e);
- } catch (IOException ioe) {
- Log.w("PreKeyRecord", ioe);
- throw new InvalidKeyIdException(ioe);
- } catch (InvalidMessageException ime) {
- Log.w("PreKeyRecord", ime);
- throw new InvalidKeyIdException(ime);
- }
- }
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/storage/Record.java b/library/src/org/whispersystems/textsecure/storage/Record.java
deleted file mode 100644
index 88f9439bb4..0000000000
--- a/library/src/org/whispersystems/textsecure/storage/Record.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * Copyright (C) 2011 Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.whispersystems.textsecure.storage;
-
-import android.content.Context;
-
-import org.whispersystems.textsecure.util.Conversions;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-
-public abstract class Record {
-
- public static final String SESSIONS_DIRECTORY = "sessions";
- protected static final String SESSIONS_DIRECTORY_V2 = "sessions-v2";
- public static final String PREKEY_DIRECTORY = "prekeys";
-
- protected final String address;
- protected final String directory;
- protected final Context context;
-
- public Record(Context context, String directory, String address) {
- this.context = context;
- this.directory = directory;
- this.address = address;
- }
-
- public void delete() {
- delete(this.context, this.directory, this.address);
- }
-
- public static void delete(Context context, String directory, String address) {
- getAddressFile(context, directory, address).delete();
- }
-
- protected static boolean hasRecord(Context context, String directory, String address) {
- return getAddressFile(context, directory, address).exists();
- }
-
- protected RandomAccessFile openRandomAccessFile() throws FileNotFoundException {
- return new RandomAccessFile(getAddressFile(), "rw");
- }
-
- protected FileInputStream openInputStream() throws FileNotFoundException {
- return new FileInputStream(getAddressFile().getAbsolutePath());
- }
-
- private File getAddressFile() {
- return getAddressFile(context, directory, address);
- }
-
- private static File getAddressFile(Context context, String directory, String address) {
- File parent = getParentDirectory(context, directory);
-
- return new File(parent, address);
- }
-
- protected static File getParentDirectory(Context context, String directory) {
- File parent = new File(context.getFilesDir(), directory);
-
- if (!parent.exists()) {
- parent.mkdirs();
- }
-
- return parent;
- }
-
- protected byte[] readBlob(FileInputStream in) throws IOException {
- int length = readInteger(in);
- byte[] blobBytes = new byte[length];
-
- in.read(blobBytes, 0, blobBytes.length);
- return blobBytes;
- }
-
- protected void writeBlob(byte[] blobBytes, FileChannel out) throws IOException {
- writeInteger(blobBytes.length, out);
- ByteBuffer buffer = ByteBuffer.wrap(blobBytes);
- out.write(buffer);
- }
-
- protected int readInteger(FileInputStream in) throws IOException {
- byte[] integer = new byte[4];
- in.read(integer, 0, integer.length);
- return Conversions.byteArrayToInt(integer);
- }
-
- protected void writeInteger(int value, FileChannel out) throws IOException {
- byte[] valueBytes = Conversions.intToByteArray(value);
- ByteBuffer buffer = ByteBuffer.wrap(valueBytes);
- out.write(buffer);
- }
-
-}
diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyRecord.java b/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyRecord.java
new file mode 100644
index 0000000000..a9249de30f
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyRecord.java
@@ -0,0 +1,117 @@
+package org.whispersystems.textsecure.storage;
+
+import android.util.Log;
+
+import com.google.protobuf.ByteString;
+
+import org.whispersystems.libaxolotl.InvalidKeyException;
+import org.whispersystems.libaxolotl.InvalidMessageException;
+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.state.PreKeyRecord;
+import org.whispersystems.textsecure.crypto.MasterCipher;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.util.Conversions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class TextSecurePreKeyRecord implements PreKeyRecord {
+
+ private static final int CURRENT_VERSION_MARKER = 1;
+
+ private final MasterSecret masterSecret;
+ private StorageProtos.PreKeyRecordStructure structure;
+
+ public TextSecurePreKeyRecord(MasterSecret masterSecret, int id, ECKeyPair keyPair) {
+ this.masterSecret = masterSecret;
+ this.structure = StorageProtos.PreKeyRecordStructure.newBuilder()
+ .setId(id)
+ .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey()
+ .serialize()))
+ .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey()
+ .serialize()))
+ .build();
+ }
+
+ public TextSecurePreKeyRecord(MasterSecret masterSecret, FileInputStream in)
+ throws IOException, InvalidMessageException
+ {
+ this.masterSecret = masterSecret;
+
+ MasterCipher masterCipher = new MasterCipher(masterSecret);
+ int recordVersion = readInteger(in);
+
+ if (recordVersion != CURRENT_VERSION_MARKER) {
+ Log.w("PreKeyRecord", "Invalid version: " + recordVersion);
+ return;
+ }
+
+ this.structure =
+ StorageProtos.PreKeyRecordStructure.parseFrom(masterCipher.decryptBytes(readBlob(in)));
+
+ in.close();
+ }
+
+ @Override
+ public int getId() {
+ return this.structure.getId();
+ }
+
+ @Override
+ public ECKeyPair getKeyPair() {
+ try {
+ ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0);
+ ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray());
+
+ return new ECKeyPair(publicKey, privateKey);
+ } catch (InvalidKeyException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public byte[] serialize() {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ MasterCipher masterCipher = new MasterCipher(masterSecret);
+
+ writeInteger(CURRENT_VERSION_MARKER, out);
+ writeBlob(masterCipher.encryptBytes(structure.toByteArray()), out);
+
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+
+ private byte[] readBlob(FileInputStream in) throws IOException {
+ int length = readInteger(in);
+ byte[] blobBytes = new byte[length];
+
+ in.read(blobBytes, 0, blobBytes.length);
+ return blobBytes;
+ }
+
+ private void writeBlob(byte[] blobBytes, OutputStream out) throws IOException {
+ writeInteger(blobBytes.length, out);
+ out.write(blobBytes);
+ }
+
+ private int readInteger(FileInputStream in) throws IOException {
+ byte[] integer = new byte[4];
+ in.read(integer, 0, integer.length);
+ return Conversions.byteArrayToInt(integer);
+ }
+
+ private void writeInteger(int value, OutputStream out) throws IOException {
+ byte[] valueBytes = Conversions.intToByteArray(value);
+ out.write(valueBytes);
+ }
+
+}
diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java b/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java
new file mode 100644
index 0000000000..5a41e545b9
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/storage/TextSecurePreKeyStore.java
@@ -0,0 +1,86 @@
+package org.whispersystems.textsecure.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.InvalidMessageException;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.PreKeyStore;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+public class TextSecurePreKeyStore implements PreKeyStore {
+
+ public static final String PREKEY_DIRECTORY = "prekeys";
+ private static final String TAG = TextSecurePreKeyStore.class.getSimpleName();
+
+ private final Context context;
+ private final MasterSecret masterSecret;
+
+ public TextSecurePreKeyStore(Context context, MasterSecret masterSecret) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ }
+
+ @Override
+ public PreKeyRecord load(int preKeyId) throws InvalidKeyIdException {
+ try {
+ FileInputStream fin = new FileInputStream(getPreKeyFile(preKeyId));
+ return new TextSecurePreKeyRecord(masterSecret, fin);
+ } catch (IOException | InvalidMessageException e) {
+ Log.w(TAG, e);
+ throw new InvalidKeyIdException(e);
+ }
+ }
+
+ @Override
+ public void store(int preKeyId, PreKeyRecord record) {
+ try {
+ RandomAccessFile recordFile = new RandomAccessFile(getPreKeyFile(preKeyId), "rw");
+ FileChannel out = recordFile.getChannel();
+
+ out.position(0);
+ out.write(ByteBuffer.wrap(record.serialize()));
+ out.truncate(out.position());
+
+ recordFile.close();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public boolean contains(int preKeyId) {
+ File record = getPreKeyFile(preKeyId);
+ return record.exists();
+ }
+
+ @Override
+ public void remove(int preKeyId) {
+ File record = getPreKeyFile(preKeyId);
+ record.delete();
+ }
+
+ private File getPreKeyFile(int preKeyId) {
+ return new File(getPreKeyDirectory(), String.valueOf(preKeyId));
+ }
+
+ private File getPreKeyDirectory() {
+ File directory = new File(context.getFilesDir(), PREKEY_DIRECTORY);
+
+ if (!directory.exists()) {
+ if (!directory.mkdirs()) {
+ Log.w(TAG, "PreKey directory creation failed!");
+ }
+ }
+
+ return directory;
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java b/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java
index 3d1278c12c..ecb737f8fc 100644
--- a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java
+++ b/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java
@@ -106,7 +106,6 @@ public class TextSecureSessionStore implements SessionStore {
return results;
}
-
private File getSessionFile(long recipientId, int deviceId) {
return new File(getSessionDirectory(), getSessionName(recipientId, deviceId));
}
diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
index f4a06a553a..dc63a4f3bf 100644
--- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
+++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
@@ -32,7 +32,6 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
-import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
@@ -45,11 +44,12 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.IncomingPushMessage;
-import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.InvalidNumberException;
@@ -178,7 +178,7 @@ public class ReceiveKeyActivity extends Activity {
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0);
} else {
- this.keyExchangeMessage = new KeyExchangeMessage(messageBody);
+ this.keyExchangeMessage = new KeyExchangeMessage(Base64.decodeWithoutPadding(messageBody));
}
} catch (IOException e) {
throw new AssertionError(e);
@@ -228,7 +228,7 @@ public class ReceiveKeyActivity extends Activity {
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
- } catch (InvalidMessageException e) {
+ } catch (InvalidKeyException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
index deb4c061b2..50d2f149da 100644
--- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
+++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
-import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -41,10 +40,12 @@ import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.DuplicateMessageException;
+import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -52,6 +53,7 @@ import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
+import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
@@ -428,7 +430,7 @@ public class DecryptingQueue {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false)
.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
- KeyExchangeMessage message = new KeyExchangeMessage(plaintextBody);
+ KeyExchangeMessage message = new KeyExchangeMessage(Base64.decodeWithoutPadding(plaintextBody));
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
if (processor.isStale(message)) {
@@ -440,7 +442,7 @@ public class DecryptingQueue {
} catch (InvalidVersionException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId);
- } catch (InvalidMessageException | RecipientFormattingException e) {
+ } catch (InvalidMessageException | IOException | InvalidKeyException | RecipientFormattingException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (LegacyMessageException e) {
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
index ceb2dcd0f6..0519d10953 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.DialogInterface;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
@@ -30,11 +29,13 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
+import org.whispersystems.textsecure.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -68,11 +69,11 @@ public class KeyExchangeInitiator {
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
KeyExchangeMessage message = new KeyExchangeMessage(sequence, flags,
- baseKey.getPublicKey(),
- ephemeralKey.getPublicKey(),
- identityKey.getPublicKey());
+ baseKey.getPublicKey(),
+ ephemeralKey.getPublicKey(),
+ identityKey.getPublicKey());
- OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
+ OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, Base64.encodeBytesWithoutPadding(message.serialize()));
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
SessionRecord sessionRecord = sessionStore.get(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
index eeec9cf1ee..d7809b4f4a 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
@@ -2,9 +2,7 @@ package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
-import android.util.Log;
-import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
@@ -12,25 +10,22 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
-import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
-import org.whispersystems.libaxolotl.InvalidMessageException;
-import org.whispersystems.libaxolotl.ecc.Curve;
-import org.whispersystems.libaxolotl.ecc.ECKeyPair;
-import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.SessionBuilder;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
-import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
+import org.whispersystems.libaxolotl.state.IdentityKeyStore;
+import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.PreKeyEntity;
-import org.whispersystems.textsecure.storage.InvalidKeyIdException;
-import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.storage.RecipientDevice;
+import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
-import org.whispersystems.textsecure.util.Medium;
+import org.whispersystems.textsecure.util.Base64;
/**
* This class processes key exchange interactions.
@@ -45,6 +40,7 @@ public class KeyExchangeProcessor {
private Context context;
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
+ private SessionBuilder sessionBuilder;
private SessionStore sessionStore;
public KeyExchangeProcessor(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
@@ -52,7 +48,14 @@ public class KeyExchangeProcessor {
this.context = context;
this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
- this.sessionStore = new TextSecureSessionStore(context, masterSecret);
+
+ IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
+ PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
+
+ this.sessionStore = new TextSecureSessionStore(context, masterSecret);
+ this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, identityKeyStore,
+ recipientDevice.getRecipientId(),
+ recipientDevice.getDeviceId());
}
public boolean isTrusted(PreKeyWhisperMessage message) {
@@ -87,83 +90,14 @@ public class KeyExchangeProcessor {
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException
{
- int preKeyId = message.getPreKeyId();
- ECPublicKey theirBaseKey = message.getBaseKey();
- ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
- IdentityKey theirIdentityKey = message.getIdentityKey();
-
- Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
-
- if (!PreKeyRecord.hasRecord(context, preKeyId) &&
- sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
- {
- Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
- return;
- }
-
- if (!PreKeyRecord.hasRecord(context, preKeyId))
- throw new InvalidKeyIdException("No such prekey: " + preKeyId);
-
- SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
- recipientDevice.getDeviceId());
- PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
- ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
- ECKeyPair ourEphemeralKey = ourBaseKey;
- IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
- boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
-
- if (!simultaneousInitiate) sessionRecord.reset();
- else sessionRecord.archiveCurrentState();
-
- RatchetingSession.initializeSession(sessionRecord.getSessionState(),
- ourBaseKey, theirBaseKey,
- ourEphemeralKey, theirEphemeralKey,
- ourIdentityKey, theirIdentityKey);
-
- sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
- sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
-
- if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
-
- sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
-
- if (preKeyId != Medium.MAX_VALUE) {
- PreKeyRecord.delete(context, preKeyId);
- }
-
+ sessionBuilder.process(message);
PreKeyService.initiateRefresh(context, masterSecret);
-
- DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
}
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
throws InvalidKeyException
{
- SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
- recipientDevice.getDeviceId());
- ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
- ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
- ECPublicKey theirBaseKey = message.getPublicKey();
- ECPublicKey theirEphemeralKey = theirBaseKey;
- IdentityKey theirIdentityKey = message.getIdentityKey();
- IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
-
- if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
- else sessionRecord.reset();
-
- RatchetingSession.initializeSession(sessionRecord.getSessionState(),
- ourBaseKey, theirBaseKey, ourEphemeralKey,
- theirEphemeralKey, ourIdentityKey, theirIdentityKey);
-
- sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
- sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
- sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
-
- sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
-
- DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
+ sessionBuilder.process(message);
if (threadId != -1) {
broadcastSecurityUpdateEvent(context, threadId);
@@ -171,84 +105,23 @@ public class KeyExchangeProcessor {
}
public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId)
- throws InvalidMessageException
+ throws InvalidKeyException
{
- try {
- SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
- recipientDevice.getDeviceId());
- Recipient recipient = RecipientFactory.getRecipientsForIds(context,
- String.valueOf(recipientDevice.getRecipientId()),
- false)
- .getPrimaryRecipient();
+ KeyExchangeMessage responseMessage = sessionBuilder.process(message);
+ Recipient recipient = RecipientFactory.getRecipientsForIds(context,
+ String.valueOf(recipientDevice.getRecipientId()),
+ false)
+ .getPrimaryRecipient();
- Log.w("KeyExchangeProcessor", "Received key exchange with sequence: " + message.getSequence());
-
- if (message.isInitiate()) {
- ECKeyPair ourBaseKey, ourEphemeralKey;
- IdentityKeyPair ourIdentityKey;
-
- int flags = KeyExchangeMessage.RESPONSE_FLAG;
-
- Log.w("KeyExchangeProcessor", "KeyExchange is an initiate.");
-
- if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
- Log.w("KeyExchangeProcessor", "We don't have a pending initiate...");
- ourBaseKey = Curve.generateKeyPair(true);
- ourEphemeralKey = Curve.generateKeyPair(true);
- ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
-
- sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
- ourEphemeralKey, ourIdentityKey);
- } else {
- Log.w("KeyExchangeProcessor", "We alredy have a pending initiate, responding as simultaneous initiate...");
- ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
- ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
- ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
- flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
-
- sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
- ourEphemeralKey, ourIdentityKey);
- }
-
- KeyExchangeMessage ourMessage = new KeyExchangeMessage(message.getSequence(),
- flags, ourBaseKey.getPublicKey(),
- ourEphemeralKey.getPublicKey(),
- ourIdentityKey.getPublicKey());
-
- OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient,
- ourMessage.serialize());
- MessageSender.send(context, masterSecret, textMessage, threadId, true);
- }
-
- if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
- Log.w("KeyExchangeProcessor", "No matching sequence for response. " +
- "Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
- return;
- }
-
- ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
- ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
- IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
-
- sessionRecord.reset();
-
- RatchetingSession.initializeSession(sessionRecord.getSessionState(),
- ourBaseKey, message.getBaseKey(),
- ourEphemeralKey, message.getEphemeralKey(),
- ourIdentityKey, message.getIdentityKey());
-
- sessionRecord.getSessionState().setSessionVersion(message.getVersion());
- sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
-
- DatabaseFactory.getIdentityDatabase(context)
- .saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
-
- DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
-
- broadcastSecurityUpdateEvent(context, threadId);
- } catch (InvalidKeyException e) {
- throw new InvalidMessageException(e);
+ if (responseMessage != null) {
+ String serializedResponse = Base64.encodeBytesWithoutPadding(responseMessage.serialize());
+ OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, serializedResponse);
+ MessageSender.send(context, masterSecret, textMessage, threadId, true);
}
+
+ DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
+
+ broadcastSecurityUpdateEvent(context, threadId);
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
diff --git a/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java
new file mode 100644
index 0000000000..e870c02a8c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java
@@ -0,0 +1,36 @@
+package org.thoughtcrime.securesms.crypto;
+
+import android.content.Context;
+
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.IdentityKeyPair;
+import org.whispersystems.libaxolotl.state.IdentityKeyStore;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+
+public class TextSecureIdentityKeyStore implements IdentityKeyStore {
+
+ private final Context context;
+ private final MasterSecret masterSecret;
+
+ public TextSecureIdentityKeyStore(Context context, MasterSecret masterSecret) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ }
+
+ @Override
+ public IdentityKeyPair getIdentityKeyPair() {
+ return IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ }
+
+ @Override
+ public int getLocalRegistrationId() {
+ return TextSecurePreferences.getLocalRegistrationId(context);
+ }
+
+ @Override
+ public void saveIdentity(long recipientId, IdentityKey identityKey) {
+ DatabaseFactory.getIdentityDatabase(context).saveIdentity(masterSecret, recipientId, identityKey);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/service/PreKeyService.java b/src/org/thoughtcrime/securesms/service/PreKeyService.java
index 11998c721d..034a27b775 100644
--- a/src/org/thoughtcrime/securesms/service/PreKeyService.java
+++ b/src/org/thoughtcrime/securesms/service/PreKeyService.java
@@ -10,10 +10,10 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PreKeyUtil;
import org.whispersystems.textsecure.push.PushServiceSocket;
-import org.whispersystems.textsecure.storage.PreKeyRecord;
import java.io.IOException;
import java.util.List;
diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java
index e9733f8fbd..b202310f08 100644
--- a/src/org/thoughtcrime/securesms/service/PushReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java
@@ -32,7 +32,7 @@ import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
-import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java
index f0de43cd6e..081a57fc85 100644
--- a/src/org/thoughtcrime/securesms/service/RegistrationService.java
+++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java
@@ -18,11 +18,11 @@ import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PreKeyUtil;
import org.whispersystems.textsecure.push.ExpectationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;
-import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
index 2943255242..b67cf2db17 100644
--- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
@@ -24,7 +24,6 @@ import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
-import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
@@ -44,11 +43,13 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
+import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.textsecure.crypto.MasterSecret;
-import org.whispersystems.textsecure.storage.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.RecipientDevice;
+import org.whispersystems.textsecure.util.Base64;
import java.io.IOException;
import java.util.List;
@@ -111,12 +112,12 @@ public class SmsReceiver {
Log.w("SmsReceiver", "Processing prekey message...");
try {
- Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
- RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
+ RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
- SmsTransportDetails transportDetails = new SmsTransportDetails();
- byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
- PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
+ SmsTransportDetails transportDetails = new SmsTransportDetails();
+ byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
+ PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
if (processor.isTrusted(preKeyExchange)) {
processor.processKeyExchangeMessage(preKeyExchange);
@@ -163,7 +164,7 @@ public class SmsReceiver {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
- KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(message.getMessageBody());
+ KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody()));
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
if (processor.isStale(exchangeMessage)) {
@@ -179,7 +180,7 @@ public class SmsReceiver {
} catch (InvalidVersionException e) {
Log.w("SmsReceiver", e);
message.setInvalidVersion(true);
- } catch (InvalidMessageException | RecipientFormattingException e) {
+ } catch (InvalidMessageException | InvalidKeyException | IOException | RecipientFormattingException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (LegacyMessageException e) {