Reorganize session store load/store operations.
This commit is contained in:
parent
d902c12941
commit
14b8f97de2
37 changed files with 666 additions and 635 deletions
|
@ -67,6 +67,11 @@ android {
|
|||
targetSdkVersion 19
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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() + "");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue