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

@@ -11,7 +11,6 @@ import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
@@ -21,7 +20,6 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -50,7 +48,7 @@ public class DirectoryHelper {
private static final String TAG = DirectoryHelper.class.getSimpleName();
public static void refreshDirectory(@NonNull Context context, @Nullable MasterSecret masterSecret, boolean notifyOfNewUsers)
public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers)
throws IOException
{
if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return;
@@ -64,7 +62,7 @@ public class DirectoryHelper {
.add(new MultiDeviceContactUpdateJob(context));
}
if (notifyOfNewUsers) notifyNewUsers(context, masterSecret, newlyActiveUsers);
if (notifyOfNewUsers) notifyNewUsers(context, newlyActiveUsers);
}
private static @NonNull List<Address> refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager)
@@ -122,7 +120,6 @@ public class DirectoryHelper {
}
public static RegisteredState refreshDirectoryFor(@NonNull Context context,
@Nullable MasterSecret masterSecret,
@NonNull Recipient recipient)
throws IOException
{
@@ -145,7 +142,7 @@ public class DirectoryHelper {
}
if (!activeUser && systemContact) {
notifyNewUsers(context, masterSecret, Collections.singletonList(recipient.getAddress()));
notifyNewUsers(context, Collections.singletonList(recipient.getAddress()));
}
return RegisteredState.REGISTERED;
@@ -191,22 +188,21 @@ public class DirectoryHelper {
}
private static void notifyNewUsers(@NonNull Context context,
@Nullable MasterSecret masterSecret,
@NonNull List<Address> newUsers)
{
if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return;
for (Address newUser: newUsers) {
if (!SessionUtil.hasSession(context, masterSecret, newUser) && !Util.isOwnNumber(context, newUser)) {
if (!SessionUtil.hasSession(context, newUser) && !Util.isOwnNumber(context, newUser)) {
IncomingJoinedMessage message = new IncomingJoinedMessage(newUser);
Optional<InsertResult> insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message);
if (insertResult.isPresent()) {
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hour >= 9 && hour < 23) {
MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId(), true);
MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), true);
} else {
MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId(), false);
MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), false);
}
}
}

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
@@ -9,7 +10,6 @@ import android.support.annotation.UiThread;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
@@ -47,6 +47,7 @@ public class IdentityUtil {
private static final String TAG = IdentityUtil.class.getSimpleName();
@SuppressLint("StaticFieldLeak")
@UiThread
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
@@ -67,8 +68,7 @@ public class IdentityUtil {
return future;
}
public static void markIdentityVerified(Context context, MasterSecretUnion masterSecret,
Recipient recipient, boolean verified, boolean remote)
public static void markIdentityVerified(Context context, Recipient recipient, boolean verified, boolean remote)
{
long time = System.currentTimeMillis();
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
@@ -96,13 +96,13 @@ public class IdentityUtil {
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
}
}
}
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.<SignalServiceGroup>absent(), 0);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming);
@@ -117,8 +117,7 @@ public class IdentityUtil {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
Log.w(TAG, "Inserting verified outbox...");
DatabaseFactory.getEncryptingSmsDatabase(context)
.insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
}
}
@@ -140,12 +139,12 @@ public class IdentityUtil {
}
}
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.<SignalServiceGroup>absent(), 0);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
MessageNotifier.updateNotification(context, null, insertResult.get().getThreadId());
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
}
@@ -166,7 +165,7 @@ public class IdentityUtil {
}
}
public static void processVerifiedMessage(Context context, MasterSecretUnion masterSecret, VerifiedMessage verifiedMessage) {
public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) {
synchronized (SESSION_LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Recipient recipient = Recipient.from(context, Address.fromExternal(context, verifiedMessage.getDestination()), true);
@@ -183,7 +182,7 @@ public class IdentityUtil {
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT)
{
identityDatabase.setVerified(recipient.getAddress(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
markIdentityVerified(context, masterSecret, recipient, false, true);
markIdentityVerified(context, recipient, false, true);
}
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.VERIFIED &&
@@ -193,7 +192,7 @@ public class IdentityUtil {
{
saveIdentity(context, verifiedMessage.getDestination(), verifiedMessage.getIdentityKey());
identityDatabase.setVerified(recipient.getAddress(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED);
markIdentityVerified(context, masterSecret, recipient, true, true);
markIdentityVerified(context, recipient, true, true);
}
}
}

View File

@@ -42,14 +42,14 @@ public class MediaUtil {
public static final String VIDEO_UNSPECIFIED = "video/*";
public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri)
public static @Nullable ThumbnailData generateThumbnail(Context context, String contentType, Uri uri)
throws BitmapDecodingException
{
long startMillis = System.currentTimeMillis();
ThumbnailData data = null;
if (isImageType(contentType)) {
data = new ThumbnailData(generateImageThumbnail(context, masterSecret, uri));
data = new ThumbnailData(generateImageThumbnail(context, uri));
}
if (data != null) {
@@ -61,14 +61,14 @@ public class MediaUtil {
return data;
}
private static Bitmap generateImageThumbnail(Context context, MasterSecret masterSecret, Uri uri)
private static Bitmap generateImageThumbnail(Context context, Uri uri)
throws BitmapDecodingException
{
try {
int maxSize = context.getResources().getDimensionPixelSize(R.dimen.media_bubble_height);
return GlideApp.with(context.getApplicationContext())
.asBitmap()
.load(new DecryptableUri(masterSecret, uri))
.load(new DecryptableUri(uri))
.centerCrop()
.into(maxSize, maxSize)
.get();
@@ -125,8 +125,8 @@ public class MediaUtil {
}
}
public static long getMediaSize(Context context, MasterSecret masterSecret, Uri uri) throws IOException {
InputStream in = PartAuthority.getAttachmentStream(context, masterSecret, uri);
public static long getMediaSize(Context context, Uri uri) throws IOException {
InputStream in = PartAuthority.getAttachmentStream(context, uri);
if (in == null) throw new IOException("Couldn't obtain input stream.");
long size = 0;

View File

@@ -12,7 +12,6 @@ import android.webkit.MimeTypeMap;
import android.widget.Toast;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
@@ -34,20 +33,18 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
private static final int WRITE_ACCESS_FAILURE = 2;
private final WeakReference<Context> contextReference;
private final WeakReference<MasterSecret> masterSecretReference;
private final int attachmentCount;
public SaveAttachmentTask(Context context, MasterSecret masterSecret) {
this(context, masterSecret, 1);
public SaveAttachmentTask(Context context) {
this(context, 1);
}
public SaveAttachmentTask(Context context, MasterSecret masterSecret, int count) {
public SaveAttachmentTask(Context context, int count) {
super(context,
context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments, count, count),
context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, count, count));
this.contextReference = new WeakReference<>(context);
this.masterSecretReference = new WeakReference<>(masterSecret);
this.attachmentCount = count;
}
@@ -59,7 +56,6 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
try {
Context context = contextReference.get();
MasterSecret masterSecret = masterSecretReference.get();
String directory = null;
if (!StorageUtil.canWriteInSignalStorageDir()) {
@@ -72,7 +68,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
for (Attachment attachment : attachments) {
if (attachment != null) {
directory = saveAttachment(context, masterSecret, attachment);
directory = saveAttachment(context, attachment);
if (directory == null) return new Pair<>(FAILURE, null);
}
}
@@ -85,7 +81,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
}
}
private @Nullable String saveAttachment(Context context, MasterSecret masterSecret, Attachment attachment)
private @Nullable String saveAttachment(Context context, Attachment attachment)
throws NoExternalStorageException, IOException
{
String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType);
@@ -96,7 +92,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
File outputDirectory = createOutputDirectoryFromContentType(contentType);
File mediaFile = createOutputFile(outputDirectory, fileName);
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.uri);
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri);
if (inputStream == null) {
return null;

View File

@@ -12,6 +12,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
@@ -118,6 +119,52 @@ public class TextSecurePreferences {
private static final String UNAUTHORIZED_RECEIVED = "pref_unauthorized_received";
private static final String SUCCESSFUL_DIRECTORY_PREF = "pref_successful_directory";
private static final String DATABASE_ENCRYPTED_SECRET = "pref_database_encrypted_secret";
private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret";
private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret";
private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret";
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration";
public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) {
setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value);
}
public static boolean getNeedsSqlCipherMigration(@NonNull Context context) {
return getBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, false);
}
public static void setAttachmentEncryptedSecret(@NonNull Context context, @NonNull String secret) {
setStringPreference(context, ATTACHMENT_ENCRYPTED_SECRET, secret);
}
public static void setAttachmentUnencryptedSecret(@NonNull Context context, @Nullable String secret) {
setStringPreference(context, ATTACHMENT_UNENCRYPTED_SECRET, secret);
}
public static @Nullable String getAttachmentEncryptedSecret(@NonNull Context context) {
return getStringPreference(context, ATTACHMENT_ENCRYPTED_SECRET, null);
}
public static @Nullable String getAttachmentUnencryptedSecret(@NonNull Context context) {
return getStringPreference(context, ATTACHMENT_UNENCRYPTED_SECRET, null);
}
public static void setDatabaseEncryptedSecret(@NonNull Context context, @NonNull String secret) {
setStringPreference(context, DATABASE_ENCRYPTED_SECRET, secret);
}
public static void setDatabaseUnencryptedSecret(@NonNull Context context, @Nullable String secret) {
setStringPreference(context, DATABASE_UNENCRYPTED_SECRET, secret);
}
public static @Nullable String getDatabaseUnencryptedSecret(@NonNull Context context) {
return getStringPreference(context, DATABASE_UNENCRYPTED_SECRET, null);
}
public static @Nullable String getDatabaseEncryptedSecret(@NonNull Context context) {
return getStringPreference(context, DATABASE_ENCRYPTED_SECRET, null);
}
public static void setHasSuccessfullyRetrievedDirectory(Context context, boolean value) {
setBooleanPreference(context, SUCCESSFUL_DIRECTORY_PREF, value);
}

View File

@@ -226,7 +226,7 @@ public class Util {
}
public static long copy(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[4096];
byte[] buffer = new byte[8192];
int read;
long total = 0;