Migrate from SQLite and ciphertext blobs to SQLCipher + KeyStore

This commit is contained in:
Moxie Marlinspike
2018-01-24 19:17:44 -08:00
parent d1819b6361
commit f36b296e2e
134 changed files with 3633 additions and 3544 deletions

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
@@ -16,11 +16,10 @@
*/
package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
@@ -30,17 +29,17 @@ import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.PartAuthority;
@@ -48,13 +47,13 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
import org.whispersystems.libsignal.InvalidMessageException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -83,6 +82,8 @@ public class AttachmentDatabase extends Database {
static final String DIGEST = "digest";
static final String VOICE_NOTE = "voice_note";
public static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
static final String DATA_RANDOM = "data_random";
static final String THUMBNAIL_RANDOM = "thumbnail_random";
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
@@ -95,7 +96,8 @@ public class AttachmentDatabase extends Database {
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE,
SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE};
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE,
DATA_RANDOM, THUMBNAIL_RANDOM};
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
@@ -106,7 +108,7 @@ public class AttachmentDatabase extends Database {
TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " +
UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT, " +
VOICE_NOTE + " INTEGER DEFAULT 0);";
VOICE_NOTE + " INTEGER DEFAULT 0, " + DATA_RANDOM + " BLOB, " + THUMBNAIL_RANDOM + " BLOB);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
@@ -115,31 +117,34 @@ public class AttachmentDatabase extends Database {
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
public AttachmentDatabase(Context context, SQLiteOpenHelper databaseHelper) {
private final AttachmentSecret attachmentSecret;
public AttachmentDatabase(Context context, SQLCipherOpenHelper databaseHelper, AttachmentSecret attachmentSecret) {
super(context, databaseHelper);
this.attachmentSecret = attachmentSecret;
}
public @NonNull InputStream getAttachmentStream(MasterSecret masterSecret, AttachmentId attachmentId)
public @NonNull InputStream getAttachmentStream(AttachmentId attachmentId)
throws IOException
{
InputStream dataStream = getDataStream(masterSecret, attachmentId, DATA);
InputStream dataStream = getDataStream(attachmentId, DATA);
if (dataStream == null) throw new IOException("No stream for: " + attachmentId);
else return dataStream;
}
public @NonNull InputStream getThumbnailStream(@NonNull MasterSecret masterSecret, @NonNull AttachmentId attachmentId)
public @NonNull InputStream getThumbnailStream(@NonNull AttachmentId attachmentId)
throws IOException
{
Log.w(TAG, "getThumbnailStream(" + attachmentId + ")");
InputStream dataStream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
InputStream dataStream = getDataStream(attachmentId, THUMBNAIL);
if (dataStream != null) {
return dataStream;
}
try {
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId)).get();
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId)).get();
if (generatedStream == null) throw new FileNotFoundException("No thumbnail stream available: " + attachmentId);
else return generatedStream;
@@ -162,7 +167,7 @@ public class AttachmentDatabase extends Database {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
}
public @Nullable DatabaseAttachment getAttachment(@Nullable MasterSecret masterSecret, AttachmentId attachmentId)
public @Nullable DatabaseAttachment getAttachment(@NonNull AttachmentId attachmentId)
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
@@ -170,7 +175,7 @@ public class AttachmentDatabase extends Database {
try {
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
if (cursor != null && cursor.moveToFirst()) return getAttachment(masterSecret, cursor);
if (cursor != null && cursor.moveToFirst()) return getAttachment(cursor);
else return null;
} finally {
@@ -179,7 +184,7 @@ public class AttachmentDatabase extends Database {
}
}
public @NonNull List<DatabaseAttachment> getAttachmentsForMessage(@Nullable MasterSecret masterSecret, long mmsId) {
public @NonNull List<DatabaseAttachment> getAttachmentsForMessage(long mmsId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<DatabaseAttachment> results = new LinkedList<>();
Cursor cursor = null;
@@ -189,7 +194,7 @@ public class AttachmentDatabase extends Database {
null, null, null);
while (cursor != null && cursor.moveToNext()) {
results.add(getAttachment(masterSecret, cursor));
results.add(getAttachment(cursor));
}
return results;
@@ -199,7 +204,7 @@ public class AttachmentDatabase extends Database {
}
}
public @NonNull List<DatabaseAttachment> getPendingAttachments(@NonNull MasterSecret masterSecret) {
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
final List<DatabaseAttachment> attachments = new LinkedList<>();
@@ -207,7 +212,7 @@ public class AttachmentDatabase extends Database {
try {
cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
attachments.add(getAttachment(masterSecret, cursor));
attachments.add(getAttachment(cursor));
}
} finally {
if (cursor != null) cursor.close();
@@ -217,7 +222,7 @@ public class AttachmentDatabase extends Database {
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAttachmentsForMessage(long mmsId) {
void deleteAttachmentsForMessage(long mmsId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
@@ -246,7 +251,7 @@ public class AttachmentDatabase extends Database {
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAllAttachments() {
void deleteAllAttachments() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
@@ -258,17 +263,16 @@ public class AttachmentDatabase extends Database {
}
}
public long insertAttachmentsForPlaceholder(@NonNull MasterSecret masterSecret, long mmsId,
@NonNull AttachmentId attachmentId,
@NonNull InputStream inputStream)
public void insertAttachmentsForPlaceholder(long mmsId, @NonNull AttachmentId attachmentId, @NonNull InputStream inputStream)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = setAttachmentData(masterSecret, inputStream);
ContentValues values = new ContentValues();
SQLiteDatabase database = databaseHelper.getWritableDatabase();
DataInfo dataInfo = setAttachmentData(inputStream);
ContentValues values = new ContentValues();
values.put(DATA, partData.first.getAbsolutePath());
values.put(SIZE, partData.second);
values.put(DATA, dataInfo.file.getAbsolutePath());
values.put(SIZE, dataInfo.length);
values.put(DATA_RANDOM, dataInfo.random);
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
values.put(CONTENT_LOCATION, (String)null);
values.put(CONTENT_DISPOSITION, (String)null);
@@ -278,47 +282,44 @@ public class AttachmentDatabase extends Database {
if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
//noinspection ResultOfMethodCallIgnored
partData.first.delete();
dataInfo.file.delete();
} else {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
notifyConversationListListeners();
}
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId));
return partData.second;
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
}
void insertAttachmentsForMessage(@NonNull MasterSecretUnion masterSecret,
long mmsId,
@NonNull List<Attachment> attachments)
void insertAttachmentsForMessage(long mmsId, @NonNull List<Attachment> attachments)
throws MmsException
{
Log.w(TAG, "insertParts(" + attachments.size() + ")");
for (Attachment attachment : attachments) {
AttachmentId attachmentId = insertAttachment(masterSecret, mmsId, attachment);
AttachmentId attachmentId = insertAttachment(mmsId, attachment);
Log.w(TAG, "Inserted attachment at ID: " + attachmentId);
}
}
public @NonNull Attachment updateAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull Attachment attachment,
public @NonNull Attachment updateAttachmentData(@NonNull Attachment attachment,
@NonNull MediaStream mediaStream)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
DatabaseAttachment databaseAttachment = (DatabaseAttachment) attachment;
File dataFile = getAttachmentDataFile(databaseAttachment.getAttachmentId(), DATA);
DataInfo dataInfo = getAttachmentDataFileInfo(databaseAttachment.getAttachmentId(), DATA);
if (dataFile == null) {
if (dataInfo == null) {
throw new MmsException("No attachment data found!");
}
long dataSize = setAttachmentData(masterSecret, dataFile, mediaStream.getStream());
dataInfo = setAttachmentData(dataInfo.file, mediaStream.getStream());
ContentValues contentValues = new ContentValues();
contentValues.put(SIZE, dataSize);
contentValues.put(SIZE, dataInfo.length);
contentValues.put(CONTENT_TYPE, mediaStream.getMimeType());
contentValues.put(DATA_RANDOM, dataInfo.random);
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings());
@@ -328,7 +329,7 @@ public class AttachmentDatabase extends Database {
databaseAttachment.hasThumbnail(),
mediaStream.getMimeType(),
databaseAttachment.getTransferState(),
dataSize,
dataInfo.length,
databaseAttachment.getFileName(),
databaseAttachment.getLocation(),
databaseAttachment.getKey(),
@@ -339,16 +340,11 @@ public class AttachmentDatabase extends Database {
}
public void updateAttachmentFileName(@NonNull MasterSecret masterSecret,
@NonNull AttachmentId attachmentId,
public void updateAttachmentFileName(@NonNull AttachmentId attachmentId,
@Nullable String fileName)
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
if (fileName != null) {
fileName = new MasterCipher(masterSecret).encryptBody(fileName);
}
ContentValues contentValues = new ContentValues(1);
contentValues.put(FILE_NAME, fileName);
@@ -382,28 +378,43 @@ public class AttachmentDatabase extends Database {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
protected @Nullable InputStream getDataStream(MasterSecret masterSecret, AttachmentId attachmentId, String dataType)
protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType)
{
File dataFile = getAttachmentDataFile(attachmentId, dataType);
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, dataType);
if (dataInfo == null) {
return null;
}
try {
if (dataFile != null) return DecryptingPartInputStream.createFor(masterSecret, dataFile);
else return null;
if (dataInfo.random != null && dataInfo.random.length == 32) {
return ModernDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.random, dataInfo.file);
} else {
return ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.file);
}
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
private @Nullable File getAttachmentDataFile(@NonNull AttachmentId attachmentId,
@NonNull String dataType)
private @Nullable DataInfo getAttachmentDataFileInfo(@NonNull AttachmentId attachmentId, @NonNull String dataType)
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
String randomColumn;
switch (dataType) {
case DATA: randomColumn = DATA_RANDOM; break;
case THUMBNAIL: randomColumn = THUMBNAIL_RANDOM; break;
default:throw new AssertionError("Unknown data type: " + dataType);
}
try {
cursor = database.query(TABLE_NAME, new String[]{dataType}, PART_ID_WHERE, attachmentId.toStrings(),
cursor = database.query(TABLE_NAME, new String[]{dataType, SIZE, randomColumn}, PART_ID_WHERE, attachmentId.toStrings(),
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
@@ -411,7 +422,9 @@ public class AttachmentDatabase extends Database {
return null;
}
return new File(cursor.getString(0));
return new DataInfo(new File(cursor.getString(0)),
cursor.getLong(1),
cursor.getBlob(2));
} else {
return null;
}
@@ -422,57 +435,46 @@ public class AttachmentDatabase extends Database {
}
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull Uri uri)
private @NonNull DataInfo setAttachmentData(@NonNull Uri uri)
throws MmsException
{
try {
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
return setAttachmentData(masterSecret, inputStream);
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
return setAttachmentData(inputStream);
} catch (IOException e) {
throw new MmsException(e);
}
}
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull InputStream in)
private @NonNull DataInfo setAttachmentData(@NonNull InputStream in)
throws MmsException
{
try {
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
return new Pair<>(dataFile, setAttachmentData(masterSecret, dataFile, in));
return setAttachmentData(dataFile, in);
} catch (IOException e) {
throw new MmsException(e);
}
}
private long setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull File destination,
@NonNull InputStream in)
private @NonNull DataInfo setAttachmentData(@NonNull File destination, @NonNull InputStream in)
throws MmsException
{
try {
OutputStream out = new EncryptingPartOutputStream(destination, masterSecret);
return Util.copy(in, out);
byte[] random = new byte[32];
new SecureRandom().nextBytes(random);
OutputStream out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, random, destination);
long length = Util.copy(in, out);
return new DataInfo(destination, length, random);
} catch (IOException e) {
throw new MmsException(e);
}
}
DatabaseAttachment getAttachment(@Nullable MasterSecret masterSecret, Cursor cursor) {
String encryptedFileName = cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME));
String fileName = null;
if (masterSecret != null && !TextUtils.isEmpty(encryptedFileName)) {
try {
fileName = new MasterCipher(masterSecret).decryptBody(encryptedFileName);
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
}
DatabaseAttachment getAttachment(@NonNull Cursor cursor) {
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ATTACHMENT_ID_ALIAS)),
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
@@ -481,7 +483,7 @@ public class AttachmentDatabase extends Database {
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
fileName,
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
@@ -491,23 +493,18 @@ public class AttachmentDatabase extends Database {
}
private AttachmentId insertAttachment(MasterSecretUnion masterSecret, long mmsId, Attachment attachment)
private AttachmentId insertAttachment(long mmsId, Attachment attachment)
throws MmsException
{
Log.w(TAG, "Inserting attachment for mms id: " + mmsId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = null;
long uniqueId = System.currentTimeMillis();
String fileName = null;
SQLiteDatabase database = databaseHelper.getWritableDatabase();
DataInfo dataInfo = null;
long uniqueId = System.currentTimeMillis();
if (masterSecret.getMasterSecret().isPresent() && attachment.getDataUri() != null) {
partData = setAttachmentData(masterSecret.getMasterSecret().get(), attachment.getDataUri());
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
}
if (masterSecret.getMasterSecret().isPresent() && !TextUtils.isEmpty(attachment.getFileName())) {
fileName = new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(attachment.getFileName());
if (attachment.getDataUri() != null) {
dataInfo = setAttachmentData(attachment.getDataUri());
Log.w(TAG, "Wrote part to file: " + dataInfo.file.getAbsolutePath());
}
ContentValues contentValues = new ContentValues();
@@ -519,33 +516,34 @@ public class AttachmentDatabase extends Database {
contentValues.put(DIGEST, attachment.getDigest());
contentValues.put(CONTENT_DISPOSITION, attachment.getKey());
contentValues.put(NAME, attachment.getRelay());
contentValues.put(FILE_NAME, fileName);
contentValues.put(FILE_NAME, attachment.getFileName());
contentValues.put(SIZE, attachment.getSize());
contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId());
contentValues.put(VOICE_NOTE, attachment.isVoiceNote() ? 1 : 0);
if (partData != null) {
contentValues.put(DATA, partData.first.getAbsolutePath());
contentValues.put(SIZE, partData.second);
if (dataInfo != null) {
contentValues.put(DATA, dataInfo.file.getAbsolutePath());
contentValues.put(SIZE, dataInfo.length);
contentValues.put(DATA_RANDOM, dataInfo.random);
}
long rowId = database.insert(TABLE_NAME, null, contentValues);
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
if (partData != null) {
if (dataInfo != null) {
if (MediaUtil.hasVideoThumbnail(attachment.getDataUri())) {
Bitmap bitmap = MediaUtil.getVideoThumbnail(context, attachment.getDataUri());
if (bitmap != null) {
ThumbnailData thumbnailData = new ThumbnailData(bitmap);
updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio());
updateAttachmentThumbnail(attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio());
} else {
Log.w(TAG, "Retrieving video thumbnail failed, submitting thumbnail generation job...");
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
}
} else {
Log.w(TAG, "Submitting thumbnail generation job...");
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
}
}
@@ -553,19 +551,21 @@ public class AttachmentDatabase extends Database {
}
@SuppressWarnings("WeakerAccess")
@VisibleForTesting
protected void updateAttachmentThumbnail(MasterSecret masterSecret, AttachmentId attachmentId, InputStream in, float aspectRatio)
protected void updateAttachmentThumbnail(AttachmentId attachmentId, InputStream in, float aspectRatio)
throws MmsException
{
Log.w(TAG, "updating part thumbnail for #" + attachmentId);
Pair<File, Long> thumbnailFile = setAttachmentData(masterSecret, in);
DataInfo thumbnailFile = setAttachmentData(in);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(2);
values.put(THUMBNAIL, thumbnailFile.first.getAbsolutePath());
values.put(THUMBNAIL, thumbnailFile.file.getAbsolutePath());
values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio);
values.put(THUMBNAIL_RANDOM, thumbnailFile.random);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
@@ -584,24 +584,22 @@ public class AttachmentDatabase extends Database {
@VisibleForTesting
class ThumbnailFetchCallable implements Callable<InputStream> {
private final MasterSecret masterSecret;
private final AttachmentId attachmentId;
ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) {
this.masterSecret = masterSecret;
ThumbnailFetchCallable(AttachmentId attachmentId) {
this.attachmentId = attachmentId;
}
@Override
public @Nullable InputStream call() throws Exception {
Log.w(TAG, "Executing thumbnail job...");
final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
final InputStream stream = getDataStream(attachmentId, THUMBNAIL);
if (stream != null) {
return stream;
}
DatabaseAttachment attachment = getAttachment(masterSecret, attachmentId);
DatabaseAttachment attachment = getAttachment(attachmentId);
if (attachment == null || !attachment.hasData()) {
return null;
@@ -610,34 +608,35 @@ public class AttachmentDatabase extends Database {
ThumbnailData data;
if (MediaUtil.isVideoType(attachment.getContentType())) {
data = generateVideoThumbnail(masterSecret, attachmentId);
data = generateVideoThumbnail(attachmentId);
} else{
data = MediaUtil.generateThumbnail(context, masterSecret, attachment.getContentType(), attachment.getDataUri());
data = MediaUtil.generateThumbnail(context, attachment.getContentType(), attachment.getDataUri());
}
if (data == null) {
return null;
}
updateAttachmentThumbnail(masterSecret, attachmentId, data.toDataStream(), data.getAspectRatio());
updateAttachmentThumbnail(attachmentId, data.toDataStream(), data.getAspectRatio());
return getDataStream(masterSecret, attachmentId, THUMBNAIL);
return getDataStream(attachmentId, THUMBNAIL);
}
private ThumbnailData generateVideoThumbnail(MasterSecret masterSecret, AttachmentId attachmentId) {
@SuppressLint("NewApi")
private ThumbnailData generateVideoThumbnail(AttachmentId attachmentId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.w(TAG, "Video thumbnails not supported...");
return null;
}
File mediaFile = getAttachmentDataFile(attachmentId, DATA);
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, DATA);
if (mediaFile == null) {
if (dataInfo == null) {
Log.w(TAG, "No data file found for video thumbnail...");
return null;
}
EncryptedMediaDataSource dataSource = new EncryptedMediaDataSource(masterSecret, mediaFile);
EncryptedMediaDataSource dataSource = new EncryptedMediaDataSource(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(dataSource);
@@ -647,4 +646,16 @@ public class AttachmentDatabase extends Database {
return new ThumbnailData(bitmap);
}
}
private static class DataInfo {
private final File file;
private final long length;
private final byte[] random;
private DataInfo(File file, long length, byte[] random) {
this.file = file;
this.length = length;
this.random = random;
}
}
}

View File

@@ -18,9 +18,10 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.util.Set;
public abstract class Database {
@@ -29,10 +30,10 @@ public abstract class Database {
private static final String CONVERSATION_URI = "content://textsecure/thread/";
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
protected SQLiteOpenHelper databaseHelper;
protected final Context context;
protected SQLCipherOpenHelper databaseHelper;
protected final Context context;
public Database(Context context, SQLiteOpenHelper databaseHelper) {
public Database(Context context, SQLCipherOpenHelper databaseHelper) {
this.context = context;
this.databaseHelper = databaseHelper;
}
@@ -58,7 +59,7 @@ public abstract class Database {
cursor.setNotificationUri(context.getContentResolver(), Uri.parse(CONVERSATION_LIST_URI));
}
public void reset(SQLiteOpenHelper databaseHelper) {
public void reset(SQLCipherOpenHelper databaseHelper) {
this.databaseHelper = databaseHelper;
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,15 +3,13 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.R;
import org.whispersystems.libsignal.InvalidMessageException;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.util.LinkedList;
import java.util.List;
@@ -32,18 +30,18 @@ public class DraftDatabase extends Database {
"CREATE INDEX IF NOT EXISTS draft_thread_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
};
public DraftDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public DraftDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public void insertDrafts(MasterCipher masterCipher, long threadId, List<Draft> drafts) {
public void insertDrafts(long threadId, List<Draft> drafts) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
for (Draft draft : drafts) {
ContentValues values = new ContentValues(3);
values.put(THREAD_ID, threadId);
values.put(DRAFT_TYPE, masterCipher.encryptBody(draft.getType()));
values.put(DRAFT_VALUE, masterCipher.encryptBody(draft.getValue()));
values.put(DRAFT_TYPE, draft.getType());
values.put(DRAFT_VALUE, draft.getValue());
db.insert(TABLE_NAME, null, values);
}
@@ -54,7 +52,7 @@ public class DraftDatabase extends Database {
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
}
public void clearDrafts(Set<Long> threadIds) {
void clearDrafts(Set<Long> threadIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
StringBuilder where = new StringBuilder();
List<String> arguments = new LinkedList<>();
@@ -70,29 +68,24 @@ public class DraftDatabase extends Database {
db.delete(TABLE_NAME, where.toString().substring(4), arguments.toArray(new String[0]));
}
public void clearAllDrafts() {
void clearAllDrafts() {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, null, null);
}
public List<Draft> getDrafts(MasterCipher masterCipher, long threadId) {
public List<Draft> getDrafts(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
List<Draft> results = new LinkedList<Draft>();
List<Draft> results = new LinkedList<>();
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] {threadId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
try {
String encryptedType = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_TYPE));
String encryptedValue = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_VALUE));
String type = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_TYPE));
String value = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_VALUE));
results.add(new Draft(masterCipher.decryptBody(encryptedType),
masterCipher.decryptBody(encryptedValue)));
} catch (InvalidMessageException ime) {
Log.w("DraftDatabase", ime);
}
results.add(new Draft(type, value));
}
return results;
@@ -125,7 +118,7 @@ public class DraftDatabase extends Database {
return value;
}
public String getSnippet(Context context) {
String getSnippet(Context context) {
switch (type) {
case TEXT: return value;
case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet);
@@ -158,7 +151,7 @@ public class DraftDatabase extends Database {
}
}
public @Nullable Uri getUriSnippet(Context context) {
public @Nullable Uri getUriSnippet() {
Draft imageDraft = getDraftOfType(Draft.IMAGE);
if (imageDraft != null && imageDraft.getValue() != null) {

View File

@@ -1,233 +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.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Map;
public class EncryptingSmsDatabase extends SmsDatabase {
private final PlaintextCache plaintextCache = new PlaintextCache();
public EncryptingSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
private String getAsymmetricEncryptedBody(AsymmetricMasterSecret masterSecret, String body) {
AsymmetricMasterCipher bodyCipher = new AsymmetricMasterCipher(masterSecret);
return bodyCipher.encryptBody(body);
}
private String getEncryptedBody(MasterSecret masterSecret, String body) {
MasterCipher bodyCipher = new MasterCipher(masterSecret);
String ciphertext = bodyCipher.encryptBody(body);
plaintextCache.put(ciphertext, body);
return ciphertext;
}
public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId,
OutgoingTextMessage message, boolean forceSms,
long timestamp, InsertListener insertListener)
{
long type = Types.BASE_SENDING_TYPE;
if (masterSecret.getMasterSecret().isPresent()) {
message = message.withBody(getEncryptedBody(masterSecret.getMasterSecret().get(), message.getMessageBody()));
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
message = message.withBody(getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), message.getMessageBody()));
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
}
return insertMessageOutbox(threadId, message, type, forceSms, timestamp, insertListener);
}
public Optional<InsertResult> insertMessageInbox(@NonNull MasterSecretUnion masterSecret,
@NonNull IncomingTextMessage message)
{
if (masterSecret.getMasterSecret().isPresent()) {
return insertMessageInbox(masterSecret.getMasterSecret().get(), message);
} else {
return insertMessageInbox(masterSecret.getAsymmetricMasterSecret().get(), message);
}
}
private Optional<InsertResult> insertMessageInbox(@NonNull MasterSecret masterSecret,
@NonNull IncomingTextMessage message)
{
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT;
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
return insertMessageInbox(message, type);
}
private Optional<InsertResult> insertMessageInbox(@NonNull AsymmetricMasterSecret masterSecret,
@NonNull IncomingTextMessage message)
{
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_ASYMMETRIC_BIT;
message = message.withMessageBody(getAsymmetricEncryptedBody(masterSecret, message.getMessageBody()));
return insertMessageInbox(message, type);
}
public Pair<Long, Long> updateBundleMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
String encryptedBody;
if (masterSecret.getMasterSecret().isPresent()) {
encryptedBody = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
encryptedBody = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
}
return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, type);
}
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
long type;
if (masterSecret.getMasterSecret().isPresent()) {
body = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
type = Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
body = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
}
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
public Reader getMessages(MasterSecret masterSecret, int skip, int limit) {
Cursor cursor = super.getMessages(skip, limit);
return new DecryptingReader(masterSecret, cursor);
}
public Reader getOutgoingMessages(MasterSecret masterSecret) {
Cursor cursor = super.getOutgoingMessages();
return new DecryptingReader(masterSecret, cursor);
}
public SmsMessageRecord getMessage(MasterSecret masterSecret, long messageId) throws NoSuchMessageException {
Cursor cursor = super.getMessage(messageId);
DecryptingReader reader = new DecryptingReader(masterSecret, cursor);
SmsMessageRecord record = reader.getNext();
reader.close();
if (record == null) throw new NoSuchMessageException("No message for ID: " + messageId);
else return record;
}
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
Cursor cursor = super.getDecryptInProgressMessages();
return new DecryptingReader(masterSecret, cursor);
}
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
return new DecryptingReader(masterSecret, cursor);
}
public class DecryptingReader extends SmsDatabase.Reader {
private final MasterCipher masterCipher;
public DecryptingReader(MasterSecret masterSecret, Cursor cursor) {
super(cursor);
this.masterCipher = new MasterCipher(masterSecret);
}
@Override
protected DisplayRecord.Body getBody(Cursor cursor) {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String ciphertext = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
if (ciphertext == null) {
return new DisplayRecord.Body("", true);
}
try {
if (SmsDatabase.Types.isSymmetricEncryption(type)) {
String plaintext = plaintextCache.get(ciphertext);
if (plaintext != null)
return new DisplayRecord.Body(plaintext, true);
plaintext = masterCipher.decryptBody(ciphertext);
plaintextCache.put(ciphertext, plaintext);
return new DisplayRecord.Body(plaintext, true);
} else {
return new DisplayRecord.Body(ciphertext, true);
}
} catch (InvalidMessageException e) {
Log.w("EncryptingSmsDatabase", e);
return new DisplayRecord.Body(context.getString(R.string.EncryptingSmsDatabase_error_decrypting_message), true);
}
}
}
private static class PlaintextCache {
private static final int MAX_CACHE_SIZE = 2000;
private static final Map<String, SoftReference<String>> decryptedBodyCache =
Collections.synchronizedMap(new LRUCache<String, SoftReference<String>>(MAX_CACHE_SIZE));
public void put(String ciphertext, String plaintext) {
decryptedBodyCache.put(ciphertext, new SoftReference<String>(plaintext));
}
public String get(String ciphertext) {
SoftReference<String> plaintextReference = decryptedBodyCache.get(ciphertext);
if (plaintextReference != null) {
String plaintext = plaintextReference.get();
if (plaintext != null) {
return plaintext;
}
}
return null;
}
}
}

View File

@@ -5,8 +5,6 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -14,6 +12,9 @@ import android.text.TextUtils;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
@@ -64,7 +65,7 @@ public class GroupDatabase extends Database {
AVATAR_DIGEST + " BLOB, " +
MMS + " INTEGER DEFAULT 0);";
static final String[] CREATE_INDEXS = {
public static final String[] CREATE_INDEXS = {
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
};
@@ -75,7 +76,7 @@ public class GroupDatabase extends Database {
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public GroupDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -4,10 +4,12 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.util.LinkedList;
import java.util.List;
@@ -33,7 +35,7 @@ public class GroupReceiptDatabase extends Database {
"CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
};
public GroupReceiptDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public GroupReceiptDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -19,12 +19,13 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
@@ -72,7 +73,7 @@ public class IdentityDatabase extends Database {
}
}
IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
IdentityDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -2,14 +2,14 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
public class MediaDatabase extends Database {
@@ -44,7 +44,7 @@ public class MediaDatabase extends Database {
private static final String GALLERY_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' OR " + AttachmentDatabase.CONTENT_TYPE + " LIKE 'video/%'");
private static final String DOCUMENT_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'image/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'video/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'audio/%'");
public MediaDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public MediaDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -76,9 +76,9 @@ public class MediaDatabase extends Database {
this.outgoing = outgoing;
}
public static MediaRecord from(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Cursor cursor) {
public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) {
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
DatabaseAttachment attachment = attachmentDatabase.getAttachment(masterSecret, cursor);
DatabaseAttachment attachment = attachmentDatabase.getAttachment(cursor);
String serializedAddress = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
boolean outgoing = MessagingDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)));
Address address = null;

View File

@@ -3,14 +3,15 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.documents.Document;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.libsignal.IdentityKey;
@@ -23,7 +24,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
private static final String TAG = MessagingDatabase.class.getSimpleName();
public MessagingDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public MessagingDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -19,8 +19,6 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -32,19 +30,17 @@ import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.NotificationInd;
import com.google.android.mms.pdu_alt.PduHeaders;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -63,7 +59,6 @@ import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
@@ -153,7 +148,7 @@ public class MmsDatabase extends MessagingDatabase {
private final JobManager jobManager;
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public MmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
@@ -289,14 +284,14 @@ public class MmsDatabase extends MessagingDatabase {
return cursor;
}
public Reader getExpireStartedMessages(@Nullable MasterSecret masterSecret) {
public Reader getExpireStartedMessages() {
String where = EXPIRE_STARTED + " > 0";
return readerFor(masterSecret, rawQuery(where, null));
return readerFor(rawQuery(where, null));
}
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
public Reader getDecryptInProgressMessages() {
String where = MESSAGE_BOX + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
return readerFor(masterSecret, rawQuery(where, null));
return readerFor(rawQuery(where, null));
}
private void updateMailboxBitmask(long id, long maskOff, long maskOn, Optional<Long> threadId) {
@@ -494,16 +489,8 @@ public class MmsDatabase extends MessagingDatabase {
return expiring;
}
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
body = getEncryptedBody(masterSecret, body);
long type;
if (masterSecret.getMasterSecret().isPresent()) {
type = Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
@@ -544,7 +531,7 @@ public class MmsDatabase extends MessagingDatabase {
}
}
public OutgoingMediaMessage getOutgoingMessage(MasterSecret masterSecret, long messageId)
public OutgoingMediaMessage getOutgoingMessage(long messageId)
throws MmsException, NoSuchMessageException
{
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
@@ -555,13 +542,12 @@ public class MmsDatabase extends MessagingDatabase {
if (cursor != null && cursor.moveToNext()) {
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(masterSecret, messageId));
List<Attachment> attachments = new LinkedList<>(attachmentDatabase.getAttachmentsForMessage(messageId));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
String body = getDecryptedBody(masterSecret, messageText, outboxType);
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
@@ -591,9 +577,9 @@ public class MmsDatabase extends MessagingDatabase {
}
}
public long copyMessageInbox(MasterSecret masterSecret, long messageId) throws MmsException {
public long copyMessageInbox(long messageId) throws MmsException {
try {
OutgoingMediaMessage request = getOutgoingMessage(masterSecret, messageId);
OutgoingMediaMessage request = getOutgoingMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(ADDRESS, request.getRecipient().getAddress().serialize());
contentValues.put(DATE_SENT, request.getSentTimeMillis());
@@ -623,8 +609,7 @@ public class MmsDatabase extends MessagingDatabase {
databaseAttachment.isVoiceNote()));
}
return insertMediaMessage(new MasterSecretUnion(masterSecret),
request.getBody(),
return insertMediaMessage(request.getBody(),
attachments,
contentValues,
null);
@@ -633,8 +618,7 @@ public class MmsDatabase extends MessagingDatabase {
}
}
private Optional<InsertResult> insertMessageInbox(MasterSecretUnion masterSecret,
IncomingMediaMessage retrieved,
private Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation,
long threadId, long mailbox)
throws MmsException
@@ -674,7 +658,7 @@ public class MmsDatabase extends MessagingDatabase {
return Optional.absent();
}
long messageId = insertMediaMessage(masterSecret, retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
if (!Types.isExpirationTimerUpdate(mailbox)) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
@@ -687,19 +671,12 @@ public class MmsDatabase extends MessagingDatabase {
return Optional.of(new InsertResult(messageId, threadId));
}
public Optional<InsertResult> insertMessageInbox(MasterSecretUnion masterSecret,
IncomingMediaMessage retrieved,
public Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation, long threadId)
throws MmsException
{
long type = Types.BASE_INBOX_TYPE;
if (masterSecret.getMasterSecret().isPresent()) {
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
}
if (retrieved.isPushMessage()) {
type |= Types.PUSH_MESSAGE_BIT;
}
@@ -708,22 +685,14 @@ public class MmsDatabase extends MessagingDatabase {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, type);
return insertMessageInbox(retrieved, contentLocation, threadId, type);
}
public Optional<InsertResult> insertSecureDecryptedMessageInbox(MasterSecretUnion masterSecret,
IncomingMediaMessage retrieved,
long threadId)
public Optional<InsertResult> insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId)
throws MmsException
{
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT;
if (masterSecret.getMasterSecret().isPresent()) {
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
} else {
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
}
if (retrieved.isPushMessage()) {
type |= Types.PUSH_MESSAGE_BIT;
}
@@ -732,7 +701,7 @@ public class MmsDatabase extends MessagingDatabase {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
return insertMessageInbox(masterSecret, retrieved, "", threadId, type);
return insertMessageInbox(retrieved, "", threadId, type);
}
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) {
@@ -781,17 +750,13 @@ public class MmsDatabase extends MessagingDatabase {
jobManager.add(new TrimThreadJob(context, threadId));
}
public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret,
@NonNull OutgoingMediaMessage message,
public long insertMessageOutbox(@NonNull OutgoingMediaMessage message,
long threadId, boolean forceSms,
@Nullable SmsDatabase.InsertListener insertListener)
throws MmsException
{
long type = Types.BASE_SENDING_TYPE;
if (masterSecret.getMasterSecret().isPresent()) type |= Types.ENCRYPTION_SYMMETRIC_BIT;
else type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
@@ -821,7 +786,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener);
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), contentValues, insertListener);
if (message.getRecipient().getAddress().isGroup()) {
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false);
@@ -841,56 +806,23 @@ public class MmsDatabase extends MessagingDatabase {
return messageId;
}
private String getEncryptedBody(MasterSecretUnion masterSecret, String body) {
if (masterSecret.getMasterSecret().isPresent()) {
return new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(body);
} else {
return new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBody(body);
}
}
private @Nullable String getDecryptedBody(@NonNull MasterSecret masterSecret,
@Nullable String body, long outboxType)
{
try {
if (!TextUtils.isEmpty(body) && Types.isSymmetricEncryption(outboxType)) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
return masterCipher.decryptBody(body);
} else {
return body;
}
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
return null;
}
private long insertMediaMessage(@NonNull MasterSecretUnion masterSecret,
@Nullable String body,
private long insertMediaMessage(@Nullable String body,
@NonNull List<Attachment> attachments,
@NonNull ContentValues contentValues,
@Nullable SmsDatabase.InsertListener insertListener)
throws MmsException
{
SQLiteDatabase db = databaseHelper.getWritableDatabase();
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)) ||
Types.isAsymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)))
{
if (!TextUtils.isEmpty(body)) {
contentValues.put(BODY, getEncryptedBody(masterSecret, body));
}
}
SQLiteDatabase db = databaseHelper.getWritableDatabase();
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
contentValues.put(BODY, body);
contentValues.put(PART_COUNT, attachments.size());
db.beginTransaction();
try {
long messageId = db.insert(TABLE_NAME, null, contentValues);
partsDatabase.insertAttachmentsForMessage(masterSecret, messageId, attachments);
partsDatabase.insertAttachmentsForMessage(messageId, attachments);
db.setTransactionSuccessful();
return messageId;
@@ -1016,8 +948,8 @@ public class MmsDatabase extends MessagingDatabase {
}
}
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
return new Reader(masterSecret, cursor);
public Reader readerFor(Cursor cursor) {
return new Reader(cursor);
}
public OutgoingMessageReader readerFor(OutgoingMediaMessage message, long threadId) {
@@ -1097,16 +1029,10 @@ public class MmsDatabase extends MessagingDatabase {
public class Reader {
private final Cursor cursor;
private final MasterSecret masterSecret;
private final MasterCipher masterCipher;
private final Cursor cursor;
public Reader(MasterSecret masterSecret, Cursor cursor) {
this.cursor = cursor;
this.masterSecret = masterSecret;
if (masterSecret != null) masterCipher = new MasterCipher(masterSecret);
else masterCipher = null;
public Reader(Cursor cursor) {
this.cursor = cursor;
}
public MessageRecord getNext() {
@@ -1239,27 +1165,12 @@ public class MmsDatabase extends MessagingDatabase {
}
private DisplayRecord.Body getBody(Cursor cursor) {
try {
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
if (!TextUtils.isEmpty(body) && masterCipher != null && Types.isSymmetricEncryption(box)) {
return new DisplayRecord.Body(masterCipher.decryptBody(body), true);
} else if (!TextUtils.isEmpty(body) && masterCipher == null && Types.isSymmetricEncryption(box)) {
return new DisplayRecord.Body(body, false);
} else if (!TextUtils.isEmpty(body) && Types.isAsymmetricEncryption(box)) {
return new DisplayRecord.Body(body, false);
} else {
return new DisplayRecord.Body(body == null ? "" : body, true);
}
} catch (InvalidMessageException e) {
Log.w("MmsDatabase", e);
return new DisplayRecord.Body(context.getString(R.string.MmsDatabase_error_decrypting_message), true);
}
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
return new DisplayRecord.Body(body == null ? "" : body, true);
}
private SlideDeck getSlideDeck(@NonNull Cursor cursor) {
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, cursor);
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
return new SlideDeck(context, attachment);
}

View File

@@ -71,9 +71,9 @@ public interface MmsSmsColumns {
protected static final long GROUP_QUIT_BIT = 0x20000;
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
// Encrypted Storage Information
protected static final long ENCRYPTION_MASK = 0xFF000000;
protected static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
// Encrypted Storage Information XXX
public static final long ENCRYPTION_MASK = 0xFF000000;
public static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
protected static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
protected static final long ENCRYPTION_REMOTE_BIT = 0x20000000;
protected static final long ENCRYPTION_REMOTE_FAILED_BIT = 0x10000000;

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
@@ -18,23 +18,22 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteQueryBuilder;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.HashSet;
import java.util.Set;
public class MmsSmsDatabase extends Database {
@SuppressWarnings("unused")
private static final String TAG = MmsSmsDatabase.class.getSimpleName();
public static final String TRANSPORT = "transport_type";
@@ -77,7 +76,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.NAME,
AttachmentDatabase.TRANSFER_STATE};
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -309,34 +308,23 @@ public class MmsSmsDatabase extends Database {
return db.rawQuery(query, null);
}
public Reader readerFor(@NonNull Cursor cursor, @Nullable MasterSecret masterSecret) {
return new Reader(cursor, masterSecret);
}
public Reader readerFor(@NonNull Cursor cursor) {
return new Reader(cursor);
}
public class Reader {
private final Cursor cursor;
private final Optional<MasterSecret> masterSecret;
private EncryptingSmsDatabase.Reader smsReader;
private MmsDatabase.Reader mmsReader;
public Reader(Cursor cursor, @Nullable MasterSecret masterSecret) {
this.cursor = cursor;
this.masterSecret = Optional.fromNullable(masterSecret);
}
private final Cursor cursor;
private SmsDatabase.Reader smsReader;
private MmsDatabase.Reader mmsReader;
public Reader(Cursor cursor) {
this(cursor, null);
this.cursor = cursor;
}
private EncryptingSmsDatabase.Reader getSmsReader() {
private SmsDatabase.Reader getSmsReader() {
if (smsReader == null) {
if (masterSecret.isPresent()) smsReader = DatabaseFactory.getEncryptingSmsDatabase(context).readerFor(masterSecret.get(), cursor);
else smsReader = DatabaseFactory.getSmsDatabase(context).readerFor(cursor);
smsReader = DatabaseFactory.getSmsDatabase(context).readerFor(cursor);
}
return smsReader;
@@ -344,7 +332,7 @@ public class MmsSmsDatabase extends Database {
private MmsDatabase.Reader getMmsReader() {
if (mmsReader == null) {
mmsReader = DatabaseFactory.getMmsDatabase(context).readerFor(masterSecret.orNull(), cursor);
mmsReader = DatabaseFactory.getMmsDatabase(context).readerFor(cursor);
}
return mmsReader;

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.util.StorageUtil;
@@ -14,33 +13,35 @@ public class PlaintextBackupExporter {
private static final String FILENAME = "SignalPlaintextBackup.xml";
public static void exportPlaintextToSd(Context context, MasterSecret masterSecret)
public static void exportPlaintextToSd(Context context)
throws NoExternalStorageException, IOException
{
exportPlaintext(context, masterSecret);
exportPlaintext(context);
}
public static File getPlaintextExportFile() throws NoExternalStorageException {
return new File(StorageUtil.getBackupDir(), FILENAME);
}
private static void exportPlaintext(Context context, MasterSecret masterSecret)
private static void exportPlaintext(Context context)
throws NoExternalStorageException, IOException
{
int count = DatabaseFactory.getSmsDatabase(context).getMessageCount();
XmlBackup.Writer writer = new XmlBackup.Writer(getPlaintextExportFile().getAbsolutePath(), count);
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
int count = database.getMessageCount();
XmlBackup.Writer writer = new XmlBackup.Writer(getPlaintextExportFile().getAbsolutePath(), count);
SmsMessageRecord record;
EncryptingSmsDatabase.Reader reader = null;
int skip = 0;
int ROW_LIMIT = 500;
SmsDatabase.Reader reader = null;
int skip = 0;
int ROW_LIMIT = 500;
do {
if (reader != null)
reader.close();
reader = DatabaseFactory.getEncryptingSmsDatabase(context).getMessages(masterSecret, skip, ROW_LIMIT);
reader = database.readerFor(database.getMessages(skip, ROW_LIMIT));
while ((record = reader.getNext()) != null) {
XmlBackup.XmlBackupItem item =

View File

@@ -1,13 +1,12 @@
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.Environment;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.xmlpull.v1.XmlPullParserException;
@@ -18,7 +17,7 @@ import java.util.Set;
public class PlaintextBackupImporter {
public static void importPlaintextFromSd(Context context, MasterSecret masterSecret)
public static void importPlaintextFromSd(Context context)
throws NoExternalStorageException, IOException
{
Log.w("PlaintextBackupImporter", "importPlaintext()");
@@ -28,7 +27,6 @@ public class PlaintextBackupImporter {
try {
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
XmlBackup backup = new XmlBackup(getPlaintextExportFile().getAbsolutePath());
MasterCipher masterCipher = new MasterCipher(masterSecret);
Set<Long> modifiedThreads = new HashSet<>();
XmlBackup.XmlBackupItem item;
@@ -53,7 +51,7 @@ public class PlaintextBackupImporter {
addTranslatedTypeToStatement(statement, 8, item.getType());
addNullToStatement(statement, 9);
addStringToStatement(statement, 10, item.getSubject());
addEncryptedStringToStatement(masterCipher, statement, 11, item.getBody());
addStringToStatement(statement, 11, item.getBody());
addStringToStatement(statement, 12, item.getServiceCenter());
addLongToStatement(statement, 13, threadId);
modifiedThreads.add(threadId);
@@ -80,14 +78,7 @@ public class PlaintextBackupImporter {
return !backup.exists() && oldBackup.exists() ? oldBackup : backup;
}
private static void addEncryptedStringToStatement(MasterCipher masterCipher, SQLiteStatement statement, int index, String value) {
if (value == null || value.equals("null")) {
statement.bindNull(index);
} else {
statement.bindString(index, masterCipher.encryptBody(value));
}
}
@SuppressWarnings("SameParameterValue")
private static void addTranslatedTypeToStatement(SQLiteStatement statement, int index, int type) {
statement.bindLong(index, SmsDatabase.Types.translateFromSystemBaseType(type) | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
}

View File

@@ -3,11 +3,12 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
@@ -31,7 +32,7 @@ public class PushDatabase extends Database {
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER);";
public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -12,7 +10,10 @@ import android.util.Log;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util;
@@ -119,7 +120,7 @@ public class RecipientDatabase extends Database {
SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
PROFILE_SHARING + " INTEGER DEFAULT 0);";
public RecipientDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}

View File

@@ -20,9 +20,6 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
@@ -30,9 +27,13 @@ import android.util.Pair;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
@@ -107,7 +108,7 @@ public class SmsDatabase extends MessagingDatabase {
private final JobManager jobManager;
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public SmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
@@ -410,7 +411,17 @@ public class SmsDatabase extends MessagingDatabase {
return results;
}
protected Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
public Pair<Long, Long> updateBundleMessageBody(long messageId, String body) {
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
@@ -427,31 +438,33 @@ public class SmsDatabase extends MessagingDatabase {
}
public Pair<Long, Long> copyMessageInbox(long messageId) {
Reader reader = readerFor(getMessage(messageId));
SmsMessageRecord record = reader.getNext();
try {
SmsMessageRecord record = getMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, record.getDateSent());
contentValues.put(PROTOCOL, 31337);
contentValues.put(READ, 0);
contentValues.put(BODY, record.getBody().getBody());
contentValues.put(THREAD_ID, record.getThreadId());
contentValues.put(EXPIRES_IN, record.getExpiresIn());
ContentValues contentValues = new ContentValues();
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, record.getDateSent());
contentValues.put(PROTOCOL, 31337);
contentValues.put(READ, 0);
contentValues.put(BODY, record.getBody().getBody());
contentValues.put(THREAD_ID, record.getThreadId());
contentValues.put(EXPIRES_IN, record.getExpiresIn());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
notifyConversationListeners(record.getThreadId());
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
notifyConversationListeners(record.getThreadId());
jobManager.add(new TrimThreadJob(context, record.getThreadId()));
reader.close();
return new Pair<>(newMessageId, record.getThreadId());
jobManager.add(new TrimThreadJob(context, record.getThreadId()));
return new Pair<>(newMessageId, record.getThreadId());
} catch (NoSuchMessageException e) {
throw new AssertionError(e);
}
}
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull Address address) {
@@ -587,10 +600,11 @@ public class SmsDatabase extends MessagingDatabase {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE);
}
protected long insertMessageOutbox(long threadId, OutgoingTextMessage message,
long type, boolean forceSms, long date,
InsertListener insertListener)
public long insertMessageOutbox(long threadId, OutgoingTextMessage message,
boolean forceSms, long date, InsertListener insertListener)
{
long type = Types.BASE_SENDING_TYPE;
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
@@ -670,12 +684,21 @@ public class SmsDatabase extends MessagingDatabase {
return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null);
}
public Cursor getMessage(long messageId) {
public SmsMessageRecord getMessage(long messageId) throws NoSuchMessageException {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""},
null, null, null);
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""}, null, null, null);
Reader reader = new Reader(cursor);
SmsMessageRecord record = reader.getNext();
reader.close();
if (record == null) throw new NoSuchMessageException("No message for ID: " + messageId);
else return record;
}
public Cursor getMessageCursor(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
}
public boolean deleteMessage(long messageId) {

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
@@ -18,15 +18,14 @@ package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -40,19 +39,6 @@ public class SmsMigrator {
private static final String TAG = SmsMigrator.class.getSimpleName();
private static void addEncryptedStringToStatement(Context context, SQLiteStatement statement,
Cursor cursor, MasterSecret masterSecret,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
} else {
statement.bindString(index, encrypt(masterSecret, cursor.getString(columnIndex)));
}
}
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
@@ -77,8 +63,8 @@ public class SmsMigrator {
}
}
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
@SuppressWarnings("SameParameterValue")
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor, int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
@@ -99,9 +85,8 @@ public class SmsMigrator {
ourType == MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE;
}
private static void getContentValuesForRow(Context context, MasterSecret masterSecret,
Cursor cursor, long threadId,
SQLiteStatement statement)
private static void getContentValuesForRow(Context context, Cursor cursor,
long threadId, SQLiteStatement statement)
{
String theirAddress = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
statement.bindString(1, Address.fromExternal(context, theirAddress).serialize());
@@ -115,7 +100,7 @@ public class SmsMigrator {
addTranslatedTypeToStatement(statement, cursor, 8, SmsDatabase.TYPE);
addIntToStatement(statement, cursor, 9, SmsDatabase.REPLY_PATH_PRESENT);
addStringToStatement(statement, cursor, 10, SmsDatabase.SUBJECT);
addEncryptedStringToStatement(context, statement, cursor, masterSecret, 11, SmsDatabase.BODY);
addStringToStatement(statement, cursor, 11, SmsDatabase.BODY);
addStringToStatement(statement, cursor, 12, SmsDatabase.SERVICE_CENTER);
statement.bindLong(13, threadId);
@@ -159,14 +144,7 @@ public class SmsMigrator {
else return recipientList;
}
private static String encrypt(MasterSecret masterSecret, String body)
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
return masterCipher.encryptBody(body);
}
private static void migrateConversation(Context context, MasterSecret masterSecret,
SmsMigrationProgressListener listener,
private static void migrateConversation(Context context, SmsMigrationProgressListener listener,
ProgressDescription progress,
long theirThreadId, long ourThreadId)
{
@@ -191,7 +169,7 @@ public class SmsMigrator {
int typeColumn = cursor.getColumnIndex(SmsDatabase.TYPE);
if (cursor.isNull(typeColumn) || isAppropriateTypeForMigration(cursor, typeColumn)) {
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
getContentValuesForRow(context, cursor, ourThreadId, statement);
statement.execute();
}
@@ -208,9 +186,7 @@ public class SmsMigrator {
}
}
public static void migrateDatabase(Context context,
MasterSecret masterSecret,
SmsMigrationProgressListener listener)
public static void migrateDatabase(Context context, SmsMigrationProgressListener listener)
{
// if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
// return;
@@ -231,7 +207,7 @@ public class SmsMigrator {
if (ourRecipients != null) {
if (ourRecipients.size() == 1) {
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients.iterator().next());
migrateConversation(context, masterSecret, listener, progress, theirThreadId, ourThreadId);
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
} else if (ourRecipients.size() > 1) {
ourRecipients.add(Recipient.from(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), true));
@@ -245,7 +221,7 @@ public class SmsMigrator {
Recipient ourGroupRecipient = Recipient.from(context, Address.fromSerialized(ourGroupId), true);
long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
migrateConversation(context, masterSecret, listener, progress, theirThreadId, ourThreadId);
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
}
}
@@ -262,7 +238,7 @@ public class SmsMigrator {
}
public interface SmsMigrationProgressListener {
public void progressUpdate(ProgressDescription description);
void progressUpdate(ProgressDescription description);
}
public static class ProgressDescription {
@@ -271,8 +247,8 @@ public class SmsMigrator {
public final int secondaryTotal;
public final int secondaryComplete;
public ProgressDescription(int primaryTotal, int primaryComplete,
int secondaryTotal, int secondaryComplete)
ProgressDescription(int primaryTotal, int primaryComplete,
int secondaryTotal, int secondaryComplete)
{
this.primaryTotal = primaryTotal;
this.primaryComplete = primaryComplete;
@@ -280,14 +256,14 @@ public class SmsMigrator {
this.secondaryComplete = secondaryComplete;
}
public ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
this.primaryComplete = that.primaryComplete;
this.primaryTotal = that.primaryTotal;
this.secondaryComplete = secondaryComplete;
this.secondaryTotal = secondaryTotal;
}
public void incrementPrimaryComplete() {
void incrementPrimaryComplete() {
primaryComplete += 1;
}
}

View File

@@ -21,21 +21,19 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -46,7 +44,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -90,7 +87,7 @@ public class ThreadDatabase extends Database {
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);";
static final String[] CREATE_INDEXS = {
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
"CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");",
};
@@ -109,7 +106,7 @@ public class ThreadDatabase extends Database {
Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
.toList();
public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) {
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -617,8 +614,8 @@ public class ThreadDatabase extends Database {
void onProgress(int complete, int total);
}
public Reader readerFor(Cursor cursor, MasterCipher masterCipher) {
return new Reader(cursor, masterCipher);
public Reader readerFor(Cursor cursor) {
return new Reader(cursor);
}
public static class DistributionTypes {
@@ -631,12 +628,10 @@ public class ThreadDatabase extends Database {
public class Reader {
private final Cursor cursor;
private final MasterCipher masterCipher;
private final Cursor cursor;
public Reader(Cursor cursor, MasterCipher masterCipher) {
this.cursor = cursor;
this.masterCipher = masterCipher;
public Reader(Cursor cursor) {
this.cursor = cursor;
}
public ThreadRecord getNext() {
@@ -686,21 +681,7 @@ public class ThreadDatabase extends Database {
}
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {
try {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET));
if (!TextUtils.isEmpty(body) && masterCipher != null && MmsSmsColumns.Types.isSymmetricEncryption(type)) {
return new DisplayRecord.Body(masterCipher.decryptBody(body), true);
} else if (!TextUtils.isEmpty(body) && masterCipher == null && MmsSmsColumns.Types.isSymmetricEncryption(type)) {
return new DisplayRecord.Body(body, false);
} else {
return new DisplayRecord.Body(body, true);
}
} catch (InvalidMessageException e) {
Log.w("ThreadDatabase", e);
return new DisplayRecord.Body(context.getString(R.string.ThreadDatabase_error_decrypting_message), true);
}
return new DisplayRecord.Body(cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET)), true);
}
private @Nullable Uri getSnippetUri(Cursor cursor) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.annimon.stream.function.Function;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidMessageException;
import java.io.IOException;
public class SQLCipherMigrationHelper {
private static final String TAG = SQLCipherMigrationHelper.class.getSimpleName();
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
static void migratePlaintext(@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
{
modernDb.beginTransaction();
try {
copyTable("identities", legacyDb, modernDb, null);
copyTable("push", legacyDb, modernDb, null);
copyTable("groups", legacyDb, modernDb, null);
copyTable("recipient_preferences", legacyDb, modernDb, null);
copyTable("group_receipts", legacyDb, modernDb, null);
modernDb.setTransactionSuccessful();
} finally {
modernDb.endTransaction();
}
}
public static void migrateCiphertext(@NonNull Context context,
@NonNull MasterSecret masterSecret,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
{
MasterCipher legacyCipher = new MasterCipher(masterSecret);
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
modernDb.beginTransaction();
try {
copyTable("sms", legacyDb, modernDb, (row) -> {
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
row.getAsLong("type"),
row.getAsString("body"));
row.put("body", plaintext.second);
row.put("type", plaintext.first);
return row;
});
copyTable("mms", legacyDb, modernDb, (row) -> {
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
row.getAsLong("msg_box"),
row.getAsString("body"));
row.put("body", plaintext.second);
row.put("msg_box", plaintext.first);
return row;
});
copyTable("part", legacyDb, modernDb, (row) -> {
String fileName = row.getAsString("file_name");
String mediaKey = row.getAsString("cd");
try {
if (!TextUtils.isEmpty(fileName)) {
row.put("file_name", legacyCipher.decryptBody(fileName));
}
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
try {
if (!TextUtils.isEmpty(mediaKey)) {
byte[] plaintext;
if (mediaKey.startsWith("?ASYNC-")) {
plaintext = legacyAsymmetricCipher.decryptBytes(Base64.decode(mediaKey.substring("?ASYNC-".length())));
} else {
plaintext = legacyCipher.decryptBytes(Base64.decode(mediaKey));
}
row.put("cd", Base64.encodeBytes(plaintext));
}
} catch (IOException | InvalidMessageException e) {
Log.w(TAG, e);
}
return row;
});
copyTable("thread", legacyDb, modernDb, (row) -> {
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
row.getAsLong("snippet_type"),
row.getAsString("snippet"));
row.put("snippet", plaintext.second);
row.put("snippet_type", plaintext.first);
return row;
});
copyTable("drafts", legacyDb, modernDb, (row) -> {
String draftType = row.getAsString("type");
String draft = row.getAsString("value");
try {
if (!TextUtils.isEmpty(draftType)) row.put("type", legacyCipher.decryptBody(draftType));
if (!TextUtils.isEmpty(draft)) row.put("value", legacyCipher.decryptBody(draft));
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
return row;
});
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
modernDb.setTransactionSuccessful();
} finally {
modernDb.endTransaction();
}
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
}
private static void copyTable(@NonNull String tableName,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
@Nullable Function<ContentValues, ContentValues> transformer)
{
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
ContentValues row = new ContentValues();
for (int i=0;i<cursor.getColumnCount();i++) {
String columnName = cursor.getColumnName(i);
switch (cursor.getType(i)) {
case Cursor.FIELD_TYPE_STRING: row.put(columnName, cursor.getString(i)); break;
case Cursor.FIELD_TYPE_FLOAT: row.put(columnName, cursor.getFloat(i)); break;
case Cursor.FIELD_TYPE_INTEGER: row.put(columnName, cursor.getLong(i)); break;
case Cursor.FIELD_TYPE_BLOB: row.put(columnName, cursor.getBlob(i)); break;
}
}
if (transformer != null) {
row = transformer.apply(row);
}
modernDb.insert(tableName, null, row);
}
}
}
private static Pair<Long, String> getPlaintextBody(@NonNull MasterCipher legacyCipher,
@NonNull AsymmetricMasterCipher legacyAsymmetricCipher,
long type,
@Nullable String body)
{
try {
if (!TextUtils.isEmpty(body)) {
if ((type & ENCRYPTION_SYMMETRIC_BIT) != 0) body = legacyCipher.decryptBody(body);
else if ((type & ENCRYPTION_ASYMMETRIC_BIT) != 0) body = legacyAsymmetricCipher.decryptBody(body);
}
} catch (InvalidMessageException | IOException e) {
Log.w(TAG, e);
}
type &= ~(ENCRYPTION_SYMMETRIC_BIT);
type &= ~(ENCRYPTION_ASYMMETRIC_BIT);
return new Pair<>(type, body);
}
}

View File

@@ -0,0 +1,109 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
private final DatabaseSecret databaseSecret;
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteDatabase db) {
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
}
@Override
public void postKey(SQLiteDatabase db) {
db.rawExecSQL("PRAGMA kdf_iter = '1';");
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
}
});
this.context = context.getApplicationContext();
this.databaseSecret = databaseSecret;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SmsDatabase.CREATE_TABLE);
db.execSQL(MmsDatabase.CREATE_TABLE);
db.execSQL(AttachmentDatabase.CREATE_TABLE);
db.execSQL(ThreadDatabase.CREATE_TABLE);
db.execSQL(IdentityDatabase.CREATE_TABLE);
db.execSQL(DraftDatabase.CREATE_TABLE);
db.execSQL(PushDatabase.CREATE_TABLE);
db.execSQL(GroupDatabase.CREATE_TABLE);
db.execSQL(RecipientDatabase.CREATE_TABLE);
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, DraftDatabase.CREATE_INDEXS);
executeStatements(db, GroupDatabase.CREATE_INDEXS);
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
SQLCipherMigrationHelper.migratePlaintext(legacyDb, db);
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db);
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public SQLiteDatabase getReadableDatabase() {
return getReadableDatabase(databaseSecret.asString());
}
public SQLiteDatabase getWritableDatabase() {
return getWritableDatabase(databaseSecret.asString());
}
private void executeStatements(SQLiteDatabase db, String[] statements) {
for (String statement : statements)
db.execSQL(statement);
}
}

View File

@@ -9,7 +9,6 @@ import android.support.v4.content.AsyncTaskLoader;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MediaDatabase;
@@ -28,15 +27,14 @@ import java.util.Map;
public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMediaLoader.BucketedThreadMedia> {
@SuppressWarnings("unused")
private static final String TAG = BucketedThreadMediaLoader.class.getSimpleName();
private final MasterSecret masterSecret;
private final Address address;
public BucketedThreadMediaLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Address address) {
public BucketedThreadMediaLoader(@NonNull Context context, @NonNull Address address) {
super(context);
this.masterSecret = masterSecret;
this.address = address;
this.address = address;
onContentChanged();
}
@@ -60,7 +58,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMed
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) {
while (cursor != null && cursor.moveToNext()) {
result.add(MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor));
result.add(MediaDatabase.MediaRecord.from(getContext(), cursor));
}
}

View File

@@ -37,7 +37,7 @@ public class MessageDetailsLoader extends AbstractCursorLoader {
public Cursor getCursor() {
switch (type) {
case MmsSmsDatabase.SMS_TRANSPORT:
return DatabaseFactory.getEncryptingSmsDatabase(context).getMessage(messageId);
return DatabaseFactory.getSmsDatabase(context).getMessageCursor(messageId);
case MmsSmsDatabase.MMS_TRANSPORT:
return DatabaseFactory.getMmsDatabase(context).getMessage(messageId);
default: