Reorganize session store load/store operations.

master
Moxie Marlinspike 2014-04-22 14:33:29 -07:00
parent d902c12941
commit 14b8f97de2
37 changed files with 666 additions and 635 deletions

View File

@ -67,6 +67,11 @@ android {
targetSdkVersion 19
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
android {
sourceSets {
main {

View File

@ -0,0 +1,54 @@
package org.whispersystems.test;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import java.util.LinkedList;
import java.util.List;
public class InMemorySessionRecord implements SessionRecord {
private SessionState currentSessionState;
private List<SessionState> previousSessionStates;
public InMemorySessionRecord() {
currentSessionState = new InMemorySessionState();
previousSessionStates = new LinkedList<>();
}
public InMemorySessionRecord(SessionRecord copy) {
currentSessionState = new InMemorySessionState(copy.getSessionState());
previousSessionStates = new LinkedList<>();
for (SessionState previousState : copy.getPreviousSessionStates()) {
previousSessionStates.add(new InMemorySessionState(previousState));
}
}
@Override
public SessionState getSessionState() {
return currentSessionState;
}
@Override
public List<SessionState> getPreviousSessionStates() {
return previousSessionStates;
}
@Override
public void reset() {
this.currentSessionState = new InMemorySessionState();
this.previousSessionStates = new LinkedList<>();
}
@Override
public void archiveCurrentState() {
this.previousSessionStates.add(currentSessionState);
this.currentSessionState = new InMemorySessionState();
}
@Override
public byte[] serialize() {
throw new AssertionError();
}
}

View File

@ -3,7 +3,7 @@ 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.state.SessionState;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.ChainKey;

View File

@ -1,44 +1,63 @@
package org.whispersystems.test;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.SessionStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.util.Pair;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class InMemorySessionStore implements SessionStore {
private SessionState currentSessionState;
private List<SessionState> previousSessionStates;
private Map<Pair<Long, Integer>, SessionRecord> sessions = new HashMap<>();
private SessionState checkedOutSessionState;
private List<SessionState> checkedOutPreviousSessionStates;
public InMemorySessionStore() {}
public InMemorySessionStore(SessionState sessionState) {
this.currentSessionState = sessionState;
this.previousSessionStates = new LinkedList<>();
this.checkedOutPreviousSessionStates = new LinkedList<>();
@Override
public SessionRecord get(long recipientId, int deviceId) {
if (contains(recipientId, deviceId)) {
return new InMemorySessionRecord(sessions.get(new Pair<>(recipientId, deviceId)));
} else {
return new InMemorySessionRecord();
}
}
@Override
public SessionState getSessionState() {
checkedOutSessionState = new InMemorySessionState(currentSessionState);
return checkedOutSessionState;
}
public List<Integer> getSubDeviceSessions(long recipientId) {
List<Integer> deviceIds = new LinkedList<>();
@Override
public List<SessionState> getPreviousSessionStates() {
checkedOutPreviousSessionStates = new LinkedList<>();
for (SessionState state : previousSessionStates) {
checkedOutPreviousSessionStates.add(new InMemorySessionState(state));
for (Pair<Long, Integer> key : sessions.keySet()) {
if (key.first() == recipientId) {
deviceIds.add(key.second());
}
}
return checkedOutPreviousSessionStates;
return deviceIds;
}
@Override
public void save() {
this.currentSessionState = this.checkedOutSessionState;
this.previousSessionStates = this.checkedOutPreviousSessionStates;
public void put(long recipientId, int deviceId, SessionRecord record) {
sessions.put(new Pair<>(recipientId, deviceId), record);
}
@Override
public boolean contains(long recipientId, int deviceId) {
return sessions.containsKey(new Pair<>(recipientId, deviceId));
}
@Override
public void delete(long recipientId, int deviceId) {
sessions.remove(new Pair<>(recipientId, deviceId));
}
@Override
public void deleteAll(long recipientId) {
for (Pair<Long, Integer> key : sessions.keySet()) {
if (key.first() == recipientId) {
sessions.remove(key);
}
}
}
}

View File

@ -9,8 +9,9 @@ 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.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
@ -24,16 +25,19 @@ public class SessionCipherTest extends AndroidTestCase {
throws InvalidKeyException, DuplicateMessageException,
LegacyMessageException, InvalidMessageException
{
SessionState aliceSessionState = new InMemorySessionState();
SessionState bobSessionState = new InMemorySessionState();
SessionRecord aliceSessionRecord = new InMemorySessionRecord();
SessionRecord bobSessionRecord = new InMemorySessionRecord();
initializeSessions(aliceSessionState, bobSessionState);
initializeSessions(aliceSessionRecord.getSessionState(), bobSessionRecord.getSessionState());
SessionStore aliceSessionStore = new InMemorySessionStore(aliceSessionState);
SessionStore bobSessionStore = new InMemorySessionStore(bobSessionState);
SessionStore aliceSessionStore = new InMemorySessionStore();
SessionStore bobSessionStore = new InMemorySessionStore();
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore);
SessionCipher bobCipher = new SessionCipher(bobSessionStore);
aliceSessionStore.put(2L, 1, aliceSessionRecord);
bobSessionStore.put(3L, 1, bobSessionRecord);
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore, 2L, 1);
SessionCipher bobCipher = new SessionCipher(bobSessionStore, 3L, 1);
byte[] alicePlaintext = "This is a plaintext message.".getBytes();
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);

View File

@ -6,7 +6,7 @@ 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.state.SessionState;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;

View File

@ -25,6 +25,9 @@ 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.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.Pair;
@ -45,18 +48,23 @@ public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final SessionStore sessionStore;
private final long recipientId;
private final int deviceId;
public SessionCipher(SessionStore sessionStore) {
public SessionCipher(SessionStore sessionStore, long recipientId, int deviceId) {
this.sessionStore = sessionStore;
this.recipientId = recipientId;
this.deviceId = deviceId;
}
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();
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
SessionState sessionState = sessionRecord.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(),
@ -74,7 +82,7 @@ public class SessionCipher {
}
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionStore.save();
sessionStore.put(recipientId, deviceId, sessionRecord);
return ciphertextMessage;
}
}
@ -83,13 +91,14 @@ public class SessionCipher {
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
synchronized (SESSION_LOCK) {
SessionState sessionState = sessionStore.getSessionState();
List<SessionState> previousStates = sessionStore.getPreviousSessionStates();
List<Exception> exceptions = new LinkedList<Exception>();
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates();
List<Exception> exceptions = new LinkedList<>();
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionStore.save();
sessionStore.put(recipientId, deviceId, sessionRecord);
return plaintext;
} catch (InvalidMessageException e) {
@ -99,7 +108,7 @@ public class SessionCipher {
for (SessionState previousState : previousStates) {
try {
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionStore.save();
sessionStore.put(recipientId, deviceId, sessionRecord);
return plaintext;
} catch (InvalidMessageException e) {
@ -137,7 +146,8 @@ public class SessionCipher {
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
return sessionStore.getSessionState().getRemoteRegistrationId();
SessionRecord record = sessionStore.get(recipientId, deviceId);
return record.getSessionState().getRemoteRegistrationId();
}
}
@ -201,9 +211,7 @@ public class SessionCipher {
messageKeys.getCounter());
return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
@ -214,9 +222,7 @@ public class SessionCipher {
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
@ -232,13 +238,9 @@ public class SessionCipher {
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) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | java.security.InvalidKeyException |
InvalidAlgorithmParameterException e)
{
throw new AssertionError(e);
}
}

View File

@ -1,12 +0,0 @@
package org.whispersystems.libaxolotl;
import java.util.List;
public interface SessionStore {
public SessionState getSessionState();
public List<SessionState> getPreviousSessionStates();
public void save();
}

View File

@ -16,18 +16,15 @@
*/
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.state.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;

View File

@ -0,0 +1,13 @@
package org.whispersystems.libaxolotl.state;
import java.util.List;
public interface SessionRecord {
public SessionState getSessionState();
public List<SessionState> getPreviousSessionStates();
public void reset();
public void archiveCurrentState();
public byte[] serialize();
}

View File

@ -1,5 +1,8 @@
package org.whispersystems.libaxolotl;
package org.whispersystems.libaxolotl.state;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.ChainKey;

View File

@ -0,0 +1,14 @@
package org.whispersystems.libaxolotl.state;
import java.util.List;
public interface SessionStore {
public SessionRecord get(long recipientId, int deviceId);
public List<Integer> getSubDeviceSessions(long recipientId);
public void put(long recipientId, int deviceId, SessionRecord record);
public boolean contains(long recipientId, int deviceId);
public void delete(long recipientId, int deviceId);
public void deleteAll(long recipientId);
}

View File

@ -31,6 +31,11 @@ android {
compileSdkVersion 19
buildToolsVersion '19.1.0'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
android {
sourceSets {
main {

View File

@ -20,8 +20,9 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
public class SessionCipherFactory {
@ -29,9 +30,10 @@ public class SessionCipherFactory {
MasterSecret masterSecret,
RecipientDevice recipient)
{
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
SessionRecordV2 record = new SessionRecordV2(context, masterSecret, recipient);
return new SessionCipher(record);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
if (sessionStore.contains(recipient.getRecipientId(), recipient.getDeviceId())) {
return new SessionCipher(sessionStore, recipient.getRecipientId(), recipient.getDeviceId());
} else {
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
}

View File

@ -1,80 +0,0 @@
package org.whispersystems.textsecure.storage;
import android.content.Context;
import android.util.Log;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.legacy.LocalKeyRecord;
import org.whispersystems.textsecure.storage.legacy.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.legacy.SessionRecordV1;
/**
* Helper class for generating key pairs and calculating ECDH agreements.
*
* @author Moxie Marlinspike
*/
public class Session {
public static void clearV1SessionFor(Context context, CanonicalRecipient recipient) {
//XXX Obviously we should probably do something more thorough here eventually.
LocalKeyRecord.delete(context, recipient);
RemoteKeyRecord.delete(context, recipient);
SessionRecordV1.delete(context, recipient);
}
public static void abortSessionFor(Context context, CanonicalRecipient recipient) {
Log.w("Session", "Aborting session, deleting keys...");
clearV1SessionFor(context, recipient);
SessionRecordV2.deleteAll(context, recipient);
}
public static boolean hasSession(Context context, MasterSecret masterSecret,
CanonicalRecipient recipient)
{
Log.w("Session", "Checking session...");
return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
{
RecipientDevice device = new RecipientDevice(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
return hasEncryptCapableSession(context, masterSecret, recipient, device);
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient,
RecipientDevice device)
{
return hasSession(context, masterSecret, recipient) &&
!SessionRecordV2.needsRefresh(context, masterSecret, device);
}
public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret,
CanonicalRecipient recipient)
{
return getRemoteIdentityKey(context, masterSecret, recipient.getRecipientId());
}
public static IdentityKey getRemoteIdentityKey(Context context,
MasterSecret masterSecret,
long recipientId)
{
if (SessionRecordV2.hasSession(context, masterSecret, recipientId,
RecipientDevice.DEFAULT_DEVICE_ID))
{
return new SessionRecordV2(context, masterSecret, recipientId,
RecipientDevice.DEFAULT_DEVICE_ID).getSessionState()
.getRemoteIdentityKey();
} else {
return null;
}
}
}

View File

@ -1,229 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.storage;
import android.content.Context;
import android.util.Log;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.SessionStore;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.LinkedList;
import java.util.List;
import static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
/**
* A disk record representing a current session.
*
* @author Moxie Marlinspike
*/
public class SessionRecordV2 extends Record implements SessionStore {
private static final Object FILE_LOCK = new Object();
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int CURRENT_VERSION = 2;
private final MasterSecret masterSecret;
private TextSecureSessionState sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
private List<SessionState> previousStates = new LinkedList<SessionState>();
public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
}
public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId, int deviceId) {
super(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId));
this.masterSecret = masterSecret;
loadData();
}
private static String getRecordName(long recipientId, int deviceId) {
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
}
public TextSecureSessionState getSessionState() {
return sessionState;
}
public List<SessionState> getPreviousSessionStates() {
return previousStates;
}
public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) {
List<Integer> results = new LinkedList<Integer>();
File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
String[] children = parent.list();
if (children == null) return results;
for (String child : children) {
try {
String[] parts = child.split("[.]", 2);
long sessionRecipientId = Long.parseLong(parts[0]);
if (sessionRecipientId == recipient.getRecipientId() && parts.length > 1) {
results.add(Integer.parseInt(parts[1]));
}
} catch (NumberFormatException e) {
Log.w("SessionRecordV2", e);
}
}
return results;
}
public static void deleteAll(Context context, CanonicalRecipient recipient) {
List<Integer> devices = getSessionSubDevices(context, recipient);
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID));
for (int device : devices) {
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(), device));
}
}
public static void delete(Context context, RecipientDevice recipientDevice) {
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId()));
}
public static boolean hasSession(Context context, MasterSecret masterSecret,
RecipientDevice recipient)
{
return hasSession(context, masterSecret, recipient.getRecipientId(), recipient.getDeviceId());
}
public static boolean hasSession(Context context, MasterSecret masterSecret,
long recipientId, int deviceId)
{
return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) &&
new SessionRecordV2(context, masterSecret, recipientId, deviceId).sessionState.hasSenderChain();
}
public static boolean needsRefresh(Context context, MasterSecret masterSecret,
RecipientDevice recipient)
{
return new SessionRecordV2(context, masterSecret,
recipient.getRecipientId(),
recipient.getDeviceId()).getSessionState()
.getNeedsRefresh();
}
public void clear() {
this.sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
this.previousStates = new LinkedList<SessionState>();
}
public void archiveCurrentState() {
this.previousStates.add(sessionState);
this.sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
}
public void save() {
synchronized (FILE_LOCK) {
try {
List<SessionStructure> previousStructures = new LinkedList<SessionStructure>();
for (SessionState previousState : previousStates) {
previousStructures.add(((TextSecureSessionState)previousState).getStructure());
}
RecordStructure record = RecordStructure.newBuilder()
.setCurrentSession(sessionState.getStructure())
.addAllPreviousSessions(previousStructures)
.build();
RandomAccessFile file = openRandomAccessFile();
FileChannel out = file.getChannel();
out.position(0);
MasterCipher cipher = new MasterCipher(masterSecret);
writeInteger(CURRENT_VERSION, out);
writeBlob(cipher.encryptBytes(record.toByteArray()), out);
out.truncate(out.position());
file.close();
} catch (IOException ioe) {
throw new IllegalArgumentException(ioe);
}
}
}
private void loadData() {
synchronized (FILE_LOCK) {
try {
FileInputStream in = this.openInputStream();
int versionMarker = readInteger(in);
if (versionMarker > CURRENT_VERSION) {
throw new AssertionError("Unknown version: " + versionMarker);
}
MasterCipher cipher = new MasterCipher(masterSecret);
byte[] encryptedBlob = readBlob(in);
if (versionMarker == SINGLE_STATE_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
SessionStructure sessionStructure = SessionStructure.parseFrom(plaintextBytes);
this.sessionState = new TextSecureSessionState(sessionStructure);
} else if (versionMarker == ARCHIVE_STATES_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
RecordStructure recordStructure = RecordStructure.parseFrom(plaintextBytes);
this.sessionState = new TextSecureSessionState(recordStructure.getCurrentSession());
this.previousStates = new LinkedList<SessionState>();
for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) {
this.previousStates.add(new TextSecureSessionState(sessionStructure));
}
} else {
throw new AssertionError("Unknown version: " + versionMarker);
}
in.close();
} catch (FileNotFoundException e) {
Log.w("SessionRecordV2", "No session information found.");
// XXX
} catch (IOException ioe) {
Log.w("SessionRecordV2", ioe);
// XXX
} catch (InvalidMessageException ime) {
Log.w("SessionRecordV2", ime);
// XXX
}
}
}
}

View File

@ -0,0 +1,32 @@
package org.whispersystems.textsecure.storage;
import android.content.Context;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
public class SessionUtil {
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
{
return hasEncryptCapableSession(context, masterSecret,
new RecipientDevice(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID));
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
RecipientDevice recipientDevice)
{
long recipientId = recipientDevice.getRecipientId();
int deviceId = recipientDevice.getDeviceId();
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
return
sessionStore.contains(recipientId, deviceId) &&
!sessionStore.get(recipientId, deviceId).getSessionState().getNeedsRefresh();
}
}

View File

@ -0,0 +1,142 @@
package org.whispersystems.textsecure.storage;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
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.OutputStream;
import java.util.LinkedList;
import java.util.List;
import static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
public class TextSecureSessionRecord implements SessionRecord {
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int CURRENT_VERSION = 2;
private TextSecureSessionState sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
private List<SessionState> previousStates = new LinkedList<>();
private final MasterSecret masterSecret;
public TextSecureSessionRecord(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
}
public TextSecureSessionRecord(MasterSecret masterSecret, FileInputStream in)
throws IOException, InvalidMessageException
{
this.masterSecret = masterSecret;
int versionMarker = readInteger(in);
if (versionMarker > CURRENT_VERSION) {
throw new AssertionError("Unknown version: " + versionMarker);
}
MasterCipher cipher = new MasterCipher(masterSecret);
byte[] encryptedBlob = readBlob(in);
if (versionMarker == SINGLE_STATE_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
SessionStructure sessionStructure = SessionStructure.parseFrom(plaintextBytes);
this.sessionState = new TextSecureSessionState(sessionStructure);
} else if (versionMarker == ARCHIVE_STATES_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
RecordStructure recordStructure = RecordStructure.parseFrom(plaintextBytes);
this.sessionState = new TextSecureSessionState(recordStructure.getCurrentSession());
this.previousStates = new LinkedList<>();
for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) {
this.previousStates.add(new TextSecureSessionState(sessionStructure));
}
} else {
throw new AssertionError("Unknown version: " + versionMarker);
}
in.close();
}
@Override
public SessionState getSessionState() {
return sessionState;
}
@Override
public List<SessionState> getPreviousSessionStates() {
return previousStates;
}
@Override
public void reset() {
this.sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
this.previousStates = new LinkedList<>();
}
@Override
public void archiveCurrentState() {
this.previousStates.add(sessionState);
this.sessionState = new TextSecureSessionState(SessionStructure.newBuilder().build());
}
@Override
public byte[] serialize() {
try {
List<SessionStructure> previousStructures = new LinkedList<>();
for (SessionState previousState : previousStates) {
previousStructures.add(((TextSecureSessionState)previousState).getStructure());
}
RecordStructure record = RecordStructure.newBuilder()
.setCurrentSession(sessionState.getStructure())
.addAllPreviousSessions(previousStructures)
.build();
ByteArrayOutputStream serialized = new ByteArrayOutputStream();
MasterCipher cipher = new MasterCipher(masterSecret);
writeInteger(CURRENT_VERSION, serialized);
writeBlob(cipher.encryptBytes(record.toByteArray()), serialized);
return serialized.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);
}
}

View File

@ -23,7 +23,7 @@ import com.google.protobuf.ByteString;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionState;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;

View File

@ -0,0 +1,130 @@
package org.whispersystems.textsecure.storage;
import android.content.Context;
import android.util.Log;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
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;
import java.util.LinkedList;
import java.util.List;
public class TextSecureSessionStore implements SessionStore {
private static final String TAG = TextSecureSessionStore.class.getSimpleName();
private static final String SESSIONS_DIRECTORY_V2 = "sessions-v2";
private static final Object FILE_LOCK = new Object();
private final Context context;
private final MasterSecret masterSecret;
public TextSecureSessionStore(Context context, MasterSecret masterSecret) {
this.context = context.getApplicationContext();
this.masterSecret = masterSecret;
}
@Override
public SessionRecord get(long recipientId, int deviceId) {
synchronized (FILE_LOCK) {
try {
FileInputStream input = new FileInputStream(getSessionFile(recipientId, deviceId));
return new TextSecureSessionRecord(masterSecret, input);
} catch (InvalidMessageException | IOException e) {
Log.w(TAG, "No existing session information found.");
return new TextSecureSessionRecord(masterSecret);
}
}
}
@Override
public void put(long recipientId, int deviceId, SessionRecord record) {
try {
RandomAccessFile sessionFile = new RandomAccessFile(getSessionFile(recipientId, deviceId), "rw");
FileChannel out = sessionFile.getChannel();
out.position(0);
out.write(ByteBuffer.wrap(record.serialize()));
out.truncate(out.position());
sessionFile.close();
} catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
public boolean contains(long recipientId, int deviceId) {
return getSessionFile(recipientId, deviceId).exists() &&
get(recipientId, deviceId).getSessionState().hasSenderChain();
}
@Override
public void delete(long recipientId, int deviceId) {
getSessionFile(recipientId, deviceId).delete();
}
@Override
public void deleteAll(long recipientId) {
List<Integer> devices = getSubDeviceSessions(recipientId);
delete(recipientId, RecipientDevice.DEFAULT_DEVICE_ID);
for (int device : devices) {
delete(recipientId, device);
}
}
@Override
public List<Integer> getSubDeviceSessions(long recipientId) {
List<Integer> results = new LinkedList<>();
File parent = getSessionDirectory();
String[] children = parent.list();
if (children == null) return results;
for (String child : children) {
try {
String[] parts = child.split("[.]", 2);
long sessionRecipientId = Long.parseLong(parts[0]);
if (sessionRecipientId == recipientId && parts.length > 1) {
results.add(Integer.parseInt(parts[1]));
}
} catch (NumberFormatException e) {
Log.w("SessionRecordV2", e);
}
}
return results;
}
private File getSessionFile(long recipientId, int deviceId) {
return new File(getSessionDirectory(), getSessionName(recipientId, deviceId));
}
private File getSessionDirectory() {
File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2);
if (!directory.exists()) {
if (!directory.mkdirs()) {
Log.w(TAG, "Session directory creation failed!");
}
}
return directory;
}
private String getSessionName(long recipientId, int deviceId) {
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
}
}

View File

@ -1,34 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.storage.legacy;
import android.content.Context;
import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.Record;
public class LocalKeyRecord {
public static void delete(Context context, CanonicalRecipient recipient) {
Record.delete(context, Record.SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
private static String getFileNameForRecipient(CanonicalRecipient recipient) {
return recipient.getRecipientId() + "-local";
}
}

View File

@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.storage.legacy;
import android.content.Context;
import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.Record;
/**
* Represents the current and last public key belonging to the "remote"
* endpoint in an encrypted session. These are stored on disk.
*
* @author Moxie Marlinspike
*/
public class RemoteKeyRecord {
public static void delete(Context context, CanonicalRecipient recipient) {
Record.delete(context, Record.SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
}
private static String getFileNameForRecipient(CanonicalRecipient recipient) {
return recipient.getRecipientId() + "-remote";
}
}

View File

@ -1,18 +0,0 @@
package org.whispersystems.textsecure.storage.legacy;
import android.content.Context;
import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.Record;
/**
* A disk record representing a current session.
*
* @author Moxie Marlinspike
*/
public class SessionRecordV1 {
public static void delete(Context context, CanonicalRecipient recipient) {
Record.delete(context, Record.SESSIONS_DIRECTORY, recipient.getRecipientId() + "");
}
}

View File

@ -30,10 +30,10 @@ import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
/**
* Activity which prompts the user to initiate a secure
@ -65,8 +65,8 @@ public class AutoInitiateActivity extends Activity {
private void initializeResources() {
this.threadId = this.getIntent().getLongExtra("threadId", -1);
this.recipient = (Recipient)this.getIntent().getParcelableExtra("recipient");
this.masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("masterSecret");
this.recipient = this.getIntent().getParcelableExtra("recipient");
this.masterSecret = this.getIntent().getParcelableExtra("masterSecret");
((Button)findViewById(R.id.initiate_button)).setOnClickListener(new OkListener());
((Button)findViewById(R.id.cancel_button)).setOnClickListener(new CancelListener());
@ -117,6 +117,7 @@ public class AutoInitiateActivity extends Activity {
MasterSecret masterSecret,
Recipient recipient)
{
return !Session.hasSession(context, masterSecret, recipient);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
return sessionStore.contains(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
}
}

View File

@ -103,11 +103,11 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
@ -316,9 +316,11 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (isEncryptedConversation && isSingleConversation()) {
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean hasSession = Session.hasSession(this, masterSecret, primaryRecipient);
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
boolean isSecureDestination = isSingleConversation() && sessionStore.contains(primaryRecipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
getMenuInflater().inflate(R.menu.conversation_button_context, menu);
@ -334,7 +336,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
menu.removeItem(R.id.menu_context_send_push);
}
if (!hasSession) {
if (!isSecureDestination) {
menu.removeItem(R.id.menu_context_send_encrypted_mms);
menu.removeItem(R.id.menu_context_send_encrypted_sms);
}
@ -418,25 +420,14 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
@Override
public void onClick(DialogInterface dialog, int which) {
if (isSingleConversation()) {
ConversationActivity self = ConversationActivity.this;
Recipient recipient = getRecipients().getPrimaryRecipient();
ConversationActivity self = ConversationActivity.this;
if (SessionRecordV2.hasSession(self, masterSecret,
recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID))
{
OutgoingEndSessionMessage endSessionMessage =
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE"));
OutgoingEndSessionMessage endSessionMessage =
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE"));
long allocatedThreadId = MessageSender.send(self, masterSecret,
endSessionMessage, threadId, false);
long allocatedThreadId = MessageSender.send(self, masterSecret, endSessionMessage, threadId, false);
sendComplete(recipients, allocatedThreadId, allocatedThreadId != self.threadId);
} else {
Session.abortSessionFor(self, recipient);
initializeSecurity();
initializeTitleBar();
}
sendComplete(recipients, allocatedThreadId, allocatedThreadId != self.threadId);
}
}
});
@ -699,9 +690,11 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private void initializeSecurity() {
TypedArray drawables = obtainStyledAttributes(SEND_ATTRIBUTES);
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
boolean isSecureDestination = isSingleConversation() && Session.hasSession(this, masterSecret, primaryRecipient);
boolean isSecureDestination = isSingleConversation() && sessionStore.contains(primaryRecipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
if (isPushDestination || isSecureDestination) {
this.isEncryptedConversation = true;

View File

@ -35,6 +35,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.VersionTracker;
import java.io.File;
import java.util.SortedSet;
import java.util.TreeSet;
@ -45,6 +46,7 @@ public class DatabaseUpgradeActivity extends Activity {
public static final int TOFU_IDENTITIES_VERSION = 50;
public static final int CURVE25519_VERSION = 63;
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73;
public static final int NO_V1_VERSION = 83;
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@ -137,6 +139,22 @@ public class DatabaseUpgradeActivity extends Activity {
}
}
if (params[0] < NO_V1_VERSION) {
File v1sessions = new File(context.getFilesDir(), "sessions");
if (v1sessions.exists() && v1sessions.isDirectory()) {
File[] contents = v1sessions.listFiles();
if (contents != null) {
for (File session : contents) {
session.delete();
}
}
v1sessions.delete();
}
}
return null;
}

View File

@ -27,9 +27,12 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
/**
* Activity for verifying identity keys.
@ -92,7 +95,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
}
if (identityKey == null) {
identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient);
identityKey = getRemoteIdentityKey(masterSecret, recipient);
}
if (identityKey == null) {
@ -128,7 +131,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
@Override
protected void initiateScan() {
IdentityKey identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient);
IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient);
if (identityKey == null) {
Toast.makeText(this, R.string.VerifyIdentityActivity_recipient_has_no_identity_key_exclamation,
@ -150,7 +153,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
@Override
protected IdentityKey getIdentityKeyToCompare() {
return Session.getRemoteIdentityKey(this, masterSecret, recipient);
return getRemoteIdentityKey(masterSecret, recipient);
}
@Override
@ -177,4 +180,16 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
protected String getVerifiedTitle() {
return getString(R.string.VerifyIdentityActivity_verified_exclamation);
}
private IdentityKey getRemoteIdentityKey(MasterSecret masterSecret, Recipient recipient) {
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
SessionRecord record = sessionStore.get(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
if (record == null) {
return null;
}
return record.getSessionState().getRemoteIdentityKey();
}
}

View File

@ -47,12 +47,12 @@ import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
@ -197,11 +197,12 @@ public class DecryptingQueue {
public void run() {
try {
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false);
Recipient recipient = recipients.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
if (!SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
if (!sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
sendResult(PushReceiver.RESULT_NO_SESSION);
return;
}
@ -211,18 +212,12 @@ public class DecryptingQueue {
message = message.withBody(plaintextBody);
sendResult(PushReceiver.RESULT_OK);
} catch (InvalidMessageException e) {
Log.w("DecryptionQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
} catch (RecipientFormattingException e) {
} catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
} catch (DuplicateMessageException e) {
Log.w("DecryptingQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_DUPLICATE);
} catch (LegacyMessageException e) {
Log.w("DecryptionQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_FAILED);
}
}
@ -270,6 +265,7 @@ public class DecryptingQueue {
try {
String messageFrom = pdu.getFrom().getString();
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, messageFrom, false);
Recipient recipient = recipients.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
@ -281,7 +277,7 @@ public class DecryptingQueue {
return;
}
if (!Session.hasSession(context, masterSecret, recipient)) {
if (!sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
Log.w("DecryptingQueue", "No such recipient session for MMS...");
database.markAsNoSession(messageId, threadId);
return;
@ -316,24 +312,15 @@ public class DecryptingQueue {
Log.w("DecryptingQueue", "Successfully decrypted MMS!");
database.insertSecureDecryptedMessageInbox(masterSecret, new IncomingMediaMessage(plaintextPdu), threadId);
database.delete(messageId);
} catch (RecipientFormattingException rfe) {
} catch (RecipientFormattingException | IOException | MmsException | InvalidMessageException rfe) {
Log.w("DecryptingQueue", rfe);
database.markAsDecryptFailed(messageId, threadId);
} catch (InvalidMessageException ime) {
Log.w("DecryptingQueue", ime);
database.markAsDecryptFailed(messageId, threadId);
} catch (DuplicateMessageException dme) {
Log.w("DecryptingQueue", dme);
database.markAsDecryptDuplicate(messageId, threadId);
} catch (LegacyMessageException lme) {
Log.w("DecryptingQueue", lme);
database.markAsLegacyVersion(messageId, threadId);
} catch (MmsException mme) {
Log.w("DecryptingQueue", mme);
database.markAsDecryptFailed(messageId, threadId);
} catch (IOException e) {
Log.w("DecryptingQueue", e);
database.markAsDecryptFailed(messageId, threadId);
}
}
}
@ -373,6 +360,7 @@ public class DecryptingQueue {
String plaintextBody;
try {
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false);
Recipient recipient = recipients.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
@ -380,7 +368,7 @@ public class DecryptingQueue {
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes());
if (!Session.hasSession(context, masterSecret, recipient)) {
if (!sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
if (WhisperMessage.isLegacy(decodedCiphertext)) database.markAsLegacyVersion(messageId);
else database.markAsNoSession(messageId);
return;
@ -393,11 +381,11 @@ public class DecryptingQueue {
if (isEndSession &&
"TERMINATE".equals(plaintextBody) &&
SessionRecordV2.hasSession(context, masterSecret, recipientDevice))
sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
{
Session.abortSessionFor(context, recipient);
sessionStore.delete(recipientDevice.getRecipientId(), recipientDevice.getDeviceId());
}
} catch (InvalidMessageException e) {
} catch (InvalidMessageException | IOException | RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
@ -405,14 +393,6 @@ public class DecryptingQueue {
Log.w("DecryptionQueue", lme);
database.markAsLegacyVersion(messageId);
return;
} catch (RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
} catch (IOException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
} catch (DuplicateMessageException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptDuplicate(messageId);

View File

@ -30,9 +30,11 @@ 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.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.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@ -70,12 +72,12 @@ public class KeyExchangeInitiator {
ephemeralKey.getPublicKey(),
identityKey.getPublicKey());
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize());
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
SessionRecord sessionRecord = sessionStore.get(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
sessionRecordV2.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionRecordV2.save();
sessionRecord.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionStore.put(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID, sessionRecord);
MessageSender.send(context, masterSecret, textMessage, -1, false);
}
@ -83,11 +85,10 @@ public class KeyExchangeInitiator {
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
Recipient recipient)
{
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
return
new SessionRecordV2(context, masterSecret, recipientDevice)
.getSessionState()
.hasPendingKeyExchange();
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
SessionRecord sessionRecord = sessionStore.get(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
return sessionRecord.getSessionState().hasPendingPreKey();
}
private static int getRandomSequence() {

View File

@ -21,13 +21,14 @@ import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
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.Session;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Medium;
/**
@ -41,14 +42,14 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
private Context context;
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionRecordV2 sessionRecord;
private SessionStore sessionStore;
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
{
this.context = context;
this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
this.sessionRecord = new SessionRecordV2(context, masterSecret, recipientDevice);
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
}
public boolean isTrusted(PreKeyWhisperMessage message) {
@ -70,7 +71,10 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
}
public boolean isStale(KeyExchangeMessage m) {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) m;
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
return
message.isResponse() &&
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
@ -88,7 +92,9 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
if (!PreKeyRecord.hasRecord(context, preKeyId) && SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
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;
}
@ -96,13 +102,15 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
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.clear();
if (!simultaneousInitiate) sessionRecord.reset();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
@ -110,13 +118,12 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ourEphemeralKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionRecord.save();
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
if (preKeyId != Medium.MAX_VALUE) {
PreKeyRecord.delete(context, preKeyId);
@ -131,6 +138,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
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();
@ -139,7 +148,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.clear();
else sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey, ourEphemeralKey,
@ -149,7 +158,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.save();
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
@ -164,11 +173,13 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
throws InvalidMessageException
{
try {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message;
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) _message;
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
@ -219,7 +230,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
sessionRecord.clear();
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, message.getBaseKey(),
@ -227,8 +238,7 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ourIdentityKey, message.getIdentityKey());
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.save();
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());

View File

@ -33,7 +33,6 @@ import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Util;
@ -403,8 +402,15 @@ public class DatabaseFactory {
String name = session.getName();
if (name.matches("[0-9]+")) {
long recipientId = Long.parseLong(name);
IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId);
long recipientId = Long.parseLong(name);
IdentityKey identityKey = null;
// NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse
// V1 session records. Despite our usual attempts to avoid using shared code in the
// upgrade path, this is too complex to put here directly. Thus, unfortunately
// this operation is now lost to the ages. From the git log, it seems to have been
// almost exactly a year since this went in, so hopefully the bulk of people have
// already upgraded.
// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId);
if (identityKey != null) {
MasterCipher masterCipher = new MasterCipher(masterSecret);

View File

@ -29,12 +29,13 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
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.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
import ws.com.google.android.mms.MmsException;
@ -131,19 +132,12 @@ public class PushReceiver {
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
} catch (InvalidKeyException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (InvalidVersionException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, true);
} catch (InvalidKeyIdException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (InvalidMessageException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (RecipientFormattingException e) {
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
RecipientFormattingException e)
{
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
}
@ -189,7 +183,9 @@ public class PushReceiver {
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage);
database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody());
Session.abortSessionFor(context, recipient);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
sessionStore.deleteAll(recipient.getRecipientId());
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
} catch (RecipientFormattingException e) {
Log.w("PushReceiver", e);

View File

@ -42,8 +42,9 @@ import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
public class SmsSender {
@ -61,7 +62,7 @@ public class SmsSender {
if (SendReceiveService.SEND_SMS_ACTION.equals(intent.getAction())) {
handleSendMessage(masterSecret, intent);
} else if (SendReceiveService.SENT_SMS_ACTION.equals(intent.getAction())) {
handleSentMessage(intent);
handleSentMessage(masterSecret, intent);
} else if (SendReceiveService.DELIVERED_SMS_ACTION.equals(intent.getAction())) {
handleDeliveredMessage(intent);
}
@ -116,7 +117,7 @@ public class SmsSender {
}
}
private void handleSentMessage(Intent intent) {
private void handleSentMessage(MasterSecret masterSecret, Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
int result = intent.getIntExtra("ResultCode", -31337);
boolean upgraded = intent.getBooleanExtra("upgraded", false);
@ -138,7 +139,8 @@ public class SmsSender {
if (record != null && record.isEndSession()) {
Log.w("SmsSender", "Ending session...");
Session.abortSessionFor(context, record.getIndividualRecipient());
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
sessionStore.deleteAll(record.getIndividualRecipient().getRecipientId());
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, record.getThreadId());
}

View File

@ -38,7 +38,7 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
@ -168,13 +168,15 @@ public class MmsTransport {
return encryptedPdu;
}
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) throws InsecureFallbackApprovalException {
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes)
throws InsecureFallbackApprovalException
{
try {
TextTransport transportDetails = new TextTransport();
Recipient recipient = RecipientFactory.getRecipientsFromString(context, recipientString, false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) {
if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipientDevice)) {
throw new InsecureFallbackApprovalException("No session exists for this secure message.");
}

View File

@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.AttachmentCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
@ -55,7 +56,8 @@ import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.StaleDevices;
import org.whispersystems.textsecure.push.StaleDevicesException;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.InvalidNumberException;
@ -93,7 +95,8 @@ public class PushTransport extends BaseTransport {
deliver(socket, recipient, threadId, plaintext);
if (message.isEndSession()) {
SessionRecordV2.deleteAll(context, recipient);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
sessionStore.deleteAll(recipient.getRecipientId());
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, threadId);
}
@ -121,8 +124,8 @@ public class PushTransport extends BaseTransport {
recipients = RecipientFactory.getRecipientsFromString(context, destination, false);
}
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<UntrustedIdentityException>();
List<UnregisteredUserException> unregisteredUsers = new LinkedList<UnregisteredUserException>();
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
for (Recipient recipient : recipients.getRecipientsList()) {
try {
@ -164,7 +167,7 @@ public class PushTransport extends BaseTransport {
private List<PushAttachmentPointer> getPushAttachmentPointers(PushServiceSocket socket, PduBody body)
throws IOException
{
List<PushAttachmentPointer> attachments = new LinkedList<PushAttachmentPointer>();
List<PushAttachmentPointer> attachments = new LinkedList<>();
for (int i=0;i<body.getPartsNum();i++) {
String contentType = Util.toIsoString(body.getPart(i).getContentType());
@ -198,12 +201,12 @@ public class PushTransport extends BaseTransport {
throws InvalidNumberException, IOException, UntrustedIdentityException
{
try {
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
long recipientId = recipient.getRecipientId();
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
long recipientId = recipient.getRecipientId();
for (int extraDeviceId : mismatchedDevices.getExtraDevices()) {
PushAddress address = PushAddress.create(context, recipientId, e164number, extraDeviceId);
SessionRecordV2.delete(context, address);
sessionStore.delete(recipientId, extraDeviceId);
}
for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
@ -222,19 +225,12 @@ public class PushTransport extends BaseTransport {
}
}
private void handleStaleDevices(Recipient recipient, StaleDevices staleDevices)
throws IOException
{
try {
long recipientId = recipient.getRecipientId();
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
private void handleStaleDevices(Recipient recipient, StaleDevices staleDevices) {
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
long recipientId = recipient.getRecipientId();
for (int staleDeviceId : staleDevices.getStaleDevices()) {
PushAddress address = PushAddress.create(context, recipientId, e164number, staleDeviceId);
SessionRecordV2.delete(context, address);
}
} catch (InvalidNumberException e) {
throw new IOException(e);
for (int staleDeviceId : staleDevices.getStaleDevices()) {
sessionStore.delete(recipientId, staleDeviceId);
}
}
@ -306,15 +302,16 @@ public class PushTransport extends BaseTransport {
Recipient recipient, byte[] plaintext)
throws IOException, InvalidNumberException, UntrustedIdentityException
{
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
long recipientId = recipient.getRecipientId();
PushAddress masterDevice = PushAddress.create(context, recipientId, e164number, 1);
PushBody masterBody = getEncryptedMessage(socket, threadId, masterDevice, plaintext);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
long recipientId = recipient.getRecipientId();
PushAddress masterDevice = PushAddress.create(context, recipientId, e164number, 1);
PushBody masterBody = getEncryptedMessage(socket, threadId, masterDevice, plaintext);
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
List<OutgoingPushMessage> messages = new LinkedList<>();
messages.add(new OutgoingPushMessage(masterDevice, masterBody));
for (int deviceId : SessionRecordV2.getSessionSubDevices(context, recipient)) {
for (int deviceId : sessionStore.getSubDeviceSessions(recipientId)) {
PushAddress device = PushAddress.create(context, recipientId, e164number, deviceId);
PushBody body = getEncryptedMessage(socket, threadId, device, plaintext);
@ -328,9 +325,7 @@ public class PushTransport extends BaseTransport {
PushAddress pushAddress, byte[] plaintext)
throws IOException, UntrustedIdentityException
{
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress) ||
SessionRecordV2.needsRefresh(context, masterSecret, pushAddress))
{
if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, pushAddress)) {
try {
List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress);

View File

@ -32,10 +32,13 @@ import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import java.util.ArrayList;
@ -174,7 +177,7 @@ public class SmsTransport extends BaseTransport {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) {
if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipientDevice)) {
throw new InsecureFallbackApprovalException("No session exists for this secure message.");
}

View File

@ -38,7 +38,7 @@ import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.ContactTokenDetails;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.Session;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.util.DirectoryUtil;
import org.whispersystems.textsecure.util.InvalidNumberException;
@ -177,7 +177,7 @@ public class UniversalTransport {
Log.w("UniversalTransport", "Falling back to MMS");
DatabaseFactory.getMmsDatabase(context).markAsForcedSms(mediaMessage.getDatabaseMessageId());
return mmsTransport.deliver(mediaMessage);
} else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) {
} else if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipient)) {
Log.w("UniversalTransport", "Marking message as pending insecure SMS fallback");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {
@ -199,7 +199,7 @@ public class UniversalTransport {
Log.w("UniversalTransport", "Falling back to SMS");
DatabaseFactory.getSmsDatabase(context).markAsForcedSms(smsMessage.getId());
smsTransport.deliver(smsMessage);
} else if (!Session.hasEncryptCapableSession(context, masterSecret, recipient)) {
} else if (!SessionUtil.hasEncryptCapableSession(context, masterSecret, recipient)) {
Log.w("UniversalTransport", "Marking message as pending insecure fallback.");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {