Beginning of libtextsecure refactor.

1) Break out appropriate components.

2) Switch the incoming pipeline from SendReceiveService to
   the JobManager.
This commit is contained in:
Moxie Marlinspike 2014-11-03 15:16:04 -08:00
parent 4cab657ebe
commit a3f1d9cdfd
152 changed files with 3521 additions and 3280 deletions

View File

@ -16,10 +16,14 @@
*/
package org.whispersystems.jobqueue;
import android.util.Log;
import org.whispersystems.jobqueue.persistence.PersistentStorage;
public class JobConsumer extends Thread {
private static final String TAG = JobConsumer.class.getSimpleName();
enum JobResult {
SUCCESS,
FAILURE,
@ -69,6 +73,7 @@ public class JobConsumer extends Thread {
job.onRun();
return JobResult.SUCCESS;
} catch (Throwable throwable) {
Log.w(TAG, throwable);
if (!job.onShouldRetry(throwable)) {
return JobResult.FAILURE;
} else if (!job.isRequirementsMet()) {

View File

@ -39,14 +39,16 @@ public class JobManager implements RequirementListener {
private final PersistentStorage persistentStorage;
public JobManager(Context context, String name,
RequirementProvider requirementProvider,
List<RequirementProvider> requirementProviders,
JobSerializer jobSerializer, int consumers)
{
this.persistentStorage = new PersistentStorage(context, name, jobSerializer);
eventExecutor.execute(new LoadTask(null));
if (requirementProvider != null) {
requirementProvider.setListener(this);
if (requirementProviders != null && !requirementProviders.isEmpty()) {
for (RequirementProvider provider : requirementProviders) {
provider.setListener(this);
}
}
for (int i=0;i<consumers;i++) {

View File

@ -0,0 +1,155 @@
package org.whispersystems.textsecure.api;
import android.content.Context;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream;
import org.whispersystems.textsecure.crypto.TextSecureCipher;
import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushAddress;
import org.whispersystems.textsecure.push.PushServiceSocket;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.Type.DELIVER;
public class TextSecureMessageReceiver {
private final String signalingKey;
private final AxolotlStore axolotlStore;
private final PushServiceSocket socket;
public TextSecureMessageReceiver(Context context, String signalingKey, String url,
PushServiceSocket.TrustStore trustStore,
String user, String password,
AxolotlStore axolotlStore)
{
this.axolotlStore = axolotlStore;
this.signalingKey = signalingKey;
this.socket = new PushServiceSocket(context, url, trustStore, user, password);
}
public InputStream retrieveAttachment(TextSecureAttachmentPointer pointer, File destination)
throws IOException, InvalidMessageException
{
socket.retrieveAttachment(pointer.getRelay().orNull(), pointer.getId(), destination);
return new AttachmentCipherInputStream(destination, pointer.getKey());
}
public IncomingPushMessage receiveSignal(String signal)
throws IOException, InvalidVersionException
{
IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(signal, signalingKey);
return encrypted.getIncomingPushMessage();
}
public TextSecureMessage receiveMessage(long recipientId, IncomingPushMessage signal)
throws InvalidVersionException, InvalidMessageException, NoSessionException,
LegacyMessageException, InvalidKeyIdException, DuplicateMessageException,
InvalidKeyException, UntrustedIdentityException
{
try {
PushAddress sender = new PushAddress(recipientId, signal.getSource(), signal.getSourceDevice(), signal.getRelay());
TextSecureCipher cipher = new TextSecureCipher(axolotlStore, sender);
PushMessageContent message;
if (signal.isPreKeyBundle()) {
PreKeyWhisperMessage bundle = new PreKeyWhisperMessage(signal.getBody());
message = PushMessageContent.parseFrom(cipher.decrypt(bundle));
} else if (signal.isSecureMessage()) {
WhisperMessage ciphertext = new WhisperMessage(signal.getBody());
message = PushMessageContent.parseFrom(cipher.decrypt(ciphertext));
} else if (signal.isPlaintext()) {
message = PushMessageContent.parseFrom(signal.getBody());
} else {
throw new InvalidMessageException("Unknown type: " + signal.getType());
}
return createTextSecureMessage(signal, message);
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
}
}
private TextSecureMessage createTextSecureMessage(IncomingPushMessage signal, PushMessageContent content) {
TextSecureGroup groupInfo = createGroupInfo(signal, content);
List<TextSecureAttachment> attachments = new LinkedList<>();
boolean endSession = ((content.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0);
boolean secure = signal.isSecureMessage() || signal.isPreKeyBundle();
for (AttachmentPointer pointer : content.getAttachmentsList()) {
attachments.add(new TextSecureAttachmentPointer(pointer.getId(),
pointer.getContentType(),
pointer.getKey().toByteArray(),
signal.getRelay()));
}
return new TextSecureMessage(signal.getTimestampMillis(), groupInfo, attachments,
content.getBody(), secure, endSession);
}
private TextSecureGroup createGroupInfo(IncomingPushMessage signal, PushMessageContent content) {
if (!content.hasGroup()) return null;
TextSecureGroup.Type type;
switch (content.getGroup().getType()) {
case DELIVER: type = TextSecureGroup.Type.DELIVER; break;
case UPDATE: type = TextSecureGroup.Type.UPDATE; break;
case QUIT: type = TextSecureGroup.Type.QUIT; break;
default: type = TextSecureGroup.Type.UNKNOWN; break;
}
if (content.getGroup().getType() != DELIVER) {
String name = null;
List<String> members = null;
TextSecureAttachmentPointer avatar = null;
if (content.getGroup().hasName()) {
name = content.getGroup().getName();
}
if (content.getGroup().getMembersCount() > 0) {
members = content.getGroup().getMembersList();
}
if (content.getGroup().hasAvatar()) {
avatar = new TextSecureAttachmentPointer(content.getGroup().getAvatar().getId(),
content.getGroup().getAvatar().getContentType(),
content.getGroup().getAvatar().getKey().toByteArray(),
signal.getRelay());
}
return new TextSecureGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar);
}
return new TextSecureGroup(content.getGroup().getId().toByteArray());
}
}

View File

@ -0,0 +1,303 @@
package org.whispersystems.textsecure.api;
import android.content.Context;
import android.util.Log;
import com.google.protobuf.ByteString;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.crypto.TextSecureCipher;
import org.whispersystems.textsecure.crypto.UntrustedIdentityException;
import org.whispersystems.textsecure.push.MismatchedDevices;
import org.whispersystems.textsecure.push.OutgoingPushMessage;
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
import org.whispersystems.textsecure.push.PushAddress;
import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushBody;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.StaleDevices;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
import org.whispersystems.textsecure.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Type;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.AttachmentPointer;
import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext;
public class TextSecureMessageSender {
private static final String TAG = TextSecureMessageSender.class.getSimpleName();
private final PushServiceSocket socket;
private final AxolotlStore store;
private final Optional<EventListener> eventListener;
public TextSecureMessageSender(Context context, String url,
PushServiceSocket.TrustStore trustStore,
String user, String password,
AxolotlStore store,
Optional<EventListener> eventListener)
{
this.socket = new PushServiceSocket(context, url, trustStore, user, password);
this.store = store;
this.eventListener = eventListener;
}
public void sendMessage(PushAddress recipient, TextSecureMessage message)
throws UntrustedIdentityException, IOException
{
byte[] content = createMessageContent(message);
sendMessage(recipient, message.getTimestamp(), content);
if (message.isEndSession()) {
store.deleteAllSessions(recipient.getRecipientId());
if (eventListener.isPresent()) {
eventListener.get().onSecurityEvent(recipient.getRecipientId());
}
}
}
public void sendMessage(List<PushAddress> recipients, TextSecureMessage message)
throws IOException, EncapsulatedExceptions
{
byte[] content = createMessageContent(message);
sendMessage(recipients, message.getTimestamp(), content);
}
private byte[] createMessageContent(TextSecureMessage message) throws IOException {
PushMessageContent.Builder builder = PushMessageContent.newBuilder();
List<AttachmentPointer> pointers = createAttachmentPointers(message.getAttachments());
if (!pointers.isEmpty()) {
builder.addAllAttachments(pointers);
}
if (message.getBody().isPresent()) {
builder.setBody(message.getBody().get());
}
if (message.getGroupInfo().isPresent()) {
builder.setGroup(createGroupContent(message.getGroupInfo().get()));
}
if (message.isEndSession()) {
builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE);
}
return builder.build().toByteArray();
}
private GroupContext createGroupContent(TextSecureGroup group) throws IOException {
GroupContext.Builder builder = GroupContext.newBuilder();
builder.setId(ByteString.copyFrom(group.getGroupId()));
if (group.getType() != TextSecureGroup.Type.DELIVER) {
if (group.getType() == TextSecureGroup.Type.UPDATE) builder.setType(GroupContext.Type.UPDATE);
else if (group.getType() == TextSecureGroup.Type.QUIT) builder.setType(GroupContext.Type.QUIT);
else throw new AssertionError("Unknown type: " + group.getType());
if (group.getName().isPresent()) builder.setName(group.getName().get());
if (group.getMembers().isPresent()) builder.addAllMembers(group.getMembers().get());
if (group.getAvatar().isPresent() && group.getAvatar().get().isStream()) {
AttachmentPointer pointer = createAttachmentPointer(group.getAvatar().get().asStream());
builder.setAvatar(pointer);
}
} else {
builder.setType(GroupContext.Type.DELIVER);
}
return builder.build();
}
private void sendMessage(List<PushAddress> recipients, long timestamp, byte[] content)
throws IOException, EncapsulatedExceptions
{
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
for (PushAddress recipient : recipients) {
try {
sendMessage(recipient, timestamp, content);
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
untrustedIdentities.add(e);
} catch (UnregisteredUserException e) {
Log.w(TAG, e);
unregisteredUsers.add(e);
}
}
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) {
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers);
}
}
private void sendMessage(PushAddress recipient, long timestamp, byte[] content)
throws UntrustedIdentityException, IOException
{
for (int i=0;i<3;i++) {
try {
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content);
socket.sendMessage(messages);
return;
} catch (MismatchedDevicesException mde) {
Log.w(TAG, mde);
handleMismatchedDevices(socket, recipient, mde.getMismatchedDevices());
} catch (StaleDevicesException ste) {
Log.w(TAG, ste);
handleStaleDevices(recipient, ste.getStaleDevices());
}
}
}
private List<AttachmentPointer> createAttachmentPointers(Optional<List<TextSecureAttachment>> attachments) throws IOException {
List<AttachmentPointer> pointers = new LinkedList<>();
if (!attachments.isPresent() || attachments.get().isEmpty()) {
return pointers;
}
for (TextSecureAttachment attachment : attachments.get()) {
if (attachment.isStream()) {
pointers.add(createAttachmentPointer(attachment.asStream()));
}
}
return pointers;
}
private AttachmentPointer createAttachmentPointer(TextSecureAttachmentStream attachment)
throws IOException
{
byte[] attachmentKey = Util.getSecretBytes(64);
PushAttachmentData attachmentData = new PushAttachmentData(attachment.getContentType(),
attachment.getInputStream(),
attachment.getLength(),
attachmentKey);
long attachmentId = socket.sendAttachment(attachmentData);
return AttachmentPointer.newBuilder()
.setContentType(attachment.getContentType())
.setId(attachmentId)
.setKey(ByteString.copyFrom(attachmentKey))
.build();
}
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket,
PushAddress recipient,
long timestamp,
byte[] plaintext)
throws IOException, UntrustedIdentityException
{
PushBody masterBody = getEncryptedMessage(socket, recipient, plaintext);
List<OutgoingPushMessage> messages = new LinkedList<>();
messages.add(new OutgoingPushMessage(recipient, masterBody));
for (int deviceId : store.getSubDeviceSessions(recipient.getRecipientId())) {
PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(), deviceId, recipient.getRelay());
PushBody body = getEncryptedMessage(socket, device, plaintext);
messages.add(new OutgoingPushMessage(device, body));
}
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay(), messages);
}
private PushBody getEncryptedMessage(PushServiceSocket socket, PushAddress recipient, byte[] plaintext)
throws IOException, UntrustedIdentityException
{
if (!store.containsSession(recipient.getRecipientId(), recipient.getDeviceId())) {
try {
List<PreKeyBundle> preKeys = socket.getPreKeys(recipient);
for (PreKeyBundle preKey : preKeys) {
try {
SessionBuilder sessionBuilder = new SessionBuilder(store, recipient.getRecipientId(), recipient.getDeviceId());
sessionBuilder.process(preKey);
} catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) {
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey());
}
}
if (eventListener.isPresent()) {
eventListener.get().onSecurityEvent(recipient.getRecipientId());
}
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
TextSecureCipher cipher = new TextSecureCipher(store, recipient);
CiphertextMessage message = cipher.encrypt(plaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_TYPE) {
return new PushBody(Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize());
} else if (message.getType() == CiphertextMessage.WHISPER_TYPE) {
return new PushBody(Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize());
} else {
throw new AssertionError("Unknown ciphertext type: " + message.getType());
}
}
private void handleMismatchedDevices(PushServiceSocket socket, PushAddress recipient,
MismatchedDevices mismatchedDevices)
throws IOException, UntrustedIdentityException
{
try {
for (int extraDeviceId : mismatchedDevices.getExtraDevices()) {
store.deleteSession(recipient.getRecipientId(), extraDeviceId);
}
for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(),
missingDeviceId, recipient.getRelay());
PreKeyBundle preKey = socket.getPreKey(device);
try {
SessionBuilder sessionBuilder = new SessionBuilder(store, device.getRecipientId(), device.getDeviceId());
sessionBuilder.process(preKey);
} catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) {
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey());
}
}
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
private void handleStaleDevices(PushAddress recipient, StaleDevices staleDevices) {
long recipientId = recipient.getRecipientId();
for (int staleDeviceId : staleDevices.getStaleDevices()) {
store.deleteSession(recipientId, staleDeviceId);
}
}
public static interface EventListener {
public void onSecurityEvent(long recipientId);
}
}

View File

@ -0,0 +1,27 @@
package org.whispersystems.textsecure.api.messages;
import java.io.InputStream;
public abstract class TextSecureAttachment {
private final String contentType;
protected TextSecureAttachment(String contentType) {
this.contentType = contentType;
}
public String getContentType() {
return contentType;
}
public abstract boolean isStream();
public abstract boolean isPointer();
public TextSecureAttachmentStream asStream() {
return (TextSecureAttachmentStream)this;
}
public TextSecureAttachmentPointer asPointer() {
return (TextSecureAttachmentPointer)this;
}
}

View File

@ -0,0 +1,39 @@
package org.whispersystems.textsecure.api.messages;
import org.whispersystems.libaxolotl.util.guava.Optional;
public class TextSecureAttachmentPointer extends TextSecureAttachment {
private final long id;
private final byte[] key;
private final Optional<String> relay;
public TextSecureAttachmentPointer(long id, String contentType, byte[] key, String relay) {
super(contentType);
this.id = id;
this.key = key;
this.relay = Optional.fromNullable(relay);
}
public long getId() {
return id;
}
public byte[] getKey() {
return key;
}
@Override
public boolean isStream() {
return false;
}
@Override
public boolean isPointer() {
return true;
}
public Optional<String> getRelay() {
return relay;
}
}

View File

@ -0,0 +1,33 @@
package org.whispersystems.textsecure.api.messages;
import java.io.InputStream;
public class TextSecureAttachmentStream extends TextSecureAttachment {
private final InputStream inputStream;
private final long length;
public TextSecureAttachmentStream(InputStream inputStream, String contentType, long length) {
super(contentType);
this.inputStream = inputStream;
this.length = length;
}
@Override
public boolean isStream() {
return false;
}
@Override
public boolean isPointer() {
return true;
}
public InputStream getInputStream() {
return inputStream;
}
public long getLength() {
return length;
}
}

View File

@ -0,0 +1,58 @@
package org.whispersystems.textsecure.api.messages;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.List;
public class TextSecureGroup {
public enum Type {
UNKNOWN,
UPDATE,
DELIVER,
QUIT
}
private final byte[] groupId;
private final Type type;
private final Optional<String> name;
private final Optional<List<String>> members;
private final Optional<TextSecureAttachment> avatar;
public TextSecureGroup(byte[] groupId) {
this(Type.DELIVER, groupId, null, null, null);
}
public TextSecureGroup(Type type, byte[] groupId, String name,
List<String> members,
TextSecureAttachment avatar)
{
this.type = type;
this.groupId = groupId;
this.name = Optional.fromNullable(name);
this.members = Optional.fromNullable(members);
this.avatar = Optional.fromNullable(avatar);
}
public byte[] getGroupId() {
return groupId;
}
public Type getType() {
return type;
}
public Optional<String> getName() {
return name;
}
public Optional<List<String>> getMembers() {
return members;
}
public Optional<TextSecureAttachment> getAvatar() {
return avatar;
}
}

View File

@ -0,0 +1,64 @@
package org.whispersystems.textsecure.api.messages;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.List;
public class TextSecureMessage {
private final long timestamp;
private final Optional<List<TextSecureAttachment>> attachments;
private final Optional<String> body;
private final Optional<TextSecureGroup> group;
private final boolean secure;
private final boolean endSession;
public TextSecureMessage(long timestamp, String body) {
this(timestamp, null, body);
}
public TextSecureMessage(long timestamp, List<TextSecureAttachment> attachments, String body) {
this(timestamp, null, attachments, body);
}
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body) {
this(timestamp, group, attachments, body, true, false);
}
public TextSecureMessage(long timestamp, TextSecureGroup group, List<TextSecureAttachment> attachments, String body, boolean secure, boolean endSession) {
this.timestamp = timestamp;
this.attachments = Optional.fromNullable(attachments);
this.body = Optional.fromNullable(body);
this.group = Optional.fromNullable(group);
this.secure = secure;
this.endSession = endSession;
}
public long getTimestamp() {
return timestamp;
}
public Optional<List<TextSecureAttachment>> getAttachments() {
return attachments;
}
public Optional<String> getBody() {
return body;
}
public Optional<TextSecureGroup> getGroupInfo() {
return group;
}
public boolean isSecure() {
return secure;
}
public boolean isEndSession() {
return endSession;
}
public boolean isGroupUpdate() {
return group.isPresent() && group.get().getType() != TextSecureGroup.Type.DELIVER;
}
}

View File

@ -1,161 +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.crypto;
import android.util.Log;
import org.whispersystems.libaxolotl.InvalidMacException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Arrays;
/**
* Encrypts push attachments.
*
* @author Moxie Marlinspike
*/
public class AttachmentCipher {
static final int CIPHER_KEY_SIZE = 32;
static final int MAC_KEY_SIZE = 32;
private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey;
private final Cipher cipher;
private final Mac mac;
public AttachmentCipher() {
this.cipherKey = initializeRandomCipherKey();
this.macKey = initializeRandomMacKey();
this.cipher = initializeCipher();
this.mac = initializeMac();
}
public AttachmentCipher(byte[] combinedKeyMaterial) {
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
this.cipherKey = new SecretKeySpec(parts[0], "AES");
this.macKey = new SecretKeySpec(parts[1], "HmacSHA256");
this.cipher = initializeCipher();
this.mac = initializeMac();
}
public byte[] getCombinedKeyMaterial() {
return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded());
}
public byte[] encrypt(byte[] plaintext) {
try {
this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey);
this.mac.init(this.macKey);
byte[] ciphertext = this.cipher.doFinal(plaintext);
byte[] iv = this.cipher.getIV();
byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext));
return Util.combine(iv, ciphertext, mac);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public byte[] decrypt(byte[] ciphertext)
throws InvalidMacException, InvalidMessageException
{
try {
if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) {
throw new InvalidMessageException("Message too short!");
}
byte[][] ciphertextParts = Util.split(ciphertext,
this.cipher.getBlockSize(),
ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(),
this.mac.getMacLength());
this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength());
byte[] ourMac = this.mac.doFinal();
if (!Arrays.equals(ourMac, ciphertextParts[2])) {
throw new InvalidMacException("Mac doesn't match!");
}
this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey,
new IvParameterSpec(ciphertextParts[0]));
return cipher.doFinal(ciphertextParts[1]);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new InvalidMessageException(e);
} catch (ParseException e) {
throw new InvalidMessageException(e);
}
}
private Mac initializeMac() {
try {
Mac mac = Mac.getInstance("HmacSHA256");
return mac;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private Cipher initializeCipher() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
}
}
private SecretKeySpec initializeRandomCipherKey() {
byte[] key = new byte[CIPHER_KEY_SIZE];
Util.getSecureRandom().nextBytes(key);
return new SecretKeySpec(key, "AES");
}
private SecretKeySpec initializeRandomMacKey() {
byte[] key = new byte[MAC_KEY_SIZE];
Util.getSecureRandom().nextBytes(key);
return new SecretKeySpec(key, "HmacSHA256");
}
}

View File

@ -48,13 +48,15 @@ import javax.crypto.spec.SecretKeySpec;
public class AttachmentCipherInputStream extends FileInputStream {
private static final int BLOCK_SIZE = 16;
private static final int BLOCK_SIZE = 16;
private static final int CIPHER_KEY_SIZE = 32;
private static final int MAC_KEY_SIZE = 32;
private Cipher cipher;
private boolean done;
private long totalDataSize;
private long totalRead;
private byte[] overflowBuffer;
private byte[] overflowBuffer;
public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial)
throws IOException, InvalidMessageException
@ -62,11 +64,9 @@ public class AttachmentCipherInputStream extends FileInputStream {
super(file);
try {
byte[][] parts = Util.split(combinedKeyMaterial,
AttachmentCipher.CIPHER_KEY_SIZE,
AttachmentCipher.MAC_KEY_SIZE);
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
Mac mac = Mac.getInstance("HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(parts[1], "HmacSHA256"));
if (file.length() <= BLOCK_SIZE + mac.getMacLength()) {
@ -84,16 +84,10 @@ public class AttachmentCipherInputStream extends FileInputStream {
this.done = false;
this.totalRead = 0;
this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (InvalidMacException e) {
throw new InvalidMessageException(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}

View File

@ -0,0 +1,105 @@
package org.whispersystems.textsecure.crypto;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class AttachmentCipherOutputStream extends OutputStream {
private final Cipher cipher;
private final Mac mac;
private final OutputStream outputStream;
private long ciphertextLength = 0;
public AttachmentCipherOutputStream(byte[] combinedKeyMaterial,
OutputStream outputStream)
throws IOException
{
try {
this.outputStream = outputStream;
this.cipher = initializeCipher();
this.mac = initializeMac();
byte[][] keyParts = Util.split(combinedKeyMaterial, 32, 32);
this.cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyParts[0], "AES"));
this.mac.init(new SecretKeySpec(keyParts[1], "HmacSHA256"));
mac.update(cipher.getIV());
outputStream.write(cipher.getIV());
ciphertextLength += cipher.getIV().length;
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
@Override
public void write(byte[] buffer) throws IOException {
write(buffer, 0, buffer.length);
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
byte[] ciphertext = cipher.update(buffer, offset, length);
if (ciphertext != null) {
mac.update(ciphertext);
outputStream.write(ciphertext);
ciphertextLength += ciphertext.length;
}
}
@Override
public void write(int b) {
throw new AssertionError("NYI");
}
@Override
public void flush() throws IOException {
try {
byte[] ciphertext = cipher.doFinal();
byte[] auth = mac.doFinal(ciphertext);
outputStream.write(ciphertext);
outputStream.write(auth);
ciphertextLength += ciphertext.length;
ciphertextLength += auth.length;
outputStream.flush();
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
public static long getCiphertextLength(long plaintextLength) {
return 16 + (((plaintextLength / 16) +1) * 16) + 32;
}
private Mac initializeMac() {
try {
return Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private Cipher initializeCipher() {
try {
return Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new AssertionError(e);
}
}
}

View File

@ -1,6 +1,4 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
package org.whispersystems.textsecure.crypto;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
@ -13,32 +11,23 @@ import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.textsecure.push.PushAddress;
import org.whispersystems.textsecure.push.PushTransportDetails;
public class TextSecureCipher {
private final SessionCipher sessionCipher;
private final TransportDetails transportDetails;
public TextSecureCipher(Context context, MasterSecret masterSecret,
RecipientDevice recipient, TransportDetails transportDetails)
{
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
public TextSecureCipher(AxolotlStore axolotlStore, PushAddress pushAddress) {
int sessionVersion = axolotlStore.loadSession(pushAddress.getRecipientId(),
pushAddress.getDeviceId())
.getSessionState().getSessionVersion();
this.transportDetails = transportDetails;
this.sessionCipher = new SessionCipher(sessionStore, preKeyStore, signedPreKeyStore, identityKeyStore,
recipient.getRecipientId(), recipient.getDeviceId());
this.transportDetails = new PushTransportDetails(sessionVersion);
this.sessionCipher = new SessionCipher(axolotlStore, pushAddress.getRecipientId(),
pushAddress.getDeviceId());
}
public CiphertextMessage encrypt(byte[] unpaddedMessage) {
@ -54,7 +43,7 @@ public class TextSecureCipher {
public byte[] decrypt(PreKeyWhisperMessage message)
throws InvalidKeyException, LegacyMessageException, InvalidMessageException,
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, NoSessionException
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, NoSessionException
{
byte[] paddedMessage = sessionCipher.decrypt(message);
return transportDetails.getStrippedPaddingMessageBody(paddedMessage);
@ -65,3 +54,4 @@ public class TextSecureCipher {
}
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.transport;
package org.whispersystems.textsecure.crypto;
import org.whispersystems.libaxolotl.IdentityKey;

View File

@ -1,86 +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.directory;
import org.whispersystems.textsecure.util.Conversions;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* A simple bloom filter implementation that backs the RedPhone directory.
*
* @author Moxie Marlinspike
*
*/
public class BloomFilter {
private final MappedByteBuffer buffer;
private final long length;
private final int hashCount;
public BloomFilter(File bloomFilter, int hashCount)
throws IOException
{
this.length = bloomFilter.length();
this.buffer = new FileInputStream(bloomFilter).getChannel()
.map(FileChannel.MapMode.READ_ONLY, 0, length);
this.hashCount = hashCount;
}
public int getHashCount() {
return hashCount;
}
private boolean isBitSet(long bitIndex) {
int byteInQuestion = this.buffer.get((int)(bitIndex / 8));
int bitOffset = (0x01 << (bitIndex % 8));
return (byteInQuestion & bitOffset) > 0;
}
public boolean contains(String entity) {
try {
for (int i=0;i<this.hashCount;i++) {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec((i+"").getBytes(), "HmacSHA1"));
byte[] hashValue = mac.doFinal(entity.getBytes());
long bitIndex = Math.abs(Conversions.byteArrayToLong(hashValue, 0)) % (this.length * 8);
if (!isBitSet(bitIndex))
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@ -1,26 +0,0 @@
package org.whispersystems.textsecure.directory;
public class DirectoryDescriptor {
private String version;
private long capacity;
private int hashCount;
public String getUrl() {
return url;
}
public int getHashCount() {
return hashCount;
}
public long getCapacity() {
return capacity;
}
public String getVersion() {
return version;
}
private String url;
}

View File

@ -1,225 +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.directory;
import android.content.Context;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.annotations.SerializedName;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.GZIPInputStream;
/**
* Handles providing lookups, serializing, and deserializing the RedPhone directory.
*
* @author Moxie Marlinspike
*
*/
public class NumberFilter {
private static NumberFilter instance;
public synchronized static NumberFilter getInstance(Context context) {
if (instance == null)
instance = NumberFilter.deserializeFromFile(context);
return instance;
}
private static final String DIRECTORY_META_FILE = "directory.stat";
private File bloomFilter;
private String version;
private long capacity;
private int hashCount;
private Context context;
private NumberFilter(Context context, File bloomFilter, long capacity,
int hashCount, String version)
{
this.context = context.getApplicationContext();
this.bloomFilter = bloomFilter;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
}
public synchronized boolean containsNumber(String number) {
try {
if (bloomFilter == null) return false;
else if (number == null || number.length() == 0) return false;
return new BloomFilter(bloomFilter, hashCount).contains(number);
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return false;
}
}
public synchronized boolean containsNumbers(List<String> numbers) {
try {
if (bloomFilter == null) return false;
if (numbers == null || numbers.size() == 0) return false;
BloomFilter filter = new BloomFilter(bloomFilter, hashCount);
for (String number : numbers) {
if (!filter.contains(number)) {
return false;
}
}
return true;
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return false;
}
}
public synchronized void update(DirectoryDescriptor descriptor, File compressedData) {
try {
File uncompressed = File.createTempFile("directory", ".dat", context.getFilesDir());
FileInputStream fin = new FileInputStream (compressedData);
GZIPInputStream gin = new GZIPInputStream(fin);
FileOutputStream out = new FileOutputStream(uncompressed);
byte[] buffer = new byte[4096];
int read;
while ((read = gin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
compressedData.delete();
update(uncompressed, descriptor.getCapacity(), descriptor.getHashCount(), descriptor.getVersion());
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
}
}
private synchronized void update(File bloomFilter, long capacity, int hashCount, String version)
{
if (this.bloomFilter != null)
this.bloomFilter.delete();
this.bloomFilter = bloomFilter;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
serializeToFile(context);
}
private void serializeToFile(Context context) {
if (this.bloomFilter == null)
return;
try {
FileOutputStream fout = context.openFileOutput(DIRECTORY_META_FILE, 0);
NumberFilterStorage storage = new NumberFilterStorage(bloomFilter.getAbsolutePath(),
capacity, hashCount, version);
storage.serializeToStream(fout);
fout.close();
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
}
}
private static NumberFilter deserializeFromFile(Context context) {
try {
FileInputStream fis = context.openFileInput(DIRECTORY_META_FILE);
NumberFilterStorage storage = NumberFilterStorage.fromStream(fis);
if (storage == null) return new NumberFilter(context, null, 0, 0, "0");
else return new NumberFilter(context,
new File(storage.getDataPath()),
storage.getCapacity(),
storage.getHashCount(),
storage.getVersion());
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return new NumberFilter(context, null, 0, 0, "0");
}
}
private static class NumberFilterStorage {
@SerializedName("data_path")
private String dataPath;
@SerializedName("capacity")
private long capacity;
@SerializedName("hash_count")
private int hashCount;
@SerializedName("version")
private String version;
public NumberFilterStorage(String dataPath, long capacity, int hashCount, String version) {
this.dataPath = dataPath;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
}
public String getDataPath() {
return dataPath;
}
public long getCapacity() {
return capacity;
}
public int getHashCount() {
return hashCount;
}
public String getVersion() {
return version;
}
public void serializeToStream(OutputStream out) throws IOException {
out.write(new Gson().toJson(this).getBytes());
}
public static NumberFilterStorage fromStream(InputStream in) throws IOException {
try {
return new Gson().fromJson(new BufferedReader(new InputStreamReader(in)),
NumberFilterStorage.class);
} catch (JsonParseException jpe) {
Log.w("NumberFilter", jpe);
throw new IOException("JSON Parse Exception");
}
}
}
}

View File

@ -69,15 +69,7 @@ public class IncomingEncryptedPushMessage {
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
Log.w("IncomingEncryptedPushMessage", e);

View File

@ -10,7 +10,7 @@ public class PushAddress extends RecipientDevice {
private final String e164number;
private final String relay;
private PushAddress(long recipientId, String e164number, int deviceId, String relay) {
public PushAddress(long recipientId, String e164number, int deviceId, String relay) {
super(recipientId, deviceId);
this.e164number = e164number;
this.relay = relay;

View File

@ -1,21 +1,34 @@
package org.whispersystems.textsecure.push;
import java.io.InputStream;
public class PushAttachmentData {
private final String contentType;
private final byte[] data;
private final String contentType;
private final InputStream data;
private final long dataSize;
private final byte[] key;
public PushAttachmentData(String contentType, byte[] data) {
public PushAttachmentData(String contentType, InputStream data, long dataSize, byte[] key) {
this.contentType = contentType;
this.data = data;
this.dataSize = dataSize;
this.key = key;
}
public String getContentType() {
return contentType;
}
public byte[] getData() {
public InputStream getData() {
return data;
}
public long getDataSize() {
return dataSize;
}
public byte[] getKey() {
return key;
}
}

View File

@ -28,6 +28,7 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.textsecure.crypto.AttachmentCipherOutputStream;
import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
@ -47,7 +48,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
@ -298,12 +298,13 @@ public class PushServiceSocket {
Log.w("PushServiceSocket", "Got attachment content location: " + attachmentKey.getLocation());
uploadExternalFile("PUT", attachmentKey.getLocation(), attachment.getData());
uploadAttachment("PUT", attachmentKey.getLocation(), attachment.getData(),
attachment.getDataSize(), attachment.getKey());
return attachmentKey.getId();
}
public File retrieveAttachment(String relay, long attachmentId) throws IOException {
public void retrieveAttachment(String relay, long attachmentId, File destination) throws IOException {
String path = String.format(ATTACHMENT_PATH, String.valueOf(attachmentId));
if (!Util.isEmpty(relay)) {
@ -315,12 +316,7 @@ public class PushServiceSocket {
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + descriptor.getLocation());
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
attachment.deleteOnExit();
downloadExternalFile(descriptor.getLocation(), attachment);
return attachment;
downloadExternalFile(descriptor.getLocation(), destination);
}
public List<ContactTokenDetails> retrieveDirectory(Set<String> contactTokens) {
@ -356,7 +352,7 @@ public class PushServiceSocket {
try {
if (connection.getResponseCode() != 200) {
throw new IOException("Bad response: " + connection.getResponseCode());
throw new NonSuccessfulResponseCodeException("Bad response: " + connection.getResponseCode());
}
OutputStream output = new FileOutputStream(localDestination);
@ -375,20 +371,23 @@ public class PushServiceSocket {
}
}
private void uploadExternalFile(String method, String url, byte[] data)
private void uploadAttachment(String method, String url, InputStream data, long dataSize, byte[] key)
throws IOException
{
URL uploadUrl = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) uploadUrl.openConnection();
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode((int) AttachmentCipherOutputStream.getCiphertextLength(dataSize));
connection.setRequestMethod(method);
connection.setRequestProperty("Content-Type", "application/octet-stream");
connection.connect();
try {
OutputStream out = connection.getOutputStream();
out.write(data);
out.close();
OutputStream stream = connection.getOutputStream();
AttachmentCipherOutputStream out = new AttachmentCipherOutputStream(key, stream);
Util.copy(data, out);
out.flush();
if (connection.getResponseCode() != 200) {
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
@ -532,14 +531,8 @@ public class PushServiceSocket {
trustManagerFactory.init(keyStore);
return BlacklistingTrustManager.createFor(trustManagerFactory.getTrustManagers());
} catch (KeyStoreException kse) {
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException kse) {
throw new AssertionError(kse);
} catch (CertificateException e) {
throw new AssertionError(e);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (IOException ioe) {
throw new AssertionError(ioe);
}
}

View File

@ -1,27 +0,0 @@
package org.whispersystems.textsecure.push;
import org.whispersystems.textsecure.crypto.TransportDetails;
import java.io.IOException;
public class RawTransportDetails implements TransportDetails {
@Override
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
return messageWithPadding;
}
@Override
public byte[] getPaddedMessageBody(byte[] messageBody) {
return messageBody;
}
@Override
public byte[] getEncodedMessage(byte[] messageWithMac) {
return messageWithMac;
}
@Override
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
return encodedMessageBytes;
}
}

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.transport;
package org.whispersystems.textsecure.push.exceptions;
import org.whispersystems.textsecure.crypto.UntrustedIdentityException;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import java.util.List;

View File

@ -1,41 +0,0 @@
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 int getSessionVersion(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
return
new TextSecureSessionStore(context, masterSecret)
.loadSession(recipient.getRecipientId(), recipient.getDeviceId())
.getSessionState()
.getSessionVersion();
}
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.containsSession(recipientId, deviceId);
}
}

View File

@ -94,12 +94,17 @@ public class Util {
}
public static String getSecret(int size) {
byte[] secret = getSecretBytes(size);
return Base64.encodeBytes(secret);
}
public static byte[] getSecretBytes(int size) {
try {
byte[] secret = new byte[size];
SecureRandom.getInstance("SHA1PRNG").nextBytes(secret);
return Base64.encodeBytes(secret);
} catch (NoSuchAlgorithmException nsae) {
throw new AssertionError(nsae);
return secret;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}

View File

@ -20,11 +20,16 @@ import android.app.Application;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.PRNGFixes;
import org.thoughtcrime.securesms.jobs.EncryptingJobSerializer;
import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.requirements.NetworkRequirementProvider;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
import java.util.LinkedList;
import java.util.List;
/**
* Will be called once when the TextSecure process is created.
@ -58,8 +63,12 @@ public class ApplicationContext extends Application {
}
private void initializeJobManager() {
this.jobManager = new JobManager(this, "TextSecureJobs",
new NetworkRequirementProvider(this),
List<RequirementProvider> providers = new LinkedList<RequirementProvider>() {{
add(new NetworkRequirementProvider(ApplicationContext.this));
add(new MasterSecretRequirementProvider(ApplicationContext.this));
}};
this.jobManager = new JobManager(this, "TextSecureJobs", providers,
new EncryptingJobSerializer(this), 5);
}

View File

@ -64,7 +64,7 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;

View File

@ -31,9 +31,9 @@ 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.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
/**
* Activity which prompts the user to initiate a secure

View File

@ -64,7 +64,7 @@ import org.thoughtcrime.securesms.components.EmojiToggle;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
@ -104,10 +104,10 @@ 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.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
@ -818,7 +818,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
};
registerReceiver(securityUpdateReceiver,
new IntentFilter(KeyExchangeProcessor.SECURITY_UPDATE_EVENT),
new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT),
KeyCachingService.KEY_PERMISSION, null);
registerReceiver(groupUpdateReceiver,

View File

@ -25,7 +25,7 @@ import android.view.ViewGroup;
import android.widget.AbsListView;
import android.support.v4.widget.CursorAdapter;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;

View File

@ -38,9 +38,17 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.Util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.sql.Date;
import java.text.SimpleDateFormat;

View File

@ -38,12 +38,14 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -51,7 +53,6 @@ import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.Emoji;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.FutureTaskListener;
import org.whispersystems.textsecure.util.ListenableFutureTask;
@ -490,13 +491,10 @@ public class ConversationItem extends LinearLayout {
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
Intent intent = new Intent(context, SendReceiveService.class);
intent.putExtra("content_location", new String(notificationRecord.getContentLocation()));
intent.putExtra("message_id", notificationRecord.getId());
intent.putExtra("transaction_id", notificationRecord.getTransactionId());
intent.putExtra("thread_id", notificationRecord.getThreadId());
intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION);
context.startService(intent);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MmsDownloadJob(context, messageRecord.getId(),
messageRecord.getThreadId(), false));
}
}

View File

@ -49,7 +49,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class ConversationListActivity extends PassphraseRequiredActionBarActivity

View File

@ -24,8 +24,8 @@ import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.CursorAdapter;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;

View File

@ -51,7 +51,7 @@ import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Dialogs;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.Set;

View File

@ -27,14 +27,18 @@ import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import org.whispersystems.jobqueue.EncryptionKeys;
import java.io.File;
import java.util.SortedSet;
@ -49,6 +53,7 @@ public class DatabaseUpgradeActivity extends Activity {
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73;
public static final int NO_V1_VERSION = 83;
public static final int SIGNED_PREKEY_VERSION = 83;
public static final int NO_DECRYPT_QUEUE_VERSION = 84;
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@ -57,6 +62,7 @@ public class DatabaseUpgradeActivity extends Activity {
add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
add(NO_V1_VERSION);
add(SIGNED_PREKEY_VERSION);
add(NO_DECRYPT_QUEUE_VERSION);
}};
private MasterSecret masterSecret;
@ -77,7 +83,10 @@ public class DatabaseUpgradeActivity extends Activity {
.execute(VersionTracker.getLastSeenVersion(this));
} else {
VersionTracker.updateLastSeenVersion(this);
DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
ApplicationContext.getInstance(this)
.getJobManager()
.setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret)));
// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
@ -165,6 +174,25 @@ public class DatabaseUpgradeActivity extends Activity {
.add(new CreateSignedPreKeyJob(context, masterSecret));
}
if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
SmsDatabase.Reader reader = null;
SmsMessageRecord record;
try {
reader = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext())
.getDecryptInProgressMessages(masterSecret);
while ((record = reader.getNext()) != null) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new SmsDecryptJob(getApplicationContext(), record.getId()));
}
} finally {
if (reader != null)
reader.close();
}
}
return null;
}
@ -180,7 +208,11 @@ public class DatabaseUpgradeActivity extends Activity {
@Override
protected void onPostExecute(Void result) {
VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this);
DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
ApplicationContext.getInstance(DatabaseUpgradeActivity.this)
.getJobManager()
.setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret)));
MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));

View File

@ -14,11 +14,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.Dialogs;
//import org.thoughtcrime.securesms.database.EncryptedBackupExporter;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.database.PlaintextBackupExporter;
import org.thoughtcrime.securesms.util.Dialogs;
import java.io.IOException;

View File

@ -27,7 +27,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Pair;
@ -62,7 +61,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.util.InvalidNumberException;

View File

@ -9,7 +9,7 @@ import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class ImportExportActivity extends PassphraseRequiredActionBarActivity {

View File

@ -14,14 +14,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.database.EncryptedBackupExporter;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptedBackupExporter;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.database.PlaintextBackupImporter;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs;
import java.io.IOException;

View File

@ -33,6 +33,7 @@ import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.providers.PartProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -40,7 +41,6 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.whispersystems.textsecure.crypto.MasterSecret;
import java.io.IOException;
import java.io.InputStream;

View File

@ -16,18 +16,16 @@
*/
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.MenuItem;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.whispersystems.textsecure.crypto.MasterSecret;
public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity {
@ -72,7 +70,6 @@ public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
handleDownloadMmsPendingApn();
finish();
return true;
}
@ -82,13 +79,7 @@ public class MmsPreferencesActivity extends PassphraseRequiredActionBarActivity
@Override
public void onBackPressed() {
handleDownloadMmsPendingApn();
super.onBackPressed();
}
private void handleDownloadMmsPendingApn() {
Intent intent = new Intent(this, SendReceiveService.class);
intent.setAction(SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION);
startService(intent);
}
}

View File

@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.ArrayList;
import java.util.LinkedList;

View File

@ -23,7 +23,7 @@ import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.ActionBarActivity;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.MemoryCleaner;
@ -53,7 +53,7 @@ public abstract class PassphraseActivity extends ActionBarActivity {
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
keyCachingService.setMasterSecret(masterSecret);
PassphraseActivity.this.unbindService(PassphraseActivity.this.serviceConnection);

View File

@ -26,7 +26,7 @@ import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;

View File

@ -23,10 +23,10 @@ import android.os.Bundle;
import android.support.v7.app.ActionBar;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.VersionTracker;
import org.whispersystems.textsecure.util.Util;

View File

@ -42,7 +42,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Util;
/**

View File

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PassphraseRequiredActionBarActivity extends ActionBarActivity implements PassphraseRequiredActivity {

View File

@ -1,6 +1,6 @@
package org.thoughtcrime.securesms;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public interface PassphraseRequiredActivity {
public void onMasterSecretCleared();

View File

@ -2,23 +2,19 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.view.WindowManager;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class PassphraseRequiredMixin {
private KeyCachingServiceConnection serviceConnection;
private BroadcastReceiver clearKeyReceiver;
private BroadcastReceiver newKeyReceiver;
@ -29,13 +25,12 @@ public class PassphraseRequiredMixin {
public <T extends Activity & PassphraseRequiredActivity> void onResume(T activity) {
initializeScreenshotSecurity(activity);
initializeNewKeyReceiver(activity);
initializeServiceConnection(activity);
initializeFromMasterSecret(activity);
KeyCachingService.registerPassphraseActivityStarted(activity);
}
public <T extends Activity & PassphraseRequiredActivity> void onPause(T activity) {
removeNewKeyReceiver(activity);
removeServiceConnection(activity);
KeyCachingService.registerPassphraseActivityStopped(activity);
}
@ -78,14 +73,14 @@ public class PassphraseRequiredMixin {
activity.registerReceiver(newKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
}
private <T extends Activity & PassphraseRequiredActivity> void initializeServiceConnection(T activity) {
Intent cachingIntent = new Intent(activity, KeyCachingService.class);
activity.startService(cachingIntent);
private <T extends Activity & PassphraseRequiredActivity> void initializeFromMasterSecret(T activity) {
MasterSecret masterSecret = KeyCachingService.getMasterSecret(activity);
this.serviceConnection = new KeyCachingServiceConnection(activity);
Intent bindIntent = new Intent(activity, KeyCachingService.class);
activity.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
if (masterSecret == null) {
activity.onMasterSecretCleared();
} else {
activity.onNewMasterSecret(masterSecret);
}
}
private void removeClearKeyReceiver(Context context) {
@ -101,36 +96,4 @@ public class PassphraseRequiredMixin {
newKeyReceiver = null;
}
}
private void removeServiceConnection(Context context) {
if (this.serviceConnection != null) {
context.unbindService(this.serviceConnection);
}
}
private static class KeyCachingServiceConnection implements ServiceConnection {
private final PassphraseRequiredActivity activity;
public KeyCachingServiceConnection(PassphraseRequiredActivity activity) {
this.activity = activity;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
MasterSecret masterSecret = keyCachingService.getMasterSecret();
if (masterSecret == null) {
activity.onMasterSecretCleared();
} else {
activity.onNewMasterSecret(masterSecret);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}

View File

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PassphraseRequiredSherlockListActivity extends ActionBarListActivity implements PassphraseRequiredActivity {

View File

@ -30,48 +30,34 @@ import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.crypto.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.InvalidNumberException;
import java.io.IOException;
import static org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Type;
/**
* Activity for displaying sent/received session keys.
*
@ -87,13 +73,11 @@ public class ReceiveKeyActivity extends Activity {
private Recipient recipient;
private int recipientDeviceId;
private long threadId;
private long messageId;
private MasterSecret masterSecret;
private PreKeyWhisperMessage keyExchangeMessageBundle;
private KeyExchangeMessage keyExchangeMessage;
private IdentityKey identityUpdateMessage;
private MasterSecret masterSecret;
private IncomingKeyExchangeMessage message;
private IdentityKey identityKey;
@Override
protected void onCreate(Bundle state) {
@ -118,7 +102,7 @@ public class ReceiveKeyActivity extends Activity {
}
private void initializeText() {
if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle, identityUpdateMessage)) {
if (isTrusted(this.identityKey)) {
initializeTrustedText();
} else {
initializeUntrustedText();
@ -135,16 +119,10 @@ public class ReceiveKeyActivity extends Activity {
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
IdentityKey remoteIdentity;
if (identityUpdateMessage != null) remoteIdentity = identityUpdateMessage;
else if (keyExchangeMessageBundle != null) remoteIdentity = keyExchangeMessageBundle.getIdentityKey();
else remoteIdentity = keyExchangeMessage.getIdentityKey();
Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class);
intent.putExtra("recipient", recipient);
intent.putExtra("master_secret", masterSecret);
intent.putExtra("remote_identity", new IdentityKeyParcelable(remoteIdentity));
intent.putExtra("remote_identity", new IdentityKeyParcelable(identityKey));
startActivity(intent);
}
}, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1,
@ -154,43 +132,32 @@ public class ReceiveKeyActivity extends Activity {
descriptionText.setMovementMethod(LinkMovementMethod.getInstance());
}
private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle, IdentityKey identityUpdateMessage) {
private boolean isTrusted(IdentityKey identityKey) {
long recipientId = recipient.getRecipientId();
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(this, masterSecret);
if (message != null) return identityKeyStore.isTrustedIdentity(recipientId, message.getIdentityKey());
else if (messageBundle != null) return identityKeyStore.isTrustedIdentity(recipientId, messageBundle.getIdentityKey());
else if (identityUpdateMessage != null) return identityKeyStore.isTrustedIdentity(recipientId, identityUpdateMessage);
return false;
return identityKeyStore.isTrustedIdentity(recipientId, identityKey);
}
private void initializeKey()
throws InvalidKeyException, InvalidVersionException,
InvalidMessageException, LegacyMessageException
InvalidMessageException, LegacyMessageException
{
try {
String messageBody = getIntent().getStringExtra("body");
IncomingTextMessage message = new IncomingTextMessage(recipient.getNumber(),
recipientDeviceId,
System.currentTimeMillis(),
getIntent().getStringExtra("body"),
Optional.<TextSecureGroup>absent());
if (getIntent().getBooleanExtra("is_bundle", false)) {
boolean isPush = getIntent().getBooleanExtra("is_push", false);
byte[] body;
if (isPush) {
body = Base64.decode(messageBody.getBytes());
} else {
body = new SmsTransportDetails().getDecodedMessage(messageBody.getBytes());
}
this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body);
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0);
} else {
this.keyExchangeMessage = new KeyExchangeMessage(Base64.decodeWithoutPadding(messageBody));
}
} catch (IOException e) {
throw new AssertionError(e);
if (getIntent().getBooleanExtra("is_bundle", false)) {
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody());
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.message = new IncomingIdentityUpdateMessage(message, message.getMessageBody());
} else {
this.message = new IncomingKeyExchangeMessage(message, message.getMessageBody());
}
this.identityKey = getIdentityKey(this.message);
}
private void initializeResources() {
@ -199,7 +166,6 @@ public class ReceiveKeyActivity extends Activity {
this.cancelButton = (Button) findViewById(R.id.cancel_button);
this.recipient = getIntent().getParcelableExtra("recipient");
this.recipientDeviceId = getIntent().getIntExtra("recipient_device_id", -1);
this.threadId = getIntent().getLongExtra("thread_id", -1);
this.messageId = getIntent().getLongExtra("message_id", -1);
this.masterSecret = getIntent().getParcelableExtra("master_secret");
}
@ -209,6 +175,26 @@ public class ReceiveKeyActivity extends Activity {
this.cancelButton.setOnClickListener(new CancelListener());
}
private IdentityKey getIdentityKey(IncomingKeyExchangeMessage message)
throws InvalidKeyException, InvalidVersionException,
InvalidMessageException, LegacyMessageException
{
try {
if (message.isIdentityUpdate()) {
return new IdentityKey(Base64.decodeWithoutPadding(message.getMessageBody()), 0);
} else if (message.isPreKeyBundle()) {
boolean isPush = getIntent().getBooleanExtra("is_push", false);
if (isPush) return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
else return new PreKeyWhisperMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
} else {
return new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
}
} catch (IOException e) {
throw new AssertionError(e);
}
}
private class OkListener implements View.OnClickListener {
@Override
public void onClick(View v) {
@ -225,75 +211,20 @@ public class ReceiveKeyActivity extends Activity {
@Override
protected Void doInBackground(Void... params) {
if (keyExchangeMessage != null) {
try {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(),
recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this);
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this);
Context context = ReceiveKeyActivity.this;
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this,
masterSecret);
identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessage.getIdentityKey());
identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey);
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
} catch (InvalidKeyException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
} catch (StaleKeyExchangeException e) {
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsStaleKeyExchange(messageId);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} else if (keyExchangeMessageBundle != null) {
try {
Context context = ReceiveKeyActivity.this;
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
TransportDetails transportDetails = getIntent().getBooleanExtra("is_push", false) ?
new PushTransportDetails(keyExchangeMessageBundle.getMessageVersion()) :
new SmsTransportDetails();
TextSecureCipher cipher = new TextSecureCipher(ReceiveKeyActivity.this, masterSecret, recipientDevice, transportDetails);
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this,
masterSecret);
identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessageBundle.getIdentityKey());
byte[] plaintext = cipher.decrypt(keyExchangeMessageBundle);
database.updateBundleMessageBody(masterSecret, messageId, "");
database.updateMessageBody(masterSecret, messageId, new String(plaintext));
} catch (InvalidKeyIdException | InvalidKeyException | LegacyMessageException | NoSessionException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
} catch (InvalidMessageException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsDecryptFailed(messageId);
} catch (DuplicateMessageException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsDecryptDuplicate(messageId);
} catch (UntrustedIdentityException e) {
Log.w("ReceiveKeyActivity", e);
Toast.makeText(ReceiveKeyActivity.this, "Untrusted!", Toast.LENGTH_LONG).show();
}
} else if (identityUpdateMessage != null) {
DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this)
.saveIdentity(masterSecret, recipient.getRecipientId(), identityUpdateMessage);
DatabaseFactory.getSmsDatabase(ReceiveKeyActivity.this).markAsProcessedKeyExchange(messageId);
if (message.isIdentityUpdate()) {
smsDatabase.markAsProcessedKeyExchange(messageId);
} else {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SmsDecryptJob(context, messageId));
}
return null;
}

View File

@ -27,7 +27,7 @@ import com.google.i18n.phonenumbers.Phonenumber;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import org.whispersystems.textsecure.util.Util;

View File

@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.service.RegistrationService;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.exceptions.RateLimitException;

View File

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class RoutingActivity extends PassphraseRequiredActionBarActivity {

View File

@ -19,19 +19,16 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.WindowManager;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
/**
* An activity to quickly share content with contacts

View File

@ -31,7 +31,7 @@ import android.widget.ListView;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
/**
* A fragment to select and share to open conversations

View File

@ -27,8 +27,8 @@ import android.widget.AbsListView;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
/**
* A CursorAdapter for building a list of open conversations

View File

@ -29,10 +29,10 @@ 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.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
/**
* Activity for verifying identity keys.

View File

@ -20,7 +20,7 @@ import android.os.Bundle;
import android.widget.TextView;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
/**
* Activity for displaying an identity key.

View File

@ -24,7 +24,7 @@ import android.view.MenuItem;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
/**
* Activity that displays the local identity key and offers the option to regenerate it.

View File

@ -8,7 +8,7 @@ import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PushRegistrationReminder extends Reminder {
public static final long REMINDER_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000;

View File

@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.DatabaseMigrationActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class SystemSmsImportReminder extends Reminder {

View File

@ -23,9 +23,6 @@ import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;

View File

@ -37,8 +37,6 @@ import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
import org.whispersystems.textsecure.crypto.MasterSecret;
/**
* Class for streaming an encrypted MMS "part" off the disk.
*

View File

@ -1,481 +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.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.PushReceiver;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.PduParser;
import ws.com.google.android.mms.pdu.RetrieveConf;
/**
* A work queue for processing a number of encryption operations.
*
* @author Moxie Marlinspike
*/
public class DecryptingQueue {
private static final Executor executor = Executors.newSingleThreadExecutor();
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
long messageId, long threadId, MultimediaMessagePdu mms)
{
MmsDecryptionItem runnable = new MmsDecryptionItem(context, masterSecret, messageId, threadId, mms);
executor.execute(runnable);
}
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
long messageId, long threadId, String originator, int deviceId,
String body, boolean isSecureMessage, boolean isKeyExchange,
boolean isEndSession)
{
DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId,
originator, deviceId, body,
isSecureMessage, isKeyExchange, isEndSession);
executor.execute(runnable);
}
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
long messageId, IncomingPushMessage message)
{
PushDecryptionWorkItem runnable = new PushDecryptionWorkItem(context, masterSecret,
messageId, message);
executor.execute(runnable);
}
public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) {
Log.w("DecryptingQueue", "Processing pending decrypts...");
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
EncryptingSmsDatabase.Reader smsReader = null;
PushDatabase.Reader pushReader = null;
SmsMessageRecord record;
IncomingPushMessage message;
try {
smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret);
pushReader = pushDatabase.readerFor(pushDatabase.getPending());
while ((record = smsReader.getNext()) != null) {
scheduleDecryptFromCursor(context, masterSecret, record);
}
while ((message = pushReader.getNext()) != null) {
if (message.isPreKeyBundle()) {
Intent intent = new Intent(context, SendReceiveService.class);
intent.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
intent.putExtra("message", message);
context.startService(intent);
pushDatabase.delete(pushReader.getCurrentId());
} else {
scheduleDecryption(context, masterSecret, pushReader.getCurrentId(), message);
}
}
} finally {
if (smsReader != null)
smsReader.close();
if (pushReader != null)
pushReader.close();
}
}
public static void scheduleRogueMessages(Context context, MasterSecret masterSecret, Recipient recipient) {
SmsDatabase.Reader reader = null;
SmsMessageRecord record;
try {
Cursor cursor = DatabaseFactory.getSmsDatabase(context).getEncryptedRogueMessages(recipient);
reader = DatabaseFactory.getEncryptingSmsDatabase(context).readerFor(masterSecret, cursor);
while ((record = reader.getNext()) != null) {
DatabaseFactory.getSmsDatabase(context).markAsDecrypting(record.getId());
scheduleDecryptFromCursor(context, masterSecret, record);
}
} finally {
if (reader != null)
reader.close();
}
}
private static void scheduleDecryptFromCursor(Context context, MasterSecret masterSecret,
SmsMessageRecord record)
{
long messageId = record.getId();
long threadId = record.getThreadId();
String body = record.getBody().getBody();
String originator = record.getIndividualRecipient().getNumber();
int originatorDeviceId = record.getRecipientDeviceId();
boolean isSecureMessage = record.isSecure();
boolean isKeyExchange = record.isKeyExchange();
boolean isEndSession = record.isEndSession();
scheduleDecryption(context, masterSecret, messageId, threadId,
originator, originatorDeviceId, body,
isSecureMessage, isKeyExchange, isEndSession);
}
private static class PushDecryptionWorkItem implements Runnable {
private Context context;
private MasterSecret masterSecret;
private long messageId;
private IncomingPushMessage message;
public PushDecryptionWorkItem(Context context, MasterSecret masterSecret,
long messageId, IncomingPushMessage message)
{
this.context = context;
this.masterSecret = masterSecret;
this.messageId = messageId;
this.message = message;
}
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 (!sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
sendResult(PushReceiver.RESULT_NO_SESSION);
return;
}
int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, recipientDevice);
TransportDetails transportDetails = new PushTransportDetails(sessionVersion);
TextSecureCipher textSecureCipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] plaintextBody = textSecureCipher.decrypt(new WhisperMessage(message.getBody()));
message = message.withBody(plaintextBody);
sendResult(PushReceiver.RESULT_OK);
} 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 (NoSessionException e) {
Log.w("DecryptingQueue", e);
sendResult(PushReceiver.RESULT_NO_SESSION);
}
}
private void sendResult(int result) {
Intent intent = new Intent(context, SendReceiveService.class);
intent.setAction(SendReceiveService.DECRYPTED_PUSH_ACTION);
intent.putExtra("message", message);
intent.putExtra("message_id", messageId);
intent.putExtra("result", result);
context.startService(intent);
}
}
private static class MmsDecryptionItem implements Runnable {
private long messageId;
private long threadId;
private Context context;
private MasterSecret masterSecret;
private MultimediaMessagePdu pdu;
public MmsDecryptionItem(Context context, MasterSecret masterSecret,
long messageId, long threadId, MultimediaMessagePdu pdu)
{
this.context = context;
this.masterSecret = masterSecret;
this.messageId = messageId;
this.threadId = threadId;
this.pdu = pdu;
}
private byte[] getEncryptedData() {
for (int i=0;i<pdu.getBody().getPartsNum();i++) {
Log.w("DecryptingQueue", "Content type (" + i + "): " + new String(pdu.getBody().getPart(i).getContentType()));
if (new String(pdu.getBody().getPart(i).getContentType()).equals(ContentType.TEXT_PLAIN)) {
return pdu.getBody().getPart(i).getData();
}
}
return null;
}
@Override
public void run() {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
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);
byte[] ciphertextPduBytes = getEncryptedData();
if (ciphertextPduBytes == null) {
Log.w("DecryptingQueue", "No encoded PNG data found on parts.");
database.markAsDecryptFailed(messageId, threadId);
return;
}
if (!sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
Log.w("DecryptingQueue", "No such recipient session for MMS...");
database.markAsNoSession(messageId, threadId);
return;
}
byte[] plaintextPduBytes;
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
TextTransport transportDetails = new TextTransport();
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
try {
plaintextPduBytes = cipher.decrypt(new WhisperMessage(decodedCiphertext));
} catch (InvalidMessageException ime) {
// XXX - For some reason, Sprint seems to append a single character to the
// end of message text segments. I don't know why, so here we just try
// truncating the message by one if the MAC fails.
if (ciphertextPduBytes.length > 2) {
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
byte[] truncated = Util.trim(ciphertextPduBytes, ciphertextPduBytes.length - 1);
decodedCiphertext = transportDetails.getDecodedMessage(truncated);
plaintextPduBytes = cipher.decrypt(new WhisperMessage(decodedCiphertext));
} else {
throw ime;
}
}
MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu)new PduParser(plaintextPduBytes).parse();
RetrieveConf plaintextPdu = new RetrieveConf(plaintextGenericPdu.getPduHeaders(),
plaintextGenericPdu.getBody());
Log.w("DecryptingQueue", "Successfully decrypted MMS!");
database.insertSecureDecryptedMessageInbox(masterSecret, new IncomingMediaMessage(plaintextPdu), threadId);
database.delete(messageId);
} catch (RecipientFormattingException | IOException | MmsException | InvalidMessageException rfe) {
Log.w("DecryptingQueue", rfe);
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 (NoSessionException nse) {
Log.w("DecryptingQueue", nse);
database.markAsNoSession(messageId, threadId);
}
}
}
private static class DecryptionWorkItem implements Runnable {
private final long messageId;
private final long threadId;
private final Context context;
private final MasterSecret masterSecret;
private final String body;
private final String originator;
private final int deviceId;
private final boolean isSecureMessage;
private final boolean isKeyExchange;
private final boolean isEndSession;
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId,
String originator, int deviceId, String body, boolean isSecureMessage,
boolean isKeyExchange, boolean isEndSession)
{
this.context = context;
this.messageId = messageId;
this.threadId = threadId;
this.masterSecret = masterSecret;
this.body = body;
this.originator = originator;
this.deviceId = deviceId;
this.isSecureMessage = isSecureMessage;
this.isKeyExchange = isKeyExchange;
this.isEndSession = isEndSession;
}
private void handleRemoteAsymmetricEncrypt() {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
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);
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes());
if (!sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId())) {
if (WhisperMessage.isLegacy(decodedCiphertext)) database.markAsLegacyVersion(messageId);
else database.markAsNoSession(messageId);
return;
}
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] paddedPlaintext = cipher.decrypt(new WhisperMessage(decodedCiphertext));
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
if (isEndSession &&
"TERMINATE".equals(plaintextBody) &&
sessionStore.containsSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
{
sessionStore.deleteSession(recipientDevice.getRecipientId(), recipientDevice.getDeviceId());
}
} catch (InvalidMessageException | IOException | RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
} catch (LegacyMessageException lme) {
Log.w("DecryptionQueue", lme);
database.markAsLegacyVersion(messageId);
return;
} catch (DuplicateMessageException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptDuplicate(messageId);
return;
} catch (NoSessionException e) {
Log.w("DecryptingQueue", e);
database.markAsNoSession(messageId);
return;
}
database.updateMessageBody(masterSecret, messageId, plaintextBody);
MessageNotifier.updateNotification(context, masterSecret);
}
private void handleLocalAsymmetricEncrypt() {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
String plaintextBody;
try {
AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
plaintextBody = asymmetricMasterCipher.decryptBody(body);
if (isKeyExchange) {
handleKeyExchangeProcessing(plaintextBody);
}
database.updateMessageBody(masterSecret, messageId, plaintextBody);
MessageNotifier.updateNotification(context, masterSecret);
} catch (InvalidMessageException | IOException ime) {
Log.w("DecryptionQueue", ime);
database.markAsDecryptFailed(messageId);
}
}
private void handleKeyExchangeProcessing(String plaintextBody) {
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false)
.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
KeyExchangeMessage message = new KeyExchangeMessage(Base64.decodeWithoutPadding(plaintextBody));
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
processor.processKeyExchangeMessage(message, threadId);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId);
} catch (InvalidVersionException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId);
} catch (InvalidMessageException | IOException | InvalidKeyException | RecipientFormattingException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (LegacyMessageException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageId);
} catch (StaleKeyExchangeException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
} catch (UntrustedIdentityException e) {
Log.w("DecryptingQueue", e);
}
}
}
@Override
public void run() {
if (isSecureMessage || isEndSession) {
handleRemoteAsymmetricEncrypt();
} else {
handleLocalAsymmetricEncrypt();
}
}
}
}

View File

@ -32,8 +32,6 @@ import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
import org.whispersystems.textsecure.crypto.MasterSecret;
/**
* A class for streaming an encrypted MMS "part" to disk.
*

View File

@ -14,7 +14,7 @@
* 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.crypto;
package org.thoughtcrime.securesms.crypto;
import android.os.Parcel;
import android.os.Parcelable;

View File

@ -28,8 +28,6 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException;

View File

@ -22,6 +22,9 @@ import android.content.Context;
import android.content.DialogInterface;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
@ -33,10 +36,7 @@ import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
public class KeyExchangeInitiator {

View File

@ -1,98 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class KeyExchangeProcessor {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
private Context context;
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionBuilder sessionBuilder;
public KeyExchangeProcessor(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
{
this.context = context;
this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
identityKeyStore, recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
}
public void processKeyExchangeMessage(PreKeyBundle bundle, long threadId)
throws InvalidKeyException, UntrustedIdentityException
{
sessionBuilder.process(bundle);
if (threadId != -1) {
broadcastSecurityUpdateEvent(context, threadId);
}
}
public OutgoingKeyExchangeMessage processKeyExchangeMessage(KeyExchangeMessage message, long threadId)
throws InvalidKeyException, UntrustedIdentityException, StaleKeyExchangeException
{
KeyExchangeMessage responseMessage = sessionBuilder.process(message);
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
if (responseMessage != null) {
String serializedResponse = Base64.encodeBytesWithoutPadding(responseMessage.serialize());
return new OutgoingKeyExchangeMessage(recipient, serializedResponse);
} else {
return null;
}
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
}
}

View File

@ -15,7 +15,7 @@
* 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.crypto;
package org.thoughtcrime.securesms.crypto;
import android.util.Log;

View File

@ -14,7 +14,7 @@
* 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.crypto;
package org.thoughtcrime.securesms.crypto;
import android.os.Parcel;
import android.os.Parcelable;

View File

@ -26,8 +26,6 @@ import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Util;

View File

@ -0,0 +1,134 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.crypto.TextSecureCipher;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduComposer;
import ws.com.google.android.mms.pdu.PduParser;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.RetrieveConf;
import ws.com.google.android.mms.pdu.SendReq;
public class MmsCipher {
private static final String TAG = MmsCipher.class.getSimpleName();
private final TextTransport textTransport = new TextTransport();
private final AxolotlStore axolotlStore;
public MmsCipher(AxolotlStore axolotlStore) {
this.axolotlStore = axolotlStore;
}
public MultimediaMessagePdu decrypt(Context context, MultimediaMessagePdu pdu)
throws InvalidMessageException, LegacyMessageException, DuplicateMessageException,
NoSessionException
{
try {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, pdu.getFrom().getString(), false);
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipientId, 1);
Optional<byte[]> ciphertext = getEncryptedData(pdu);
if (!ciphertext.isPresent()) {
throw new InvalidMessageException("No ciphertext present!");
}
byte[] decodedCiphertext = textTransport.getDecodedMessage(ciphertext.get());
byte[] plaintext;
try {
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
} catch (InvalidMessageException e) {
// NOTE - For some reason, Sprint seems to append a single character to the
// end of message text segments. I don't know why, so here we just try
// truncating the message by one if the MAC fails.
if (ciphertext.get().length > 2) {
Log.w(TAG, "Attempting truncated decrypt...");
byte[] truncated = Util.trim(ciphertext.get(), ciphertext.get().length - 1);
decodedCiphertext = textTransport.getDecodedMessage(truncated);
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
} else {
throw e;
}
}
MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu) new PduParser(plaintext).parse();
return new RetrieveConf(plaintextGenericPdu.getPduHeaders(), plaintextGenericPdu.getBody());
} catch (RecipientFormattingException | IOException e) {
throw new InvalidMessageException(e);
}
}
public SendReq encrypt(Context context, SendReq message)
throws NoSessionException, RecipientFormattingException
{
EncodedStringValue[] encodedRecipient = message.getTo();
String recipientString = encodedRecipient[0].getString();
Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientString, false);
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
byte[] pduBytes = new PduComposer(context, message).make();
if (!axolotlStore.containsSession(recipientId, RecipientDevice.DEFAULT_DEVICE_ID)) {
throw new NoSessionException("No session for: " + recipientId);
}
SessionCipher cipher = new SessionCipher(axolotlStore, recipientId, RecipientDevice.DEFAULT_DEVICE_ID);
CiphertextMessage ciphertextMessage = cipher.encrypt(textTransport.getPaddedMessageBody(pduBytes));
byte[] encryptedPduBytes = textTransport.getEncodedMessage(ciphertextMessage.serialize());
PduBody body = new PduBody();
PduPart part = new PduPart();
SendReq encryptedPdu = new SendReq(message.getPduHeaders(), body);
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setName((System.currentTimeMillis()+"").getBytes());
part.setData(encryptedPduBytes);
body.addPart(part);
encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
encryptedPdu.setBody(body);
return encryptedPdu;
}
private Optional<byte[]> getEncryptedData(MultimediaMessagePdu pdu) {
for (int i=0;i<pdu.getBody().getPartsNum();i++) {
if (new String(pdu.getBody().getPart(i).getContentType()).equals(ContentType.TEXT_PLAIN)) {
return Optional.of(pdu.getBody().getPart(i).getData());
}
}
return Optional.absent();
}
}

View File

@ -15,13 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
@ -33,7 +34,6 @@ import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.util.Medium;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.util.Util;
import java.io.File;

View File

@ -15,7 +15,7 @@
* 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.crypto;
package org.thoughtcrime.securesms.crypto;
import android.util.Log;

View File

@ -0,0 +1,43 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Base64;
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class SecurityEvent {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
}
}

View File

@ -0,0 +1,129 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.textsecure.storage.RecipientDevice;
import java.io.IOException;
public class SmsCipher {
private final SmsTransportDetails transportDetails = new SmsTransportDetails();
private final AxolotlStore axolotlStore;
public SmsCipher(AxolotlStore axolotlStore) {
this.axolotlStore = axolotlStore;
}
public IncomingTextMessage decrypt(Context context, IncomingTextMessage message)
throws LegacyMessageException, InvalidMessageException,
DuplicateMessageException, NoSessionException
{
try {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false);
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
WhisperMessage whisperMessage = new WhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipientId, 1);
byte[] padded = sessionCipher.decrypt(whisperMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
if (message.isEndSession() && "TERMINATE".equals(new String(plaintext))) {
axolotlStore.deleteSession(recipientId, 1);
}
return message.withMessageBody(new String(plaintext));
} catch (RecipientFormattingException | IOException e) {
throw new InvalidMessageException(e);
}
}
public IncomingEncryptedMessage decrypt(Context context, IncomingPreKeyBundleMessage message)
throws InvalidVersionException, InvalidMessageException, DuplicateMessageException,
UntrustedIdentityException, LegacyMessageException
{
try {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false);
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyMessage = new PreKeyWhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, recipients.getPrimaryRecipient().getRecipientId(), 1);
byte[] padded = sessionCipher.decrypt(preKeyMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
return new IncomingEncryptedMessage(message, new String(plaintext));
} catch (RecipientFormattingException | IOException | InvalidKeyException | InvalidKeyIdException e) {
throw new InvalidMessageException(e);
}
}
public OutgoingTextMessage encrypt(OutgoingTextMessage message) throws NoSessionException {
byte[] paddedBody = transportDetails.getPaddedMessageBody(message.getMessageBody().getBytes());
long recipientId = message.getRecipients().getPrimaryRecipient().getRecipientId();
if (!axolotlStore.containsSession(recipientId, RecipientDevice.DEFAULT_DEVICE_ID)) {
throw new NoSessionException("No session for: " + recipientId);
}
SessionCipher cipher = new SessionCipher(axolotlStore, recipientId, RecipientDevice.DEFAULT_DEVICE_ID);
CiphertextMessage ciphertextMessage = cipher.encrypt(paddedBody);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {
return new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
} else {
return message.withBody(encodedCiphertext);
}
}
public OutgoingKeyExchangeMessage process(Context context, IncomingKeyExchangeMessage message)
throws UntrustedIdentityException, StaleKeyExchangeException,
InvalidVersionException, LegacyMessageException, InvalidMessageException
{
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes()));
SessionBuilder sessionBuilder = new SessionBuilder(axolotlStore, recipient.getRecipientId(), 1);
KeyExchangeMessage response = sessionBuilder.process(exchangeMessage);
if (response != null) {
byte[] serializedResponse = transportDetails.getEncodedMessage(response.serialize());
return new OutgoingKeyExchangeMessage(recipient, new String(serializedResponse));
} else {
return null;
}
} catch (RecipientFormattingException | IOException | InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
}

View File

@ -0,0 +1,128 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import java.util.List;
public class TextSecureAxolotlStore implements AxolotlStore {
private final PreKeyStore preKeyStore;
private final SignedPreKeyStore signedPreKeyStore;
private final IdentityKeyStore identityKeyStore;
private final SessionStore sessionStore;
public TextSecureAxolotlStore(Context context, MasterSecret masterSecret) {
this.preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
this.signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
this.identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return identityKeyStore.getIdentityKeyPair();
}
@Override
public int getLocalRegistrationId() {
return identityKeyStore.getLocalRegistrationId();
}
@Override
public void saveIdentity(long recipientId, IdentityKey identityKey) {
identityKeyStore.saveIdentity(recipientId, identityKey);
}
@Override
public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey) {
return identityKeyStore.isTrustedIdentity(recipientId, identityKey);
}
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
return preKeyStore.loadPreKey(preKeyId);
}
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
preKeyStore.storePreKey(preKeyId, record);
}
@Override
public boolean containsPreKey(int preKeyId) {
return preKeyStore.containsPreKey(preKeyId);
}
@Override
public void removePreKey(int preKeyId) {
preKeyStore.removePreKey(preKeyId);
}
@Override
public SessionRecord loadSession(long recipientId, int deviceId) {
return sessionStore.loadSession(recipientId, deviceId);
}
@Override
public List<Integer> getSubDeviceSessions(long recipientId) {
return sessionStore.getSubDeviceSessions(recipientId);
}
@Override
public void storeSession(long recipientId, int deviceId, SessionRecord record) {
sessionStore.storeSession(recipientId, deviceId, record);
}
@Override
public boolean containsSession(long recipientId, int deviceId) {
return sessionStore.containsSession(recipientId, deviceId);
}
@Override
public void deleteSession(long recipientId, int deviceId) {
sessionStore.deleteSession(recipientId, deviceId);
}
@Override
public void deleteAllSessions(long recipientId) {
sessionStore.deleteAllSessions(recipientId);
}
@Override
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
return signedPreKeyStore.loadSignedPreKeys();
}
@Override
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
}
@Override
public boolean containsSignedPreKey(int signedPreKeyId) {
return signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
}
@Override
public void removeSignedPreKey(int signedPreKeyId) {
signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
}
}

View File

@ -1,13 +1,14 @@
package org.thoughtcrime.securesms.crypto;
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
public class TextSecureIdentityKeyStore implements IdentityKeyStore {

View File

@ -1,16 +1,16 @@
package org.whispersystems.textsecure.storage;
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Conversions;
import java.io.File;
@ -132,7 +132,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
private byte[] loadSerializedRecord(File recordFile)
throws IOException, InvalidMessageException
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
MasterCipher masterCipher = new MasterCipher(masterSecret);
FileInputStream fin = new FileInputStream(recordFile);
int recordVersion = readInteger(fin);

View File

@ -1,14 +1,15 @@
package org.whispersystems.textsecure.storage;
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
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.util.Conversions;
import java.io.File;
@ -44,7 +45,7 @@ public class TextSecureSessionStore implements SessionStore {
public SessionRecord loadSession(long recipientId, int deviceId) {
synchronized (FILE_LOCK) {
try {
MasterCipher cipher = new MasterCipher(masterSecret);
MasterCipher cipher = new MasterCipher(masterSecret);
FileInputStream in = new FileInputStream(getSessionFile(recipientId, deviceId));
int versionMarker = readInteger(in);

View File

@ -26,13 +26,12 @@ import android.util.Log;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
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.util.Base64;
import org.whispersystems.textsecure.util.Util;
@ -467,7 +466,7 @@ public class DatabaseFactory {
db.setTransactionSuccessful();
db.endTransaction();
DecryptingQueue.schedulePendingDecrypts(context, masterSecret);
// DecryptingQueue.schedulePendingDecrypts(context, masterSecret);
MessageNotifier.updateNotification(context, masterSecret);
}

View File

@ -8,7 +8,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import java.util.LinkedList;
import java.util.List;

View File

@ -22,7 +22,7 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import ws.com.google.android.mms.pdu.PduPart;

View File

@ -29,8 +29,8 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.lang.ref.SoftReference;
import java.util.Collections;
@ -73,7 +73,9 @@ public class EncryptingSmsDatabase extends SmsDatabase {
{
long type = Types.BASE_INBOX_TYPE;
if (!message.isSecureMessage() && !message.isEndSession()) {
if (masterSecret == null && message.isSecureMessage()) {
type |= Types.ENCRYPTION_REMOTE_BIT;
} else {
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
}
@ -97,8 +99,9 @@ public class EncryptingSmsDatabase extends SmsDatabase {
}
public void updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) {
updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK,
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_REMOTE_BIT | Types.SECURE_MESSAGE_BIT);
String encryptedBody = getEncryptedBody(masterSecret, body);
updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK,
Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT);
}
public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) {

View File

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
@ -103,7 +104,7 @@ public class GroupDatabase extends Database {
}
public void create(byte[] groupId, String title, List<String> members,
AttachmentPointer avatar, String relay)
TextSecureAttachmentPointer avatar, String relay)
{
ContentValues contentValues = new ContentValues();
contentValues.put(GROUP_ID, GroupUtil.getEncodedId(groupId));
@ -112,7 +113,7 @@ public class GroupDatabase extends Database {
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray());
contentValues.put(AVATAR_KEY, avatar.getKey());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
}
@ -123,14 +124,14 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
}
public void update(byte[] groupId, String title, AttachmentPointer avatar) {
public void update(byte[] groupId, String title, TextSecureAttachmentPointer avatar) {
ContentValues contentValues = new ContentValues();
if (title != null) contentValues.put(TITLE, title);
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
contentValues.put(AVATAR_KEY, avatar.getKey().toByteArray());
contentValues.put(AVATAR_KEY, avatar.getKey());
}
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,

View File

@ -28,8 +28,8 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException;

View File

@ -34,8 +34,8 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Trimmer;
@ -429,6 +430,32 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
database.update(TABLE_NAME, contentValues, null, null);
}
public Optional<NotificationInd> getNotification(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {String.valueOf(messageId)}, null, null, null);
if (cursor != null && cursor.moveToNext()) {
PduHeaders headers = getHeadersFromCursor(cursor);
addressDatabase.getAddressesForId(messageId, headers);
return Optional.of(new NotificationInd(headers));
} else {
return Optional.absent();
}
} catch (InvalidHeaderValueException e) {
Log.w("MmsDatabase", e);
return Optional.absent();
} finally {
if (cursor != null)
cursor.close();
}
}
public SendReq[] getOutgoingMessages(MasterSecret masterSecret, long messageId)
throws MmsException
{

View File

@ -155,6 +155,10 @@ public interface MmsSmsColumns {
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
}
public static boolean isAsymmetricEncryption(long type) {
return (type & ENCRYPTION_ASYMMETRIC_BIT) != 0;
}
public static boolean isFailedDecryptType(long type) {
return (type & ENCRYPTION_REMOTE_FAILED_BIT) != 0;
}

View File

@ -24,7 +24,7 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.HashSet;
import java.util.Set;

View File

@ -4,7 +4,7 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.os.Environment;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import java.io.File;

View File

@ -6,8 +6,8 @@ import android.database.sqlite.SQLiteStatement;
import android.os.Environment;
import android.util.Log;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;

View File

@ -4,6 +4,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.util.Base64;
@ -40,6 +41,32 @@ public class PushDatabase extends Database {
return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
}
public IncomingPushMessage get(long id) throws NoSuchMessageException {
Cursor cursor = null;
try {
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, ID_WHERE,
new String[] {String.valueOf(id)},
null, null, null);
if (cursor != null && cursor.moveToNext()) {
return new IncomingPushMessage(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)),
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)),
Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY))),
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)));
}
} catch (IOException e) {
Log.w("PushDatabase", e);
throw new NoSuchMessageException(e);
} finally {
if (cursor != null)
cursor.close();
}
throw new NoSuchMessageException("Not found");
}
public Cursor getPending() {
return databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
}
@ -85,4 +112,9 @@ public class PushDatabase extends Database {
}
}
public static class NoSuchMessageException extends Exception {
public NoSuchMessageException(String s) {super(s);}
public NoSuchMessageException(Exception e) {super(e);}
}
}

View File

@ -350,7 +350,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
} else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
@ -358,7 +358,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
}
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;

View File

@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.util.Log;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;

View File

@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.whispersystems.textsecure.util.Util;
import java.util.Arrays;

View File

@ -88,6 +88,10 @@ public abstract class MessageRecord extends DisplayRecord {
return MmsSmsColumns.Types.isLegacyType(type);
}
public boolean isAsymmetricEncryption() {
return MmsSmsColumns.Types.isAsymmetricEncryption(type);
}
@Override
public SpannableString getDisplayBody() {
if (isGroupUpdate() && isOutgoing()) {

View File

@ -8,20 +8,10 @@ import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.jobs.PushReceiveJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.directory.NotInDirectoryException;
import org.whispersystems.textsecure.push.ContactTokenDetails;
import org.whispersystems.textsecure.push.IncomingEncryptedPushMessage;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
public class GcmBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = GcmBroadcastReceiver.class.getSimpleName();
@ -48,44 +38,8 @@ public class GcmBroadcastReceiver extends BroadcastReceiver {
}
private void handleReceivedMessage(Context context, String data) {
try {
String sessionKey = TextSecurePreferences.getSignalingKey(context);
IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(data, sessionKey);
IncomingPushMessage message = encrypted.getIncomingPushMessage();
if (!isActiveNumber(context, message.getSource())) {
Directory directory = Directory.getInstance(context);
ContactTokenDetails contactTokenDetails = new ContactTokenDetails();
contactTokenDetails.setNumber(message.getSource());
directory.setNumber(contactTokenDetails, true);
}
Intent receiveService = new Intent(context, SendReceiveService.class);
receiveService.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
receiveService.putExtra("message", message);
context.startService(receiveService);
if (!message.isReceipt()) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
jobManager.add(new DeliveryReceiptJob(context, message.getSource(),
message.getTimestampMillis(),
message.getRelay()));
}
} catch (IOException | InvalidVersionException e) {
Log.w(TAG, e);
}
}
private boolean isActiveNumber(Context context, String e164number) {
boolean isActiveNumber;
try {
isActiveNumber = Directory.getInstance(context).isActiveNumber(e164number);
} catch (NotInDirectoryException e) {
isActiveNumber = false;
}
return isActiveNumber;
ApplicationContext.getInstance(context)
.getJobManager()
.add(new PushReceiveJob(context, data));
}
}

Some files were not shown because too many files have changed in this diff Show More