diff --git a/build.gradle b/build.gradle index 9a4da0f4d..55bdd8b56 100644 --- a/build.gradle +++ b/build.gradle @@ -117,6 +117,7 @@ dependencies { } compile 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' compile 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' + compile 'net.zetetic:android-database-sqlcipher:3.5.9' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:1.7.1' @@ -183,6 +184,7 @@ dependencyVerification { 'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9', 'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1', 'com.github.dmytrodanylyk.circular-progress-button:library:8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e', + 'net.zetetic:android-database-sqlcipher:eff93b3222f4bdc349ffee2d2e3b2a2507241f17435fb998947bcce486618f1d', 'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70', 'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49', 'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086', diff --git a/proguard-sqlite.pro b/proguard-sqlite.pro index 7660512e6..b7837ef87 100644 --- a/proguard-sqlite.pro +++ b/proguard-sqlite.pro @@ -1,2 +1,5 @@ -keep class org.sqlite.** { *; } --keep class org.sqlite.database.** { *; } \ No newline at end of file +-keep class org.sqlite.database.** { *; } + +-keep class net.sqlcipher.** { *; } +-dontwarn net.sqlcipher.** \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java index 9bc527a3e..601efb3b7 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java @@ -11,8 +11,7 @@ import java.util.Locale; import java.util.Set; public interface BindableConversationItem extends Unbindable { - void bind(@NonNull MasterSecret masterSecret, - @NonNull MessageRecord messageRecord, + void bind(@NonNull MessageRecord messageRecord, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set batchSelected, diff --git a/src/org/thoughtcrime/securesms/BindableConversationListItem.java b/src/org/thoughtcrime/securesms/BindableConversationListItem.java index 8aff5c79e..d35460ad6 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationListItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationListItem.java @@ -11,7 +11,7 @@ import java.util.Set; public interface BindableConversationListItem extends Unbindable { - public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, + public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode); } diff --git a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java index 1f9605167..6fc9f7ec6 100644 --- a/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ b/src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms; +import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; @@ -10,7 +11,6 @@ import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.widget.TextView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -35,12 +35,12 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; public class ConfirmIdentityDialog extends AlertDialog { + @SuppressWarnings("unused") private static final String TAG = ConfirmIdentityDialog.class.getSimpleName(); private OnClickListener callback; public ConfirmIdentityDialog(Context context, - MasterSecret masterSecret, MessageRecord messageRecord, IdentityKeyMismatch mismatch) { @@ -59,7 +59,7 @@ public class ConfirmIdentityDialog extends AlertDialog { setTitle(name); setMessage(spannableString); - setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(masterSecret, messageRecord, mismatch, recipient.getAddress())); + setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getAddress())); setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener()); } @@ -76,18 +76,17 @@ public class ConfirmIdentityDialog extends AlertDialog { private class AcceptListener implements OnClickListener { - private final MasterSecret masterSecret; private final MessageRecord messageRecord; private final IdentityKeyMismatch mismatch; private final Address address; - private AcceptListener(MasterSecret masterSecret, MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) { - this.masterSecret = masterSecret; + private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) { this.messageRecord = messageRecord; this.mismatch = mismatch; this.address = address; } + @SuppressLint("StaticFieldLeak") @Override public void onClick(DialogInterface dialog, int which) { new AsyncTask() @@ -115,7 +114,7 @@ public class ConfirmIdentityDialog extends AlertDialog { private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) { MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext()); Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId); - MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor, masterSecret); + MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor); MessageRecord record; try { @@ -144,14 +143,14 @@ public class ConfirmIdentityDialog extends AlertDialog { if (messageRecord.getRecipient().isPushGroupRecipient()) { MessageSender.resendGroupMessage(getContext(), messageRecord, mismatch.getAddress()); } else { - MessageSender.resend(getContext(), masterSecret, messageRecord); + MessageSender.resend(getContext(), messageRecord); } } else { smsDatabase.removeMismatchedIdentity(messageRecord.getId(), mismatch.getAddress(), mismatch.getIdentityKey()); - MessageSender.resend(getContext(), masterSecret, messageRecord); + MessageSender.resend(getContext(), messageRecord); } } diff --git a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java index e50b8d491..9cdabd4a6 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionActivity.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionActivity.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2015 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -24,7 +24,6 @@ import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import org.thoughtcrime.securesms.components.ContactFilterToolbar; -import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.DynamicLanguage; @@ -53,8 +52,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB protected ContactSelectionListFragment contactsFragment; - private MasterSecret masterSecret; - private ContactFilterToolbar toolbar; + private ContactFilterToolbar toolbar; @Override protected void onPreCreate() { @@ -64,7 +62,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB @Override protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, TextSecurePreferences.isSmsEnabled(this) @@ -94,6 +91,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB this.toolbar = ViewUtil.findById(this, R.id.toolbar); setSupportActionBar(toolbar); + assert getSupportActionBar() != null; getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setIcon(null); @@ -107,11 +105,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB } private void initializeSearch() { - toolbar.setOnFilterChangedListener(new OnFilterChangedListener() { - @Override public void onFilterChanged(String filter) { - contactsFragment.setQueryFilter(filter); - } - }); + toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter)); } @Override @@ -128,19 +122,16 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB private static class RefreshDirectoryTask extends AsyncTask { private final WeakReference activity; - private final MasterSecret masterSecret; private RefreshDirectoryTask(ContactSelectionActivity activity) { - this.activity = new WeakReference<>(activity); - this.masterSecret = activity.masterSecret; + this.activity = new WeakReference<>(activity); } - @Override protected Void doInBackground(Context... params) { try { - DirectoryHelper.refreshDirectory(params[0], masterSecret, true); + DirectoryHelper.refreshDirectory(params[0], true); } catch (IOException e) { Log.w(TAG, e); } diff --git a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 0f4f59968..3767925d1 100644 --- a/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -221,7 +220,7 @@ public class ContactSelectionListFragment extends Fragment @Override public Loader onCreateLoader(int id, Bundle args) { - return new ContactsCursorLoader(getActivity(), KeyCachingService.getMasterSecret(getContext()), + return new ContactsCursorLoader(getActivity(), getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL), cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false)); } @@ -263,7 +262,7 @@ public class ContactSelectionListFragment extends Fragment @Override protected Boolean doInBackground(Void... voids) { try { - DirectoryHelper.refreshDirectory(getContext(), null, false); + DirectoryHelper.refreshDirectory(getContext(), false); return true; } catch (IOException e) { Log.w(TAG, e); diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index d631d245a..e4a34a453 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -97,7 +97,6 @@ import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.Address; @@ -214,7 +213,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int PICK_GIF = 9; private static final int SMS_DEFAULT = 10; - private MasterSecret masterSecret; private GlideRequests glideRequests; protected ComposeText composeText; private AnimatingToggle buttonToggle; @@ -263,7 +261,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) { Log.w(TAG, "onCreate()"); - this.masterSecret = masterSecret; supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); setContentView(R.layout.conversation_activity); @@ -440,7 +437,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity break; case PICK_LOCATION: SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); - attachmentManager.setLocation(masterSecret, place, getCurrentMediaConstraints()); + attachmentManager.setLocation(place, getCurrentMediaConstraints()); break; case PICK_GIF: setMedia(data.getData(), MediaType.GIF); @@ -595,7 +592,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipient(), System.currentTimeMillis(), expirationTime * 1000); - MessageSender.send(ConversationActivity.this, masterSecret, outgoingMessage, threadId, false, null); + MessageSender.send(ConversationActivity.this, outgoingMessage, threadId, false, null); return null; } @@ -681,7 +678,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleRegisterForSignal() { Intent intent = new Intent(this, RegistrationActivity.class); intent.putExtra("cancel_button", true); - intent.putExtra("master_secret", masterSecret); startActivity(intent); } @@ -723,7 +719,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new AsyncTask() { @Override protected Long doInBackground(OutgoingEndSessionMessage... messages) { - return MessageSender.send(context, masterSecret, messages[0], threadId, false, null); + return MessageSender.send(context, messages[0], threadId, false, null); } @Override @@ -768,7 +764,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity .build(); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipient(), context, null, System.currentTimeMillis(), 0); - MessageSender.send(self, masterSecret, outgoingMessage, threadId, false, null); + MessageSender.send(self, outgoingMessage, threadId, false, null); DatabaseFactory.getGroupDatabase(self).remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(self))); initializeEnabledCheck(); } catch (IOException e) { @@ -1006,9 +1002,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new AsyncTask>() { @Override protected List doInBackground(Void... params) { - MasterCipher masterCipher = new MasterCipher(masterSecret); DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this); - List results = draftDatabase.getDrafts(masterCipher, threadId); + List results = draftDatabase.getDrafts(threadId); draftDatabase.clearDrafts(threadId); @@ -1024,7 +1019,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeText.setText(draft.getValue()); break; case Draft.LOCATION: - attachmentManager.setLocation(masterSecret, SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()); + attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()); break; case Draft.IMAGE: setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE); @@ -1078,7 +1073,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (registeredState == RegisteredState.UNKNOWN) { try { Log.w(TAG, "Refreshing directory for user: " + recipient.getAddress().serialize()); - registeredState = DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipient); + registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient); } catch (IOException e) { Log.w(TAG, e); } @@ -1246,7 +1241,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity attachmentTypeSelector = null; attachmentManager = new AttachmentManager(this, this); - audioRecorder = new AudioRecorder(this, masterSecret); + audioRecorder = new AudioRecorder(this); SendButtonListener sendButtonListener = new SendButtonListener(); ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); @@ -1385,7 +1380,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) { if (uri == null) return; - attachmentManager.setMedia(masterSecret, glideRequests, uri, mediaType, getCurrentMediaConstraints()); + attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints()); } private void addAttachmentContactInfo(Uri contactUri) { @@ -1440,7 +1435,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity final Drafts drafts = getDraftsForCurrentState(); final long thisThreadId = this.threadId; - final MasterSecret thisMasterSecret = this.masterSecret.parcelClone(); final int thisDistributionType = this.distributionType; new AsyncTask() { @@ -1453,9 +1447,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (drafts.size() > 0) { if (threadId == -1) threadId = threadDatabase.getThreadIdFor(getRecipient(), thisDistributionType); - draftDatabase.insertDrafts(new MasterCipher(thisMasterSecret), threadId, drafts); + draftDatabase.insertDrafts(threadId, drafts); threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), - drafts.getUriSnippet(ConversationActivity.this), + drafts.getUriSnippet(), System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); } else if (threadId > 0) { threadDatabase.update(threadId, false); @@ -1588,7 +1582,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Context context = ConversationActivity.this; List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false); - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); MarkReadReceiver.process(context, messageIds); return null; @@ -1701,7 +1695,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); } - return MessageSender.send(context, masterSecret, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); + return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); } @Override @@ -1746,7 +1740,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); } - return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); + return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); } @Override @@ -1799,7 +1793,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onImageCapture(@NonNull final byte[] imageBytes) { setMedia(PersistentBlobProvider.getInstance(this) - .create(masterSecret, imageBytes, MediaUtil.IMAGE_JPEG, null), + .create(this, imageBytes, MediaUtil.IMAGE_JPEG, null), MediaType.IMAGE); quickAttachmentDrawer.hide(false); } @@ -1862,7 +1856,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new AsyncTask() { @Override protected Void doInBackground(Void... params) { - PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first); + PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1891,7 +1885,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity new AsyncTask() { @Override protected Void doInBackground(Void... params) { - PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first); + PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 2b899a932..ac0d48a02 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -26,13 +26,10 @@ import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.TextView; import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter; @@ -92,7 +89,6 @@ public class ConversationAdapter private final Set batchSelected = Collections.synchronizedSet(new HashSet()); private final @Nullable ItemClickListener clickListener; - private final @NonNull MasterSecret masterSecret; private final @NonNull GlideRequests glideRequests; private final @NonNull Locale locale; private final @NonNull Recipient recipient; @@ -142,7 +138,6 @@ public class ConversationAdapter ConversationAdapter(Context context, Cursor cursor) { super(context, cursor); try { - this.masterSecret = null; this.glideRequests = null; this.locale = null; this.clickListener = null; @@ -157,7 +152,6 @@ public class ConversationAdapter } public ConversationAdapter(@NonNull Context context, - @NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @Nullable ItemClickListener clickListener, @@ -167,7 +161,6 @@ public class ConversationAdapter super(context, cursor); try { - this.masterSecret = masterSecret; this.glideRequests = glideRequests; this.locale = locale; this.clickListener = clickListener; @@ -193,7 +186,7 @@ public class ConversationAdapter @Override protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) { long start = System.currentTimeMillis(); - viewHolder.getView().bind(masterSecret, messageRecord, glideRequests, locale, batchSelected, recipient); + viewHolder.getView().bind(messageRecord, glideRequests, locale, batchSelected, recipient); Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start)); } @@ -302,7 +295,7 @@ public class ConversationAdapter if (record != null) return record; } - final MessageRecord messageRecord = db.readerFor(cursor, masterSecret).getCurrent(); + final MessageRecord messageRecord = db.readerFor(cursor).getCurrent(); messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord)); return messageRecord; diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 0a1b31f7b..a98c25388 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -52,7 +52,6 @@ import android.widget.Toast; import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; @@ -92,7 +91,6 @@ public class ConversationFragment extends Fragment private ConversationFragmentListener listener; - private MasterSecret masterSecret; private Recipient recipient; private long threadId; private long lastSeen; @@ -110,12 +108,11 @@ public class ConversationFragment extends Fragment @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - this.masterSecret = getArguments().getParcelable("master_secret"); - this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); + this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { final View view = inflater.inflate(R.layout.conversation_fragment, container, false); list = ViewUtil.findById(view, android.R.id.list); composeDivider = ViewUtil.findById(view, R.id.compose_divider); @@ -192,7 +189,7 @@ public class ConversationFragment extends Fragment private void initializeListAdapter() { if (this.recipient != null && this.threadId != -1) { - ConversationAdapter adapter = new ConversationAdapter(getActivity(), masterSecret, GlideApp.with(this), locale, selectionClickListener, null, this.recipient); + ConversationAdapter adapter = new ConversationAdapter(getActivity(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient); list.setAdapter(adapter); list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); @@ -352,7 +349,6 @@ public class ConversationFragment extends Fragment private void handleDisplayDetails(MessageRecord message) { Intent intent = new Intent(getActivity(), MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId()); intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); @@ -380,7 +376,7 @@ public class ConversationFragment extends Fragment new AsyncTask() { @Override protected Void doInBackground(MessageRecord... messageRecords) { - MessageSender.resend(context, masterSecret, messageRecords[0]); + MessageSender.resend(context, messageRecords[0]); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); @@ -391,7 +387,7 @@ public class ConversationFragment extends Fragment public void onClick(DialogInterface dialog, int which) { for (Slide slide : message.getSlideDeck().getSlides()) { if ((slide.hasImage() || slide.hasVideo() || slide.hasAudio() || slide.hasDocument()) && slide.getUri() != null) { - SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret); + SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived(), slide.getFileName().orNull())); return; } diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 11c559c7f..62c4460af 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -184,8 +184,7 @@ public class ConversationItem extends LinearLayout } @Override - public void bind(@NonNull MasterSecret masterSecret, - @NonNull MessageRecord messageRecord, + public void bind(@NonNull MessageRecord messageRecord, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set batchSelected, @@ -366,7 +365,7 @@ public class ConversationItem extends LinearLayout if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); //noinspection ConstantConditions - audioViewStub.get().setAudio(masterSecret, ((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls); + audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls); audioViewStub.get().setDownloadClickListener(downloadClickListener); audioViewStub.get().setOnLongClickListener(passthroughClickListener); @@ -389,7 +388,7 @@ public class ConversationItem extends LinearLayout if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); //noinspection ConstantConditions - mediaThumbnailStub.get().setImageResource(masterSecret, glideRequests, + mediaThumbnailStub.get().setImageResource(glideRequests, ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(), showControls, false); mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener()); @@ -564,7 +563,7 @@ public class ConversationItem extends LinearLayout throw new AssertionError("Identity mismatch count: " + mismatches.size()); } - new ConfirmIdentityDialog(context, masterSecret, messageRecord, mismatches.get(0)).show(); + new ConfirmIdentityDialog(context, messageRecord, mismatches.get(0)).show(); } @Override @@ -660,7 +659,6 @@ public class ConversationItem extends LinearLayout parent.onClick(v); } else if (messageRecord.isFailed()) { Intent intent = new Intent(context, MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret); intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId()); intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, messageRecord.getThreadId()); intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 0c3c56843..81410143d 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -16,7 +16,6 @@ */ package org.thoughtcrime.securesms; -import android.*; import android.Manifest; import android.annotation.SuppressLint; import android.content.ActivityNotFoundException; @@ -60,7 +59,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private ConversationListFragment fragment; - private MasterSecret masterSecret; private SearchToolbar searchToolbar; private ImageView searchAction; @@ -72,8 +70,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; - setContentView(R.layout.conversation_list_activity); Toolbar toolbar = findViewById(R.id.toolbar); @@ -211,7 +207,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit Context context = ConversationListActivity.this; List messageIds = DatabaseFactory.getThreadDatabase(context).setAllThreadsRead(); - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); MarkReadReceiver.process(context, messageIds); return null; diff --git a/src/org/thoughtcrime/securesms/ConversationListAdapter.java b/src/org/thoughtcrime/securesms/ConversationListAdapter.java index 16d74d364..1e5e28492 100644 --- a/src/org/thoughtcrime/securesms/ConversationListAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationListAdapter.java @@ -25,8 +25,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; @@ -53,8 +51,6 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter getBatchSelections() { + Set getBatchSelections() { return batchSet; } - public void initializeBatchMode(boolean toggle) { + void initializeBatchMode(boolean toggle) { this.batchMode = toggle; unselectAllThreads(); } - public void unselectAllThreads() { + private void unselectAllThreads() { this.batchSet.clear(); this.notifyDataSetChanged(); } - public void selectAllThreads() { + void selectAllThreads() { for (int i = 0; i < getItemCount(); i++) { long threadId = getThreadRecord(getCursorAtPositionOrThrow(i)).getThreadId(); if (threadId != -1) batchSet.add(threadId); diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java index cc3f398bb..755435d61 100644 --- a/src/org/thoughtcrime/securesms/ConversationListFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java @@ -221,7 +221,7 @@ public class ConversationListFragment extends Fragment } private void initializeListAdapter() { - list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, GlideApp.with(this), locale, null, this)); + list.setAdapter(new ConversationListAdapter(getActivity(), GlideApp.with(this), locale, null, this)); getLoaderManager().restartLoader(0, null, this); } @@ -302,7 +302,7 @@ public class ConversationListFragment extends Fragment @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(getActivity()).deleteConversations(selectedConversations); - MessageNotifier.updateNotification(getActivity(), masterSecret); + MessageNotifier.updateNotification(getActivity()); return null; } @@ -519,7 +519,7 @@ public class ConversationListFragment extends Fragment if (unreadCount > 0) { List messageIds = DatabaseFactory.getThreadDatabase(getActivity()).setRead(threadId, false); - MessageNotifier.updateNotification(getActivity(), masterSecret); + MessageNotifier.updateNotification(getActivity()); MarkReadReceiver.process(getActivity(), messageIds); } } @@ -530,7 +530,7 @@ public class ConversationListFragment extends Fragment if (unreadCount > 0) { DatabaseFactory.getThreadDatabase(getActivity()).incrementUnread(threadId, unreadCount); - MessageNotifier.updateNotification(getActivity(), masterSecret); + MessageNotifier.updateNotification(getActivity()); } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId); diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 9072680ae..f173220d5 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -16,7 +16,6 @@ */ package org.thoughtcrime.securesms; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; @@ -38,7 +37,6 @@ import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.DeliveryStatusView; import org.thoughtcrime.securesms.components.FromTextView; import org.thoughtcrime.securesms.components.ThumbnailView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; @@ -108,7 +106,7 @@ public class ConversationListItem extends RelativeLayout } @Override - public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, + public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { @@ -139,7 +137,7 @@ public class ConversationListItem extends RelativeLayout } setStatusIcons(thread); - setThumbnailSnippet(masterSecret, thread); + setThumbnailSnippet(thread); setBatchState(batchMode); setRippleColor(recipient); setUnreadIndicator(thread); @@ -175,10 +173,10 @@ public class ConversationListItem extends RelativeLayout return lastSeen; } - private void setThumbnailSnippet(MasterSecret masterSecret, ThreadRecord thread) { + private void setThumbnailSnippet(ThreadRecord thread) { if (thread.getSnippetUri() != null) { this.thumbnailView.setVisibility(View.VISIBLE); - this.thumbnailView.setImageResource(masterSecret, glideRequests, thread.getSnippetUri()); + this.thumbnailView.setImageResource(glideRequests, thread.getSnippetUri()); LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams(); subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.thumbnail); diff --git a/src/org/thoughtcrime/securesms/ConversationListItemAction.java b/src/org/thoughtcrime/securesms/ConversationListItemAction.java index 42afdec57..63c7d4e39 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItemAction.java +++ b/src/org/thoughtcrime/securesms/ConversationListItemAction.java @@ -8,7 +8,6 @@ import android.util.AttributeSet; import android.widget.LinearLayout; import android.widget.TextView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.util.ViewUtil; @@ -40,7 +39,7 @@ public class ConversationListItemAction extends LinearLayout implements Bindable } @Override - public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { + public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { this.description.setText(getContext().getString(R.string.ConversationListItemAction_archived_conversations_d, thread.getCount())); } diff --git a/src/org/thoughtcrime/securesms/ConversationListItemInboxZero.java b/src/org/thoughtcrime/securesms/ConversationListItemInboxZero.java index 336f629bf..cf9be6c99 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItemInboxZero.java +++ b/src/org/thoughtcrime/securesms/ConversationListItemInboxZero.java @@ -9,7 +9,6 @@ import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.widget.LinearLayout; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.mms.GlideRequests; @@ -40,7 +39,7 @@ public class ConversationListItemInboxZero extends LinearLayout implements Binda } @Override - public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { + public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set selectedThreads, boolean batchMode) { } } diff --git a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java index 4f6560b10..d4be4eba5 100644 --- a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java +++ b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java @@ -15,7 +15,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -38,7 +37,6 @@ public class ConversationUpdateItem extends LinearLayout { private static final String TAG = ConversationUpdateItem.class.getSimpleName(); - private MasterSecret masterSecret; private Set batchSelected; private ImageView icon; @@ -60,22 +58,20 @@ public class ConversationUpdateItem extends LinearLayout public void onFinishInflate() { super.onFinishInflate(); - this.icon = (ImageView)findViewById(R.id.conversation_update_icon); - this.body = (TextView)findViewById(R.id.conversation_update_body); - this.date = (TextView)findViewById(R.id.conversation_update_date); + this.icon = findViewById(R.id.conversation_update_icon); + this.body = findViewById(R.id.conversation_update_body); + this.date = findViewById(R.id.conversation_update_date); this.setOnClickListener(new InternalClickListener(null)); } @Override - public void bind(@NonNull MasterSecret masterSecret, - @NonNull MessageRecord messageRecord, + public void bind(@NonNull MessageRecord messageRecord, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set batchSelected, @NonNull Recipient conversationRecipient) { - this.masterSecret = masterSecret; this.batchSelected = batchSelected; bind(messageRecord, locale); @@ -172,12 +168,7 @@ public class ConversationUpdateItem extends LinearLayout @Override public void onModified(Recipient recipient) { - Util.runOnMain(new Runnable() { - @Override - public void run() { - bind(messageRecord, locale); - } - }); + Util.runOnMain(() -> bind(messageRecord, locale)); } @Override @@ -196,7 +187,7 @@ public class ConversationUpdateItem extends LinearLayout @Nullable private final View.OnClickListener parent; - public InternalClickListener(@Nullable View.OnClickListener parent) { + InternalClickListener(@Nullable View.OnClickListener parent) { this.parent = parent; } diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java index f8c9442ce..5486c37e0 100644 --- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java +++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java @@ -76,6 +76,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { public static final int SCREENSHOTS = 300; public static final int PERSISTENT_BLOBS = 317; public static final int INTERNALIZE_CONTACTS = 317; + public static final int SQLCIPHER = 334; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION); @@ -93,6 +94,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { add(SCREENSHOTS); add(INTERNALIZE_CONTACTS); add(PERSISTENT_BLOBS); + add(SQLCIPHER); }}; private MasterSecret masterSecret; @@ -106,14 +108,14 @@ public class DatabaseUpgradeActivity extends BaseActivity { Log.w("DatabaseUpgradeActivity", "Upgrading..."); setContentView(R.layout.database_upgrade_activity); - ProgressBar indeterminateProgress = (ProgressBar)findViewById(R.id.indeterminate_progress); - ProgressBar determinateProgress = (ProgressBar)findViewById(R.id.determinate_progress); + ProgressBar indeterminateProgress = findViewById(R.id.indeterminate_progress); + ProgressBar determinateProgress = findViewById(R.id.determinate_progress); new DatabaseUpgradeTask(indeterminateProgress, determinateProgress) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VersionTracker.getLastSeenVersion(this)); } else { VersionTracker.updateLastSeenVersion(this); - updateNotifications(this, masterSecret); + updateNotifications(this); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); finish(); } @@ -149,11 +151,11 @@ public class DatabaseUpgradeActivity extends BaseActivity { } @SuppressLint("StaticFieldLeak") - private void updateNotifications(final Context context, final MasterSecret masterSecret) { + private void updateNotifications(final Context context) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -171,7 +173,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { private final ProgressBar indeterminateProgress; private final ProgressBar determinateProgress; - public DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) { + DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) { this.indeterminateProgress = indeterminateProgress; this.determinateProgress = determinateProgress; } @@ -278,11 +280,11 @@ public class DatabaseUpgradeActivity extends BaseActivity { private void schedulePendingIncomingParts(Context context) { final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context); final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context); - final List pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments(masterSecret); + final List pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments(); Log.w(TAG, pendingAttachments.size() + " pending parts."); for (DatabaseAttachment attachment : pendingAttachments) { - final Reader reader = mmsDb.readerFor(masterSecret, mmsDb.getMessage(attachment.getMmsId())); + final Reader reader = mmsDb.readerFor(mmsDb.getMessage(attachment.getMmsId())); final MessageRecord record = reader.getNext(); if (attachment.hasData()) { @@ -329,7 +331,7 @@ public class DatabaseUpgradeActivity extends BaseActivity { @Override protected void onPostExecute(Void result) { VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this); - updateNotifications(DatabaseUpgradeActivity.this, masterSecret); + updateNotifications(DatabaseUpgradeActivity.this); startActivity((Intent)getIntent().getParcelableExtra("next_intent")); finish(); diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java index ab4323be5..0b8d758c5 100644 --- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -18,7 +18,6 @@ package org.thoughtcrime.securesms; import android.app.Activity; -import android.content.AsyncTaskLoader; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; @@ -102,7 +101,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private ListView lv; private ImageView avatar; private TextView creatingText; - private MasterSecret masterSecret; private Bitmap avatarBmp; @NonNull private Optional groupToUpdate = Optional.absent(); @@ -115,8 +113,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity @Override protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; - setContentView(R.layout.group_create_activity); //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -192,12 +188,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity recipientsPanel.setPanelChangeListener(this); findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener()); avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this))); - avatar.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Crop.pickImage(GroupCreateActivity.this); - } - }); + avatar.setOnClickListener(view -> Crop.pickImage(GroupCreateActivity.this)); } private void initializeExistingGroup() { @@ -252,14 +243,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity return; } if (isSignalGroup()) { - new CreateSignalGroupTask(this, masterSecret, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new CreateSignalGroupTask(this, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { - new CreateMmsGroupTask(this, masterSecret, getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new CreateMmsGroupTask(this, getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } private void handleGroupUpdate() { - new UpdateSignalGroupTask(this, masterSecret, groupToUpdate.get().id, avatarBmp, + new UpdateSignalGroupTask(this, groupToUpdate.get().id, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -313,7 +304,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity .override(AVATAR_SIZE, AVATAR_SIZE) .into(new SimpleTarget() { @Override - public void onResourceReady(Bitmap resource, Transition transition) { + public void onResourceReady(@NonNull Bitmap resource, Transition transition) { setAvatar(Crop.getOutput(data), resource); } }); @@ -332,12 +323,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private static class CreateMmsGroupTask extends AsyncTask { private final GroupCreateActivity activity; - private final MasterSecret masterSecret; private final Set members; - public CreateMmsGroupTask(GroupCreateActivity activity, MasterSecret masterSecret, Set members) { + public CreateMmsGroupTask(GroupCreateActivity activity, Set members) { this.activity = activity; - this.masterSecret = masterSecret; this.members = members; } @@ -368,20 +357,18 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private abstract static class SignalGroupTask extends AsyncTask> { + protected GroupCreateActivity activity; - protected MasterSecret masterSecret; protected Bitmap avatar; protected Set members; protected String name; public SignalGroupTask(GroupCreateActivity activity, - MasterSecret masterSecret, Bitmap avatar, String name, Set members) { this.activity = activity; - this.masterSecret = masterSecret; this.avatar = avatar; this.name = name; this.members = members; @@ -408,13 +395,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private static class CreateSignalGroupTask extends SignalGroupTask { - public CreateSignalGroupTask(GroupCreateActivity activity, MasterSecret masterSecret, Bitmap avatar, String name, Set members) { - super(activity, masterSecret, avatar, name, members); + public CreateSignalGroupTask(GroupCreateActivity activity, Bitmap avatar, String name, Set members) { + super(activity, avatar, name, members); } @Override protected Optional doInBackground(Void... aVoid) { - return Optional.of(GroupManager.createGroup(activity, masterSecret, members, avatar, name, false)); + return Optional.of(GroupManager.createGroup(activity, members, avatar, name, false)); } @Override @@ -434,18 +421,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity private static class UpdateSignalGroupTask extends SignalGroupTask { private String groupId; - public UpdateSignalGroupTask(GroupCreateActivity activity, - MasterSecret masterSecret, String groupId, Bitmap avatar, String name, - Set members) + public UpdateSignalGroupTask(GroupCreateActivity activity, String groupId, + Bitmap avatar, String name, Set members) { - super(activity, masterSecret, avatar, name, members); + super(activity, avatar, name, members); this.groupId = groupId; } @Override protected Optional doInBackground(Void... aVoid) { try { - return Optional.of(GroupManager.updateGroup(activity, masterSecret, groupId, members, avatar, name)); + return Optional.of(GroupManager.updateGroup(activity, groupId, members, avatar, name)); } catch (InvalidNumberException e) { return Optional.absent(); } diff --git a/src/org/thoughtcrime/securesms/ImportExportFragment.java b/src/org/thoughtcrime/securesms/ImportExportFragment.java index cdbcb239c..2f0fa0519 100644 --- a/src/org/thoughtcrime/securesms/ImportExportFragment.java +++ b/src/org/thoughtcrime/securesms/ImportExportFragment.java @@ -16,7 +16,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.database.PlaintextBackupExporter; import org.thoughtcrime.securesms.database.PlaintextBackupImporter; @@ -35,13 +34,11 @@ public class ImportExportFragment extends Fragment { private static final int NO_SD_CARD = 1; private static final int ERROR_IO = 2; - private MasterSecret masterSecret; private ProgressDialog progressDialog; @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); - this.masterSecret = getArguments().getParcelable("master_secret"); } @Override @@ -87,7 +84,6 @@ public class ImportExportFragment extends Fragment { .onAllGranted(() -> { Intent intent = new Intent(getActivity(), ApplicationMigrationService.class); intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE); - intent.putExtra("master_secret", masterSecret); getActivity().startService(intent); Intent nextIntent = new Intent(getActivity(), ConversationListActivity.class); @@ -185,7 +181,7 @@ public class ImportExportFragment extends Fragment { @Override protected Integer doInBackground(Void... params) { try { - PlaintextBackupImporter.importPlaintextFromSd(getActivity(), masterSecret); + PlaintextBackupImporter.importPlaintextFromSd(getActivity()); return SUCCESS; } catch (NoExternalStorageException e) { Log.w("ImportFragment", e); @@ -212,7 +208,7 @@ public class ImportExportFragment extends Fragment { @Override protected Integer doInBackground(Void... params) { try { - PlaintextBackupExporter.exportPlaintextToSd(getActivity(), masterSecret); + PlaintextBackupExporter.exportPlaintextToSd(getActivity()); return SUCCESS; } catch (NoExternalStorageException e) { Log.w("ExportFragment", e); diff --git a/src/org/thoughtcrime/securesms/InviteActivity.java b/src/org/thoughtcrime/securesms/InviteActivity.java index 85b8538c8..d1057a59c 100644 --- a/src/org/thoughtcrime/securesms/InviteActivity.java +++ b/src/org/thoughtcrime/securesms/InviteActivity.java @@ -235,7 +235,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen Recipient recipient = Recipient.from(context, Address.fromExternal(context, number), false); int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); - MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null); + MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null); if (recipient.getContactUri() != null) { DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient, true); diff --git a/src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java b/src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java index 96c3e658c..b90e61e05 100644 --- a/src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java +++ b/src/org/thoughtcrime/securesms/MediaDocumentsAdapter.java @@ -17,7 +17,6 @@ import android.widget.Toast; import org.thoughtcrime.securesms.MediaDocumentsAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.MediaDocumentsAdapter.ViewHolder; import org.thoughtcrime.securesms.components.DocumentView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.mms.DocumentSlide; @@ -36,14 +35,12 @@ import static com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager.TAG; public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter implements StickyHeaderDecoration.StickyHeaderAdapter { - private final MasterSecret masterSecret; private final Calendar calendar; private final Locale locale; - public MediaDocumentsAdapter(Context context, MasterSecret masterSecret, Cursor cursor, Locale locale) { + MediaDocumentsAdapter(Context context, Cursor cursor, Locale locale) { super(context, cursor); - this.masterSecret = masterSecret; this.calendar = Calendar.getInstance(); this.locale = locale; } @@ -55,7 +52,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter @Override public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) { - MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor); + MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor); Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment()); if (slide != null && slide.hasDocument()) { @@ -89,7 +86,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter if (position < 0) return -1; Cursor cursor = getCursorAtPositionOrThrow(position); - MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor); + MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor); calendar.setTime(new Date(mediaRecord.getDate())); return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); @@ -103,7 +100,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter @Override public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { Cursor cursor = getCursorAtPositionOrThrow(position); - MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor); + MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor); viewHolder.textView.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate())); } @@ -114,8 +111,8 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter public ViewHolder(View itemView) { super(itemView); - this.documentView = (DocumentView)itemView.findViewById(R.id.document_view); - this.date = (TextView)itemView.findViewById(R.id.date); + this.documentView = itemView.findViewById(R.id.document_view); + this.date = itemView.findViewById(R.id.date); } } @@ -125,7 +122,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter HeaderViewHolder(View itemView) { super(itemView); - this.textView = (TextView)itemView.findViewById(R.id.text); + this.textView = itemView.findViewById(R.id.text); } } diff --git a/src/org/thoughtcrime/securesms/MediaGalleryAdapter.java b/src/org/thoughtcrime/securesms/MediaGalleryAdapter.java index b8ee0fad5..6942b1c06 100644 --- a/src/org/thoughtcrime/securesms/MediaGalleryAdapter.java +++ b/src/org/thoughtcrime/securesms/MediaGalleryAdapter.java @@ -27,7 +27,6 @@ import android.widget.TextView; import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter; import org.thoughtcrime.securesms.components.ThumbnailView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia; @@ -39,10 +38,10 @@ import java.util.Locale; class MediaGalleryAdapter extends StickyHeaderGridAdapter { + @SuppressWarnings("unused") private static final String TAG = MediaGalleryAdapter.class.getSimpleName(); private final Context context; - private final MasterSecret masterSecret; private final GlideRequests glideRequests; private final Locale locale; private final Address address; @@ -67,11 +66,10 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter { } } - MediaGalleryAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, + MediaGalleryAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, BucketedThreadMedia media, Locale locale, Address address) { this.context = context; - this.masterSecret = masterSecret; this.glideRequests = glideRequests; this.locale = locale; this.media = media; @@ -105,7 +103,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter { Slide slide = MediaUtil.getSlideForAttachment(context, mediaRecord.getAttachment()); if (slide != null) { - thumbnailView.setImageResource(masterSecret, glideRequests, slide, false, false); + thumbnailView.setImageResource(glideRequests, slide, false, false); } thumbnailView.setOnClickListener(new OnMediaClickListener(mediaRecord)); diff --git a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java index bc911e0e3..f76d3288a 100644 --- a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -207,7 +207,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity this.noMedia = ViewUtil.findById(view, R.id.no_images); this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols)); - this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), masterSecret, GlideApp.with(this), new BucketedThreadMedia(getContext()), locale, recipient.getAddress())); + this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), GlideApp.with(this), new BucketedThreadMedia(getContext()), locale, recipient.getAddress())); this.recyclerView.setLayoutManager(gridManager); this.recyclerView.setHasFixedSize(true); @@ -225,7 +225,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity @Override public Loader onCreateLoader(int i, Bundle bundle) { - return new BucketedThreadMediaLoader(getContext(), masterSecret, recipient.getAddress()); + return new BucketedThreadMediaLoader(getContext(), recipient.getAddress()); } @Override @@ -248,7 +248,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.media_overview_documents_fragment, container, false); - MediaDocumentsAdapter adapter = new MediaDocumentsAdapter(getContext(), masterSecret, null, locale); + MediaDocumentsAdapter adapter = new MediaDocumentsAdapter(getContext(), null, locale); this.recyclerView = ViewUtil.findById(view, R.id.recycler_view); this.noMedia = ViewUtil.findById(view, R.id.no_documents); diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index 1d3829927..3991c86f1 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -81,8 +81,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private MasterSecret masterSecret; - private ViewPager mediaPager; private Uri initialMediaUri; private String initialMediaType; @@ -95,7 +93,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @SuppressWarnings("ConstantConditions") @Override protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; this.setTheme(R.style.TextSecure_DarkTheme); dynamicLanguage.onCreate(this); @@ -203,7 +200,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im if (conversationRecipient != null) { getSupportLoaderManager().restartLoader(0, null, this); } else { - mediaPager.setAdapter(new SingleItemPagerAdapter(this, masterSecret, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize)); + mediaPager.setAdapter(new SingleItemPagerAdapter(this, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize)); } } @@ -246,7 +243,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) .onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) .onAllGranted(() -> { - SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this, masterSecret); + SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis(); saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); }) @@ -304,7 +301,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im public void onLoadFinished(Loader> loader, @Nullable Pair data) { if (data != null) { @SuppressWarnings("ConstantConditions") - CursorPagerAdapter adapter = new CursorPagerAdapter(this, masterSecret, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent); + CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent); mediaPager.setAdapter(adapter); adapter.setActive(true); @@ -350,7 +347,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter { - private final MasterSecret masterSecret; private final GlideRequests glideRequests; private final Window window; private final Uri uri; @@ -359,11 +355,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private final LayoutInflater inflater; - SingleItemPagerAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret, - @NonNull GlideRequests glideRequests, @NonNull Window window, - @NonNull Uri uri, @NonNull String mediaType, long size) + SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, + @NonNull Window window, @NonNull Uri uri, @NonNull String mediaType, + long size) { - this.masterSecret = masterSecret; this.glideRequests = glideRequests; this.window = window; this.uri = uri; @@ -388,7 +383,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im MediaView mediaView = itemView.findViewById(R.id.media_view); try { - mediaView.set(masterSecret, glideRequests, window, uri, mediaType, size, true); + mediaView.set(glideRequests, window, uri, mediaType, size, true); } catch (IOException e) { Log.w(TAG, e); } @@ -422,7 +417,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private final WeakHashMap mediaViews = new WeakHashMap<>(); private final Context context; - private final MasterSecret masterSecret; private final GlideRequests glideRequests; private final Window window; private final Cursor cursor; @@ -431,12 +425,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private boolean active; private int autoPlayPosition; - CursorPagerAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret, - @NonNull GlideRequests glideRequests, @NonNull Window window, - @NonNull Cursor cursor, int autoPlayPosition, boolean leftIsRecent) + CursorPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, + @NonNull Window window, @NonNull Cursor cursor, int autoPlayPosition, + boolean leftIsRecent) { this.context = context.getApplicationContext(); - this.masterSecret = masterSecret; this.glideRequests = glideRequests; this.window = window; this.cursor = cursor; @@ -471,11 +464,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im cursor.moveToPosition(cursorPosition); - MediaRecord mediaRecord = MediaRecord.from(context, masterSecret, cursor); + MediaRecord mediaRecord = MediaRecord.from(context, cursor); try { //noinspection ConstantConditions - mediaView.set(masterSecret, glideRequests, window, mediaRecord.getAttachment().getDataUri(), mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay); + mediaView.set(glideRequests, window, mediaRecord.getAttachment().getDataUri(), mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay); } catch (IOException e) { Log.w(TAG, e); } @@ -497,7 +490,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im public MediaItem getMediaItemFor(int position) { cursor.moveToPosition(getCursorPosition(position)); - MediaRecord mediaRecord = MediaRecord.from(context, masterSecret, cursor); + MediaRecord mediaRecord = MediaRecord.from(context, cursor); Address address = mediaRecord.getAddress(); if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index f7d22a47c..16a966ccd 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -72,14 +71,12 @@ import java.util.Locale; public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks, RecipientModifiedListener { private final static String TAG = MessageDetailsActivity.class.getSimpleName(); - public final static String MASTER_SECRET_EXTRA = "master_secret"; public final static String MESSAGE_ID_EXTRA = "message_id"; public final static String THREAD_ID_EXTRA = "thread_id"; public final static String IS_PUSH_GROUP_EXTRA = "is_push_group"; public final static String TYPE_EXTRA = "type"; public final static String ADDRESS_EXTRA = "address"; - private MasterSecret masterSecret; private GlideRequests glideRequests; private long threadId; private boolean isPushGroup; @@ -166,21 +163,20 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity inflater = LayoutInflater.from(this); View header = inflater.inflate(R.layout.message_details_header, recipientsList, false); - masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); isPushGroup = getIntent().getBooleanExtra(IS_PUSH_GROUP_EXTRA, false); glideRequests = GlideApp.with(this); - itemParent = (ViewGroup) header.findViewById(R.id.item_container); - recipientsList = (ListView ) findViewById(R.id.recipients_list); - metadataContainer = header.findViewById(R.id.metadata_container); - errorText = (TextView ) header.findViewById(R.id.error_text); - sentDate = (TextView ) header.findViewById(R.id.sent_time); - receivedContainer = header.findViewById(R.id.received_container); - receivedDate = (TextView ) header.findViewById(R.id.received_time); - transport = (TextView ) header.findViewById(R.id.transport); - toFrom = (TextView ) header.findViewById(R.id.tofrom); - expiresContainer = header.findViewById(R.id.expires_container); - expiresInText = (TextView) header.findViewById(R.id.expires_in); + itemParent = header.findViewById(R.id.item_container); + recipientsList = findViewById(R.id.recipients_list); + metadataContainer = header.findViewById(R.id.metadata_container); + errorText = header.findViewById(R.id.error_text); + sentDate = header.findViewById(R.id.sent_time); + receivedContainer = header.findViewById(R.id.received_container); + receivedDate = header.findViewById(R.id.received_time); + transport = header.findViewById(R.id.transport); + toFrom = header.findViewById(R.id.tofrom); + expiresContainer = header.findViewById(R.id.expires_container); + expiresInText = header.findViewById(R.id.expires_in); recipientsList.setHeaderDividersEnabled(false); recipientsList.addHeaderView(header, null, false); } @@ -253,8 +249,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity toFromRes = R.string.message_details_header__from; } toFrom.setText(toFromRes); - conversationItem.bind(masterSecret, messageRecord, glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient); - recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, glideRequests, messageRecord, recipients, isPushGroup)); + conversationItem.bind(messageRecord, glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient); + recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup)); } private void inflateMessageViewIfAbsent(MessageRecord messageRecord) { @@ -273,12 +269,12 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity private @Nullable MessageRecord getMessageRecord(Context context, Cursor cursor, String type) { switch (type) { case MmsSmsDatabase.SMS_TRANSPORT: - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsDatabase.Reader reader = smsDatabase.readerFor(masterSecret, cursor); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); + SmsDatabase.Reader reader = smsDatabase.readerFor(cursor); return reader.getNext(); case MmsSmsDatabase.MMS_TRANSPORT: MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(masterSecret, cursor); + MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(cursor); return mmsReader.getNext(); default: throw new AssertionError("no valid message type specified"); diff --git a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java index 93bc3d387..bb1ddfec1 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsRecipientAdapter.java @@ -8,7 +8,6 @@ import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; @@ -21,18 +20,16 @@ import java.util.List; class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener { private final Context context; - private final MasterSecret masterSecret; private final GlideRequests glideRequests; private final MessageRecord record; private final List members; private final boolean isPushGroup; - MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret, - @NonNull GlideRequests glideRequests, @NonNull MessageRecord record, - @NonNull List members, boolean isPushGroup) + MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, + @NonNull MessageRecord record, @NonNull List members, + boolean isPushGroup) { this.context = context; - this.masterSecret = masterSecret; this.glideRequests = glideRequests; this.record = record; this.isPushGroup = isPushGroup; @@ -66,7 +63,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView. RecipientDeliveryStatus member = members.get(position); - ((MessageRecipientListItem)convertView).set(masterSecret, glideRequests, record, member, isPushGroup); + ((MessageRecipientListItem)convertView).set(glideRequests, record, member, isPushGroup); return convertView; } diff --git a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java index 98a4c2b4d..6ca94abbe 100644 --- a/src/org/thoughtcrime/securesms/MessageRecipientListItem.java +++ b/src/org/thoughtcrime/securesms/MessageRecipientListItem.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms; +import android.annotation.SuppressLint; import android.content.Context; import android.os.AsyncTask; import android.text.TextUtils; @@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDelive import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.DeliveryStatusView; import org.thoughtcrime.securesms.components.FromTextView; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.util.Util; public class MessageRecipientListItem extends RelativeLayout implements RecipientModifiedListener { + @SuppressWarnings("unused") private final static String TAG = MessageRecipientListItem.class.getSimpleName(); private RecipientDeliveryStatus member; @@ -81,8 +82,7 @@ public class MessageRecipientListItem extends RelativeLayout this.deliveryStatusView = findViewById(R.id.delivery_status); } - public void set(final MasterSecret masterSecret, - final GlideRequests glideRequests, + public void set(final GlideRequests glideRequests, final MessageRecord record, final RecipientDeliveryStatus member, final boolean isPushGroup) @@ -93,11 +93,10 @@ public class MessageRecipientListItem extends RelativeLayout member.getRecipient().addListener(this); fromView.setText(member.getRecipient()); contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false); - setIssueIndicators(masterSecret, record, isPushGroup); + setIssueIndicators(record, isPushGroup); } - private void setIssueIndicators(final MasterSecret masterSecret, - final MessageRecord record, + private void setIssueIndicators(final MessageRecord record, final boolean isPushGroup) { final NetworkFailure networkFailure = getNetworkFailure(record); @@ -110,7 +109,7 @@ public class MessageRecipientListItem extends RelativeLayout conflictButton.setVisibility(View.VISIBLE); errorText = getContext().getString(R.string.MessageDetailsRecipient_new_safety_number); - conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), masterSecret, record, keyMismatch).show()); + conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), record, keyMismatch).show()); } else if (networkFailure != null || (!isPushGroup && record.isFailed())) { resendButton.setVisibility(View.VISIBLE); resendButton.setEnabled(true); @@ -123,7 +122,7 @@ public class MessageRecipientListItem extends RelativeLayout errorDescription.setVisibility(View.GONE); actionDescription.setVisibility(View.VISIBLE); actionDescription.setText(R.string.message_recipients_list_item__resending); - new ResendAsyncTask(masterSecret, record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new ResendAsyncTask(record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }); } else { if (record.isOutgoing()) { @@ -185,15 +184,14 @@ public class MessageRecipientListItem extends RelativeLayout }); } + @SuppressLint("StaticFieldLeak") private class ResendAsyncTask extends AsyncTask { private final Context context; - private final MasterSecret masterSecret; private final MessageRecord record; private final NetworkFailure failure; - ResendAsyncTask(MasterSecret masterSecret, MessageRecord record, NetworkFailure failure) { + ResendAsyncTask(MessageRecord record, NetworkFailure failure) { this.context = getContext().getApplicationContext(); - this.masterSecret = masterSecret; this.record = record; this.failure = failure; } @@ -206,7 +204,7 @@ public class MessageRecipientListItem extends RelativeLayout if (record.getRecipient().isPushGroupRecipient()) { MessageSender.resendGroupMessage(context, record, failure.getAddress()); } else { - MessageSender.resend(context, masterSecret, record); + MessageSender.resend(context, record); } return null; } diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java index 8519d81b3..baec5ae74 100644 --- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java @@ -18,7 +18,6 @@ package org.thoughtcrime.securesms; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v7.app.ActionBar; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; @@ -66,6 +65,7 @@ public class PassphraseCreateActivity extends PassphraseActivity { MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this); VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); + TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this)); TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true); TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true); diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 7037a8029..544e14137 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -131,7 +131,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity public void onPause() { super.onPause(); if (!isPassingAlongMedia && resolvedExtra != null) { - PersistentBlobProvider.getInstance(this).delete(resolvedExtra); + PersistentBlobProvider.getInstance(this).delete(this, resolvedExtra); } if (!isFinishing()) { finish(); @@ -334,7 +334,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity if (cursor != null) cursor.close(); } - return PersistentBlobProvider.getInstance(context).create(masterSecret, inputStream, mimeType, fileName, fileSize); + return PersistentBlobProvider.getInstance(context).create(context, inputStream, mimeType, fileName, fileSize); } catch (IOException ioe) { Log.w(TAG, ioe); return null; diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 586ff28fa..d61dbee84 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -618,7 +618,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity isChecked ? VerifiedStatus.VERIFIED : VerifiedStatus.DEFAULT)); - IdentityUtil.markIdentityVerified(getActivity(), new MasterSecretUnion(masterSecret), recipient, isChecked, false); + IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false); } return null; } diff --git a/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java b/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java index 43a7ab263..9fdb0bd7b 100644 --- a/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java +++ b/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java @@ -7,7 +7,6 @@ import android.support.annotation.NonNull; import android.util.Log; import org.spongycastle.util.encoders.Hex; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.Util; @@ -38,7 +37,6 @@ public class AttachmentServer implements Runnable { private static final String TAG = AttachmentServer.class.getSimpleName(); private final Context context; - private final MasterSecret masterSecret; private final Attachment attachment; private final ServerSocket socket; private final int port; @@ -46,12 +44,11 @@ public class AttachmentServer implements Runnable { private volatile boolean isRunning; - public AttachmentServer(Context context, MasterSecret masterSecret, Attachment attachment) + public AttachmentServer(Context context, Attachment attachment) throws IOException { try { this.context = context; - this.masterSecret = masterSecret; this.attachment = attachment; this.socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1})); this.port = socket.getLocalPort(); @@ -189,7 +186,7 @@ public class AttachmentServer implements Runnable { } protected void execute() throws IOException { - InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri()); + InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri()); long fileSize = attachment.getSize(); String headers = ""; diff --git a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java index f9b45a14a..bf5269e58 100644 --- a/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/PointerAttachment.java @@ -4,9 +4,8 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; -import org.thoughtcrime.securesms.crypto.MediaKey; import org.thoughtcrime.securesms.database.AttachmentDatabase; +import org.thoughtcrime.securesms.util.Base64; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -15,7 +14,7 @@ import java.util.List; public class PointerAttachment extends Attachment { - public PointerAttachment(@NonNull String contentType, int transferState, long size, + private PointerAttachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName, @NonNull String location, @NonNull String key, @NonNull String relay, @Nullable byte[] digest, boolean voiceNote) @@ -36,19 +35,20 @@ public class PointerAttachment extends Attachment { } - public static List forPointers(@NonNull MasterSecretUnion masterSecret, Optional> pointers) { + public static List forPointers(Optional> pointers) { List results = new LinkedList<>(); if (pointers.isPresent()) { for (SignalServiceAttachment pointer : pointers.get()) { if (pointer.isPointer()) { - String encryptedKey = MediaKey.getEncrypted(masterSecret, pointer.asPointer().getKey()); + String encodedKey = Base64.encodeBytes(pointer.asPointer().getKey()); + results.add(new PointerAttachment(pointer.getContentType(), AttachmentDatabase.TRANSFER_PROGRESS_PENDING, pointer.asPointer().getSize().or(0), pointer.asPointer().getFileName().orNull(), String.valueOf(pointer.asPointer().getId()), - encryptedKey, pointer.asPointer().getRelay().orNull(), + encodedKey, pointer.asPointer().getRelay().orNull(), pointer.asPointer().getDigest().orNull(), pointer.asPointer().getVoiceNote())); } diff --git a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java index 6b0b73fa5..49a59c016 100644 --- a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -9,7 +9,6 @@ import android.support.annotation.NonNull; import android.util.Log; import android.util.Pair; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ThreadUtil; @@ -28,41 +27,35 @@ public class AudioRecorder { private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor(); private final Context context; - private final MasterSecret masterSecret; private final PersistentBlobProvider blobProvider; private AudioCodec audioCodec; private Uri captureUri; - public AudioRecorder(@NonNull Context context, @NonNull MasterSecret masterSecret) { + public AudioRecorder(@NonNull Context context) { this.context = context; - this.masterSecret = masterSecret; this.blobProvider = PersistentBlobProvider.getInstance(context.getApplicationContext()); } public void startRecording() { Log.w(TAG, "startRecording()"); - executor.execute(new Runnable() { - @Override - public void run() { - Log.w(TAG, "Running startRecording() + " + Thread.currentThread().getId()); - try { - if (audioCodec != null) { - throw new AssertionError("We can only record once at a time."); - } - - ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe(); - - captureUri = blobProvider.create(masterSecret, - new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), - MediaUtil.AUDIO_AAC, null, null); - audioCodec = new AudioCodec(); - - audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1])); - } catch (IOException e) { - Log.w(TAG, e); + executor.execute(() -> { + Log.w(TAG, "Running startRecording() + " + Thread.currentThread().getId()); + try { + if (audioCodec != null) { + throw new AssertionError("We can only record once at a time."); } + + ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe(); + + captureUri = blobProvider.create(context, new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), + MediaUtil.AUDIO_AAC, null, null); + audioCodec = new AudioCodec(); + + audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1])); + } catch (IOException e) { + Log.w(TAG, e); } }); } @@ -72,47 +65,34 @@ public class AudioRecorder { final SettableFuture> future = new SettableFuture<>(); - executor.execute(new Runnable() { - @Override - public void run() { - if (audioCodec == null) { - sendToFuture(future, new IOException("MediaRecorder was never initialized successfully!")); - return; - } - - audioCodec.stop(); - - try { - long size = MediaUtil.getMediaSize(context, masterSecret, captureUri); - sendToFuture(future, new Pair<>(captureUri, size)); - } catch (IOException ioe) { - Log.w(TAG, ioe); - sendToFuture(future, ioe); - } - - audioCodec = null; - captureUri = null; + executor.execute(() -> { + if (audioCodec == null) { + sendToFuture(future, new IOException("MediaRecorder was never initialized successfully!")); + return; } + + audioCodec.stop(); + + try { + long size = MediaUtil.getMediaSize(context, captureUri); + sendToFuture(future, new Pair<>(captureUri, size)); + } catch (IOException ioe) { + Log.w(TAG, ioe); + sendToFuture(future, ioe); + } + + audioCodec = null; + captureUri = null; }); return future; } private void sendToFuture(final SettableFuture future, final Exception exception) { - Util.runOnMain(new Runnable() { - @Override - public void run() { - future.setException(exception); - } - }); + Util.runOnMain(() -> future.setException(exception)); } private void sendToFuture(final SettableFuture future, final T result) { - Util.runOnMain(new Runnable() { - @Override - public void run() { - future.set(result); - } - }); + Util.runOnMain(() -> future.set(result)); } } diff --git a/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java b/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java index 4bb212ce9..d641f7f51 100644 --- a/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java +++ b/src/org/thoughtcrime/securesms/audio/AudioSlidePlayer.java @@ -36,7 +36,6 @@ public class AudioSlidePlayer implements SensorEventListener { private static @NonNull Optional playing = Optional.absent(); private final @NonNull Context context; - private final @NonNull MasterSecret masterSecret; private final @NonNull AudioSlide slide; private final @NonNull Handler progressEventHandler; private final @NonNull AudioManager audioManager; @@ -50,7 +49,6 @@ public class AudioSlidePlayer implements SensorEventListener { private long startTime; public synchronized static AudioSlidePlayer createFor(@NonNull Context context, - @NonNull MasterSecret masterSecret, @NonNull AudioSlide slide, @NonNull Listener listener) { @@ -58,17 +56,15 @@ public class AudioSlidePlayer implements SensorEventListener { playing.get().setListener(listener); return playing.get(); } else { - return new AudioSlidePlayer(context, masterSecret, slide, listener); + return new AudioSlidePlayer(context, slide, listener); } } private AudioSlidePlayer(@NonNull Context context, - @NonNull MasterSecret masterSecret, @NonNull AudioSlide slide, @NonNull Listener listener) { this.context = context; - this.masterSecret = masterSecret; this.slide = slide; this.listener = new WeakReference<>(listener); this.progressEventHandler = new ProgressEventHandler(this); @@ -91,7 +87,7 @@ public class AudioSlidePlayer implements SensorEventListener { if (this.mediaPlayer != null) return; this.mediaPlayer = new MediaPlayerWrapper(); - this.audioAttachmentServer = new AttachmentServer(context, masterSecret, slide.asAttachment()); + this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment()); this.startTime = System.currentTimeMillis(); audioAttachmentServer.start(); diff --git a/src/org/thoughtcrime/securesms/components/AudioView.java b/src/org/thoughtcrime/securesms/components/AudioView.java index 438bd0962..527fa3ca9 100644 --- a/src/org/thoughtcrime/securesms/components/AudioView.java +++ b/src/org/thoughtcrime/securesms/components/AudioView.java @@ -27,7 +27,6 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.events.PartProgressEvent; import org.thoughtcrime.securesms.mms.AudioSlide; @@ -108,8 +107,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener EventBus.getDefault().unregister(this); } - public void setAudio(final @NonNull MasterSecret masterSecret, - final @NonNull AudioSlide audio, + public void setAudio(final @NonNull AudioSlide audio, final boolean showControls) { @@ -128,7 +126,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener if (downloadProgress.isSpinning()) downloadProgress.stopSpinning(); } - this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), masterSecret, audio, this); + this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this); } public void cleanup() { diff --git a/src/org/thoughtcrime/securesms/components/MediaView.java b/src/org/thoughtcrime/securesms/components/MediaView.java index e392783c9..d8d809ff5 100644 --- a/src/org/thoughtcrime/securesms/components/MediaView.java +++ b/src/org/thoughtcrime/securesms/components/MediaView.java @@ -13,7 +13,6 @@ import android.view.Window; import android.widget.FrameLayout; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.util.views.Stub; @@ -54,8 +53,7 @@ public class MediaView extends FrameLayout { this.videoView = new Stub<>(findViewById(R.id.video_player_stub)); } - public void set(@NonNull MasterSecret masterSecret, - @NonNull GlideRequests glideRequests, + public void set(@NonNull GlideRequests glideRequests, @NonNull Window window, @NonNull Uri source, @NonNull String mediaType, @@ -66,12 +64,12 @@ public class MediaView extends FrameLayout { if (mediaType.startsWith("image/")) { imageView.setVisibility(View.VISIBLE); if (videoView.resolved()) videoView.get().setVisibility(View.GONE); - imageView.setImageUri(masterSecret, glideRequests, source, mediaType); + imageView.setImageUri(glideRequests, source, mediaType); } else if (mediaType.startsWith("video/")) { imageView.setVisibility(View.GONE); videoView.get().setVisibility(View.VISIBLE); videoView.get().setWindow(window); - videoView.get().setVideoSource(masterSecret, new VideoSlide(getContext(), source, size), autoplay); + videoView.get().setVideoSource(new VideoSlide(getContext(), source, size), autoplay); } else { throw new IOException("Unsupported media type: " + mediaType); } diff --git a/src/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java b/src/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java index 44b55504f..4e7d58b8d 100644 --- a/src/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java +++ b/src/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java @@ -91,11 +91,11 @@ public class ThreadPhotoRailView extends FrameLayout { @Override public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) { ThumbnailView imageView = viewHolder.imageView; - MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor); + MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor); Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment()); if (slide != null) { - imageView.setImageResource(masterSecret, glideRequests, slide, false, false); + imageView.setImageResource(glideRequests, slide, false, false); } imageView.setOnClickListener(v -> { diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 91dbbf13f..3433c37b7 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -17,7 +17,6 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; @@ -58,8 +57,8 @@ public class ThumbnailView extends FrameLayout { inflate(context, R.layout.thumbnail_view, this); this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); - this.image = (ImageView) findViewById(R.id.thumbnail_image); - this.playOverlay = (ImageView) findViewById(R.id.play_overlay); + this.image = findViewById(R.id.thumbnail_image); + this.playOverlay = findViewById(R.id.play_overlay); super.setOnClickListener(new ThumbnailClickDispatcher()); if (attrs != null) { @@ -88,7 +87,7 @@ public class ThumbnailView extends FrameLayout { private TransferControlView getTransferControls() { if (!transferControls.isPresent()) { - transferControls = Optional.of((TransferControlView)ViewUtil.inflateStub(this, R.id.transfer_controls_stub)); + transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub)); } return transferControls.get(); } @@ -97,8 +96,8 @@ public class ThumbnailView extends FrameLayout { this.backgroundColorHint = color; } - public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, - @NonNull Slide slide, boolean showControls, boolean isPreview) + public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, + boolean showControls, boolean isPreview) { if (showControls) { getTransferControls().setSlide(slide); @@ -134,15 +133,15 @@ public class ThumbnailView extends FrameLayout { this.slide = slide; - if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(masterSecret, glideRequests, slide).into(image); + if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(glideRequests, slide).into(image); else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(glideRequests, slide).into(image); else glideRequests.clear(image); } - public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Uri uri) { + public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); - glideRequests.load(new DecryptableUri(masterSecret, uri)) + glideRequests.load(new DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) .transform(new RoundedCorners(radius)) .transition(withCrossFade()) @@ -172,8 +171,8 @@ public class ThumbnailView extends FrameLayout { getTransferControls().showProgressSpinner(); } - private RequestBuilder buildThumbnailGlideRequest(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Slide slide) { - RequestBuilder builder = glideRequests.load(new DecryptableUri(masterSecret, slide.getThumbnailUri())) + private RequestBuilder buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { + RequestBuilder builder = glideRequests.load(new DecryptableUri(slide.getThumbnailUri())) .diskCacheStrategy(DiskCacheStrategy.NONE) .transform(new RoundedCorners(radius)) .centerCrop() diff --git a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java index 066e118b3..094bfa02e 100644 --- a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java +++ b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java @@ -22,7 +22,6 @@ import com.github.chrisbanes.photoview.PhotoView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder; import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -61,8 +60,7 @@ public class ZoomingImageView extends FrameLayout { } @SuppressLint("StaticFieldLeak") - public void setImageUri(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, - @NonNull Uri uri, @NonNull String contentType) + public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType) { final Context context = getContext(); final int maxTextureSize = BitmapUtil.getMaxTextureSize(); @@ -75,7 +73,7 @@ public class ZoomingImageView extends FrameLayout { if (MediaUtil.isGif(contentType)) return null; try { - InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri); + InputStream inputStream = PartAuthority.getAttachmentStream(context, uri); return BitmapUtil.getDimensions(inputStream); } catch (IOException | BitmapDecodingException e) { Log.w(TAG, e); @@ -88,29 +86,29 @@ public class ZoomingImageView extends FrameLayout { if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) { Log.w(TAG, "Loading in standard image view..."); - setImageViewUri(masterSecret, glideRequests, uri); + setImageViewUri(glideRequests, uri); } else { Log.w(TAG, "Loading in subsampling image view..."); - setSubsamplingImageViewUri(masterSecret, uri); + setSubsamplingImageViewUri(uri); } } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private void setImageViewUri(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Uri uri) { + private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { photoView.setVisibility(View.VISIBLE); subsamplingImageView.setVisibility(View.GONE); - glideRequests.load(new DecryptableUri(masterSecret, uri)) + glideRequests.load(new DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) .dontTransform() .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .into(photoView); } - private void setSubsamplingImageViewUri(@NonNull MasterSecret masterSecret, @NonNull Uri uri) { - subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory(masterSecret)); - subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory(masterSecret)); + private void setSubsamplingImageViewUri(@NonNull Uri uri) { + subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory()); + subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory()); subsamplingImageView.setVisibility(View.VISIBLE); photoView.setVisibility(View.GONE); @@ -124,31 +122,16 @@ public class ZoomingImageView extends FrameLayout { } private static class AttachmentBitmapDecoderFactory implements DecoderFactory { - - private final MasterSecret masterSecret; - - private AttachmentBitmapDecoderFactory(MasterSecret masterSecret) { - this.masterSecret = masterSecret; - } - @Override public AttachmentBitmapDecoder make() throws IllegalAccessException, InstantiationException { - return new AttachmentBitmapDecoder(masterSecret); + return new AttachmentBitmapDecoder(); } - } private static class AttachmentRegionDecoderFactory implements DecoderFactory { - - private final MasterSecret masterSecret; - - private AttachmentRegionDecoderFactory(@NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; - } - @Override public AttachmentRegionDecoder make() throws IllegalAccessException, InstantiationException { - return new AttachmentRegionDecoder(masterSecret); + return new AttachmentRegionDecoder(); } } } diff --git a/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java b/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java index 229074626..a6ba5848e 100644 --- a/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java +++ b/src/org/thoughtcrime/securesms/components/reminder/SystemSmsImportReminder.java @@ -22,7 +22,6 @@ public class SystemSmsImportReminder extends Reminder { public void onClick(View v) { Intent intent = new Intent(context, ApplicationMigrationService.class); intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE); - intent.putExtra("master_secret", masterSecret); context.startService(intent); Intent nextIntent = new Intent(context, ConversationListActivity.class); diff --git a/src/org/thoughtcrime/securesms/components/subsampling/AttachmentBitmapDecoder.java b/src/org/thoughtcrime/securesms/components/subsampling/AttachmentBitmapDecoder.java index 39bb7bdaa..9e77d034c 100644 --- a/src/org/thoughtcrime/securesms/components/subsampling/AttachmentBitmapDecoder.java +++ b/src/org/thoughtcrime/securesms/components/subsampling/AttachmentBitmapDecoder.java @@ -4,26 +4,18 @@ package org.thoughtcrime.securesms.components.subsampling; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Rect; import android.net.Uri; -import android.support.annotation.NonNull; import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder; import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.service.KeyCachingService; import java.io.InputStream; public class AttachmentBitmapDecoder implements ImageDecoder{ - private final MasterSecret masterSecret; - - public AttachmentBitmapDecoder(@NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; - } + public AttachmentBitmapDecoder() {} @Override public Bitmap decode(Context context, Uri uri) throws Exception { @@ -31,7 +23,7 @@ public class AttachmentBitmapDecoder implements ImageDecoder{ return new SkiaImageDecoder().decode(context, uri); } - InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri); + InputStream inputStream = PartAuthority.getAttachmentStream(context, uri); try { BitmapFactory.Options options = new BitmapFactory.Options(); diff --git a/src/org/thoughtcrime/securesms/components/subsampling/AttachmentRegionDecoder.java b/src/org/thoughtcrime/securesms/components/subsampling/AttachmentRegionDecoder.java index 87a5cecec..cf357f6f1 100644 --- a/src/org/thoughtcrime/securesms/components/subsampling/AttachmentRegionDecoder.java +++ b/src/org/thoughtcrime/securesms/components/subsampling/AttachmentRegionDecoder.java @@ -8,13 +8,11 @@ import android.graphics.BitmapRegionDecoder; import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; -import android.support.annotation.NonNull; import android.util.Log; import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder; import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.PartAuthority; import java.io.InputStream; @@ -23,16 +21,10 @@ public class AttachmentRegionDecoder implements ImageRegionDecoder { private static final String TAG = AttachmentRegionDecoder.class.getName(); - private final MasterSecret masterSecret; - private SkiaImageRegionDecoder passthrough; private BitmapRegionDecoder bitmapRegionDecoder; - public AttachmentRegionDecoder(@NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; - } - @Override public Point init(Context context, Uri uri) throws Exception { Log.w(TAG, "Init!"); @@ -41,7 +33,7 @@ public class AttachmentRegionDecoder implements ImageRegionDecoder { return passthrough.init(context, uri); } - InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri); + InputStream inputStream = PartAuthority.getAttachmentStream(context, uri); this.bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); inputStream.close(); diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 7ea52a3e6..f6a732bb3 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -28,16 +28,14 @@ import android.text.TextUtils; import android.util.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.NumberUtil; -import org.thoughtcrime.securesms.permissions.Permissions; import java.util.ArrayList; @@ -61,17 +59,14 @@ public class ContactsCursorLoader extends CursorLoader { ContactsDatabase.CONTACT_TYPE_COLUMN}; - private final MasterSecret masterSecret; private final String filter; private final int mode; private final boolean recents; - public ContactsCursorLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, - int mode, String filter, boolean recents) + public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents) { super(context); - this.masterSecret = masterSecret; this.filter = filter; this.mode = mode; this.recents = recents; @@ -88,7 +83,7 @@ public class ContactsCursorLoader extends CursorLoader { MatrixCursor synthesizedContacts = new MatrixCursor(CONTACT_PROJECTION); synthesizedContacts.addRow(new Object[] {getContext().getString(R.string.ContactsCursorLoader_recent_chats), "", ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactsDatabase.DIVIDER_TYPE}); - ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations, new MasterCipher(masterSecret)); + ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations); ThreadRecord threadRecord; diff --git a/src/org/thoughtcrime/securesms/contacts/ContactsSyncAdapter.java b/src/org/thoughtcrime/securesms/contacts/ContactsSyncAdapter.java index 25f8f215a..09f71c123 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactsSyncAdapter.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactsSyncAdapter.java @@ -8,7 +8,6 @@ import android.content.SyncResult; import android.os.Bundle; import android.util.Log; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -30,7 +29,7 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter { if (TextSecurePreferences.isPushRegistered(getContext())) { try { - DirectoryHelper.refreshDirectory(getContext(), KeyCachingService.getMasterSecret(getContext()), true); + DirectoryHelper.refreshDirectory(getContext(), true); } catch (IOException e) { Log.w(TAG, e); } diff --git a/src/org/thoughtcrime/securesms/crypto/AttachmentSecret.java b/src/org/thoughtcrime/securesms/crypto/AttachmentSecret.java new file mode 100644 index 000000000..21ddeaa44 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/AttachmentSecret.java @@ -0,0 +1,115 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.support.annotation.NonNull; +import android.util.Base64; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import org.thoughtcrime.securesms.util.JsonUtils; + +import java.io.IOException; + +/** + * Encapsulates the key material used to encrypt attachments on disk. + * + * There are two logical pieces of material, a deprecated set of keys used to encrypt + * legacy attachments, and a key that is used to encrypt attachments going forward. + */ +public class AttachmentSecret { + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] classicCipherKey; + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] classicMacKey; + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] modernKey; + + public AttachmentSecret(byte[] classicCipherKey, byte[] classicMacKey, byte[] modernKey) + { + this.classicCipherKey = classicCipherKey; + this.classicMacKey = classicMacKey; + this.modernKey = modernKey; + } + + @SuppressWarnings("unused") + public AttachmentSecret() { + + } + + @JsonIgnore + byte[] getClassicCipherKey() { + return classicCipherKey; + } + + @JsonIgnore + byte[] getClassicMacKey() { + return classicMacKey; + } + + @JsonIgnore + byte[] getModernKey() { + return modernKey; + } + + @JsonIgnore + void setClassicCipherKey(byte[] classicCipherKey) { + this.classicCipherKey = classicCipherKey; + } + + @JsonIgnore + void setClassicMacKey(byte[] classicMacKey) { + this.classicMacKey = classicMacKey; + } + + public String serialize() { + try { + return JsonUtils.toJson(this); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + static AttachmentSecret fromString(@NonNull String value) { + try { + return JsonUtils.fromJson(value, AttachmentSecret.class); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static class ByteArraySerializer extends JsonSerializer { + @Override + public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING)); + } + } + + private static class ByteArrayDeserializer extends JsonDeserializer { + + @Override + public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING); + } + } + + + +} diff --git a/src/org/thoughtcrime/securesms/crypto/AttachmentSecretProvider.java b/src/org/thoughtcrime/securesms/crypto/AttachmentSecretProvider.java new file mode 100644 index 000000000..1c0ee3626 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/AttachmentSecretProvider.java @@ -0,0 +1,103 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.content.Context; +import android.os.Build; +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +import java.security.SecureRandom; + +/** + * A provider that is responsible for creating or retrieving the AttachmentSecret model. + * + * On modern Android, the serialized secrets are themselves encrypted using a key that lives + * in the system KeyStore, for whatever that is worth. + */ +public class AttachmentSecretProvider { + + private static AttachmentSecretProvider provider; + + public static synchronized AttachmentSecretProvider getInstance(@NonNull Context context) { + if (provider == null) provider = new AttachmentSecretProvider(context.getApplicationContext()); + return provider; + } + + private final Context context; + + private AttachmentSecret attachmentSecret; + + private AttachmentSecretProvider(@NonNull Context context) { + this.context = context.getApplicationContext(); + } + + public synchronized AttachmentSecret getOrCreateAttachmentSecret() { + if (attachmentSecret != null) return attachmentSecret; + + String unencryptedSecret = TextSecurePreferences.getAttachmentUnencryptedSecret(context); + String encryptedSecret = TextSecurePreferences.getAttachmentEncryptedSecret(context); + + if (unencryptedSecret != null) attachmentSecret = getUnencryptedAttachmentSecret(context, unencryptedSecret); + else if (encryptedSecret != null) attachmentSecret = getEncryptedAttachmentSecret(encryptedSecret); + else attachmentSecret = createAndStoreAttachmentSecret(context); + + return attachmentSecret; + } + + public synchronized AttachmentSecret setClassicKey(@NonNull Context context, @NonNull byte[] classicCipherKey, @NonNull byte[] classicMacKey) { + AttachmentSecret currentSecret = getOrCreateAttachmentSecret(); + currentSecret.setClassicCipherKey(classicCipherKey); + currentSecret.setClassicMacKey(classicMacKey); + + storeAttachmentSecret(context, attachmentSecret); + + return attachmentSecret; + } + + private AttachmentSecret getUnencryptedAttachmentSecret(@NonNull Context context, @NonNull String unencryptedSecret) + { + AttachmentSecret attachmentSecret = AttachmentSecret.fromString(unencryptedSecret); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return attachmentSecret; + } else { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes()); + + TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize()); + TextSecurePreferences.setAttachmentUnencryptedSecret(context, null); + + return attachmentSecret; + } + } + + private AttachmentSecret getEncryptedAttachmentSecret(@NonNull String serializedEncryptedSecret) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!"); + } else { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret); + return AttachmentSecret.fromString(new String(KeyStoreHelper.unseal(encryptedSecret))); + } + } + + private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) { + SecureRandom random = new SecureRandom(); + byte[] secret = new byte[32]; + random.nextBytes(secret); + + AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret); + storeAttachmentSecret(context, attachmentSecret); + + return attachmentSecret; + } + + private void storeAttachmentSecret(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes()); + TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize()); + } else { + TextSecurePreferences.setAttachmentUnencryptedSecret(context, attachmentSecret.serialize()); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java b/src/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java similarity index 87% rename from src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java rename to src/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java index 56ca56d7c..16befec96 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingPartInputStream.java +++ b/src/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.crypto; +import android.support.annotation.NonNull; import android.util.Log; import org.thoughtcrime.securesms.util.LimitedInputStream; @@ -37,14 +38,14 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -public class DecryptingPartInputStream { +public class ClassicDecryptingPartInputStream { - private static final String TAG = DecryptingPartInputStream.class.getSimpleName(); + private static final String TAG = ClassicDecryptingPartInputStream.class.getSimpleName(); private static final int IV_LENGTH = 16; private static final int MAC_LENGTH = 20; - public static InputStream createFor(MasterSecret masterSecret, File file) + public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file) throws IOException { try { @@ -52,7 +53,7 @@ public class DecryptingPartInputStream { throw new IOException("File too short"); } - verifyMac(masterSecret, file); + verifyMac(attachmentSecret, file); FileInputStream fileStream = new FileInputStream(file); byte[] ivBytes = new byte[IV_LENGTH]; @@ -60,7 +61,7 @@ public class DecryptingPartInputStream { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(ivBytes); - cipher.init(Cipher.DECRYPT_MODE, masterSecret.getEncryptionKey(), iv); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(attachmentSecret.getClassicCipherKey(), "AES"), iv); return new CipherInputStreamWrapper(new LimitedInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { @@ -68,8 +69,8 @@ public class DecryptingPartInputStream { } } - private static void verifyMac(MasterSecret masterSecret, File file) throws IOException { - Mac mac = initializeMac(masterSecret.getMacKey()); + private static void verifyMac(AttachmentSecret attachmentSecret, File file) throws IOException { + Mac mac = initializeMac(new SecretKeySpec(attachmentSecret.getClassicMacKey(), "HmacSHA1")); FileInputStream macStream = new FileInputStream(file); InputStream dataStream = new LimitedInputStream(new FileInputStream(file), file.length() - MAC_LENGTH); byte[] theirMac = new byte[MAC_LENGTH]; diff --git a/src/org/thoughtcrime/securesms/crypto/DatabaseSecret.java b/src/org/thoughtcrime/securesms/crypto/DatabaseSecret.java new file mode 100644 index 000000000..ce58d94d5 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/DatabaseSecret.java @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.Hex; + +import java.io.IOException; + +public class DatabaseSecret { + + private final byte[] key; + private final String encoded; + + public DatabaseSecret(@NonNull byte[] key) { + this.key = key; + this.encoded = Hex.toStringCondensed(key); + } + + public DatabaseSecret(@NonNull String encoded) throws IOException { + this.key = Hex.fromStringCondensed(encoded); + this.encoded = encoded; + } + + public String asString() { + return encoded; + } + + public byte[] asBytes() { + return key; + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/DatabaseSecretProvider.java b/src/org/thoughtcrime/securesms/crypto/DatabaseSecretProvider.java new file mode 100644 index 000000000..86393e191 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/DatabaseSecretProvider.java @@ -0,0 +1,78 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.content.Context; +import android.os.Build; +import android.support.annotation.NonNull; + +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +import java.io.IOException; +import java.security.SecureRandom; + +public class DatabaseSecretProvider { + + @SuppressWarnings("unused") + private static final String TAG = DatabaseSecretProvider.class.getSimpleName(); + + private final Context context; + + public DatabaseSecretProvider(@NonNull Context context) { + this.context = context.getApplicationContext(); + } + + public DatabaseSecret getOrCreateDatabaseSecret() { + String unencryptedSecret = TextSecurePreferences.getDatabaseUnencryptedSecret(context); + String encryptedSecret = TextSecurePreferences.getDatabaseEncryptedSecret(context); + + if (unencryptedSecret != null) return getUnencryptedDatabaseSecret(context, unencryptedSecret); + else if (encryptedSecret != null) return getEncryptedDatabaseSecret(encryptedSecret); + else return createAndStoreDatabaseSecret(context); + } + + private DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret) + { + try { + DatabaseSecret databaseSecret = new DatabaseSecret(unencryptedSecret); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return databaseSecret; + } else { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes()); + + TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize()); + TextSecurePreferences.setDatabaseUnencryptedSecret(context, null); + + return databaseSecret; + } + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!"); + } else { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret); + return new DatabaseSecret(KeyStoreHelper.unseal(encryptedSecret)); + } + } + + private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) { + SecureRandom random = new SecureRandom(); + byte[] secret = new byte[32]; + random.nextBytes(secret); + + DatabaseSecret databaseSecret = new DatabaseSecret(secret); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes()); + TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize()); + } else { + TextSecurePreferences.setDatabaseUnencryptedSecret(context, databaseSecret.asString()); + } + + return databaseSecret; + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java b/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java deleted file mode 100644 index 8ee91ed93..000000000 --- a/src/org/thoughtcrime/securesms/crypto/EncryptingPartOutputStream.java +++ /dev/null @@ -1,122 +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 . - */ -package org.thoughtcrime.securesms.crypto; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.SecretKeySpec; - -import android.util.Log; - -/** - * A class for streaming an encrypted MMS "part" to disk. - * - * @author Moxie Marlinspike - */ - -public class EncryptingPartOutputStream extends FileOutputStream { - - private Cipher cipher; - private Mac mac; - private boolean closed; - - public EncryptingPartOutputStream(File file, MasterSecret masterSecret) throws FileNotFoundException { - super(file); - - try { - mac = initializeMac(masterSecret.getMacKey()); - cipher = initializeCipher(mac, masterSecret.getEncryptionKey()); - closed = false; - } catch (IOException ioe) { - Log.w("EncryptingPartOutputStream", ioe); - throw new FileNotFoundException("Couldn't write IV"); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); - } - } - - @Override - public void write(byte[] buffer) throws IOException { - this.write(buffer, 0, buffer.length); - } - - @Override - public void write(byte[] buffer, int offset, int length) throws IOException { - byte[] encryptedBuffer = cipher.update(buffer, offset, length); - - if (encryptedBuffer != null) { - mac.update(encryptedBuffer); - super.write(encryptedBuffer, 0, encryptedBuffer.length); - } - } - - @Override - public void close() throws IOException { - try { - if (!closed) { - byte[] encryptedRemainder = cipher.doFinal(); - mac.update(encryptedRemainder); - - byte[] macBytes = mac.doFinal(); - - super.write(encryptedRemainder, 0, encryptedRemainder.length); - super.write(macBytes, 0, macBytes.length); - - closed = true; - } - - super.close(); - } catch (BadPaddingException bpe) { - throw new AssertionError(bpe); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } - } - - private Mac initializeMac(SecretKeySpec key) throws NoSuchAlgorithmException, InvalidKeyException { - Mac hmac = Mac.getInstance("HmacSHA1"); - hmac.init(key); - - return hmac; - } - - private Cipher initializeCipher(Mac mac, SecretKeySpec key) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - - byte[] ivBytes = cipher.getIV(); - mac.update(ivBytes); - super.write(ivBytes, 0, ivBytes.length); - - return cipher; - } - -} diff --git a/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java new file mode 100644 index 000000000..6db8b2468 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java @@ -0,0 +1,180 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.os.Build; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.util.Base64; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import org.thoughtcrime.securesms.util.JsonUtils; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +public class KeyStoreHelper { + + private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; + private static final String KEY_ALIAS = "SignalSecret"; + + @RequiresApi(Build.VERSION_CODES.M) + public static SealedData seal(@NonNull byte[] input) { + SecretKey secretKey = getOrCreateKeyStoreEntry(); + + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + byte[] iv = cipher.getIV(); + byte[] data = cipher.doFinal(input); + + return new SealedData(iv, data); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { + throw new AssertionError(e); + } + } + + @RequiresApi(Build.VERSION_CODES.M) + public static byte[] unseal(@NonNull SealedData sealedData) { + SecretKey secretKey = getKeyStoreEntry(); + + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); + + return cipher.doFinal(sealedData.data); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + throw new AssertionError(e); + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private static SecretKey getOrCreateKeyStoreEntry() { + if (hasKeyStoreEntry()) return getKeyStoreEntry(); + else return createKeyStoreEntry(); + } + + @RequiresApi(Build.VERSION_CODES.M) + private static SecretKey createKeyStoreEntry() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); + KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build(); + + keyGenerator.init(keyGenParameterSpec); + + return keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private static SecretKey getKeyStoreEntry() { + try { + KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + + return ((KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getSecretKey(); + } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableEntryException e) { + throw new AssertionError(e); + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private static boolean hasKeyStoreEntry() { + try { + KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE); + ks.load(null); + + return ks.containsAlias(KEY_ALIAS) && ks.entryInstanceOf(KEY_ALIAS, KeyStore.SecretKeyEntry.class); + } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { + throw new AssertionError(e); + } + } + + public static class SealedData { + + @SuppressWarnings("unused") + private static final String TAG = SealedData.class.getSimpleName(); + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] iv; + + @JsonProperty + @JsonSerialize(using = ByteArraySerializer.class) + @JsonDeserialize(using = ByteArrayDeserializer.class) + private byte[] data; + + SealedData(@NonNull byte[] iv, @NonNull byte[] data) { + this.iv = iv; + this.data = data; + } + + @SuppressWarnings("unused") + public SealedData() {} + + public String serialize() { + try { + return JsonUtils.toJson(this); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + static SealedData fromString(@NonNull String value) { + try { + return JsonUtils.fromJson(value, SealedData.class); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static class ByteArraySerializer extends JsonSerializer { + @Override + public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING)); + } + } + + private static class ByteArrayDeserializer extends JsonDeserializer { + + @Override + public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING); + } + } + + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/MediaKey.java b/src/org/thoughtcrime/securesms/crypto/MediaKey.java deleted file mode 100644 index 1c25b5ac5..000000000 --- a/src/org/thoughtcrime/securesms/crypto/MediaKey.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.thoughtcrime.securesms.crypto; - -import android.support.annotation.NonNull; - -import org.thoughtcrime.securesms.util.Base64; -import org.whispersystems.libsignal.InvalidMessageException; - -import java.io.IOException; - -public class MediaKey { - - public static String getEncrypted(@NonNull MasterSecretUnion masterSecret, @NonNull byte[] key) { - if (masterSecret.getMasterSecret().isPresent()) { - return Base64.encodeBytes(new MasterCipher(masterSecret.getMasterSecret().get()).encryptBytes(key)); - } else { - return "?ASYNC-" + Base64.encodeBytes(new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBytes(key)); - } - } - - public static byte[] getDecrypted(@NonNull MasterSecret masterSecret, - @NonNull AsymmetricMasterSecret asymmetricMasterSecret, - @NonNull String encodedKey) - throws IOException, InvalidMessageException - { - if (encodedKey.startsWith("?ASYNC-")) { - return new AsymmetricMasterCipher(asymmetricMasterSecret).decryptBytes(Base64.decode(encodedKey.substring("?ASYNC-".length()))); - } else { - return new MasterCipher(masterSecret).decryptBytes(Base64.decode(encodedKey)); - } - } -} diff --git a/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java b/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java new file mode 100644 index 000000000..4ac402093 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.support.annotation.NonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class ModernDecryptingPartInputStream { + + public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file) + throws IOException + { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256")); + + FileInputStream fileInputStream = new FileInputStream(file); + byte[] iv = new byte[16]; + byte[] key = mac.doFinal(random); + + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); + + return new CipherInputStream(fileInputStream, cipher); + } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { + throw new AssertionError(e); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java b/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java new file mode 100644 index 000000000..9ad21ecc5 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/ModernEncryptingPartOutputStream.java @@ -0,0 +1,48 @@ +package org.thoughtcrime.securesms.crypto; + + +import android.support.annotation.NonNull; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Constructs an OutputStream that encrypts data written to it with the AttachmentSecret provided. + * + * The on-disk format is very simple, and intentionally no longer includes authentication. + */ +public class ModernEncryptingPartOutputStream { + + public static OutputStream createFor(@NonNull AttachmentSecret attachmentSecret, byte[] random, File file) + throws IOException + { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256")); + + FileOutputStream fileOutputStream = new FileOutputStream(file); + byte[] iv = new byte[16]; + byte[] key = mac.doFinal(random); + + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); + + return new CipherOutputStream(fileOutputStream, cipher); + } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) { + throw new AssertionError(e); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/SessionUtil.java b/src/org/thoughtcrime/securesms/crypto/SessionUtil.java index 8d4a61649..827470954 100644 --- a/src/org/thoughtcrime/securesms/crypto/SessionUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/SessionUtil.java @@ -15,12 +15,12 @@ import java.util.List; public class SessionUtil { - public static boolean hasSession(Context context, MasterSecret masterSecret, Recipient recipient) { - return hasSession(context, masterSecret, recipient.getAddress()); + public static boolean hasSession(Context context, Recipient recipient) { + return hasSession(context, recipient.getAddress()); } - public static boolean hasSession(Context context, MasterSecret masterSecret, @NonNull Address address) { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + public static boolean hasSession(Context context, @NonNull Address address) { + SessionStore sessionStore = new TextSecureSessionStore(context, null); SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(address.serialize(), SignalServiceAddress.DEFAULT_DEVICE_ID); return sessionStore.containsSession(axolotlAddress); diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 5322e9146..09c508613 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -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 getAttachmentsForMessage(@Nullable MasterSecret masterSecret, long mmsId) { + public @NonNull List getAttachmentsForMessage(long mmsId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); List 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 getPendingAttachments(@NonNull MasterSecret masterSecret) { + public @NonNull List getPendingAttachments() { final SQLiteDatabase database = databaseHelper.getReadableDatabase(); final List 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 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 attachments) + void insertAttachmentsForMessage(long mmsId, @NonNull List 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 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 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 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 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 { - 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; + } + } } diff --git a/src/org/thoughtcrime/securesms/database/Database.java b/src/org/thoughtcrime/securesms/database/Database.java index cb11baa13..41cc67326 100644 --- a/src/org/thoughtcrime/securesms/database/Database.java +++ b/src/org/thoughtcrime/securesms/database/Database.java @@ -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; } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index dbb882623..5eb5d4705 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -1,5 +1,5 @@ -/** - * Copyright (C) 2011 Whisper Systems +/* + * Copyright (C) 2018 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,127 +16,42 @@ */ package org.thoughtcrime.securesms.database; -import android.Manifest; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteConstraintException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; -import android.provider.ContactsContract; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; +import android.support.annotation.NonNull; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber; -import com.google.i18n.phonenumbers.ShortNumberInfo; +import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.contacts.ContactsDatabase; -import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream; -import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.AttachmentSecret; +import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; +import org.thoughtcrime.securesms.crypto.DatabaseSecret; +import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.DelimiterUtil; -import org.thoughtcrime.securesms.util.Hex; -import org.thoughtcrime.securesms.util.JsonUtils; -import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper; +import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper; +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.InvalidMessageException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; public class DatabaseFactory { - private static final int INTRODUCED_IDENTITIES_VERSION = 2; - private static final int INTRODUCED_INDEXES_VERSION = 3; - private static final int INTRODUCED_DATE_SENT_VERSION = 4; - private static final int INTRODUCED_DRAFTS_VERSION = 5; - private static final int INTRODUCED_NEW_TYPES_VERSION = 6; - private static final int INTRODUCED_MMS_BODY_VERSION = 7; - private static final int INTRODUCED_MMS_FROM_VERSION = 8; - private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; - private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; - private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; - private static final int INTRODUCED_PUSH_FIX_VERSION = 12; - private static final int INTRODUCED_DELIVERY_RECEIPTS = 13; - private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; - private static final int INTRODUCED_THUMBNAILS_VERSION = 15; - private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; - private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; - private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18; - private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19; - private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20; - private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; - private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; - private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; - private static final int INTRODUCED_ARCHIVE_VERSION = 24; - private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; - private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; - private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27; - private static final int INTRODUCED_EXPIRE_MESSAGES_VERSION = 28; - private static final int INTRODUCED_LAST_SEEN = 29; - private static final int INTRODUCED_DIGEST = 30; - private static final int INTRODUCED_NOTIFIED = 31; - private static final int INTRODUCED_DOCUMENTS = 32; - private static final int INTRODUCED_FAST_PREFLIGHT = 33; - private static final int INTRODUCED_VOICE_NOTES = 34; - private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35; - private static final int SANIFY_ATTACHMENT_DOWNLOAD = 36; - private static final int NO_MORE_CANONICAL_ADDRESS_DATABASE = 37; - private static final int NO_MORE_RECIPIENTS_PLURAL = 38; - private static final int INTERNAL_DIRECTORY = 39; - private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40; - private static final int PROFILES = 41; - private static final int PROFILE_SHARING_APPROVAL = 42; - private static final int UNSEEN_NUMBER_OFFER = 43; - private static final int READ_RECEIPTS = 44; - private static final int GROUP_RECEIPT_TRACKING = 45; - private static final int UNREAD_COUNT_VERSION = 46; - private static final int MORE_RECIPIENT_FIELDS = 47; - private static final int DATABASE_VERSION = 47; - - private static final String DATABASE_NAME = "messages.db"; - private static final Object lock = new Object(); + private static final Object lock = new Object(); private static DatabaseFactory instance; - private DatabaseHelper databaseHelper; - - private final SmsDatabase sms; - private final EncryptingSmsDatabase encryptingSms; - private final MmsDatabase mms; - private final AttachmentDatabase attachments; - private final MediaDatabase media; - private final ThreadDatabase thread; - private final MmsSmsDatabase mmsSmsDatabase; - private final IdentityDatabase identityDatabase; - private final DraftDatabase draftDatabase; - private final PushDatabase pushDatabase; - private final GroupDatabase groupDatabase; - private final RecipientDatabase recipientDatabase; - private final ContactsDatabase contactsDatabase; + private final SQLCipherOpenHelper databaseHelper; + private final SmsDatabase sms; + private final MmsDatabase mms; + private final AttachmentDatabase attachments; + private final MediaDatabase media; + private final ThreadDatabase thread; + private final MmsSmsDatabase mmsSmsDatabase; + private final IdentityDatabase identityDatabase; + private final DraftDatabase draftDatabase; + private final PushDatabase pushDatabase; + private final GroupDatabase groupDatabase; + private final RecipientDatabase recipientDatabase; + private final ContactsDatabase contactsDatabase; private final GroupReceiptDatabase groupReceiptDatabase; public static DatabaseFactory getInstance(Context context) { @@ -164,10 +79,6 @@ public class DatabaseFactory { return getInstance(context).mms; } - public static EncryptingSmsDatabase getEncryptingSmsDatabase(Context context) { - return getInstance(context).encryptingSms; - } - public static AttachmentDatabase getAttachmentDatabase(Context context) { return getInstance(context).attachments; } @@ -204,12 +115,16 @@ public class DatabaseFactory { return getInstance(context).groupReceiptDatabase; } - private DatabaseFactory(Context context) { - this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); + private DatabaseFactory(@NonNull Context context) { + SQLiteDatabase.loadLibs(context); + + DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret(); + AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); + + this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret); this.sms = new SmsDatabase(context, databaseHelper); - this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper); this.mms = new MmsDatabase(context, databaseHelper); - this.attachments = new AttachmentDatabase(context, databaseHelper); + this.attachments = new AttachmentDatabase(context, databaseHelper, attachmentSecret); this.media = new MediaDatabase(context, databaseHelper); this.thread = new ThreadDatabase(context, databaseHelper); this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper); @@ -222,1410 +137,25 @@ public class DatabaseFactory { this.contactsDatabase = new ContactsDatabase(context); } - public void reset(Context context) { - DatabaseHelper old = this.databaseHelper; - this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); - - this.sms.reset(databaseHelper); - this.encryptingSms.reset(databaseHelper); - this.mms.reset(databaseHelper); - this.attachments.reset(databaseHelper); - this.thread.reset(databaseHelper); - this.mmsSmsDatabase.reset(databaseHelper); - this.identityDatabase.reset(databaseHelper); - this.draftDatabase.reset(databaseHelper); - this.pushDatabase.reset(databaseHelper); - this.groupDatabase.reset(databaseHelper); - this.recipientDatabase.reset(databaseHelper); - this.groupReceiptDatabase.reset(databaseHelper); - old.close(); - } - - public void onApplicationLevelUpgrade(Context context, MasterSecret masterSecret, int fromVersion, - DatabaseUpgradeActivity.DatabaseUpgradeListener listener) + public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret, + int fromVersion, DatabaseUpgradeActivity.DatabaseUpgradeListener listener) { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.beginTransaction(); - - if (fromVersion < DatabaseUpgradeActivity.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) { - String KEY_EXCHANGE = "?TextSecureKeyExchange"; - String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; - String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; - int ROW_LIMIT = 500; - - MasterCipher masterCipher = new MasterCipher(masterSecret); - int smsCount = 0; - int threadCount = 0; - int skip = 0; - - Cursor cursor = db.query("sms", new String[] {"COUNT(*)"}, "type & " + 0x80000000 + " != 0", - null, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - smsCount = cursor.getInt(0); - cursor.close(); - } - - cursor = db.query("thread", new String[] {"COUNT(*)"}, "snippet_type & " + 0x80000000 + " != 0", - null, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - threadCount = cursor.getInt(0); - cursor.close(); - } - - Cursor smsCursor = null; - - Log.w("DatabaseFactory", "Upgrade count: " + (smsCount + threadCount)); - - do { - Log.w("DatabaseFactory", "Looping SMS cursor..."); - if (smsCursor != null) - smsCursor.close(); - - smsCursor = db.query("sms", new String[] {"_id", "type", "body"}, - "type & " + 0x80000000 + " != 0", - null, null, null, "_id", skip + "," + ROW_LIMIT); - - while (smsCursor != null && smsCursor.moveToNext()) { - listener.setProgress(smsCursor.getPosition() + skip, smsCount + threadCount); - - try { - String body = masterCipher.decryptBody(smsCursor.getString(smsCursor.getColumnIndexOrThrow("body"))); - long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type")); - long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id")); - - if (body.startsWith(KEY_EXCHANGE)) { - body = body.substring(KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= 0x8000; - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) { - body = body.substring(PROCESSED_KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= (0x8000 | 0x2000); - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } else if (body.startsWith(STALE_KEY_EXCHANGE)) { - body = body.substring(STALE_KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= (0x8000 | 0x4000); - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } - } catch (InvalidMessageException e) { - Log.w("DatabaseFactory", e); - } - } - - skip += ROW_LIMIT; - } while (smsCursor != null && smsCursor.getCount() > 0); - - - - Cursor threadCursor = null; - skip = 0; - - do { - Log.w("DatabaseFactory", "Looping thread cursor..."); - - if (threadCursor != null) - threadCursor.close(); - - threadCursor = db.query("thread", new String[] {"_id", "snippet_type", "snippet"}, - "snippet_type & " + 0x80000000 + " != 0", - null, null, null, "_id", skip + "," + ROW_LIMIT); - - while (threadCursor != null && threadCursor.moveToNext()) { - listener.setProgress(smsCount + threadCursor.getPosition(), smsCount + threadCount); - - try { - String snippet = threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet")); - long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type")); - long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); - - if (!TextUtils.isEmpty(snippet)) { - snippet = masterCipher.decryptBody(snippet); - } - - if (snippet.startsWith(KEY_EXCHANGE)) { - snippet = snippet.substring(KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= 0x8000; - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) { - snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= (0x8000 | 0x2000); - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } else if (snippet.startsWith(STALE_KEY_EXCHANGE)) { - snippet = snippet.substring(STALE_KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= (0x8000 | 0x4000); - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } - } catch (InvalidMessageException e) { - Log.w("DatabaseFactory", e); - } - } - - skip += ROW_LIMIT; - } while (threadCursor != null && threadCursor.getCount() > 0); - - if (smsCursor != null) - smsCursor.close(); - - if (threadCursor != null) - threadCursor.close(); - } - - if (fromVersion < DatabaseUpgradeActivity.MMS_BODY_VERSION) { - Log.w("DatabaseFactory", "Update MMS bodies..."); - MasterCipher masterCipher = new MasterCipher(masterSecret); - Cursor mmsCursor = db.query("mms", new String[] {"_id"}, - "msg_box & " + 0x80000000L + " != 0", - null, null, null, null); - - Log.w("DatabaseFactory", "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount())); - - while (mmsCursor != null && mmsCursor.moveToNext()) { - listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount()); - - long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id")); - String body = null; - int partCount = 0; - Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"}, - "mid = ?", new String[] {mmsId+""}, null, null, null); - - while (partCursor != null && partCursor.moveToNext()) { - String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct")); - - if (MediaUtil.isTextType(contentType)) { - try { - long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id")); - String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data")); - boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1; - File dataFile = new File(dataLocation); - - InputStream is; - - if (encrypted) is = DecryptingPartInputStream.createFor(masterSecret, dataFile); - else is = new FileInputStream(dataFile); - - body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is); - - //noinspection ResultOfMethodCallIgnored - dataFile.delete(); - db.delete("part", "_id = ?", new String[] {partId+""}); - } catch (IOException e) { - Log.w("DatabaseFactory", e); - } - } else if (MediaUtil.isAudioType(contentType) || - MediaUtil.isImageType(contentType) || - MediaUtil.isVideoType(contentType)) - { - partCount++; - } - } - - if (!TextUtils.isEmpty(body)) { - body = masterCipher.encryptBody(body); - db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?", - new String[] {body, partCount+"", mmsId+""}); - } else { - db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?", - new String[] {partCount+"", mmsId+""}); - } - - Log.w("DatabaseFactory", "Updated body: " + body + " and part_count: " + partCount); - } - } - - if (fromVersion < DatabaseUpgradeActivity.TOFU_IDENTITIES_VERSION) { - File sessionDirectory = new File(context.getFilesDir() + File.separator + "sessions"); - - if (sessionDirectory.exists() && sessionDirectory.isDirectory()) { - File[] sessions = sessionDirectory.listFiles(); - - if (sessions != null) { - for (File session : sessions) { - String name = session.getName(); - - if (name.matches("[0-9]+")) { - long recipientId = Long.parseLong(name); - IdentityKey identityKey = null; - // NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse - // V1 session records. Despite our usual attempts to avoid using shared code in the - // upgrade path, this is too complex to put here directly. Thus, unfortunately - // this operation is now lost to the ages. From the git log, it seems to have been - // almost exactly a year since this went in, so hopefully the bulk of people have - // already upgraded. -// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId); - - if (identityKey != null) { - MasterCipher masterCipher = new MasterCipher(masterSecret); - String identityKeyString = Base64.encodeBytes(identityKey.serialize()); - String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId + - identityKeyString)); - - db.execSQL("REPLACE INTO identities (recipient, key, mac) VALUES (?, ?, ?)", - new String[] {recipientId+"", identityKeyString, macString}); - } - } - } - } - } - } + ClassicOpenHelper legacyOpenHelper = null; if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) { - if (!MasterSecretUtil.hasAsymmericMasterSecret(context)) { - MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret); - - MasterCipher masterCipher = new MasterCipher(masterSecret); - Cursor cursor = null; - - try { - cursor = db.query(SmsDatabase.TABLE_NAME, - new String[] {SmsDatabase.ID, SmsDatabase.BODY, SmsDatabase.TYPE}, - SmsDatabase.TYPE + " & ? == 0", - new String[] {String.valueOf(SmsDatabase.Types.ENCRYPTION_MASK)}, - null, null, null); - - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String body = cursor.getString(1); - long type = cursor.getLong(2); - - String encryptedBody = masterCipher.encryptBody(body); - - ContentValues update = new ContentValues(); - update.put(SmsDatabase.BODY, encryptedBody); - update.put(SmsDatabase.TYPE, type | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT); - - db.update(SmsDatabase.TABLE_NAME, update, SmsDatabase.ID + " = ?", - new String[] {String.valueOf(id)}); - } - } finally { - if (cursor != null) - cursor.close(); - } - } + legacyOpenHelper = new ClassicOpenHelper(context); + legacyOpenHelper.onApplicationLevelUpgrade(context, masterSecret, fromVersion, listener); } - db.setTransactionSuccessful(); - db.endTransaction(); - -// DecryptingQueue.schedulePendingDecrypts(context, masterSecret); - MessageNotifier.updateNotification(context, masterSecret); - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - - private static final String TAG = DatabaseHelper.class.getSimpleName(); - - private final Context context; - - public DatabaseHelper(Context context, String name, CursorFactory factory, int version) { - super(context, name, factory, version); - this.context = context.getApplicationContext(); - } - - @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); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.beginTransaction(); - - if (oldVersion < INTRODUCED_IDENTITIES_VERSION) { - db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, key TEXT UNIQUE, name TEXT UNIQUE, mac TEXT);"); + if (fromVersion < DatabaseUpgradeActivity.SQLCIPHER && TextSecurePreferences.getNeedsSqlCipherMigration(context)) { + if (legacyOpenHelper == null) { + legacyOpenHelper = new ClassicOpenHelper(context); } - if (oldVersion < INTRODUCED_INDEXES_VERSION) { - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON sms (thread_id);", - "CREATE INDEX IF NOT EXISTS sms_read_index ON sms (read);", - "CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON sms (read,thread_id);", - "CREATE INDEX IF NOT EXISTS sms_type_index ON sms (type);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON mms (thread_id);", - "CREATE INDEX IF NOT EXISTS mms_read_index ON mms (read);", - "CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON mms (read,thread_id);", - "CREATE INDEX IF NOT EXISTS mms_message_box_index ON mms (msg_box);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS part_mms_id_index ON part (mid);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON thread (recipient_ids);", - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON mms_addresses (mms_id);", - }); - } - - if (oldVersion < INTRODUCED_DATE_SENT_VERSION) { - db.execSQL("ALTER TABLE sms ADD COLUMN date_sent INTEGER;"); - db.execSQL("UPDATE sms SET date_sent = date;"); - - db.execSQL("ALTER TABLE mms ADD COLUMN date_received INTEGER;"); - db.execSQL("UPDATE mms SET date_received = date;"); - } - - if (oldVersion < INTRODUCED_DRAFTS_VERSION) { - db.execSQL("CREATE TABLE drafts (_id INTEGER PRIMARY KEY, thread_id INTEGER, type TEXT, value TEXT);"); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS draft_thread_index ON drafts (thread_id);", - }); - } - - if (oldVersion < INTRODUCED_NEW_TYPES_VERSION) { - String KEY_EXCHANGE = "?TextSecureKeyExchange"; - String SYMMETRIC_ENCRYPT = "?TextSecureLocalEncrypt"; - String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt"; - String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt"; - String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; - String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; - - // SMS Updates - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {20L+"", 1L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {21L+"", 43L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {22L+"", 4L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {23L+"", 2L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {24L+"", 5L+""}); - - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(21L | 0x800000L)+"", 42L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(23L | 0x800000L)+"", 44L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L)+"", 45L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x10000000L)+"", 46L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L)+"", 47L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x08000000L)+"", 48L+""}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", - 0x80000000L+"", - SYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", - 0x40000000L+"", - ASYMMETRIC_LOCAL_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", - (0x800000L | 0x20000000L)+"", - ASYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(KEY_EXCHANGE.length()+1)+"", - 0x8000L+"", - KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x2000L)+"", - PROCESSED_KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x4000L)+"", - STALE_KEY_EXCHANGE + "%"}); - - // MMS Updates - - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L)+"", 1+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L)+"", 2+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L)+"", 4+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(24L | 0x80000000L)+"", 12+""}); - - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L | 0x800000L) +"", 5+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L | 0x800000L) +"", 6+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x20000000L | 0x800000L) +"", 7+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L | 0x800000L) +"", 8+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x08000000L | 0x800000L) +"", 9+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x10000000L | 0x800000L) +"", 10+""}); - - // Thread Updates - - db.execSQL("ALTER TABLE thread ADD COLUMN snippet_type INTEGER;"); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", - 0x80000000L+"", - SYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", - 0x40000000L+"", - ASYMMETRIC_LOCAL_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", - (0x800000L | 0x20000000L)+"", - ASYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(KEY_EXCHANGE.length()+1)+"", - 0x8000L+"", - KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x4000L)+"", - STALE_KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x2000L)+"", - PROCESSED_KEY_EXCHANGE + "%"}); - } - - if (oldVersion < INTRODUCED_MMS_BODY_VERSION) { - db.execSQL("ALTER TABLE mms ADD COLUMN body TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN part_count INTEGER"); - } - - if (oldVersion < INTRODUCED_MMS_FROM_VERSION) { - db.execSQL("ALTER TABLE mms ADD COLUMN address TEXT"); - - Cursor cursor = db.query("mms_addresses", null, "type = ?", new String[] {0x89+""}, - null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long mmsId = cursor.getLong(cursor.getColumnIndexOrThrow("mms_id")); - String address = cursor.getString(cursor.getColumnIndexOrThrow("address")); - - if (!TextUtils.isEmpty(address)) { - db.execSQL("UPDATE mms SET address = ? WHERE _id = ?", new String[]{address, mmsId+""}); - } - } - - if (cursor != null) - cursor.close(); - } - - if (oldVersion < INTRODUCED_TOFU_IDENTITY_VERSION) { - db.execSQL("DROP TABLE identities"); - db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);"); - } - - if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { - db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); - db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); - db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);"); - } - - if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) { - db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER, active INTEGER DEFAULT 1);"); - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);"); - db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;"); - db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); - db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); - } - - if (oldVersion < INTRODUCED_PUSH_FIX_VERSION) { - db.execSQL("CREATE TEMPORARY table push_backup (_id INTEGER PRIMARY KEY, type INTEGER, source, TEXT, destinations TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); - db.execSQL("INSERT INTO push_backup(_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push;"); - db.execSQL("DROP TABLE push"); - db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); - db.execSQL("INSERT INTO push (_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push_backup;"); - db.execSQL("DROP TABLE push_backup;"); - } - - if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) { - db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); - db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);"); - } - - if (oldVersion < INTRODUCED_PART_DATA_SIZE_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN data_size INTEGER DEFAULT 0;"); - } - - if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT;"); - db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL;"); - } - - if (oldVersion < INTRODUCED_IDENTITY_COLUMN_VERSION) { - db.execSQL("ALTER TABLE sms ADD COLUMN mismatched_identities TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN mismatched_identities TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT"); - } - - if (oldVersion < INTRODUCED_UNIQUE_PART_IDS_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) { - db.execSQL("CREATE TABLE recipient_preferences " + - "(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " + - "notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)"); - } - - if (oldVersion < INTRODUCED_ENVELOPE_CONTENT_VERSION) { - db.execSQL("ALTER TABLE push ADD COLUMN content TEXT"); - } - - if (oldVersion < INTRODUCED_COLOR_PREFERENCE_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN color TEXT DEFAULT NULL"); - } - - if (oldVersion < INTRODUCED_DB_OPTIMIZATIONS_VERSION) { - db.execSQL("UPDATE mms SET date_received = (date_received * 1000), date = (date * 1000);"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_thread_date_index ON sms (thread_id, date);"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);"); - } - - if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL"); - } - - if (oldVersion < INTRODUCED_ARCHIVE_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0"); - db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)"); - } - - if (oldVersion < INTRODUCED_CONVERSATION_LIST_STATUS_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN status INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE thread ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0"); - } - - if (oldVersion < MIGRATED_CONVERSATION_LIST_STATUS_VERSION) { - Cursor threadCursor = db.query("thread", new String[] {"_id"}, null, null, null, null, null); - - while (threadCursor != null && threadCursor.moveToNext()) { - long threadId = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); - - Cursor cursor = db.rawQuery("SELECT DISTINCT date AS date_received, status, " + - "delivery_receipt_count FROM sms WHERE (thread_id = ?1) " + - "UNION ALL SELECT DISTINCT date_received, -1 AS status, " + - "delivery_receipt_count FROM mms WHERE (thread_id = ?1) " + - "ORDER BY date_received DESC LIMIT 1", new String[]{threadId + ""}); - - if (cursor != null && cursor.moveToNext()) { - int status = cursor.getInt(cursor.getColumnIndexOrThrow("status")); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow("delivery_receipt_count")); - - db.execSQL("UPDATE thread SET status = ?, delivery_receipt_count = ? WHERE _id = ?", - new String[]{status + "", receiptCount + "", threadId + ""}); - } - } - } - - if (oldVersion < INTRODUCED_SUBSCRIPTION_ID_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN default_subscription_id INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE sms ADD COLUMN subscription_id INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1"); - } - - if (oldVersion < INTRODUCED_EXPIRE_MESSAGES_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN expire_messages INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE sms ADD COLUMN expires_in INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN expires_in INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE sms ADD COLUMN expire_started INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN expire_started INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE thread ADD COLUMN expires_in INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_LAST_SEEN) { - db.execSQL("ALTER TABLE thread ADD COLUMN last_seen INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_DIGEST) { - db.execSQL("ALTER TABLE part ADD COLUMN digest BLOB"); - db.execSQL("ALTER TABLE groups ADD COLUMN avatar_digest BLOB"); - } - - if (oldVersion < INTRODUCED_NOTIFIED) { - db.execSQL("ALTER TABLE sms ADD COLUMN notified INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN notified INTEGER DEFAULT 0"); - - db.execSQL("DROP INDEX sms_read_and_thread_id_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_read_and_notified_and_thread_id_index ON sms(read,notified,thread_id)"); - - db.execSQL("DROP INDEX mms_read_and_thread_id_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_read_and_notified_and_thread_id_index ON mms(read,notified,thread_id)"); - } - - if (oldVersion < INTRODUCED_DOCUMENTS) { - db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT"); - } - - if (oldVersion < INTRODUCED_FAST_PREFLIGHT) { - db.execSQL("ALTER TABLE part ADD COLUMN fast_preflight_id TEXT"); - } - - if (oldVersion < INTRODUCED_VOICE_NOTES) { - db.execSQL("ALTER TABLE part ADD COLUMN voice_note INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) { - db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN verified INTEGER DEFAULT 0"); - - db.execSQL("DROP INDEX archived_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)"); - } - - if (oldVersion < SANIFY_ATTACHMENT_DOWNLOAD) { - db.execSQL("UPDATE part SET pending_push = '2' WHERE pending_push = '1'"); - } - - if (oldVersion < NO_MORE_CANONICAL_ADDRESS_DATABASE) { - DatabaseHelper canonicalAddressDatabaseHelper = new DatabaseHelper(context, "canonical_address.db", null, 1); - SQLiteDatabase canonicalAddressDatabase = canonicalAddressDatabaseHelper.getReadableDatabase(); - NumberMigrator numberMigrator = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)); - - // Migrate Thread Database - Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - String recipientIdsList = cursor.getString(1); - String[] recipientIds = recipientIdsList.split(" "); - String[] addresses = new String[recipientIds.length]; - - for (int i=0;i newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); - db.update("sms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - if (cursor != null) cursor.close(); - - // Migrate MMS mismatched identities - cursor = db.query("mms", new String[] {"_id", "mismatched_identities"}, "mismatched_identities IS NOT NULL", null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String document = cursor.getString(1); - - if (!TextUtils.isEmpty(document)) { - try { - PreCanonicalAddressIdentityMismatchList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressIdentityMismatchList.class); - List newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); - db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - if (cursor != null) cursor.close(); - - // Migrate MMS network failures - cursor = db.query("mms", new String[] {"_id", "network_failures"}, "network_failures IS NOT NULL", null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String document = cursor.getString(1); - - if (!TextUtils.isEmpty(document)) { - try { - PreCanonicalAddressNetworkFailureList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressNetworkFailureList.class); - List newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressNetworkFailureDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressNetworkFailureDocument(numberMigrator.migrate(address))); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("network_failures", JsonUtils.toJson(new PostCanonicalAddressNetworkFailureList(newDocumentList))); - db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - // Migrate sessions - File sessionsDirectory = new File(context.getFilesDir(), "sessions-v2"); - - if (sessionsDirectory.exists() && sessionsDirectory.isDirectory()) { - File[] sessions = sessionsDirectory.listFiles(); - - for (File session : sessions) { - try { - String[] sessionParts = session.getName().split("[.]"); - long recipientId = Long.parseLong(sessionParts[0]); - - int deviceId; - - if (sessionParts.length > 1) deviceId = Integer.parseInt(sessionParts[1]); - else deviceId = 1; - - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToNext()) { - String address = resolved.getString(0); - File destination = new File(session.getParentFile(), address + (deviceId != 1 ? "." + deviceId : "")); - - if (!session.renameTo(destination)) { - Log.w(TAG, "Session rename failed: " + destination); - } - } - - if (resolved != null) resolved.close(); - } catch (NumberFormatException e) { - Log.w(TAG, e); - } - } - } - - } - - if (oldVersion < NO_MORE_RECIPIENTS_PLURAL) { - db.execSQL("ALTER TABLE groups ADD COLUMN mms INTEGER DEFAULT 0"); - - Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - String addressListString = cursor.getString(1); - String[] addressList = DelimiterUtil.split(addressListString, ' '); - - if (addressList.length == 1) { - ContentValues contentValues = new ContentValues(); - contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); - db.update("thread", contentValues, "_id = ?", new String[] {String.valueOf(threadId)}); - } else { - byte[] groupId = new byte[16]; - List members = new LinkedList<>(); - - new SecureRandom().nextBytes(groupId); - - for (String address : addressList) { - members.add(DelimiterUtil.escape(DelimiterUtil.unescape(address, ' '), ',')); - } - - members.add(DelimiterUtil.escape(TextSecurePreferences.getLocalNumber(context), ',')); - - Collections.sort(members); - - String encodedGroupId = "__signal_mms_group__!" + Hex.toStringCondensed(groupId); - ContentValues groupValues = new ContentValues(); - ContentValues threadValues = new ContentValues(); - - groupValues.put("group_id", encodedGroupId); - groupValues.put("members", Util.join(members, ",")); - groupValues.put("mms", 1); - - threadValues.put("recipient_ids", encodedGroupId); - - db.insert("groups", null, groupValues); - db.update("thread", threadValues, "_id = ?", new String[] {String.valueOf(threadId)}); - db.update("recipient_preferences", threadValues, "recipient_ids = ?", new String[] {addressListString}); - } - } - - if (cursor != null) cursor.close(); - - cursor = db.query("recipient_preferences", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String addressListString = cursor.getString(1); - String[] addressList = DelimiterUtil.split(addressListString, ' '); - - if (addressList.length == 1) { - ContentValues contentValues = new ContentValues(); - contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); - db.update("recipient_preferences", contentValues, "_id = ?", new String[] {String.valueOf(id)}); - } else { - Log.w(TAG, "Found preferences for MMS thread that appears to be gone: " + addressListString); - db.delete("recipient_preferences", "_id = ?", new String[] {String.valueOf(id)}); - } - } - - if (cursor != null) cursor.close(); - - cursor = db.rawQuery("SELECT mms._id, thread.recipient_ids FROM mms, thread WHERE mms.address IS NULL AND mms.thread_id = thread._id", null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - ContentValues contentValues = new ContentValues(1); - - contentValues.put("address", cursor.getString(1)); - db.update("mms", contentValues, "_id = ?", new String[] {String.valueOf(id)}); - } - - if (cursor != null) cursor.close(); - } - - if (oldVersion < INTERNAL_DIRECTORY) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN registered INTEGER DEFAULT 0"); - - OldDirectoryDatabaseHelper directoryDatabaseHelper = new OldDirectoryDatabaseHelper(context); - SQLiteDatabase directoryDatabase = directoryDatabaseHelper.getWritableDatabase(); - - Cursor cursor = directoryDatabase.query("directory", new String[] {"number", "registered"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - String address = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)).migrate(cursor.getString(0)); - ContentValues contentValues = new ContentValues(1); - - contentValues.put("registered", cursor.getInt(1) == 1 ? 1 : 2); - - if (db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}) < 1) { - contentValues.put("recipient_ids", address); - db.insert("recipient_preferences", null, contentValues); - } - } - - if (cursor != null) cursor.close(); - } - - if (oldVersion < INTERNAL_SYSTEM_DISPLAY_NAME) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_display_name TEXT DEFAULT NULL"); - } - - if (oldVersion < PROFILES) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_key TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_name TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_avatar TEXT DEFAULT NULL"); - } - - if (oldVersion < PROFILE_SHARING_APPROVAL) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0"); - } - - if (oldVersion < UNSEEN_NUMBER_OFFER) { - db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0"); - } - - if (oldVersion < READ_RECEIPTS) { - db.execSQL("ALTER TABLE sms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - } - - if (oldVersion < GROUP_RECEIPT_TRACKING) { - db.execSQL("CREATE TABLE group_receipts (_id INTEGER PRIMARY KEY, mms_id INTEGER, address TEXT, status INTEGER, timestamp INTEGER)"); - db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)"); - } - - if (oldVersion < UNREAD_COUNT_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN unread_count INTEGER DEFAULT 0"); - - try (Cursor cursor = db.query("thread", new String[] {"_id"}, "read = 0", null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - int unreadCount = 0; - - try (Cursor smsCursor = db.rawQuery("SELECT COUNT(*) FROM sms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { - if (smsCursor != null && smsCursor.moveToFirst()) { - unreadCount += smsCursor.getInt(0); - } - } - - try (Cursor mmsCursor = db.rawQuery("SELECT COUNT(*) FROM mms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { - if (mmsCursor != null && mmsCursor.moveToFirst()) { - unreadCount += mmsCursor.getInt(0); - } - } - - db.execSQL("UPDATE thread SET unread_count = ? WHERE _id = ?", - new String[] {String.valueOf(unreadCount), - String.valueOf(threadId)}); - } - } - } - - if (oldVersion < MORE_RECIPIENT_FIELDS) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_photo TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_phone_label TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_uri TEXT DEFAULT NULL"); - - if (Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { - try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"))); - - if (address.isPhone() && !TextUtils.isEmpty(address.toPhoneString())) { - Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString())); - - try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, - ContactsContract.PhoneLookup.LOOKUP_KEY, - ContactsContract.PhoneLookup._ID, - ContactsContract.PhoneLookup.NUMBER, - ContactsContract.PhoneLookup.LABEL, - ContactsContract.PhoneLookup.PHOTO_URI}, - null, null, null)) - { - if (contactCursor != null && contactCursor.moveToFirst()) { - ContentValues contentValues = new ContentValues(3); - contentValues.put("system_contact_photo", contactCursor.getString(5)); - contentValues.put("system_phone_label", contactCursor.getString(4)); - contentValues.put("system_contact_uri", ContactsContract.Contacts.getLookupUri(contactCursor.getLong(2), contactCursor.getString(1)).toString()); - - db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address.toPhoneString()}); - } - } - } - } - } - } - } - - db.setTransactionSuccessful(); - db.endTransaction(); - } - - private void executeStatements(SQLiteDatabase db, String[] statements) { - for (String statement : statements) - db.execSQL(statement); - } - - } - - private static class PreCanonicalAddressIdentityMismatchList { - @JsonProperty(value = "m") - private List list; - } - - private static class PostCanonicalAddressIdentityMismatchList { - @JsonProperty(value = "m") - private List list; - - public PostCanonicalAddressIdentityMismatchList(List list) { - this.list = list; + SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, + legacyOpenHelper.getWritableDatabase(), + databaseHelper.getWritableDatabase()); } } - private static class PreCanonicalAddressIdentityMismatchDocument { - @JsonProperty(value = "r") - private long recipientId; - - @JsonProperty(value = "k") - private String identityKey; - } - - private static class PostCanonicalAddressIdentityMismatchDocument { - @JsonProperty(value = "a") - private String address; - - @JsonProperty(value = "k") - private String identityKey; - - public PostCanonicalAddressIdentityMismatchDocument() {} - - public PostCanonicalAddressIdentityMismatchDocument(String address, String identityKey) { - this.address = address; - this.identityKey = identityKey; - } - } - - private static class PreCanonicalAddressNetworkFailureList { - @JsonProperty(value = "l") - private List list; - } - - private static class PostCanonicalAddressNetworkFailureList { - @JsonProperty(value = "l") - private List list; - - public PostCanonicalAddressNetworkFailureList(List list) { - this.list = list; - } - } - - private static class PreCanonicalAddressNetworkFailureDocument { - @JsonProperty(value = "r") - private long recipientId; - } - - private static class PostCanonicalAddressNetworkFailureDocument { - @JsonProperty(value = "a") - private String address; - - public PostCanonicalAddressNetworkFailureDocument() {} - - public PostCanonicalAddressNetworkFailureDocument(String address) { - this.address = address; - } - } - - private static class NumberMigrator { - - private static final String TAG = NumberMigrator.class.getSimpleName(); - - private static final Set SHORT_COUNTRIES = new HashSet() {{ - add("NU"); - add("TK"); - add("NC"); - add("AC"); - }}; - - private final Phonenumber.PhoneNumber localNumber; - private final String localNumberString; - private final String localCountryCode; - - private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); - - - public NumberMigrator(String localNumber) { - try { - this.localNumberString = localNumber; - this.localNumber = phoneNumberUtil.parse(localNumber, null); - this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(this.localNumber); - } catch (NumberParseException e) { - throw new AssertionError(e); - } - } - - public String migrate(@Nullable String number) { - if (number == null) return "Unknown"; - if (number.startsWith("__textsecure_group__!")) return number; - if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); - - String bareNumber = number.replaceAll("[^0-9+]", ""); - - if (bareNumber.length() == 0) { - if (TextUtils.isEmpty(number.trim())) return "Unknown"; - else return number.trim(); - } - - // libphonenumber doesn't seem to be correct for Germany and Finland - if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) { - return bareNumber; - } - - // libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes. - if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) { - return bareNumber; - } - - try { - Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode); - - if (ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode)) { - return bareNumber; - } - - return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); - } catch (NumberParseException e) { - Log.w(TAG, e); - if (bareNumber.charAt(0) == '+') - return bareNumber; - - String localNumberImprecise = localNumberString; - - if (localNumberImprecise.charAt(0) == '+') - localNumberImprecise = localNumberImprecise.substring(1); - - if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) - return "+" + number; - - int difference = localNumberImprecise.length() - bareNumber.length(); - - return "+" + localNumberImprecise.substring(0, difference) + bareNumber; - } - } - } - - private static class OldDirectoryDatabaseHelper extends SQLiteOpenHelper { - - private static final int INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER = 2; - private static final int INTRODUCED_VOICE_COLUMN = 4; - private static final int INTRODUCED_VIDEO_COLUMN = 5; - - private static final String DATABASE_NAME = "whisper_directory.db"; - private static final int DATABASE_VERSION = 5; - - private static final String TABLE_NAME = "directory"; - private static final String ID = "_id"; - private static final String NUMBER = "number"; - private static final String REGISTERED = "registered"; - private static final String RELAY = "relay"; - private static final String TIMESTAMP = "timestamp"; - private static final String VOICE = "voice"; - private static final String VIDEO = "video"; - - private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY, " + - NUMBER + " TEXT UNIQUE, " + - REGISTERED + " INTEGER, " + - RELAY + " TEXT, " + - TIMESTAMP + " INTEGER, " + - VOICE + " INTEGER, " + - VIDEO + " INTEGER);"; - - public OldDirectoryDatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion < INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER) { - db.execSQL("DROP TABLE directory;"); - db.execSQL("CREATE TABLE directory ( _id INTEGER PRIMARY KEY, " + - "number TEXT UNIQUE, " + - "registered INTEGER, " + - "relay TEXT, " + - "supports_sms INTEGER, " + - "timestamp INTEGER);"); - } - - if (oldVersion < INTRODUCED_VOICE_COLUMN) { - db.execSQL("ALTER TABLE directory ADD COLUMN voice INTEGER;"); - } - - if (oldVersion < INTRODUCED_VIDEO_COLUMN) { - db.execSQL("ALTER TABLE directory ADD COLUMN video INTEGER;"); - } - } - } } diff --git a/src/org/thoughtcrime/securesms/database/DraftDatabase.java b/src/org/thoughtcrime/securesms/database/DraftDatabase.java index 886dd2b31..5ca274cd5 100644 --- a/src/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/src/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -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 drafts) { + public void insertDrafts(long threadId, List 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 threadIds) { + void clearDrafts(Set threadIds) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); StringBuilder where = new StringBuilder(); List 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 getDrafts(MasterCipher masterCipher, long threadId) { + public List getDrafts(long threadId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - List results = new LinkedList(); + List 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) { diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java deleted file mode 100644 index b01adc442..000000000 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ /dev/null @@ -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 . - */ -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 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 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 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 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> decryptedBodyCache = - Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); - - public void put(String ciphertext, String plaintext) { - decryptedBodyCache.put(ciphertext, new SoftReference(plaintext)); - } - - public String get(String ciphertext) { - SoftReference plaintextReference = decryptedBodyCache.get(ciphertext); - - if (plaintextReference != null) { - String plaintext = plaintextReference.get(); - - if (plaintext != null) { - return plaintext; - } - } - - return null; - } - } -} diff --git a/src/org/thoughtcrime/securesms/database/GroupDatabase.java b/src/org/thoughtcrime/securesms/database/GroupDatabase.java index 059f4c51a..411975659 100644 --- a/src/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -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 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); } diff --git a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index 2d4a7b08b..962688db1 100644 --- a/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/src/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java index 715fb14fd..dc8f2a91c 100644 --- a/src/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/src/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/MediaDatabase.java b/src/org/thoughtcrime/securesms/database/MediaDatabase.java index 267f9494c..df30bf517 100644 --- a/src/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java index 266baa650..92531de35 100644 --- a/src/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 0ae9b62e3..d40eced22 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -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 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 attachments = new LinkedList(attachmentDatabase.getAttachmentsForMessage(masterSecret, messageId)); + List 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 insertMessageInbox(MasterSecretUnion masterSecret, - IncomingMediaMessage retrieved, + private Optional 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 insertMessageInbox(MasterSecretUnion masterSecret, - IncomingMediaMessage retrieved, + public Optional 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 insertSecureDecryptedMessageInbox(MasterSecretUnion masterSecret, - IncomingMediaMessage retrieved, - long threadId) + public Optional 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 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 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 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); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 840bafb72..7e3e331d8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 1fd429b1b..476bc0893 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -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; - 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; diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java index 717f4f502..7d04fa55f 100644 --- a/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java +++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupExporter.java @@ -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 = diff --git a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java index bf023d493..77be4dc6f 100644 --- a/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java +++ b/src/org/thoughtcrime/securesms/database/PlaintextBackupImporter.java @@ -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 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); } diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index c0cf991f5..6d5fce280 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java index cd1883e93..7858a9610 100644 --- a/src/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/src/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 154ee2e6e..3b71c109e 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -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 updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) { + public Pair 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 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 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 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) { diff --git a/src/org/thoughtcrime/securesms/database/SmsMigrator.java b/src/org/thoughtcrime/securesms/database/SmsMigrator.java index f79949b1d..faec7c91b 100644 --- a/src/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/src/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -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; } } diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index 81eeef01b..1155b9f33 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -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) { diff --git a/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java new file mode 100644 index 000000000..85fcc156a --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java @@ -0,0 +1,1511 @@ +package org.thoughtcrime.securesms.database.helpers; + + +import android.Manifest; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteConstraintException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.provider.ContactsContract; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; +import com.google.i18n.phonenumbers.ShortNumberInfo; + +import org.thoughtcrime.securesms.DatabaseUpgradeActivity; +import org.thoughtcrime.securesms.crypto.AttachmentSecret; +import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; +import org.thoughtcrime.securesms.crypto.MasterCipher; +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; +import org.thoughtcrime.securesms.database.Address; +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.notifications.MessageNotifier; +import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.Hex; +import org.thoughtcrime.securesms.util.JsonUtils; +import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.InvalidMessageException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +public class ClassicOpenHelper extends SQLiteOpenHelper { + + static final String NAME = "messages.db"; + + private static final int INTRODUCED_IDENTITIES_VERSION = 2; + private static final int INTRODUCED_INDEXES_VERSION = 3; + private static final int INTRODUCED_DATE_SENT_VERSION = 4; + private static final int INTRODUCED_DRAFTS_VERSION = 5; + private static final int INTRODUCED_NEW_TYPES_VERSION = 6; + private static final int INTRODUCED_MMS_BODY_VERSION = 7; + private static final int INTRODUCED_MMS_FROM_VERSION = 8; + private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; + private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; + private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; + private static final int INTRODUCED_PUSH_FIX_VERSION = 12; + private static final int INTRODUCED_DELIVERY_RECEIPTS = 13; + private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; + private static final int INTRODUCED_THUMBNAILS_VERSION = 15; + private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; + private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; + private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18; + private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19; + private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20; + private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; + private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; + private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; + private static final int INTRODUCED_ARCHIVE_VERSION = 24; + private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; + private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; + private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27; + private static final int INTRODUCED_EXPIRE_MESSAGES_VERSION = 28; + private static final int INTRODUCED_LAST_SEEN = 29; + private static final int INTRODUCED_DIGEST = 30; + private static final int INTRODUCED_NOTIFIED = 31; + private static final int INTRODUCED_DOCUMENTS = 32; + private static final int INTRODUCED_FAST_PREFLIGHT = 33; + private static final int INTRODUCED_VOICE_NOTES = 34; + private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35; + private static final int SANIFY_ATTACHMENT_DOWNLOAD = 36; + private static final int NO_MORE_CANONICAL_ADDRESS_DATABASE = 37; + private static final int NO_MORE_RECIPIENTS_PLURAL = 38; + private static final int INTERNAL_DIRECTORY = 39; + private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40; + private static final int PROFILES = 41; + private static final int PROFILE_SHARING_APPROVAL = 42; + private static final int UNSEEN_NUMBER_OFFER = 43; + private static final int READ_RECEIPTS = 44; + private static final int GROUP_RECEIPT_TRACKING = 45; + private static final int UNREAD_COUNT_VERSION = 46; + private static final int MORE_RECIPIENT_FIELDS = 47; + private static final int DATABASE_VERSION = 47; + + private static final String TAG = ClassicOpenHelper.class.getSimpleName(); + + private final Context context; + + public ClassicOpenHelper(Context context) { + super(context, NAME, null, DATABASE_VERSION); + this.context = context.getApplicationContext(); + } + + @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); + } + + public void onApplicationLevelUpgrade(Context context, MasterSecret masterSecret, int fromVersion, + DatabaseUpgradeActivity.DatabaseUpgradeListener listener) + { + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + if (fromVersion < DatabaseUpgradeActivity.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) { + String KEY_EXCHANGE = "?TextSecureKeyExchange"; + String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; + String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; + int ROW_LIMIT = 500; + + MasterCipher masterCipher = new MasterCipher(masterSecret); + int smsCount = 0; + int threadCount = 0; + int skip = 0; + + Cursor cursor = db.query("sms", new String[] {"COUNT(*)"}, "type & " + 0x80000000 + " != 0", + null, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + smsCount = cursor.getInt(0); + cursor.close(); + } + + cursor = db.query("thread", new String[] {"COUNT(*)"}, "snippet_type & " + 0x80000000 + " != 0", + null, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + threadCount = cursor.getInt(0); + cursor.close(); + } + + Cursor smsCursor = null; + + Log.w("DatabaseFactory", "Upgrade count: " + (smsCount + threadCount)); + + do { + Log.w("DatabaseFactory", "Looping SMS cursor..."); + if (smsCursor != null) + smsCursor.close(); + + smsCursor = db.query("sms", new String[] {"_id", "type", "body"}, + "type & " + 0x80000000 + " != 0", + null, null, null, "_id", skip + "," + ROW_LIMIT); + + while (smsCursor != null && smsCursor.moveToNext()) { + listener.setProgress(smsCursor.getPosition() + skip, smsCount + threadCount); + + try { + String body = masterCipher.decryptBody(smsCursor.getString(smsCursor.getColumnIndexOrThrow("body"))); + long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type")); + long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id")); + + if (body.startsWith(KEY_EXCHANGE)) { + body = body.substring(KEY_EXCHANGE.length()); + body = masterCipher.encryptBody(body); + type |= 0x8000; + + db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", + new String[] {body, type+"", id+""}); + } else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) { + body = body.substring(PROCESSED_KEY_EXCHANGE.length()); + body = masterCipher.encryptBody(body); + type |= (0x8000 | 0x2000); + + db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", + new String[] {body, type+"", id+""}); + } else if (body.startsWith(STALE_KEY_EXCHANGE)) { + body = body.substring(STALE_KEY_EXCHANGE.length()); + body = masterCipher.encryptBody(body); + type |= (0x8000 | 0x4000); + + db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", + new String[] {body, type+"", id+""}); + } + } catch (InvalidMessageException e) { + Log.w("DatabaseFactory", e); + } + } + + skip += ROW_LIMIT; + } while (smsCursor != null && smsCursor.getCount() > 0); + + + + Cursor threadCursor = null; + skip = 0; + + do { + Log.w("DatabaseFactory", "Looping thread cursor..."); + + if (threadCursor != null) + threadCursor.close(); + + threadCursor = db.query("thread", new String[] {"_id", "snippet_type", "snippet"}, + "snippet_type & " + 0x80000000 + " != 0", + null, null, null, "_id", skip + "," + ROW_LIMIT); + + while (threadCursor != null && threadCursor.moveToNext()) { + listener.setProgress(smsCount + threadCursor.getPosition(), smsCount + threadCount); + + try { + String snippet = threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet")); + long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type")); + long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); + + if (!TextUtils.isEmpty(snippet)) { + snippet = masterCipher.decryptBody(snippet); + } + + if (snippet.startsWith(KEY_EXCHANGE)) { + snippet = snippet.substring(KEY_EXCHANGE.length()); + snippet = masterCipher.encryptBody(snippet); + snippetType |= 0x8000; + + db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", + new String[] {snippet, snippetType+"", id+""}); + } else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) { + snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length()); + snippet = masterCipher.encryptBody(snippet); + snippetType |= (0x8000 | 0x2000); + + db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", + new String[] {snippet, snippetType+"", id+""}); + } else if (snippet.startsWith(STALE_KEY_EXCHANGE)) { + snippet = snippet.substring(STALE_KEY_EXCHANGE.length()); + snippet = masterCipher.encryptBody(snippet); + snippetType |= (0x8000 | 0x4000); + + db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", + new String[] {snippet, snippetType+"", id+""}); + } + } catch (InvalidMessageException e) { + Log.w("DatabaseFactory", e); + } + } + + skip += ROW_LIMIT; + } while (threadCursor != null && threadCursor.getCount() > 0); + + if (smsCursor != null) + smsCursor.close(); + + if (threadCursor != null) + threadCursor.close(); + } + + if (fromVersion < DatabaseUpgradeActivity.MMS_BODY_VERSION) { + Log.w("DatabaseFactory", "Update MMS bodies..."); + MasterCipher masterCipher = new MasterCipher(masterSecret); + Cursor mmsCursor = db.query("mms", new String[] {"_id"}, + "msg_box & " + 0x80000000L + " != 0", + null, null, null, null); + + Log.w("DatabaseFactory", "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount())); + + while (mmsCursor != null && mmsCursor.moveToNext()) { + listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount()); + + long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id")); + String body = null; + int partCount = 0; + Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"}, + "mid = ?", new String[] {mmsId+""}, null, null, null); + + while (partCursor != null && partCursor.moveToNext()) { + String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct")); + + if (MediaUtil.isTextType(contentType)) { + try { + long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id")); + String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data")); + boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1; + File dataFile = new File(dataLocation); + + InputStream is; + + AttachmentSecret attachmentSecret = new AttachmentSecret(masterSecret.getEncryptionKey().getEncoded(), + masterSecret.getMacKey().getEncoded(), null); + if (encrypted) is = ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataFile); + else is = new FileInputStream(dataFile); + + body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is); + + //noinspection ResultOfMethodCallIgnored + dataFile.delete(); + db.delete("part", "_id = ?", new String[] {partId+""}); + } catch (IOException e) { + Log.w("DatabaseFactory", e); + } + } else if (MediaUtil.isAudioType(contentType) || + MediaUtil.isImageType(contentType) || + MediaUtil.isVideoType(contentType)) + { + partCount++; + } + } + + if (!TextUtils.isEmpty(body)) { + body = masterCipher.encryptBody(body); + db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?", + new String[] {body, partCount+"", mmsId+""}); + } else { + db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?", + new String[] {partCount+"", mmsId+""}); + } + + Log.w("DatabaseFactory", "Updated body: " + body + " and part_count: " + partCount); + } + } + + if (fromVersion < DatabaseUpgradeActivity.TOFU_IDENTITIES_VERSION) { + File sessionDirectory = new File(context.getFilesDir() + File.separator + "sessions"); + + if (sessionDirectory.exists() && sessionDirectory.isDirectory()) { + File[] sessions = sessionDirectory.listFiles(); + + if (sessions != null) { + for (File session : sessions) { + String name = session.getName(); + + if (name.matches("[0-9]+")) { + long recipientId = Long.parseLong(name); + IdentityKey identityKey = null; + // NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse + // V1 session records. Despite our usual attempts to avoid using shared code in the + // upgrade path, this is too complex to put here directly. Thus, unfortunately + // this operation is now lost to the ages. From the git log, it seems to have been + // almost exactly a year since this went in, so hopefully the bulk of people have + // already upgraded. +// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId); + + if (identityKey != null) { + MasterCipher masterCipher = new MasterCipher(masterSecret); + String identityKeyString = Base64.encodeBytes(identityKey.serialize()); + String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId + + identityKeyString)); + + db.execSQL("REPLACE INTO identities (recipient, key, mac) VALUES (?, ?, ?)", + new String[] {recipientId+"", identityKeyString, macString}); + } + } + } + } + } + } + + if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) { + if (!MasterSecretUtil.hasAsymmericMasterSecret(context)) { + MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret); + + MasterCipher masterCipher = new MasterCipher(masterSecret); + Cursor cursor = null; + + try { + cursor = db.query(SmsDatabase.TABLE_NAME, + new String[] {SmsDatabase.ID, SmsDatabase.BODY, SmsDatabase.TYPE}, + SmsDatabase.TYPE + " & ? == 0", + new String[] {String.valueOf(SmsDatabase.Types.ENCRYPTION_MASK)}, + null, null, null); + + while (cursor.moveToNext()) { + long id = cursor.getLong(0); + String body = cursor.getString(1); + long type = cursor.getLong(2); + + String encryptedBody = masterCipher.encryptBody(body); + + ContentValues update = new ContentValues(); + update.put(SmsDatabase.BODY, encryptedBody); + update.put(SmsDatabase.TYPE, type | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT); + + db.update(SmsDatabase.TABLE_NAME, update, SmsDatabase.ID + " = ?", + new String[] {String.valueOf(id)}); + } + } finally { + if (cursor != null) + cursor.close(); + } + } + } + + db.setTransactionSuccessful(); + db.endTransaction(); + +// DecryptingQueue.schedulePendingDecrypts(context, masterSecret); + MessageNotifier.updateNotification(context); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.beginTransaction(); + + if (oldVersion < INTRODUCED_IDENTITIES_VERSION) { + db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, key TEXT UNIQUE, name TEXT UNIQUE, mac TEXT);"); + } + + if (oldVersion < INTRODUCED_INDEXES_VERSION) { + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON sms (thread_id);", + "CREATE INDEX IF NOT EXISTS sms_read_index ON sms (read);", + "CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON sms (read,thread_id);", + "CREATE INDEX IF NOT EXISTS sms_type_index ON sms (type);" + }); + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON mms (thread_id);", + "CREATE INDEX IF NOT EXISTS mms_read_index ON mms (read);", + "CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON mms (read,thread_id);", + "CREATE INDEX IF NOT EXISTS mms_message_box_index ON mms (msg_box);" + }); + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS part_mms_id_index ON part (mid);" + }); + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON thread (recipient_ids);", + }); + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON mms_addresses (mms_id);", + }); + } + + if (oldVersion < INTRODUCED_DATE_SENT_VERSION) { + db.execSQL("ALTER TABLE sms ADD COLUMN date_sent INTEGER;"); + db.execSQL("UPDATE sms SET date_sent = date;"); + + db.execSQL("ALTER TABLE mms ADD COLUMN date_received INTEGER;"); + db.execSQL("UPDATE mms SET date_received = date;"); + } + + if (oldVersion < INTRODUCED_DRAFTS_VERSION) { + db.execSQL("CREATE TABLE drafts (_id INTEGER PRIMARY KEY, thread_id INTEGER, type TEXT, value TEXT);"); + executeStatements(db, new String[] { + "CREATE INDEX IF NOT EXISTS draft_thread_index ON drafts (thread_id);", + }); + } + + if (oldVersion < INTRODUCED_NEW_TYPES_VERSION) { + String KEY_EXCHANGE = "?TextSecureKeyExchange"; + String SYMMETRIC_ENCRYPT = "?TextSecureLocalEncrypt"; + String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt"; + String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt"; + String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; + String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; + + // SMS Updates + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {20L+"", 1L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {21L+"", 43L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {22L+"", 4L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {23L+"", 2L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {24L+"", 5L+""}); + + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(21L | 0x800000L)+"", 42L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(23L | 0x800000L)+"", 44L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L)+"", 45L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x10000000L)+"", 46L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L)+"", 47L+""}); + db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x08000000L)+"", 48L+""}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", + 0x80000000L+"", + SYMMETRIC_ENCRYPT + "%"}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", + 0x40000000L+"", + ASYMMETRIC_LOCAL_ENCRYPT + "%"}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", + (0x800000L | 0x20000000L)+"", + ASYMMETRIC_ENCRYPT + "%"}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(KEY_EXCHANGE.length()+1)+"", + 0x8000L+"", + KEY_EXCHANGE + "%"}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", + (0x8000L | 0x2000L)+"", + PROCESSED_KEY_EXCHANGE + "%"}); + + db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", + new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", + (0x8000L | 0x4000L)+"", + STALE_KEY_EXCHANGE + "%"}); + + // MMS Updates + + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L)+"", 1+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L)+"", 2+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L)+"", 4+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(24L | 0x80000000L)+"", 12+""}); + + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L | 0x800000L) +"", 5+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L | 0x800000L) +"", 6+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x20000000L | 0x800000L) +"", 7+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L | 0x800000L) +"", 8+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x08000000L | 0x800000L) +"", 9+""}); + db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x10000000L | 0x800000L) +"", 10+""}); + + // Thread Updates + + db.execSQL("ALTER TABLE thread ADD COLUMN snippet_type INTEGER;"); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", + 0x80000000L+"", + SYMMETRIC_ENCRYPT + "%"}); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", + 0x40000000L+"", + ASYMMETRIC_LOCAL_ENCRYPT + "%"}); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", + (0x800000L | 0x20000000L)+"", + ASYMMETRIC_ENCRYPT + "%"}); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(KEY_EXCHANGE.length()+1)+"", + 0x8000L+"", + KEY_EXCHANGE + "%"}); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", + (0x8000L | 0x4000L)+"", + STALE_KEY_EXCHANGE + "%"}); + + db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + + "snippet_type = ? WHERE snippet LIKE ?", + new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", + (0x8000L | 0x2000L)+"", + PROCESSED_KEY_EXCHANGE + "%"}); + } + + if (oldVersion < INTRODUCED_MMS_BODY_VERSION) { + db.execSQL("ALTER TABLE mms ADD COLUMN body TEXT"); + db.execSQL("ALTER TABLE mms ADD COLUMN part_count INTEGER"); + } + + if (oldVersion < INTRODUCED_MMS_FROM_VERSION) { + db.execSQL("ALTER TABLE mms ADD COLUMN address TEXT"); + + Cursor cursor = db.query("mms_addresses", null, "type = ?", new String[] {0x89+""}, + null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long mmsId = cursor.getLong(cursor.getColumnIndexOrThrow("mms_id")); + String address = cursor.getString(cursor.getColumnIndexOrThrow("address")); + + if (!TextUtils.isEmpty(address)) { + db.execSQL("UPDATE mms SET address = ? WHERE _id = ?", new String[]{address, mmsId+""}); + } + } + + if (cursor != null) + cursor.close(); + } + + if (oldVersion < INTRODUCED_TOFU_IDENTITY_VERSION) { + db.execSQL("DROP TABLE identities"); + db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);"); + } + + if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { + db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); + db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); + db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);"); + } + + if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) { + db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER, active INTEGER DEFAULT 1);"); + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);"); + db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;"); + db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); + db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); + } + + if (oldVersion < INTRODUCED_PUSH_FIX_VERSION) { + db.execSQL("CREATE TEMPORARY table push_backup (_id INTEGER PRIMARY KEY, type INTEGER, source, TEXT, destinations TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); + db.execSQL("INSERT INTO push_backup(_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push;"); + db.execSQL("DROP TABLE push"); + db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); + db.execSQL("INSERT INTO push (_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push_backup;"); + db.execSQL("DROP TABLE push_backup;"); + } + + if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) { + db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); + db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); + db.execSQL("CREATE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);"); + db.execSQL("CREATE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);"); + } + + if (oldVersion < INTRODUCED_PART_DATA_SIZE_VERSION) { + db.execSQL("ALTER TABLE part ADD COLUMN data_size INTEGER DEFAULT 0;"); + } + + if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) { + db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT;"); + db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL;"); + } + + if (oldVersion < INTRODUCED_IDENTITY_COLUMN_VERSION) { + db.execSQL("ALTER TABLE sms ADD COLUMN mismatched_identities TEXT"); + db.execSQL("ALTER TABLE mms ADD COLUMN mismatched_identities TEXT"); + db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT"); + } + + if (oldVersion < INTRODUCED_UNIQUE_PART_IDS_VERSION) { + db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0"); + } + + if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) { + db.execSQL("CREATE TABLE recipient_preferences " + + "(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " + + "notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)"); + } + + if (oldVersion < INTRODUCED_ENVELOPE_CONTENT_VERSION) { + db.execSQL("ALTER TABLE push ADD COLUMN content TEXT"); + } + + if (oldVersion < INTRODUCED_COLOR_PREFERENCE_VERSION) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN color TEXT DEFAULT NULL"); + } + + if (oldVersion < INTRODUCED_DB_OPTIMIZATIONS_VERSION) { + db.execSQL("UPDATE mms SET date_received = (date_received * 1000), date = (date * 1000);"); + db.execSQL("CREATE INDEX IF NOT EXISTS sms_thread_date_index ON sms (thread_id, date);"); + db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);"); + } + + if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0"); + } + + if (oldVersion < INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL"); + } + + if (oldVersion < INTRODUCED_ARCHIVE_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0"); + db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)"); + } + + if (oldVersion < INTRODUCED_CONVERSATION_LIST_STATUS_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN status INTEGER DEFAULT -1"); + db.execSQL("ALTER TABLE thread ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0"); + } + + if (oldVersion < MIGRATED_CONVERSATION_LIST_STATUS_VERSION) { + Cursor threadCursor = db.query("thread", new String[] {"_id"}, null, null, null, null, null); + + while (threadCursor != null && threadCursor.moveToNext()) { + long threadId = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); + + Cursor cursor = db.rawQuery("SELECT DISTINCT date AS date_received, status, " + + "delivery_receipt_count FROM sms WHERE (thread_id = ?1) " + + "UNION ALL SELECT DISTINCT date_received, -1 AS status, " + + "delivery_receipt_count FROM mms WHERE (thread_id = ?1) " + + "ORDER BY date_received DESC LIMIT 1", new String[]{threadId + ""}); + + if (cursor != null && cursor.moveToNext()) { + int status = cursor.getInt(cursor.getColumnIndexOrThrow("status")); + int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow("delivery_receipt_count")); + + db.execSQL("UPDATE thread SET status = ?, delivery_receipt_count = ? WHERE _id = ?", + new String[]{status + "", receiptCount + "", threadId + ""}); + } + } + } + + if (oldVersion < INTRODUCED_SUBSCRIPTION_ID_VERSION) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN default_subscription_id INTEGER DEFAULT -1"); + db.execSQL("ALTER TABLE sms ADD COLUMN subscription_id INTEGER DEFAULT -1"); + db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1"); + } + + if (oldVersion < INTRODUCED_EXPIRE_MESSAGES_VERSION) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN expire_messages INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE sms ADD COLUMN expires_in INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE mms ADD COLUMN expires_in INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE sms ADD COLUMN expire_started INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE mms ADD COLUMN expire_started INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE thread ADD COLUMN expires_in INTEGER DEFAULT 0"); + } + + if (oldVersion < INTRODUCED_LAST_SEEN) { + db.execSQL("ALTER TABLE thread ADD COLUMN last_seen INTEGER DEFAULT 0"); + } + + if (oldVersion < INTRODUCED_DIGEST) { + db.execSQL("ALTER TABLE part ADD COLUMN digest BLOB"); + db.execSQL("ALTER TABLE groups ADD COLUMN avatar_digest BLOB"); + } + + if (oldVersion < INTRODUCED_NOTIFIED) { + db.execSQL("ALTER TABLE sms ADD COLUMN notified INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE mms ADD COLUMN notified INTEGER DEFAULT 0"); + + db.execSQL("DROP INDEX sms_read_and_thread_id_index"); + db.execSQL("CREATE INDEX IF NOT EXISTS sms_read_and_notified_and_thread_id_index ON sms(read,notified,thread_id)"); + + db.execSQL("DROP INDEX mms_read_and_thread_id_index"); + db.execSQL("CREATE INDEX IF NOT EXISTS mms_read_and_notified_and_thread_id_index ON mms(read,notified,thread_id)"); + } + + if (oldVersion < INTRODUCED_DOCUMENTS) { + db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT"); + } + + if (oldVersion < INTRODUCED_FAST_PREFLIGHT) { + db.execSQL("ALTER TABLE part ADD COLUMN fast_preflight_id TEXT"); + } + + if (oldVersion < INTRODUCED_VOICE_NOTES) { + db.execSQL("ALTER TABLE part ADD COLUMN voice_note INTEGER DEFAULT 0"); + } + + if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) { + db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE identities ADD COLUMN verified INTEGER DEFAULT 0"); + + db.execSQL("DROP INDEX archived_index"); + db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)"); + } + + if (oldVersion < SANIFY_ATTACHMENT_DOWNLOAD) { + db.execSQL("UPDATE part SET pending_push = '2' WHERE pending_push = '1'"); + } + + if (oldVersion < NO_MORE_CANONICAL_ADDRESS_DATABASE) { + SQLiteOpenHelper canonicalAddressDatabaseHelper = new SQLiteOpenHelper(context, "canonical_address.db", null, 1) { + @Override + public void onCreate(SQLiteDatabase db) { + throw new AssertionError("No canonical address DB?"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} + }; + + SQLiteDatabase canonicalAddressDatabase = canonicalAddressDatabaseHelper.getReadableDatabase(); + NumberMigrator numberMigrator = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)); + + // Migrate Thread Database + Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long threadId = cursor.getLong(0); + String recipientIdsList = cursor.getString(1); + String[] recipientIds = recipientIdsList.split(" "); + String[] addresses = new String[recipientIds.length]; + + for (int i=0;i newDocumentList = new LinkedList<>(); + + for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { + Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); + + if (resolved != null && resolved.moveToFirst()) { + String address = resolved.getString(0); + newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); + } else { + throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); + } + + if (resolved != null) resolved.close(); + } + + ContentValues values = new ContentValues(1); + values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); + db.update("sms", values, "_id = ?", new String[] {String.valueOf(id)}); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + + if (cursor != null) cursor.close(); + + // Migrate MMS mismatched identities + cursor = db.query("mms", new String[] {"_id", "mismatched_identities"}, "mismatched_identities IS NOT NULL", null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long id = cursor.getLong(0); + String document = cursor.getString(1); + + if (!TextUtils.isEmpty(document)) { + try { + PreCanonicalAddressIdentityMismatchList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressIdentityMismatchList.class); + List newDocumentList = new LinkedList<>(); + + for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { + Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); + + if (resolved != null && resolved.moveToFirst()) { + String address = resolved.getString(0); + newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); + } else { + throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); + } + + if (resolved != null) resolved.close(); + } + + ContentValues values = new ContentValues(1); + values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); + db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + + if (cursor != null) cursor.close(); + + // Migrate MMS network failures + cursor = db.query("mms", new String[] {"_id", "network_failures"}, "network_failures IS NOT NULL", null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long id = cursor.getLong(0); + String document = cursor.getString(1); + + if (!TextUtils.isEmpty(document)) { + try { + PreCanonicalAddressNetworkFailureList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressNetworkFailureList.class); + List newDocumentList = new LinkedList<>(); + + for (PreCanonicalAddressNetworkFailureDocument oldDocument : oldDocumentList.list) { + Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); + + if (resolved != null && resolved.moveToFirst()) { + String address = resolved.getString(0); + newDocumentList.add(new PostCanonicalAddressNetworkFailureDocument(numberMigrator.migrate(address))); + } else { + throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); + } + + if (resolved != null) resolved.close(); + } + + ContentValues values = new ContentValues(1); + values.put("network_failures", JsonUtils.toJson(new PostCanonicalAddressNetworkFailureList(newDocumentList))); + db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + + // Migrate sessions + File sessionsDirectory = new File(context.getFilesDir(), "sessions-v2"); + + if (sessionsDirectory.exists() && sessionsDirectory.isDirectory()) { + File[] sessions = sessionsDirectory.listFiles(); + + for (File session : sessions) { + try { + String[] sessionParts = session.getName().split("[.]"); + long recipientId = Long.parseLong(sessionParts[0]); + + int deviceId; + + if (sessionParts.length > 1) deviceId = Integer.parseInt(sessionParts[1]); + else deviceId = 1; + + Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(recipientId)}, null, null, null); + + if (resolved != null && resolved.moveToNext()) { + String address = resolved.getString(0); + File destination = new File(session.getParentFile(), address + (deviceId != 1 ? "." + deviceId : "")); + + if (!session.renameTo(destination)) { + Log.w(TAG, "Session rename failed: " + destination); + } + } + + if (resolved != null) resolved.close(); + } catch (NumberFormatException e) { + Log.w(TAG, e); + } + } + } + + } + + if (oldVersion < NO_MORE_RECIPIENTS_PLURAL) { + db.execSQL("ALTER TABLE groups ADD COLUMN mms INTEGER DEFAULT 0"); + + Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long threadId = cursor.getLong(0); + String addressListString = cursor.getString(1); + String[] addressList = DelimiterUtil.split(addressListString, ' '); + + if (addressList.length == 1) { + ContentValues contentValues = new ContentValues(); + contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); + db.update("thread", contentValues, "_id = ?", new String[] {String.valueOf(threadId)}); + } else { + byte[] groupId = new byte[16]; + List members = new LinkedList<>(); + + new SecureRandom().nextBytes(groupId); + + for (String address : addressList) { + members.add(DelimiterUtil.escape(DelimiterUtil.unescape(address, ' '), ',')); + } + + members.add(DelimiterUtil.escape(TextSecurePreferences.getLocalNumber(context), ',')); + + Collections.sort(members); + + String encodedGroupId = "__signal_mms_group__!" + Hex.toStringCondensed(groupId); + ContentValues groupValues = new ContentValues(); + ContentValues threadValues = new ContentValues(); + + groupValues.put("group_id", encodedGroupId); + groupValues.put("members", Util.join(members, ",")); + groupValues.put("mms", 1); + + threadValues.put("recipient_ids", encodedGroupId); + + db.insert("groups", null, groupValues); + db.update("thread", threadValues, "_id = ?", new String[] {String.valueOf(threadId)}); + db.update("recipient_preferences", threadValues, "recipient_ids = ?", new String[] {addressListString}); + } + } + + if (cursor != null) cursor.close(); + + cursor = db.query("recipient_preferences", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + long id = cursor.getLong(0); + String addressListString = cursor.getString(1); + String[] addressList = DelimiterUtil.split(addressListString, ' '); + + if (addressList.length == 1) { + ContentValues contentValues = new ContentValues(); + contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); + db.update("recipient_preferences", contentValues, "_id = ?", new String[] {String.valueOf(id)}); + } else { + Log.w(TAG, "Found preferences for MMS thread that appears to be gone: " + addressListString); + db.delete("recipient_preferences", "_id = ?", new String[] {String.valueOf(id)}); + } + } + + if (cursor != null) cursor.close(); + + cursor = db.rawQuery("SELECT mms._id, thread.recipient_ids FROM mms, thread WHERE mms.address IS NULL AND mms.thread_id = thread._id", null); + + while (cursor != null && cursor.moveToNext()) { + long id = cursor.getLong(0); + ContentValues contentValues = new ContentValues(1); + + contentValues.put("address", cursor.getString(1)); + db.update("mms", contentValues, "_id = ?", new String[] {String.valueOf(id)}); + } + + if (cursor != null) cursor.close(); + } + + if (oldVersion < INTERNAL_DIRECTORY) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN registered INTEGER DEFAULT 0"); + + OldDirectoryDatabaseHelper directoryDatabaseHelper = new OldDirectoryDatabaseHelper(context); + SQLiteDatabase directoryDatabase = directoryDatabaseHelper.getWritableDatabase(); + + Cursor cursor = directoryDatabase.query("directory", new String[] {"number", "registered"}, null, null, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + String address = new NumberMigrator(TextSecurePreferences.getLocalNumber(context)).migrate(cursor.getString(0)); + ContentValues contentValues = new ContentValues(1); + + contentValues.put("registered", cursor.getInt(1) == 1 ? 1 : 2); + + if (db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}) < 1) { + contentValues.put("recipient_ids", address); + db.insert("recipient_preferences", null, contentValues); + } + } + + if (cursor != null) cursor.close(); + } + + if (oldVersion < INTERNAL_SYSTEM_DISPLAY_NAME) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_display_name TEXT DEFAULT NULL"); + } + + if (oldVersion < PROFILES) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_key TEXT DEFAULT NULL"); + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_name TEXT DEFAULT NULL"); + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_avatar TEXT DEFAULT NULL"); + } + + if (oldVersion < PROFILE_SHARING_APPROVAL) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0"); + } + + if (oldVersion < UNSEEN_NUMBER_OFFER) { + db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0"); + } + + if (oldVersion < READ_RECEIPTS) { + db.execSQL("ALTER TABLE sms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE mms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + } + + if (oldVersion < GROUP_RECEIPT_TRACKING) { + db.execSQL("CREATE TABLE group_receipts (_id INTEGER PRIMARY KEY, mms_id INTEGER, address TEXT, status INTEGER, timestamp INTEGER)"); + db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)"); + } + + if (oldVersion < UNREAD_COUNT_VERSION) { + db.execSQL("ALTER TABLE thread ADD COLUMN unread_count INTEGER DEFAULT 0"); + + try (Cursor cursor = db.query("thread", new String[] {"_id"}, "read = 0", null, null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + long threadId = cursor.getLong(0); + int unreadCount = 0; + + try (Cursor smsCursor = db.rawQuery("SELECT COUNT(*) FROM sms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { + if (smsCursor != null && smsCursor.moveToFirst()) { + unreadCount += smsCursor.getInt(0); + } + } + + try (Cursor mmsCursor = db.rawQuery("SELECT COUNT(*) FROM mms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { + if (mmsCursor != null && mmsCursor.moveToFirst()) { + unreadCount += mmsCursor.getInt(0); + } + } + + db.execSQL("UPDATE thread SET unread_count = ? WHERE _id = ?", + new String[] {String.valueOf(unreadCount), + String.valueOf(threadId)}); + } + } + } + + if (oldVersion < MORE_RECIPIENT_FIELDS) { + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_photo TEXT DEFAULT NULL"); + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_phone_label TEXT DEFAULT NULL"); + db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_uri TEXT DEFAULT NULL"); + + if (Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { + try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"))); + + if (address.isPhone() && !TextUtils.isEmpty(address.toPhoneString())) { + Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString())); + + try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, + ContactsContract.PhoneLookup.LOOKUP_KEY, + ContactsContract.PhoneLookup._ID, + ContactsContract.PhoneLookup.NUMBER, + ContactsContract.PhoneLookup.LABEL, + ContactsContract.PhoneLookup.PHOTO_URI}, + null, null, null)) + { + if (contactCursor != null && contactCursor.moveToFirst()) { + ContentValues contentValues = new ContentValues(3); + contentValues.put("system_contact_photo", contactCursor.getString(5)); + contentValues.put("system_phone_label", contactCursor.getString(4)); + contentValues.put("system_contact_uri", ContactsContract.Contacts.getLookupUri(contactCursor.getLong(2), contactCursor.getString(1)).toString()); + + db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address.toPhoneString()}); + } + } + } + } + } + } + } + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void executeStatements(SQLiteDatabase db, String[] statements) { + for (String statement : statements) + db.execSQL(statement); + } + + private static class PreCanonicalAddressIdentityMismatchList { + @JsonProperty(value = "m") + private List list; + } + + private static class PostCanonicalAddressIdentityMismatchList { + @JsonProperty(value = "m") + private List list; + + public PostCanonicalAddressIdentityMismatchList(List list) { + this.list = list; + } + } + + private static class PreCanonicalAddressIdentityMismatchDocument { + @JsonProperty(value = "r") + private long recipientId; + + @JsonProperty(value = "k") + private String identityKey; + } + + private static class PostCanonicalAddressIdentityMismatchDocument { + @JsonProperty(value = "a") + private String address; + + @JsonProperty(value = "k") + private String identityKey; + + public PostCanonicalAddressIdentityMismatchDocument() {} + + public PostCanonicalAddressIdentityMismatchDocument(String address, String identityKey) { + this.address = address; + this.identityKey = identityKey; + } + } + + private static class PreCanonicalAddressNetworkFailureList { + @JsonProperty(value = "l") + private List list; + } + + private static class PostCanonicalAddressNetworkFailureList { + @JsonProperty(value = "l") + private List list; + + public PostCanonicalAddressNetworkFailureList(List list) { + this.list = list; + } + } + + private static class PreCanonicalAddressNetworkFailureDocument { + @JsonProperty(value = "r") + private long recipientId; + } + + private static class PostCanonicalAddressNetworkFailureDocument { + @JsonProperty(value = "a") + private String address; + + public PostCanonicalAddressNetworkFailureDocument() {} + + public PostCanonicalAddressNetworkFailureDocument(String address) { + this.address = address; + } + } + + private static class NumberMigrator { + + private static final String TAG = NumberMigrator.class.getSimpleName(); + + private static final Set SHORT_COUNTRIES = new HashSet() {{ + add("NU"); + add("TK"); + add("NC"); + add("AC"); + }}; + + private final Phonenumber.PhoneNumber localNumber; + private final String localNumberString; + private final String localCountryCode; + + private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); + + + public NumberMigrator(String localNumber) { + try { + this.localNumberString = localNumber; + this.localNumber = phoneNumberUtil.parse(localNumber, null); + this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(this.localNumber); + } catch (NumberParseException e) { + throw new AssertionError(e); + } + } + + public String migrate(@Nullable String number) { + if (number == null) return "Unknown"; + if (number.startsWith("__textsecure_group__!")) return number; + if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); + + String bareNumber = number.replaceAll("[^0-9+]", ""); + + if (bareNumber.length() == 0) { + if (TextUtils.isEmpty(number.trim())) return "Unknown"; + else return number.trim(); + } + + // libphonenumber doesn't seem to be correct for Germany and Finland + if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) { + return bareNumber; + } + + // libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes. + if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) { + return bareNumber; + } + + try { + Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode); + + if (ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode)) { + return bareNumber; + } + + return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); + } catch (NumberParseException e) { + Log.w(TAG, e); + if (bareNumber.charAt(0) == '+') + return bareNumber; + + String localNumberImprecise = localNumberString; + + if (localNumberImprecise.charAt(0) == '+') + localNumberImprecise = localNumberImprecise.substring(1); + + if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) + return "+" + number; + + int difference = localNumberImprecise.length() - bareNumber.length(); + + return "+" + localNumberImprecise.substring(0, difference) + bareNumber; + } + } + } + + private static class OldDirectoryDatabaseHelper extends SQLiteOpenHelper { + + private static final int INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER = 2; + private static final int INTRODUCED_VOICE_COLUMN = 4; + private static final int INTRODUCED_VIDEO_COLUMN = 5; + + private static final String DATABASE_NAME = "whisper_directory.db"; + private static final int DATABASE_VERSION = 5; + + private static final String TABLE_NAME = "directory"; + private static final String ID = "_id"; + private static final String NUMBER = "number"; + private static final String REGISTERED = "registered"; + private static final String RELAY = "relay"; + private static final String TIMESTAMP = "timestamp"; + private static final String VOICE = "voice"; + private static final String VIDEO = "video"; + + private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY, " + + NUMBER + " TEXT UNIQUE, " + + REGISTERED + " INTEGER, " + + RELAY + " TEXT, " + + TIMESTAMP + " INTEGER, " + + VOICE + " INTEGER, " + + VIDEO + " INTEGER);"; + + public OldDirectoryDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER) { + db.execSQL("DROP TABLE directory;"); + db.execSQL("CREATE TABLE directory ( _id INTEGER PRIMARY KEY, " + + "number TEXT UNIQUE, " + + "registered INTEGER, " + + "relay TEXT, " + + "supports_sms INTEGER, " + + "timestamp INTEGER);"); + } + + if (oldVersion < INTRODUCED_VOICE_COLUMN) { + db.execSQL("ALTER TABLE directory ADD COLUMN voice INTEGER;"); + } + + if (oldVersion < INTRODUCED_VIDEO_COLUMN) { + db.execSQL("ALTER TABLE directory ADD COLUMN video INTEGER;"); + } + } + } +} diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java new file mode 100644 index 000000000..6148393d7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java @@ -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 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 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 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 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 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); + } +} diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java new file mode 100644 index 000000000..0b5490ff3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -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); + } + + +} diff --git a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java index 8f30170fe..8962b89ad 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/BucketedThreadMediaLoader.java @@ -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 { + @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 members, @Nullable Bitmap avatar, @Nullable String name, @@ -55,7 +53,7 @@ public class GroupManager { if (!mms) { groupDatabase.updateAvatar(groupId, avatarBytes); DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true); - return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes); + return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes); } else { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); return new GroupActionResult(groupRecipient, threadId); @@ -63,7 +61,6 @@ public class GroupManager { } public static GroupActionResult updateGroup(@NonNull Context context, - @NonNull MasterSecret masterSecret, @NonNull String groupId, @NonNull Set members, @Nullable Bitmap avatar, @@ -80,7 +77,7 @@ public class GroupManager { groupDatabase.updateAvatar(groupId, avatarBytes); if (!GroupUtil.isMmsGroup(groupId)) { - return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes); + return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes); } else { Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); @@ -89,7 +86,6 @@ public class GroupManager { } private static GroupActionResult sendGroupUpdate(@NonNull Context context, - @NonNull MasterSecret masterSecret, @NonNull String groupId, @NonNull Set
members, @Nullable String groupName, @@ -119,7 +115,7 @@ public class GroupManager { } OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0); - long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false, null); + long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); return new GroupActionResult(groupRecipient, threadId); } catch (IOException e) { diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index 98042bef9..79176961c 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -9,13 +9,12 @@ import android.util.Log; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; import org.thoughtcrime.securesms.mms.MmsException; @@ -47,7 +46,6 @@ public class GroupMessageProcessor { private static final String TAG = GroupMessageProcessor.class.getSimpleName(); public static @Nullable Long process(@NonNull Context context, - @NonNull MasterSecretUnion masterSecret, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, boolean outgoing) @@ -63,11 +61,11 @@ public class GroupMessageProcessor { Optional record = database.getGroup(id); if (record.isPresent() && group.getType() == Type.UPDATE) { - return handleGroupUpdate(context, masterSecret, envelope, group, record.get(), outgoing); + return handleGroupUpdate(context, envelope, group, record.get(), outgoing); } else if (!record.isPresent() && group.getType() == Type.UPDATE) { - return handleGroupCreate(context, masterSecret, envelope, group, outgoing); + return handleGroupCreate(context, envelope, group, outgoing); } else if (record.isPresent() && group.getType() == Type.QUIT) { - return handleGroupLeave(context, masterSecret, envelope, group, record.get(), outgoing); + return handleGroupLeave(context, envelope, group, record.get(), outgoing); } else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) { return handleGroupInfoRequest(context, envelope, group, record.get()); } else { @@ -77,7 +75,6 @@ public class GroupMessageProcessor { } private static @Nullable Long handleGroupCreate(@NonNull Context context, - @NonNull MasterSecretUnion masterSecret, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceGroup group, boolean outgoing) @@ -100,11 +97,10 @@ public class GroupMessageProcessor { avatar != null && avatar.isPointer() ? avatar.asPointer() : null, envelope.getRelay()); - return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing); + return storeMessage(context, envelope, group, builder.build(), outgoing); } private static @Nullable Long handleGroupUpdate(@NonNull Context context, - @NonNull MasterSecretUnion masterSecret, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceGroup group, @NonNull GroupRecord groupRecord, @@ -159,7 +155,7 @@ public class GroupMessageProcessor { if (!groupRecord.isActive()) database.setActive(id, true); - return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing); + return storeMessage(context, envelope, group, builder.build(), outgoing); } private static Long handleGroupInfoRequest(@NonNull Context context, @@ -177,7 +173,6 @@ public class GroupMessageProcessor { } private static Long handleGroupLeave(@NonNull Context context, - @NonNull MasterSecretUnion masterSecret, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceGroup group, @NonNull GroupRecord record, @@ -194,7 +189,7 @@ public class GroupMessageProcessor { database.remove(id, Address.fromExternal(context, envelope.getSource())); if (outgoing) database.setActive(id, false); - return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing); + return storeMessage(context, envelope, group, builder.build(), outgoing); } return null; @@ -202,7 +197,6 @@ public class GroupMessageProcessor { private static @Nullable Long storeMessage(@NonNull Context context, - @NonNull MasterSecretUnion masterSecret, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceGroup group, @NonNull GroupContext storage, @@ -220,21 +214,21 @@ public class GroupMessageProcessor { Recipient recipient = Recipient.from(context, addres, false); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false, null); + long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); mmsDatabase.markAsSent(messageId, true); return threadId; } else { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0); - IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); + String body = Base64.encodeBytes(storage.toByteArray()); + IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0); + IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); - Optional insertResult = smsDatabase.insertMessageInbox(masterSecret, groupMessage); + Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); if (insertResult.isPresent()) { - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); return insertResult.get().getThreadId(); } else { return null; diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index a5c5f7331..4f66c695f 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -8,10 +8,7 @@ import android.util.Log; import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.AttachmentId; -import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.crypto.MediaKey; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.InjectableType; @@ -20,6 +17,7 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.util.AttachmentUtil; +import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobParameters; @@ -27,7 +25,6 @@ import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; @@ -72,7 +69,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable public void onRun(MasterSecret masterSecret) throws IOException { final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); - final Attachment attachment = database.getAttachment(masterSecret, attachmentId); + final Attachment attachment = database.getAttachment(attachmentId); if (attachment == null) { Log.w(TAG, "attachment no longer exists."); @@ -92,8 +89,8 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable Log.w(TAG, "Downloading push part " + attachmentId); database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED); - retrieveAttachment(masterSecret, messageId, attachmentId, attachment); - MessageNotifier.updateNotification(context, masterSecret); + retrieveAttachment(messageId, attachmentId, attachment); + MessageNotifier.updateNotification(context); } @Override @@ -107,8 +104,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable return (exception instanceof PushNetworkException); } - private void retrieveAttachment(MasterSecret masterSecret, - long messageId, + private void retrieveAttachment(long messageId, final AttachmentId attachmentId, final Attachment attachment) throws IOException @@ -120,26 +116,23 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable try { attachmentFile = createTempFile(); - SignalServiceAttachmentPointer pointer = createAttachmentPointer(masterSecret, attachment); - InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, new ProgressListener() { - @Override - public void onAttachmentProgress(long total, long progress) { - EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)); - } - }); + SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment); + InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress))); - database.insertAttachmentsForPlaceholder(masterSecret, messageId, attachmentId, stream); + database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream); } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) { Log.w(TAG, e); markFailed(messageId, attachmentId); } finally { - if (attachmentFile != null) + if (attachmentFile != null) { + //noinspection ResultOfMethodCallIgnored attachmentFile.delete(); + } } } @VisibleForTesting - SignalServiceAttachmentPointer createAttachmentPointer(MasterSecret masterSecret, Attachment attachment) + SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment) throws InvalidPartException { if (TextUtils.isEmpty(attachment.getLocation())) { @@ -151,10 +144,9 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable } try { - AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); - long id = Long.parseLong(attachment.getLocation()); - byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, attachment.getKey()); - String relay = null; + long id = Long.parseLong(attachment.getLocation()); + byte[] key = Base64.decode(attachment.getKey()); + String relay = null; if (TextUtils.isEmpty(attachment.getRelay())) { relay = attachment.getRelay(); @@ -173,7 +165,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable Optional.fromNullable(attachment.getDigest()), Optional.fromNullable(attachment.getFileName()), attachment.isVoiceNote()); - } catch (InvalidMessageException | IOException | ArithmeticException e) { + } catch (IOException | ArithmeticException e) { Log.w(TAG, e); throw new InvalidPartException(e); } @@ -200,8 +192,8 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable } @VisibleForTesting static class InvalidPartException extends Exception { - public InvalidPartException(String s) {super(s);} - public InvalidPartException(Exception e) {super(e);} + InvalidPartException(String s) {super(s);} + InvalidPartException(Exception e) {super(e);} } } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentFileNameJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentFileNameJob.java index 83325f893..ad91a1272 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentFileNameJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentFileNameJob.java @@ -48,7 +48,7 @@ public class AttachmentFileNameJob extends MasterSecretJob { AttachmentId attachmentId = new AttachmentId(attachmentRowId, attachmentUniqueId); String plaintextFileName = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret)).decryptBody(encryptedFileName); - DatabaseFactory.getAttachmentDatabase(context).updateAttachmentFileName(masterSecret, attachmentId, plaintextFileName); + DatabaseFactory.getAttachmentDatabase(context).updateAttachmentFileName(attachmentId, plaintextFileName); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java index 4f3bd29f5..98c58795c 100644 --- a/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java +++ b/src/org/thoughtcrime/securesms/jobs/DirectoryRefreshJob.java @@ -6,9 +6,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DirectoryHelper; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; @@ -19,15 +17,13 @@ import java.io.IOException; public class DirectoryRefreshJob extends ContextJob { @Nullable private transient Recipient recipient; - @Nullable private transient MasterSecret masterSecret; private transient boolean notifyOfNewUsers; public DirectoryRefreshJob(@NonNull Context context, boolean notifyOfNewUsers) { - this(context, null, null, notifyOfNewUsers); + this(context, null, notifyOfNewUsers); } public DirectoryRefreshJob(@NonNull Context context, - @Nullable MasterSecret masterSecret, @Nullable Recipient recipient, boolean notifyOfNewUsers) { @@ -37,7 +33,6 @@ public class DirectoryRefreshJob extends ContextJob { .create()); this.recipient = recipient; - this.masterSecret = masterSecret; this.notifyOfNewUsers = notifyOfNewUsers; } @@ -53,9 +48,9 @@ public class DirectoryRefreshJob extends ContextJob { try { wakeLock.acquire(); if (recipient == null) { - DirectoryHelper.refreshDirectory(context, KeyCachingService.getMasterSecret(context), notifyOfNewUsers); + DirectoryHelper.refreshDirectory(context, notifyOfNewUsers); } else { - DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipient); + DirectoryHelper.refreshDirectoryFor(context, recipient); } } finally { if (wakeLock.isHeld()) wakeLock.release(); diff --git a/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java deleted file mode 100644 index b126b1edf..000000000 --- a/src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; -import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; -import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.whispersystems.jobqueue.JobParameters; -import org.whispersystems.libsignal.InvalidMessageException; - -import java.io.IOException; - -public class MasterSecretDecryptJob extends MasterSecretJob { - - private static final long serialVersionUID = 1L; - private static final String TAG = MasterSecretDecryptJob.class.getSimpleName(); - - public MasterSecretDecryptJob(Context context) { - super(context, JobParameters.newBuilder() - .withRequirement(new MasterSecretRequirement(context)) - .create()); - } - - @Override - public void onRun(MasterSecret masterSecret) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsDatabase.Reader smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret); - - SmsMessageRecord smsRecord; - - while ((smsRecord = smsReader.getNext()) != null) { - try { - String body = getAsymmetricDecryptedBody(masterSecret, smsRecord.getBody().getBody()); - smsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), smsRecord.getId(), body); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - } - - MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - MmsDatabase.Reader mmsReader = mmsDatabase.getDecryptInProgressMessages(masterSecret); - - MessageRecord mmsRecord; - - while ((mmsRecord = mmsReader.getNext()) != null) { - try { - String body = getAsymmetricDecryptedBody(masterSecret, mmsRecord.getBody().getBody()); - mmsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), mmsRecord.getId(), body); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - } - - smsReader.close(); - mmsReader.close(); - - MessageNotifier.updateNotification(context, masterSecret); - } - - @Override - public boolean onShouldRetryThrowable(Exception exception) { - return false; - } - - @Override - public void onAdded() { - - } - - @Override - public void onCanceled() { - - } - - private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body) - throws InvalidMessageException - { - try { - AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret); - AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret); - - if (TextUtils.isEmpty(body)) return ""; - else return asymmetricMasterCipher.decryptBody(body); - - } catch (IOException e) { - throw new InvalidMessageException(e); - } - } - - -} diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index 01a85e345..4b6d20431 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -14,7 +14,6 @@ import com.google.android.mms.pdu_alt.RetrieveConf; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -50,6 +49,8 @@ import java.util.concurrent.TimeUnit; public class MmsDownloadJob extends MasterSecretJob { + private static final long serialVersionUID = 1L; + private static final String TAG = MmsDownloadJob.class.getSimpleName(); private final long messageId; @@ -74,7 +75,7 @@ public class MmsDownloadJob extends MasterSecretJob { public void onAdded() { if (automatic && KeyCachingService.getMasterSecret(context) == null) { DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId); - MessageNotifier.updateNotification(context, null); + MessageNotifier.updateNotification(context); } } @@ -120,19 +121,19 @@ public class MmsDownloadJob extends MasterSecretJob { throw new MmsException("RetrieveConf was null"); } - storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom()); + storeRetrievedMms(contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom()); } catch (ApnUnavailableException e) { Log.w(TAG, e); - handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE, + handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE, automatic); } catch (MmsException e) { Log.w(TAG, e); - handleDownloadError(masterSecret, messageId, threadId, + handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_HARD_FAILURE, automatic); } catch (MmsRadioException | IOException e) { Log.w(TAG, e); - handleDownloadError(masterSecret, messageId, threadId, + handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE, automatic); } catch (DuplicateMessageException e) { @@ -157,7 +158,7 @@ public class MmsDownloadJob extends MasterSecretJob { if (automatic) { database.markIncomingNotificationReceived(threadId); - MessageNotifier.updateNotification(context, null, threadId); + MessageNotifier.updateNotification(context, threadId); } } @@ -166,7 +167,7 @@ public class MmsDownloadJob extends MasterSecretJob { return false; } - private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation, + private void storeRetrievedMms(String contentLocation, long messageId, long threadId, RetrieveConf retrieved, int subscriptionId, @Nullable Address notificationFrom) throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException, @@ -229,17 +230,15 @@ public class MmsDownloadJob extends MasterSecretJob { } IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false); - Optional insertResult = database.insertMessageInbox(new MasterSecretUnion(masterSecret), - message, contentLocation, threadId); + Optional insertResult = database.insertMessageInbox(message, contentLocation, threadId); if (insertResult.isPresent()) { database.delete(messageId); - MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } - private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId, - int downloadStatus, boolean automatic) + private void handleDownloadError(long messageId, long threadId, int downloadStatus, boolean automatic) { MmsDatabase db = DatabaseFactory.getMmsDatabase(context); @@ -247,7 +246,7 @@ public class MmsDownloadJob extends MasterSecretJob { if (automatic) { db.markIncomingNotificationReceived(threadId); - MessageNotifier.updateNotification(context, masterSecret, threadId); + MessageNotifier.updateNotification(context, threadId); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 5a9dd0395..51bf70ae4 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -76,10 +76,10 @@ public class MmsSendJob extends SendJob { @Override public void onSend(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId); + OutgoingMediaMessage message = database.getOutgoingMessage(messageId); try { - SendReq pdu = constructSendPdu(masterSecret, message); + SendReq pdu = constructSendPdu(message); validateDestinations(message, pdu); @@ -168,14 +168,14 @@ public class MmsSendJob extends SendJob { } } - private SendReq constructSendPdu(MasterSecret masterSecret, OutgoingMediaMessage message) + private SendReq constructSendPdu(OutgoingMediaMessage message) throws UndeliverableMessageException { SendReq req = new SendReq(); String lineNumber = getMyNumber(context); Address destination = message.getRecipient().getAddress(); MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId()); - List scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments()); + List scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments()); if (!TextUtils.isEmpty(lineNumber)) { req.setFrom(new EncodedStringValue(lineNumber)); @@ -241,7 +241,7 @@ public class MmsSendJob extends SendJob { int index = fileName.lastIndexOf("."); String contentId = (index == -1) ? fileName : fileName.substring(0, index); part.setContentId(contentId.getBytes()); - part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri()))); + part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, attachment.getDataUri()))); body.addPart(part); size += getPartSize(part); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 60bbbf892..03cbf5749 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -11,15 +11,11 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; @@ -39,7 +35,6 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; @@ -67,7 +62,6 @@ import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; @@ -124,18 +118,11 @@ public class PushDecryptJob extends ContextJob { return; } - MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); PushDatabase database = DatabaseFactory.getPushDatabase(context); SignalServiceEnvelope envelope = database.get(messageId); - Optional optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : - Optional.absent(); + Optional optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent(); - MasterSecretUnion masterSecretUnion; - - if (masterSecret == null) masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null)); - else masterSecretUnion = new MasterSecretUnion(masterSecret); - - handleMessage(masterSecretUnion, envelope, optionalSmsMessageId); + handleMessage(envelope, optionalSmsMessageId); database.delete(messageId); } @@ -149,7 +136,7 @@ public class PushDecryptJob extends ContextJob { } - private void handleMessage(MasterSecretUnion masterSecret, SignalServiceEnvelope envelope, Optional smsMessageId) { + private void handleMessage(SignalServiceEnvelope envelope, Optional smsMessageId) { try { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); @@ -161,11 +148,11 @@ public class PushDecryptJob extends ContextJob { if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - if (message.isEndSession()) handleEndSessionMessage(masterSecret, envelope, message, smsMessageId); - else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId); - else if (message.isExpirationUpdate()) handleExpirationUpdate(masterSecret, envelope, message, smsMessageId); - else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId); - else if (message.getBody().isPresent()) handleTextMessage(masterSecret, envelope, message, smsMessageId); + if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId); + else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId); + else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId); + else if (message.getAttachments().isPresent()) handleMediaMessage(envelope, message, smsMessageId); + else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId); if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { handleUnknownGroupMessage(envelope, message.getGroupInfo().get()); @@ -177,10 +164,10 @@ public class PushDecryptJob extends ContextJob { } else if (content.getSyncMessage().isPresent()) { SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, envelope, syncMessage.getSent().get()); - else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get()); - else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(masterSecret, syncMessage.getRead().get(), envelope.getTimestamp()); - else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(masterSecret, syncMessage.getVerified().get()); + if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(envelope, syncMessage.getSent().get()); + else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get()); + else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), envelope.getTimestamp()); + else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else Log.w(TAG, "Contains no known sync types..."); } else if (content.getCallMessage().isPresent()) { Log.w(TAG, "Got call message..."); @@ -205,22 +192,22 @@ public class PushDecryptJob extends ContextJob { } } catch (InvalidVersionException e) { Log.w(TAG, e); - handleInvalidVersionMessage(masterSecret, envelope, smsMessageId); + handleInvalidVersionMessage(envelope, smsMessageId); } catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) { Log.w(TAG, e); - handleCorruptMessage(masterSecret, envelope, smsMessageId); + handleCorruptMessage(envelope, smsMessageId); } catch (NoSessionException e) { Log.w(TAG, e); - handleNoSessionMessage(masterSecret, envelope, smsMessageId); + handleNoSessionMessage(envelope, smsMessageId); } catch (LegacyMessageException e) { Log.w(TAG, e); - handleLegacyMessage(masterSecret, envelope, smsMessageId); + handleLegacyMessage(envelope, smsMessageId); } catch (DuplicateMessageException e) { Log.w(TAG, e); - handleDuplicateMessage(masterSecret, envelope, smsMessageId); + handleDuplicateMessage(envelope, smsMessageId); } catch (UntrustedIdentityException e) { Log.w(TAG, e); - handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId); + handleUntrustedIdentityMessage(envelope, smsMessageId); } } @@ -304,22 +291,21 @@ public class PushDecryptJob extends ContextJob { context.startService(intent); } - private void handleEndSessionMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleEndSessionMessage(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), - envelope.getSourceDevice(), - message.getTimestamp(), - "", Optional.absent(), 0); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), + envelope.getSourceDevice(), + message.getTimestamp(), + "", Optional.absent(), 0); Long threadId; if (!smsMessageId.isPresent()) { IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); - Optional insertResult = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage); + Optional insertResult = smsDatabase.insertMessageInbox(incomingEndSessionMessage); if (insertResult.isPresent()) threadId = insertResult.get().getThreadId(); else threadId = null; @@ -333,14 +319,13 @@ public class PushDecryptJob extends ContextJob { sessionStore.deleteAllSessions(envelope.getSource()); SecurityEvent.broadcastSecurityUpdateEvent(context); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId); + MessageNotifier.updateNotification(context, threadId); } } - private long handleSynchronizeSentEndSessionMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SentTranscriptMessage message) + private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); Recipient recipient = getSyncMessageDestination(message); OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipient, "", -1); OutgoingEndSessionMessage outgoingEndSessionMessage = new OutgoingEndSessionMessage(outgoingTextMessage); @@ -353,20 +338,20 @@ public class PushDecryptJob extends ContextJob { SecurityEvent.broadcastSecurityUpdateEvent(context); - long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingEndSessionMessage, - false, message.getTimestamp(), null); + long messageId = database.insertMessageOutbox(threadId, outgoingEndSessionMessage, + false, message.getTimestamp(), + null); database.markAsSent(messageId, true); } return threadId; } - private void handleGroupMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleGroupMessage(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) { - GroupMessageProcessor.process(context, masterSecret, envelope, message, false); + GroupMessageProcessor.process(context, envelope, message, false); if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); @@ -381,25 +366,23 @@ public class PushDecryptJob extends ContextJob { .add(new RequestGroupInfoJob(context, envelope.getSource(), group.getGroupId())); } - private void handleExpirationUpdate(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleExpirationUpdate(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); Recipient recipient = getMessageDestination(envelope, message); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, - Address.fromExternal(context, envelope.getSource()), + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000, true, Optional.fromNullable(envelope.getRelay()), - Optional.absent(), message.getGroupInfo(), - Optional.>absent()); + Optional.absent(), message.getGroupInfo(), + Optional.absent()); - database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + database.insertSecureDecryptedMessageInbox(mediaMessage, -1); DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getExpiresInSeconds()); @@ -408,14 +391,11 @@ public class PushDecryptJob extends ContextJob { } } - private void handleSynchronizeVerifiedMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull VerifiedMessage verifiedMessage) - { - IdentityUtil.processVerifiedMessage(context, masterSecret, verifiedMessage); + private void handleSynchronizeVerifiedMessage(@NonNull VerifiedMessage verifiedMessage) { + IdentityUtil.processVerifiedMessage(context, verifiedMessage); } - private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleSynchronizeSentMessage(@NonNull SignalServiceEnvelope envelope, @NonNull SentTranscriptMessage message) throws MmsException { @@ -424,15 +404,15 @@ public class PushDecryptJob extends ContextJob { Long threadId; if (message.getMessage().isEndSession()) { - threadId = handleSynchronizeSentEndSessionMessage(masterSecret, message); + threadId = handleSynchronizeSentEndSessionMessage(message); } else if (message.getMessage().isGroupUpdate()) { - threadId = GroupMessageProcessor.process(context, masterSecret, envelope, message.getMessage(), true); + threadId = GroupMessageProcessor.process(context, envelope, message.getMessage(), true); } else if (message.getMessage().isExpirationUpdate()) { - threadId = handleSynchronizeSentExpirationUpdate(masterSecret, message); + threadId = handleSynchronizeSentExpirationUpdate(message); } else if (message.getMessage().getAttachments().isPresent()) { - threadId = handleSynchronizeSentMediaMessage(masterSecret, message); + threadId = handleSynchronizeSentMediaMessage(message); } else { - threadId = handleSynchronizeSentTextMessage(masterSecret, message); + threadId = handleSynchronizeSentTextMessage(message); } if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) { @@ -453,14 +433,13 @@ public class PushDecryptJob extends ContextJob { if (threadId != null) { DatabaseFactory.getThreadDatabase(getContext()).setRead(threadId, true); - MessageNotifier.updateNotification(getContext(), masterSecret.getMasterSecret().orNull()); + MessageNotifier.updateNotification(getContext()); } MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); } - private void handleSynchronizeRequestMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull RequestMessage message) + private void handleSynchronizeRequestMessage(@NonNull RequestMessage message) { if (message.isContactsRequest()) { ApplicationContext.getInstance(context) @@ -487,9 +466,7 @@ public class PushDecryptJob extends ContextJob { } } - private void handleSynchronizeReadMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull List readMessages, - long envelopeTimestamp) + private void handleSynchronizeReadMessage(@NonNull List readMessages, long envelopeTimestamp) { for (ReadMessage readMessage : readMessages) { List> expiringText = DatabaseFactory.getSmsDatabase(context).setTimestampRead(new SyncMessageId(Address.fromExternal(context, readMessage.getSender()), readMessage.getTimestamp()), envelopeTimestamp); @@ -510,19 +487,17 @@ public class PushDecryptJob extends ContextJob { MessageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp); MessageNotifier.cancelDelayedNotifications(); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull()); + MessageNotifier.updateNotification(context); } - private void handleMediaMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleMediaMessage(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); Recipient recipient = getMessageDestination(envelope, message); - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, - Address.fromExternal(context, envelope.getSource()), + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000, false, Optional.fromNullable(envelope.getRelay()), @@ -531,36 +506,29 @@ public class PushDecryptJob extends ContextJob { message.getAttachments()); if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { - handleExpirationUpdate(masterSecret, envelope, message, Optional.absent()); + handleExpirationUpdate(envelope, message, Optional.absent()); } - Optional insertResult = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); + Optional insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); if (insertResult.isPresent()) { - List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(null, insertResult.get().getMessageId()); + List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId()); for (DatabaseAttachment attachment : attachments) { ApplicationContext.getInstance(context) .getJobManager() .add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); - - if (!masterSecret.getMasterSecret().isPresent()) { - ApplicationContext.getInstance(context) - .getJobManager() - .add(new AttachmentFileNameJob(context, masterSecret.getAsymmetricMasterSecret().get(), attachment, mediaMessage)); - } } if (smsMessageId.isPresent()) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); } - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } - private long handleSynchronizeSentExpirationUpdate(@NonNull MasterSecretUnion masterSecret, - @NonNull SentTranscriptMessage message) + private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); @@ -571,7 +539,7 @@ public class PushDecryptJob extends ContextJob { message.getMessage().getExpiresInSeconds() * 1000); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); - long messageId = database.insertMessageOutbox(masterSecret, expirationUpdateMessage, threadId, false, null); + long messageId = database.insertMessageOutbox(expirationUpdateMessage, threadId, false, null); database.markAsSent(messageId, true); @@ -580,14 +548,13 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private long handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SentTranscriptMessage message) + private long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message) throws MmsException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); Recipient recipients = getSyncMessageDestination(message); OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(), - PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()), + PointerAttachment.forPointers(message.getMessage().getAttachments()), message.getTimestamp(), -1, message.getMessage().getExpiresInSeconds() * 1000, ThreadDatabase.DistributionTypes.DEFAULT); @@ -595,15 +562,15 @@ public class PushDecryptJob extends ContextJob { mediaMessage = new OutgoingSecureMediaMessage(mediaMessage); if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) { - handleSynchronizeSentExpirationUpdate(masterSecret, message); + handleSynchronizeSentExpirationUpdate(message); } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false, null); + long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null); database.markAsSent(messageId, true); - for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(null, messageId)) { + for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId)) { ApplicationContext.getInstance(context) .getJobManager() .add(new AttachmentDownloadJob(context, messageId, attachment.getAttachmentId(), false)); @@ -621,24 +588,23 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private void handleTextMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleTextMessage(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceDataMessage message, @NonNull Optional smsMessageId) throws MmsException { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - String body = message.getBody().isPresent() ? message.getBody().get() : ""; - Recipient recipient = getMessageDestination(envelope, message); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + String body = message.getBody().isPresent() ? message.getBody().get() : ""; + Recipient recipient = getMessageDestination(envelope, message); if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { - handleExpirationUpdate(masterSecret, envelope, message, Optional.absent()); + handleExpirationUpdate(envelope, message, Optional.absent()); } Long threadId; if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { - threadId = database.updateBundleMessageBody(masterSecret, smsMessageId.get(), body).second; + threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; } else { IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), @@ -647,7 +613,7 @@ public class PushDecryptJob extends ContextJob { message.getExpiresInSeconds() * 1000); textMessage = new IncomingEncryptedMessage(textMessage, body); - Optional insertResult = database.insertMessageInbox(masterSecret, textMessage); + Optional insertResult = database.insertMessageInbox(textMessage); if (insertResult.isPresent()) threadId = insertResult.get().getThreadId(); else threadId = null; @@ -656,12 +622,11 @@ public class PushDecryptJob extends ContextJob { } if (threadId != null) { - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId); + MessageNotifier.updateNotification(context, threadId); } } - private long handleSynchronizeSentTextMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SentTranscriptMessage message) + private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message) throws MmsException { @@ -670,7 +635,7 @@ public class PushDecryptJob extends ContextJob { long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000; if (recipient.getExpireMessages() != message.getMessage().getExpiresInSeconds()) { - handleSynchronizeSentExpirationUpdate(masterSecret, message); + handleSynchronizeSentExpirationUpdate(message); } long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); @@ -682,12 +647,12 @@ public class PushDecryptJob extends ContextJob { OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT); outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage); - messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(masterSecret, outgoingMediaMessage, threadId, false, null); + messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null); database = DatabaseFactory.getMmsDatabase(context); } else { OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis); - messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp(), null); + messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null); database = DatabaseFactory.getSmsDatabase(context); } @@ -703,80 +668,76 @@ public class PushDecryptJob extends ContextJob { return threadId; } - private void handleInvalidVersionMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleInvalidVersionMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { Optional insertResult = insertPlaceholder(envelope); if (insertResult.isPresent()) { smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId()); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else { smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get()); } } - private void handleCorruptMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleCorruptMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { Optional insertResult = insertPlaceholder(envelope); if (insertResult.isPresent()) { smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else { smsDatabase.markAsDecryptFailed(smsMessageId.get()); } } - private void handleNoSessionMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleNoSessionMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { Optional insertResult = insertPlaceholder(envelope); if (insertResult.isPresent()) { smsDatabase.markAsNoSession(insertResult.get().getMessageId()); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else { smsDatabase.markAsNoSession(smsMessageId.get()); } } - private void handleLegacyMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleLegacyMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { Optional insertResult = insertPlaceholder(envelope); if (insertResult.isPresent()) { smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId()); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else { smsDatabase.markAsLegacyVersion(smsMessageId.get()); } } - private void handleDuplicateMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + @SuppressWarnings("unused") + private void handleDuplicateMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { // Let's start ignoring these now @@ -791,33 +752,32 @@ public class PushDecryptJob extends ContextJob { // } } - private void handleUntrustedIdentityMessage(@NonNull MasterSecretUnion masterSecret, - @NonNull SignalServiceEnvelope envelope, + private void handleUntrustedIdentityMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional smsMessageId) { try { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - Address sourceAddress = Address.fromExternal(context, envelope.getSource()); - byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent(); - PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized); - IdentityKey identityKey = whisperMessage.getIdentityKey(); - String encoded = Base64.encodeBytes(serialized); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + Address sourceAddress = Address.fromExternal(context, envelope.getSource()); + byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent(); + PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized); + IdentityKey identityKey = whisperMessage.getIdentityKey(); + String encoded = Base64.encodeBytes(serialized); IncomingTextMessage textMessage = new IncomingTextMessage(sourceAddress, envelope.getSourceDevice(), envelope.getTimestamp(), encoded, - Optional.absent(), 0); + Optional.absent(), 0); if (!smsMessageId.isPresent()) { IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded, envelope.hasLegacyMessage()); - Optional insertResult = database.insertMessageInbox(masterSecret, bundleMessage); + Optional insertResult = database.insertMessageInbox(bundleMessage); if (insertResult.isPresent()) { database.setMismatchedIdentity(insertResult.get().getMessageId(), sourceAddress, identityKey); - MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else { - database.updateMessageBody(masterSecret, smsMessageId.get(), encoded); + database.updateMessageBody(smsMessageId.get(), encoded); database.markAsPreKeyBundle(smsMessageId.get()); database.setMismatchedIdentity(smsMessageId.get(), sourceAddress, identityKey); } @@ -863,11 +823,11 @@ public class PushDecryptJob extends ContextJob { } private Optional insertPlaceholder(@NonNull SignalServiceEnvelope envelope) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), - envelope.getSourceDevice(), - envelope.getTimestamp(), "", - Optional.absent(), 0); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), + envelope.getSourceDevice(), + envelope.getTimestamp(), "", + Optional.absent(), 0); textMessage = new IncomingEncryptedMessage(textMessage, ""); return database.insertMessageInbox(textMessage); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 2a0926b2f..bb708d07e 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; @@ -82,10 +81,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { throws MmsException, IOException, NoSuchMessageException { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId); + OutgoingMediaMessage message = database.getOutgoingMessage(messageId); try { - deliver(masterSecret, message, filterAddress == null ? null : Address.fromSerialized(filterAddress)); + deliver(message, filterAddress == null ? null : Address.fromSerialized(filterAddress)); database.markAsSent(messageId, true); markAttachmentsUploaded(messageId, message.getAttachments()); @@ -135,7 +134,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); } - private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message, @Nullable Address filterAddress) + private void deliver(OutgoingMediaMessage message, @Nullable Address filterAddress) throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions, UndeliverableMessageException { @@ -143,8 +142,8 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { Optional profileKey = getProfileKey(message.getRecipient()); List
recipients = getGroupMessageRecipients(groupId, messageId); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); - List scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments()); - List attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments); + List scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments()); + List attachmentStreams = getAttachmentsFor(scaledAttachments); List addresses; diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 1eb3e1cb8..e2838d4ad 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -59,10 +59,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { { ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId); + OutgoingMediaMessage message = database.getOutgoingMessage(messageId); try { - deliver(masterSecret, message); + deliver(message); database.markAsSent(messageId, true); markAttachmentsUploaded(messageId, message.getAttachments()); @@ -97,7 +97,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { notifyMediaMessageDeliveryFailed(context, messageId); } - private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message) + private void deliver(OutgoingMediaMessage message) throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, UndeliverableMessageException { @@ -108,8 +108,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { try { SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress()); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); - List scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments()); - List attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments); + List scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments()); + List attachmentStreams = getAttachmentsFor(scaledAttachments); Optional profileKey = getProfileKey(message.getRecipient()); SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder() .withBody(message.getBody()) diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index d25f4c4e0..3e47b95a9 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; @@ -29,7 +28,7 @@ public abstract class PushReceivedJob extends ContextJob { if (!isActiveNumber(recipient)) { DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); - ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, KeyCachingService.getMasterSecret(context), recipient, false)); + ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false)); } if (envelope.isReceipt()) { diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index 6e8605eff..832b82319 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -80,13 +80,13 @@ public abstract class PushSendJob extends SendJob { return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay)); } - protected List getAttachmentsFor(MasterSecret masterSecret, List parts) { + protected List getAttachmentsFor(List parts) { List attachments = new LinkedList<>(); for (final Attachment attachment : parts) { try { if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!"); - InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri()); + InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri()); attachments.add(SignalServiceAttachment.newStreamBuilder() .withStream(is) .withContentType(attachment.getContentType()) diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index be5936a62..511197579 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -7,8 +7,8 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -48,8 +48,8 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { @Override public void onPushSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException { ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsMessageRecord record = database.getMessage(masterSecret, messageId); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + SmsMessageRecord record = database.getMessage(messageId); try { Log.w(TAG, "Sending message: " + messageId); diff --git a/src/org/thoughtcrime/securesms/jobs/SendJob.java b/src/org/thoughtcrime/securesms/jobs/SendJob.java index 795d4ca12..f07300e46 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendJob.java @@ -22,6 +22,7 @@ import java.util.List; public abstract class SendJob extends MasterSecretJob { + @SuppressWarnings("unused") private final static String TAG = SendJob.class.getSimpleName(); public SendJob(Context context, JobParameters parameters) { @@ -49,8 +50,7 @@ public abstract class SendJob extends MasterSecretJob { } } - protected List scaleAttachments(@NonNull MasterSecret masterSecret, - @NonNull MediaConstraints constraints, + protected List scaleAttachments(@NonNull MediaConstraints constraints, @NonNull List attachments) throws UndeliverableMessageException { @@ -59,11 +59,11 @@ public abstract class SendJob extends MasterSecretJob { for (Attachment attachment : attachments) { try { - if (constraints.isSatisfied(context, masterSecret, attachment)) { + if (constraints.isSatisfied(context, attachment)) { results.add(attachment); } else if (constraints.canResize(attachment)) { - MediaStream resized = constraints.getResizedMedia(context, masterSecret, attachment); - results.add(attachmentDatabase.updateAttachmentData(masterSecret, attachment, resized)); + MediaStream resized = constraints.getResizedMedia(context, attachment); + results.add(attachmentDatabase.updateAttachmentData(attachment, resized)); } else { throw new UndeliverableMessageException("Size constraints could not be met!"); } diff --git a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java index 4fdf22ecb..3c109cb40 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java @@ -6,15 +6,11 @@ import android.support.annotation.Nullable; import android.telephony.SmsMessage; import android.util.Log; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libsignal.util.guava.Optional; @@ -48,22 +44,13 @@ public class SmsReceiveJob extends ContextJob { public void onRun() { Log.w(TAG, "onRun()"); - Optional message = assembleMessageFragments(pdus, subscriptionId); - MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); - - MasterSecretUnion masterSecretUnion; - - if (masterSecret == null) { - masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null)); - } else { - masterSecretUnion = new MasterSecretUnion(masterSecret); - } + Optional message = assembleMessageFragments(pdus, subscriptionId); if (message.isPresent() && !isBlocked(message.get())) { - Optional insertResult = storeMessage(masterSecretUnion, message.get()); + Optional insertResult = storeMessage(message.get()); if (insertResult.isPresent()) { - MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); } } else if (message.isPresent()) { Log.w(TAG, "*** Received blocked SMS, ignoring..."); @@ -91,8 +78,8 @@ public class SmsReceiveJob extends ContextJob { return false; } - private Optional storeMessage(MasterSecretUnion masterSecret, IncomingTextMessage message) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + private Optional storeMessage(IncomingTextMessage message) { + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); if (message.isSecureMessage()) { IncomingTextMessage placeholder = new IncomingTextMessage(message, ""); @@ -101,7 +88,7 @@ public class SmsReceiveJob extends ContextJob { return insertResult; } else { - return database.insertMessageInbox(masterSecret, message); + return database.insertMessageInbox(message); } } diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java index 7d2966f49..e0d982e61 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java @@ -11,8 +11,8 @@ import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.NetworkOrServiceRequirement; @@ -43,8 +43,8 @@ public class SmsSendJob extends SendJob { @Override public void onSend(MasterSecret masterSecret) throws NoSuchMessageException { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsMessageRecord record = database.getMessage(masterSecret, messageId); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + SmsMessageRecord record = database.getMessage(messageId); try { Log.w(TAG, "Sending message: " + messageId); diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java index f7067335c..91f63267c 100644 --- a/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SmsSentJob.java @@ -8,8 +8,8 @@ import android.util.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -46,7 +46,7 @@ public class SmsSentJob extends MasterSecretJob { switch (action) { case SmsDeliveryListener.SENT_SMS_ACTION: - handleSentResult(masterSecret, messageId, result); + handleSentResult(messageId, result); break; case SmsDeliveryListener.DELIVERED_SMS_ACTION: handleDeliveredResult(messageId, result); @@ -65,13 +65,13 @@ public class SmsSentJob extends MasterSecretJob { } private void handleDeliveredResult(long messageId, int result) { - DatabaseFactory.getEncryptingSmsDatabase(context).markStatus(messageId, result); + DatabaseFactory.getSmsDatabase(context).markStatus(messageId, result); } - private void handleSentResult(MasterSecret masterSecret, long messageId, int result) { + private void handleSentResult(long messageId, int result) { try { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - SmsMessageRecord record = database.getMessage(masterSecret, messageId); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + SmsMessageRecord record = database.getMessage(messageId); switch (result) { case Activity.RESULT_OK: diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 432c0609e..1b3e11621 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -49,7 +49,6 @@ import org.thoughtcrime.securesms.components.RemovableEditableMediaView; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.location.SignalMapView; import org.thoughtcrime.securesms.components.location.SignalPlace; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; @@ -158,7 +157,7 @@ public class AttachmentManager { private void cleanup(final @Nullable Uri uri) { if (uri != null && PersistentBlobProvider.isAuthority(context, uri)) { Log.w(TAG, "cleaning up " + uri); - PersistentBlobProvider.getInstance(context).delete(uri); + PersistentBlobProvider.getInstance(context).delete(context, uri); } } @@ -177,8 +176,7 @@ public class AttachmentManager { this.slide = Optional.of(slide); } - public void setLocation(@NonNull final MasterSecret masterSecret, - @NonNull final SignalPlace place, + public void setLocation(@NonNull final SignalPlace place, @NonNull final MediaConstraints constraints) { inflateStub(); @@ -193,7 +191,7 @@ public class AttachmentManager { public void onSuccess(@NonNull Bitmap result) { byte[] blob = BitmapUtil.toByteArray(result); Uri uri = PersistentBlobProvider.getInstance(context) - .create(masterSecret, blob, MediaUtil.IMAGE_PNG, null); + .create(context, blob, MediaUtil.IMAGE_PNG, null); LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place); setSlide(locationSlide); @@ -203,8 +201,7 @@ public class AttachmentManager { } @SuppressLint("StaticFieldLeak") - public void setMedia(@NonNull final MasterSecret masterSecret, - @NonNull final GlideRequests glideRequests, + public void setMedia(@NonNull final GlideRequests glideRequests, @NonNull final Uri uri, @NonNull final MediaType mediaType, @NonNull final MediaConstraints constraints) @@ -243,7 +240,7 @@ public class AttachmentManager { Toast.makeText(context, R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment, Toast.LENGTH_SHORT).show(); - } else if (!areConstraintsSatisfied(context, masterSecret, slide, constraints)) { + } else if (!areConstraintsSatisfied(context, slide, constraints)) { attachmentViewStub.get().setVisibility(View.GONE); Toast.makeText(context, R.string.ConversationActivity_attachment_exceeds_size_limits, @@ -253,13 +250,13 @@ public class AttachmentManager { attachmentViewStub.get().setVisibility(View.VISIBLE); if (slide.hasAudio()) { - audioView.setAudio(masterSecret, (AudioSlide) slide, false); + audioView.setAudio((AudioSlide) slide, false); removableMediaView.display(audioView, false); } else if (slide.hasDocument()) { documentView.setDocument((DocumentSlide) slide, false); removableMediaView.display(documentView, false); } else { - thumbnail.setImageResource(masterSecret, glideRequests, slide, false, true); + thumbnail.setImageResource(glideRequests, slide, false, true); removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE); } @@ -296,13 +293,13 @@ public class AttachmentManager { String mimeType = null; if (PartAuthority.isLocalUri(uri)) { - mediaSize = PartAuthority.getAttachmentSize(context, masterSecret, uri); - fileName = PartAuthority.getAttachmentFileName(context, masterSecret, uri); - mimeType = PartAuthority.getAttachmentContentType(context, masterSecret, uri); + mediaSize = PartAuthority.getAttachmentSize(context, uri); + fileName = PartAuthority.getAttachmentFileName(context, uri); + mimeType = PartAuthority.getAttachmentContentType(context, uri); } if (mediaSize == null) { - mediaSize = MediaUtil.getMediaSize(context, masterSecret, uri); + mediaSize = MediaUtil.getMediaSize(context, uri); } Log.w(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms"); @@ -399,8 +396,7 @@ public class AttachmentManager { Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (captureIntent.resolveActivity(activity.getPackageManager()) != null) { if (captureUri == null) { - captureUri = PersistentBlobProvider.getInstance(context) - .createForExternal(MediaUtil.IMAGE_JPEG); + captureUri = PersistentBlobProvider.getInstance(context).createForExternal(context, MediaUtil.IMAGE_JPEG); } Log.w(TAG, "captureUri path is " + captureUri.getPath()); captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri); @@ -442,12 +438,11 @@ public class AttachmentManager { } private boolean areConstraintsSatisfied(final @NonNull Context context, - final @NonNull MasterSecret masterSecret, final @Nullable Slide slide, final @NonNull MediaConstraints constraints) { - return slide == null || - constraints.isSatisfied(context, masterSecret, slide.asAttachment()) || + return slide == null || + constraints.isSatisfied(context, slide.asAttachment()) || constraints.canResize(slide.asAttachment()); } diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java index e49d075ff..faa131b8f 100644 --- a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java +++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java @@ -21,13 +21,11 @@ class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher { private static final String TAG = DecryptableStreamLocalUriFetcher.class.getSimpleName(); - private Context context; - private MasterSecret masterSecret; + private Context context; - DecryptableStreamLocalUriFetcher(Context context, MasterSecret masterSecret, Uri uri) { + DecryptableStreamLocalUriFetcher(Context context, Uri uri) { super(context.getContentResolver(), uri); this.context = context; - this.masterSecret = masterSecret; } @Override @@ -43,7 +41,7 @@ class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher { } try { - return PartAuthority.getAttachmentStream(context, masterSecret, uri); + return PartAuthority.getAttachmentStream(context, uri); } catch (IOException ioe) { Log.w(TAG, ioe); throw new FileNotFoundException("PartAuthority couldn't load Uri resource."); diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java index 2f67e3653..37883c9f5 100644 --- a/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java +++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java @@ -11,7 +11,6 @@ import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.MultiModelLoaderFactory; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import java.io.InputStream; @@ -27,12 +26,12 @@ public class DecryptableStreamUriLoader implements ModelLoader buildLoadData(DecryptableUri decryptableUri, int width, int height, Options options) { - return new LoadData<>(decryptableUri, new DecryptableStreamLocalUriFetcher(context, decryptableUri.masterSecret, decryptableUri.uri)); + public LoadData buildLoadData(@NonNull DecryptableUri decryptableUri, int width, int height, @NonNull Options options) { + return new LoadData<>(decryptableUri, new DecryptableStreamLocalUriFetcher(context, decryptableUri.uri)); } @Override - public boolean handles(DecryptableUri decryptableUri) { + public boolean handles(@NonNull DecryptableUri decryptableUri) { return true; } @@ -45,7 +44,7 @@ public class DecryptableStreamUriLoader implements ModelLoader build(MultiModelLoaderFactory multiFactory) { + public @NonNull ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { return new DecryptableStreamUriLoader(context); } @@ -56,16 +55,14 @@ public class DecryptableStreamUriLoader implements ModelLoader dimensions = BitmapUtil.getDimensions(is); return dimensions.first > 0 && dimensions.first <= getImageMaxWidth(context) && dimensions.second > 0 && dimensions.second <= getImageMaxHeight(context); @@ -66,9 +66,7 @@ public abstract class MediaConstraints { return attachment != null && MediaUtil.isImage(attachment) && !MediaUtil.isGif(attachment); } - public MediaStream getResizedMedia(@NonNull Context context, - @NonNull MasterSecret masterSecret, - @NonNull Attachment attachment) + public MediaStream getResizedMedia(@NonNull Context context, @NonNull Attachment attachment) throws IOException { if (!canResize(attachment)) { @@ -77,7 +75,7 @@ public abstract class MediaConstraints { try { // XXX - This is loading everything into memory! We want the send path to be stream-like. - return new MediaStream(new ByteArrayInputStream(BitmapUtil.createScaledBytes(context, new DecryptableUri(masterSecret, attachment.getDataUri()), this)), + return new MediaStream(new ByteArrayInputStream(BitmapUtil.createScaledBytes(context, new DecryptableUri(attachment.getDataUri()), this)), MediaUtil.IMAGE_JPEG); } catch (BitmapDecodingException e) { throw new IOException(e); diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java index 3fa2391ae..9289b2a36 100644 --- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java +++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java @@ -41,15 +41,15 @@ public class PartAuthority { uriMatcher.addURI(SingleUseBlobProvider.AUTHORITY, SingleUseBlobProvider.PATH, SINGLE_USE_ROW); } - public static InputStream getAttachmentStream(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) + public static InputStream getAttachmentStream(@NonNull Context context, @NonNull Uri uri) throws IOException { int match = uriMatcher.match(uri); try { switch (match) { - case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(masterSecret, new PartUriParser(uri).getPartId()); - case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(masterSecret, new PartUriParser(uri).getPartId()); - case PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri)); + case PART_ROW: return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(new PartUriParser(uri).getPartId()); + case THUMB_ROW: return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(new PartUriParser(uri).getPartId()); + case PERSISTENT_ROW: return PersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri)); case SINGLE_USE_ROW: return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri)); default: return context.getContentResolver().openInputStream(uri); } @@ -58,31 +58,31 @@ public class PartAuthority { } } - public static @Nullable String getAttachmentFileName(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) { + public static @Nullable String getAttachmentFileName(@NonNull Context context, @NonNull Uri uri) { int match = uriMatcher.match(uri); switch (match) { case THUMB_ROW: case PART_ROW: - Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId()); + Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(new PartUriParser(uri).getPartId()); if (attachment != null) return attachment.getFileName(); else return null; case PERSISTENT_ROW: - return PersistentBlobProvider.getFileName(context, masterSecret, uri); + return PersistentBlobProvider.getFileName(context, uri); case SINGLE_USE_ROW: default: return null; } } - public static @Nullable Long getAttachmentSize(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) { + public static @Nullable Long getAttachmentSize(@NonNull Context context, @NonNull Uri uri) { int match = uriMatcher.match(uri); switch (match) { case THUMB_ROW: case PART_ROW: - Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId()); + Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(new PartUriParser(uri).getPartId()); if (attachment != null) return attachment.getSize(); else return null; @@ -94,13 +94,13 @@ public class PartAuthority { } } - public static @Nullable String getAttachmentContentType(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri) { + public static @Nullable String getAttachmentContentType(@NonNull Context context, @NonNull Uri uri) { int match = uriMatcher.match(uri); switch (match) { case THUMB_ROW: case PART_ROW: - Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, new PartUriParser(uri).getPartId()); + Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(new PartUriParser(uri).getPartId()); if (attachment != null) return attachment.getContentType(); else return null; diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java index bebaba132..8fed434fb 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.notifications; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -41,6 +42,7 @@ public class AndroidAutoHeardReceiver extends MasterSecretBroadcastReceiver { public static final String THREAD_IDS_EXTRA = "car_heard_thread_ids"; public static final String NOTIFICATION_ID_EXTRA = "car_notification_id"; + @SuppressLint("StaticFieldLeak") @Override protected void onReceive(final Context context, Intent intent, @Nullable final MasterSecret masterSecret) @@ -66,7 +68,7 @@ public class AndroidAutoHeardReceiver extends MasterSecretBroadcastReceiver { messageIdsCollection.addAll(messageIds); } - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); MarkReadReceiver.process(context, messageIdsCollection); return null; diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index 1ffc6e1e0..e95b3b25b 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.notifications; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -24,18 +25,15 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.RemoteInput; -import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.whispersystems.libsignal.logging.Log; -import org.whispersystems.libsignal.util.guava.Optional; import java.util.LinkedList; import java.util.List; @@ -51,6 +49,7 @@ public class AndroidAutoReplyReceiver extends MasterSecretBroadcastReceiver { public static final String VOICE_REPLY_KEY = "car_voice_reply_key"; public static final String THREAD_ID_EXTRA = "car_reply_thread_id"; + @SuppressLint("StaticFieldLeak") @Override protected void onReceive(final Context context, Intent intent, final @Nullable MasterSecret masterSecret) @@ -78,17 +77,17 @@ public class AndroidAutoReplyReceiver extends MasterSecretBroadcastReceiver { if (recipient.isGroupRecipient()) { Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); - replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false, null); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); + replyThreadId = MessageSender.send(context, reply, threadId, false, null); } else { Log.w("AndroidAutoReplyReceiver", "Sending regular message "); OutgoingTextMessage reply = new OutgoingTextMessage(recipient, responseText.toString(), expiresIn, subscriptionId); - replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false, null); + replyThreadId = MessageSender.send(context, reply, threadId, false, null); } List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true); - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); MarkReadReceiver.process(context, messageIds); return null; diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index eb3794503..2e141a9b0 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.notifications; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -33,6 +34,7 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver { public static final String THREAD_IDS_EXTRA = "thread_ids"; public static final String NOTIFICATION_ID_EXTRA = "notification_id"; + @SuppressLint("StaticFieldLeak") @Override protected void onReceive(final Context context, Intent intent, @Nullable final MasterSecret masterSecret) { @@ -57,7 +59,7 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver { process(context, messageIdsCollection); - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); return null; } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 4f05716dc..eb43027f2 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.notifications; +import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.NotificationManager; import android.app.PendingIntent; @@ -32,14 +33,12 @@ import android.os.AsyncTask; import android.os.Build; import android.service.notification.StatusBarNotification; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.app.NotificationManagerCompat; import android.text.TextUtils; import android.util.Log; import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -184,28 +183,25 @@ public class MessageNotifier { } } - public static void updateNotification(@NonNull Context context, @Nullable MasterSecret masterSecret) { + public static void updateNotification(@NonNull Context context) { if (!TextSecurePreferences.isNotificationsEnabled(context)) { return; } - updateNotification(context, masterSecret, false, 0); + updateNotification(context, false, 0); } - public static void updateNotification(@NonNull Context context, - @Nullable MasterSecret masterSecret, - long threadId) + public static void updateNotification(@NonNull Context context, long threadId) { if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) { Log.w(TAG, "Scheduling delayed notification..."); - executor.execute(new DelayedNotification(context, masterSecret, threadId)); + executor.execute(new DelayedNotification(context, threadId)); } else { - updateNotification(context, masterSecret, threadId, true); + updateNotification(context, threadId, true); } } public static void updateNotification(@NonNull Context context, - @Nullable MasterSecret masterSecret, long threadId, boolean signal) { @@ -229,12 +225,11 @@ public class MessageNotifier { if (isVisible) { sendInThreadNotification(context, threads.getRecipientForThreadId(threadId)); } else { - updateNotification(context, masterSecret, signal, 0); + updateNotification(context, signal, 0); } } - private static void updateNotification(@NonNull Context context, - @Nullable MasterSecret masterSecret, + private static void updateNotification(@NonNull Context context, boolean signal, int reminderCount) { @@ -254,7 +249,7 @@ public class MessageNotifier { return; } - NotificationState notificationState = constructNotificationState(context, masterSecret, telcoCursor); + NotificationState notificationState = constructNotificationState(context, telcoCursor); if (signal && (System.currentTimeMillis() - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS) { signal = false; @@ -265,13 +260,13 @@ public class MessageNotifier { if (notificationState.hasMultipleThreads()) { if (Build.VERSION.SDK_INT >= 23) { for (long threadId : notificationState.getThreads()) { - sendSingleThreadNotification(context, masterSecret, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); + sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); } } sendMultipleThreadNotification(context, notificationState, signal); } else { - sendSingleThreadNotification(context, masterSecret, notificationState, signal, false); + sendSingleThreadNotification(context, notificationState, signal, false); } cancelOrphanedNotifications(context, notificationState); @@ -287,7 +282,6 @@ public class MessageNotifier { } private static void sendSingleThreadNotification(@NonNull Context context, - @Nullable MasterSecret masterSecret, @NonNull NotificationState notificationState, boolean signal, boolean bundled) { @@ -296,7 +290,7 @@ public class MessageNotifier { return; } - SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, masterSecret, TextSecurePreferences.getNotificationPrivacy(context)); + SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context)); List notifications = notificationState.getNotifications(); Recipient recipient = notifications.get(0).getRecipient(); int notificationId = (int) (SUMMARY_NOTIFICATION_ID + (bundled ? notifications.get(0).getThreadId() : 0)); @@ -313,8 +307,7 @@ public class MessageNotifier { long timestamp = notifications.get(0).getTimestamp(); if (timestamp != 0) builder.setWhen(timestamp); - builder.addActions(masterSecret, - notificationState.getMarkAsReadIntent(context, notificationId), + builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId), notificationState.getQuickReplyIntent(context, notifications.get(0).getRecipient()), notificationState.getRemoteReplyIntent(context, notifications.get(0).getRecipient())); @@ -411,15 +404,12 @@ public class MessageNotifier { } private static NotificationState constructNotificationState(@NonNull Context context, - @Nullable MasterSecret masterSecret, @NonNull Cursor cursor) { - NotificationState notificationState = new NotificationState(); - MessageRecord record; - MmsSmsDatabase.Reader reader; + NotificationState notificationState = new NotificationState(); + MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor); - if (masterSecret == null) reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor); - else reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor, masterSecret); + MessageRecord record; while ((record = reader.getNext()) != null) { long id = record.getId(); @@ -495,14 +485,14 @@ public class MessageNotifier { public static final String REMINDER_ACTION = "org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"; + @SuppressLint("StaticFieldLeak") @Override public void onReceive(final Context context, final Intent intent) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); - int reminderCount = intent.getIntExtra("reminder_count", 0); - MessageNotifier.updateNotification(context, masterSecret, true, reminderCount + 1); + int reminderCount = intent.getIntExtra("reminder_count", 0); + MessageNotifier.updateNotification(context, true, reminderCount + 1); return null; } @@ -516,22 +506,19 @@ public class MessageNotifier { private final AtomicBoolean canceled = new AtomicBoolean(false); - private final Context context; - private final MasterSecret masterSecret; + private final Context context; + private final long threadId; + private final long delayUntil; - private final long threadId; - private final long delayUntil; - - private DelayedNotification(Context context, MasterSecret masterSecret, long threadId) { - this.context = context; - this.masterSecret = masterSecret; - this.threadId = threadId; - this.delayUntil = System.currentTimeMillis() + DELAY; + private DelayedNotification(Context context, long threadId) { + this.context = context; + this.threadId = threadId; + this.delayUntil = System.currentTimeMillis() + DELAY; } @Override public void run() { - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); long delayMillis = delayUntil - System.currentTimeMillis(); Log.w(TAG, "Waiting to notify: " + delayMillis); @@ -542,7 +529,7 @@ public class MessageNotifier { if (!canceled.get()) { Log.w(TAG, "Not canceled, notifying..."); - MessageNotifier.updateNotification(context, masterSecret, threadId, true); + MessageNotifier.updateNotification(context, threadId, true); MessageNotifier.cancelDelayedNotifications(); } else { Log.w(TAG, "Canceled, not notifying..."); diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 89df21301..783c0ad81 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2016 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.notifications; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; @@ -24,17 +25,14 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.RemoteInput; -import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; -import org.whispersystems.libsignal.util.guava.Optional; import java.util.LinkedList; import java.util.List; @@ -48,6 +46,7 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver { public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY"; public static final String ADDRESS_EXTRA = "address"; + @SuppressLint("StaticFieldLeak") @Override protected void onReceive(final Context context, Intent intent, final @Nullable MasterSecret masterSecret) @@ -72,16 +71,16 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver { long expiresIn = recipient.getExpireMessages() * 1000; if (recipient.isGroupRecipient()) { - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); - threadId = MessageSender.send(context, masterSecret, reply, -1, false, null); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0); + threadId = MessageSender.send(context, reply, -1, false, null); } else { OutgoingTextMessage reply = new OutgoingTextMessage(recipient, responseText.toString(), expiresIn, subscriptionId); - threadId = MessageSender.send(context, masterSecret, reply, -1, false, null); + threadId = MessageSender.send(context, reply, -1, false, null); } List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true); - MessageNotifier.updateNotification(context, masterSecret); + MessageNotifier.updateNotification(context); MarkReadReceiver.process(context, messageIds); return null; diff --git a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 456783233..83ea03de5 100644 --- a/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/src/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -43,18 +43,13 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil private final List messageBodies = new LinkedList<>(); - private SlideDeck slideDeck; - private final MasterSecret masterSecret; - + private SlideDeck slideDeck; private CharSequence contentTitle; private CharSequence contentText; - public SingleRecipientNotificationBuilder(@NonNull Context context, - @Nullable MasterSecret masterSecret, - @NonNull NotificationPrivacyPreference privacy) + public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy) { super(context, privacy); - this.masterSecret = masterSecret; setSmallIcon(R.drawable.icon_notification); setColor(context.getResources().getColor(R.color.textsecure_primary)); @@ -141,8 +136,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build())); } - public void addActions(@Nullable MasterSecret masterSecret, - @NonNull PendingIntent markReadIntent, + public void addActions(@NonNull PendingIntent markReadIntent, @NonNull PendingIntent quickReplyIntent, @NonNull PendingIntent wearableReplyIntent) { @@ -150,37 +144,31 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil context.getString(R.string.MessageNotifier_mark_read), markReadIntent); - if (masterSecret != null) { - Action replyAction = new Action(R.drawable.ic_reply_white_36dp, - context.getString(R.string.MessageNotifier_reply), - quickReplyIntent); + Action replyAction = new Action(R.drawable.ic_reply_white_36dp, + context.getString(R.string.MessageNotifier_reply), + quickReplyIntent); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp, - context.getString(R.string.MessageNotifier_reply), - wearableReplyIntent) - .addRemoteInput(new RemoteInput.Builder(MessageNotifier.EXTRA_REMOTE_REPLY) - .setLabel(context.getString(R.string.MessageNotifier_reply)).build()) - .build(); - } - - Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply, - context.getString(R.string.MessageNotifier_reply), - wearableReplyIntent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp, + context.getString(R.string.MessageNotifier_reply), + wearableReplyIntent) .addRemoteInput(new RemoteInput.Builder(MessageNotifier.EXTRA_REMOTE_REPLY) .setLabel(context.getString(R.string.MessageNotifier_reply)).build()) .build(); - - addAction(markAsReadAction); - addAction(replyAction); - - extend(new NotificationCompat.WearableExtender().addAction(markAsReadAction) - .addAction(wearableReplyAction)); - } else { - addAction(markAsReadAction); - - extend(new NotificationCompat.WearableExtender().addAction(markAsReadAction)); } + + Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply, + context.getString(R.string.MessageNotifier_reply), + wearableReplyIntent) + .addRemoteInput(new RemoteInput.Builder(MessageNotifier.EXTRA_REMOTE_REPLY) + .setLabel(context.getString(R.string.MessageNotifier_reply)).build()) + .build(); + + addAction(markAsReadAction); + addAction(replyAction); + + extend(new NotificationCompat.WearableExtender().addAction(markAsReadAction) + .addAction(wearableReplyAction)); } public void addMessageBody(@NonNull Recipient threadRecipient, @@ -204,9 +192,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil public Notification build() { if (privacy.isDisplayMessage()) { if (messageBodies.size() == 1 && hasBigPictureSlide(slideDeck)) { - assert masterSecret != null; setStyle(new NotificationCompat.BigPictureStyle() - .bigPicture(getBigPicture(masterSecret, slideDeck)) + .bigPicture(getBigPicture(slideDeck)) .setSummaryText(getBigText(messageBodies))); } else { setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText(messageBodies))); @@ -228,7 +215,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil } private boolean hasBigPictureSlide(@Nullable SlideDeck slideDeck) { - if (masterSecret == null || slideDeck == null || Build.VERSION.SDK_INT < 16) { + if (slideDeck == null || Build.VERSION.SDK_INT < 16) { return false; } @@ -240,8 +227,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil thumbnailSlide.getThumbnailUri() != null; } - private Bitmap getBigPicture(@NonNull MasterSecret masterSecret, - @NonNull SlideDeck slideDeck) + private Bitmap getBigPicture(@NonNull SlideDeck slideDeck) { try { @SuppressWarnings("ConstantConditions") @@ -249,7 +235,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil return GlideApp.with(context.getApplicationContext()) .asBitmap() - .load(new DecryptableStreamUriLoader.DecryptableUri(masterSecret, uri)) + .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) .submit(500, 500) .get(); diff --git a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 97b81b2b6..b5631e71f 100644 --- a/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/src/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -137,7 +137,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme new AsyncTask() { @Override protected Void doInBackground(Void... params) { - MessageNotifier.updateNotification(getActivity(), masterSecret); + MessageNotifier.updateNotification(getActivity()); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/src/org/thoughtcrime/securesms/providers/PartProvider.java b/src/org/thoughtcrime/securesms/providers/PartProvider.java index c1f0e17a1..e0524fd7a 100644 --- a/src/org/thoughtcrime/securesms/providers/PartProvider.java +++ b/src/org/thoughtcrime/securesms/providers/PartProvider.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -84,7 +84,7 @@ public class PartProvider extends ContentProvider { Log.w(TAG, "Parting out a single row..."); try { final PartUriParser partUri = new PartUriParser(uri); - return getParcelStreamForAttachment(masterSecret, partUri.getPartId()); + return getParcelStreamForAttachment(partUri.getPartId()); } catch (IOException ioe) { Log.w(TAG, ioe); throw new FileNotFoundException("Error opening file"); @@ -108,7 +108,7 @@ public class PartProvider extends ContentProvider { case SINGLE_ROW: PartUriParser partUriParser = new PartUriParser(uri); DatabaseAttachment attachment = DatabaseFactory.getAttachmentDatabase(getContext()) - .getAttachment(null, partUriParser.getPartId()); + .getAttachment(partUriParser.getPartId()); if (attachment != null) { return attachment.getContentType(); @@ -127,14 +127,13 @@ public class PartProvider extends ContentProvider { @Override public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.w(TAG, "query() called: " + url); - MasterSecret masterSecret = KeyCachingService.getMasterSecret(getContext()); if (projection == null || projection.length <= 0) return null; switch (uriMatcher.match(url)) { case SINGLE_ROW: PartUriParser partUri = new PartUriParser(url); - DatabaseAttachment attachment = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(masterSecret, partUri.getPartId()); + DatabaseAttachment attachment = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(partUri.getPartId()); if (attachment == null) return null; @@ -160,11 +159,11 @@ public class PartProvider extends ContentProvider { return 0; } - private ParcelFileDescriptor getParcelStreamForAttachment(MasterSecret masterSecret, AttachmentId attachmentId) throws IOException { - long plaintextLength = Util.getStreamLength(DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(masterSecret, attachmentId)); + private ParcelFileDescriptor getParcelStreamForAttachment(AttachmentId attachmentId) throws IOException { + long plaintextLength = Util.getStreamLength(DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId)); MemoryFile memoryFile = new MemoryFile(attachmentId.toString(), Util.toIntExact(plaintextLength)); - InputStream in = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(masterSecret, attachmentId); + InputStream in = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(attachmentId); OutputStream out = memoryFile.getOutputStream(); Util.copy(in, out); diff --git a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java index b2011e36f..16881bfaa 100644 --- a/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java +++ b/src/org/thoughtcrime/securesms/providers/PersistentBlobProvider.java @@ -5,20 +5,19 @@ import android.content.ContentUris; import android.content.Context; import android.content.UriMatcher; import android.net.Uri; -import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.content.FileProvider; import android.util.Log; import android.webkit.MimeTypeMap; -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.AttachmentSecret; +import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; +import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; +import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; +import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; +import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.FileProviderUtil; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.libsignal.InvalidMessageException; import java.io.ByteArrayInputStream; import java.io.File; @@ -67,83 +66,84 @@ public class PersistentBlobProvider { return instance; } - private final Context context; @SuppressLint("UseSparseArrays") private final Map cache = Collections.synchronizedMap(new HashMap()); private final ExecutorService executor = Executors.newCachedThreadPool(); - private PersistentBlobProvider(Context context) { - this.context = context.getApplicationContext(); + private final AttachmentSecret attachmentSecret; + + private PersistentBlobProvider(@NonNull Context context) { + this.attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); } - public Uri create(@NonNull MasterSecret masterSecret, + public Uri create(@NonNull Context context, @NonNull byte[] blobBytes, @NonNull String mimeType, @Nullable String fileName) { final long id = System.currentTimeMillis(); cache.put(id, blobBytes); - return create(masterSecret, new ByteArrayInputStream(blobBytes), id, mimeType, fileName, (long) blobBytes.length); + return create(context, attachmentSecret, new ByteArrayInputStream(blobBytes), id, mimeType, fileName, (long) blobBytes.length); } - public Uri create(@NonNull MasterSecret masterSecret, + public Uri create(@NonNull Context context, @NonNull InputStream input, @NonNull String mimeType, @Nullable String fileName, @Nullable Long fileSize) { - return create(masterSecret, input, System.currentTimeMillis(), mimeType, fileName, fileSize); + return create(context, attachmentSecret, input, System.currentTimeMillis(), mimeType, fileName, fileSize); } - private Uri create(@NonNull MasterSecret masterSecret, + private Uri create(@NonNull Context context, + @NonNull AttachmentSecret attachmentSecret, @NonNull InputStream input, long id, @NonNull String mimeType, @Nullable String fileName, @Nullable Long fileSize) { - persistToDisk(masterSecret, id, input); + persistToDisk(context, attachmentSecret, id, input); final Uri uniqueUri = CONTENT_URI.buildUpon() .appendPath(mimeType) - .appendPath(getEncryptedFileName(masterSecret, fileName)) + .appendPath(fileName) .appendEncodedPath(String.valueOf(fileSize)) .appendEncodedPath(String.valueOf(System.currentTimeMillis())) .build(); return ContentUris.withAppendedId(uniqueUri, id); } - private void persistToDisk(final MasterSecret masterSecret, final long id, final InputStream input) { - executor.submit(new Runnable() { - @Override - public void run() { - try { - OutputStream output = new EncryptingPartOutputStream(getFile(id), masterSecret); - Log.w(TAG, "Starting stream copy...."); - Util.copy(input, output); - Log.w(TAG, "Stream copy finished..."); - } catch (IOException e) { - Log.w(TAG, e); - } - - cache.remove(id); + private void persistToDisk(@NonNull Context context, + @NonNull AttachmentSecret attachmentSecret, + final long id, final InputStream input) + { + executor.submit(() -> { + try { + OutputStream output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, Conversions.longToByteArray(id), getFile(context, id).file); + Util.copy(input, output); + } catch (IOException e) { + Log.w(TAG, e); } + + cache.remove(id); }); } - public Uri createForExternal(@NonNull String mimeType) throws IOException { + public Uri createForExternal(@NonNull Context context, @NonNull String mimeType) throws IOException { File target = new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType)); return FileProviderUtil.getUriFor(context, target); } - public boolean delete(@NonNull Uri uri) { + public boolean delete(@NonNull Context context, @NonNull Uri uri) { switch (MATCHER.match(uri)) { case MATCH_OLD: case MATCH_NEW: long id = ContentUris.parseId(uri); cache.remove(id); - return getFile(ContentUris.parseId(uri)).delete(); + return getFile(context, ContentUris.parseId(uri)).file.delete(); } + //noinspection SimplifiableIfStatement if (isExternalBlobUri(context, uri)) { return new File(uri.getPath()).delete(); } @@ -151,31 +151,39 @@ public class PersistentBlobProvider { return false; } - public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException { + public @NonNull InputStream getStream(@NonNull Context context, long id) throws IOException { final byte[] cached = cache.get(id); - return cached != null ? new ByteArrayInputStream(cached) - : DecryptingPartInputStream.createFor(masterSecret, getFile(id)); + + if (cached != null) { + return new ByteArrayInputStream(cached); + } + + FileData fileData = getFile(context, id); + + if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, Conversions.longToByteArray(id), fileData.file); + else return ClassicDecryptingPartInputStream.createFor(attachmentSecret, fileData.file); } - private File getFile(long id) { - File legacy = getLegacyFile(id); - File cache = getCacheFile(id); + private FileData getFile(@NonNull Context context, long id) { + File legacy = getLegacyFile(context, id); + File cache = getCacheFile(context, id); + File modernCache = getModernCacheFile(context, id); - if (legacy.exists()) return legacy; - else return cache; + if (legacy.exists()) return new FileData(legacy, false); + else if (cache.exists()) return new FileData(cache, false); + else return new FileData(modernCache, true); } - private File getLegacyFile(long id) { + private File getLegacyFile(@NonNull Context context, long id) { return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION); } - private File getCacheFile(long id) { + private File getCacheFile(@NonNull Context context, long id) { return new File(context.getCacheDir(), "capture-" + id + "." + BLOB_EXTENSION); } - private @Nullable String getEncryptedFileName(@NonNull MasterSecret masterSecret, @Nullable String fileName) { - if (fileName == null) return null; - return new MasterCipher(masterSecret).encryptBody(fileName); + private File getModernCacheFile(@NonNull Context context, long id) { + return new File(context.getCacheDir(), "capture-m-" + id + "." + BLOB_EXTENSION); } public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) { @@ -185,20 +193,12 @@ public class PersistentBlobProvider { : persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT); } - public static @Nullable String getFileName(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri persistentBlobUri) { + public static @Nullable String getFileName(@NonNull Context context, @NonNull Uri persistentBlobUri) { if (!isAuthority(context, persistentBlobUri)) return null; if (isExternalBlobUri(context, persistentBlobUri)) return null; if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null; - String fileName = persistentBlobUri.getPathSegments().get(FILENAME_PATH_SEGMENT); - - try { - return new MasterCipher(masterSecret).decryptBody(fileName); - } catch (InvalidMessageException e) { - Log.w(TAG, "No valid filename for URI"); - } - - return null; + return persistentBlobUri.getPathSegments().get(FILENAME_PATH_SEGMENT); } public static @Nullable Long getFileSize(@NonNull Context context, Uri persistentBlobUri) { @@ -243,4 +243,14 @@ public class PersistentBlobProvider { return false; } } + + private static class FileData { + private final File file; + private final boolean modern; + + private FileData(File file, boolean modern) { + this.file = file; + this.modern = modern; + } + } } diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java index 675ba9dd4..99c54e71b 100644 --- a/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java +++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleActivity.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.scribbles; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Intent; import android.graphics.Bitmap; @@ -49,14 +50,12 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem private VerticalSlideColorPicker colorPicker; private ScribbleToolbar toolbar; private ScribbleView scribbleView; - private MasterSecret masterSecret; private GlideRequests glideRequests; @Override protected void onCreate(Bundle savedInstanceState, @NonNull MasterSecret masterSecret) { setContentView(R.layout.scribble_activity); - this.masterSecret = masterSecret; this.glideRequests = GlideApp.with(this); this.scribbleView = findViewById(R.id.scribble_view); this.toolbar = findViewById(R.id.toolbar); @@ -67,7 +66,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem scribbleView.setMotionViewCallback(motionViewCallback); scribbleView.setDrawingMode(false); - scribbleView.setImage(masterSecret, glideRequests, getIntent().getData()); + scribbleView.setImage(glideRequests, getIntent().getData()); colorPicker.setOnColorChangeListener(this); colorPicker.setVisibility(View.GONE); @@ -144,6 +143,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem return textLayer; } + @SuppressLint("StaticFieldLeak") @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -231,7 +231,7 @@ public class ScribbleActivity extends PassphraseRequiredActionBarActivity implem baos = null; result = null; - Uri uri = provider.create(masterSecret, data, MediaUtil.IMAGE_JPEG, null); + Uri uri = provider.create(ScribbleActivity.this, data, MediaUtil.IMAGE_JPEG, null); Intent intent = new Intent(); intent.setData(uri); setResult(RESULT_OK, intent); diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java index 649100984..d078de4b2 100644 --- a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java +++ b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.scribbles.widget; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; @@ -34,7 +35,6 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.Target; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity; @@ -53,8 +53,7 @@ public class ScribbleView extends FrameLayout { private MotionView motionView; private CanvasView canvasView; - private @Nullable Uri imageUri; - private @Nullable MasterSecret masterSecret; + private @Nullable Uri imageUri; public ScribbleView(Context context) { super(context); @@ -77,22 +76,22 @@ public class ScribbleView extends FrameLayout { initialize(context); } - public void setImage(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Uri uri) { + public void setImage(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { this.imageUri = uri; - this.masterSecret = masterSecret; - glideRequests.load(new DecryptableUri(masterSecret, uri)) + glideRequests.load(new DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) .fitCenter() .into(imageView); } + @SuppressLint("StaticFieldLeak") public @NonNull ListenableFuture getRenderedImage(@NonNull GlideRequests glideRequests) { final SettableFuture future = new SettableFuture<>(); final Context context = getContext(); final boolean isLowMemory = Util.isLowMemory(context); - if (imageUri == null || masterSecret == null) { + if (imageUri == null) { future.set(null); return future; } @@ -110,7 +109,7 @@ public class ScribbleView extends FrameLayout { } return glideRequests.asBitmap() - .load(new DecryptableUri(masterSecret, imageUri)) + .load(new DecryptableUri(imageUri)) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true) .into(width, height) diff --git a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java index eb2e0135d..64f67bf53 100644 --- a/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java +++ b/src/org/thoughtcrime/securesms/service/ApplicationMigrationService.java @@ -15,11 +15,9 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.support.v4.app.NotificationCompat; -import android.util.Log; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.SmsMigrator; import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription; @@ -55,7 +53,7 @@ public class ApplicationMigrationService extends Service if (intent == null) return START_NOT_STICKY; if (intent.getAction() != null && intent.getAction().equals(MIGRATE_DATABASE)) { - executor.execute(new ImportRunnable(intent)); + executor.execute(new ImportRunnable()); } return START_NOT_STICKY; @@ -143,12 +141,8 @@ public class ApplicationMigrationService extends Service } private class ImportRunnable implements Runnable { - private final MasterSecret masterSecret; - public ImportRunnable(Intent intent) { - this.masterSecret = intent.getParcelableExtra("master_secret"); - Log.w(TAG, "Service got mastersecret: " + masterSecret); - } + ImportRunnable() {} @Override public void run() { @@ -162,7 +156,6 @@ public class ApplicationMigrationService extends Service setState(new ImportState(ImportState.STATE_MIGRATING_BEGIN, null)); SmsMigrator.migrateDatabase(ApplicationMigrationService.this, - masterSecret, ApplicationMigrationService.this); setState(new ImportState(ImportState.STATE_MIGRATING_COMPLETE, null)); diff --git a/src/org/thoughtcrime/securesms/service/DirectShareService.java b/src/org/thoughtcrime/securesms/service/DirectShareService.java index 299855334..ae5b86a68 100644 --- a/src/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/src/org/thoughtcrime/securesms/service/DirectShareService.java @@ -14,8 +14,6 @@ import android.service.chooser.ChooserTargetService; import android.support.annotation.RequiresApi; import org.thoughtcrime.securesms.ShareActivity; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; @@ -34,18 +32,12 @@ public class DirectShareService extends ChooserTargetService { IntentFilter matchedFilter) { List results = new LinkedList<>(); - MasterSecret masterSecret = KeyCachingService.getMasterSecret(this); - - if (masterSecret == null) { - return results; - } - - ComponentName componentName = new ComponentName(this, ShareActivity.class); - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(this); - Cursor cursor = threadDatabase.getDirectShareList(); + ComponentName componentName = new ComponentName(this, ShareActivity.class); + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(this); + Cursor cursor = threadDatabase.getDirectShareList(); try { - ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor, new MasterCipher(masterSecret)); + ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor); ThreadRecord record; while ((record = reader.getNext()) != null && results.size() < 10) { diff --git a/src/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/src/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 455817ad0..2cc4674fe 100644 --- a/src/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/src/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -46,7 +46,7 @@ public class ExpiringMessageManager { } } - public void checkSchedule() { + void checkSchedule() { synchronized (expiringMessageReferences) { expiringMessageReferences.notifyAll(); } @@ -55,9 +55,9 @@ public class ExpiringMessageManager { private class LoadTask implements Runnable { public void run() { SmsDatabase.Reader smsReader = smsDatabase.readerFor(smsDatabase.getExpirationStartedMessages()); - MmsDatabase.Reader mmsReader = mmsDatabase.getExpireStartedMessages(null); + MmsDatabase.Reader mmsReader = mmsDatabase.getExpireStartedMessages(); - MessageRecord messageRecord = null; + MessageRecord messageRecord; while ((messageRecord = smsReader.getNext()) != null) { expiringMessageReferences.add(new ExpiringMessageReference(messageRecord.getId(), @@ -70,9 +70,13 @@ public class ExpiringMessageManager { messageRecord.isMms(), messageRecord.getExpireStarted() + messageRecord.getExpiresIn())); } + + smsReader.close(); + mmsReader.close(); } } + @SuppressWarnings("InfiniteLoopStatement") private class ProcessTask implements Runnable { public void run() { while (true) { diff --git a/src/org/thoughtcrime/securesms/service/KeyCachingService.java b/src/org/thoughtcrime/securesms/service/KeyCachingService.java index 2ddd2dc58..abc10d815 100644 --- a/src/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/src/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -34,7 +34,6 @@ import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.RemoteViews; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.DummyActivity; @@ -42,7 +41,6 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.jobs.MasterSecretDecryptJob; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.ServiceUtil; @@ -105,16 +103,12 @@ public class KeyCachingService extends Service { foregroundService(); broadcastNewSecret(); startTimeoutIfAppropriate(); - - if (!TextSecurePreferences.isPasswordDisabled(this)) { - ApplicationContext.getInstance(this).getJobManager().add(new MasterSecretDecryptJob(this)); - } - + new AsyncTask() { @Override protected Void doInBackground(Void... params) { if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) { - MessageNotifier.updateNotification(KeyCachingService.this, masterSecret); + MessageNotifier.updateNotification(KeyCachingService.this); } return null; } @@ -205,7 +199,7 @@ public class KeyCachingService extends Service { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - MessageNotifier.updateNotification(KeyCachingService.this, null); + MessageNotifier.updateNotification(KeyCachingService.this); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/src/org/thoughtcrime/securesms/service/QuickResponseService.java b/src/org/thoughtcrime/securesms/service/QuickResponseService.java index 860f035e9..0e69278be 100644 --- a/src/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/src/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -57,7 +57,7 @@ public class QuickResponseService extends MasterSecretIntentService { long expiresIn = recipient.getExpireMessages() * 1000; if (!TextUtils.isEmpty(content)) { - MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipient, content, expiresIn, subscriptionId), -1, false, null); + MessageSender.send(this, new OutgoingTextMessage(recipient, content, expiresIn, subscriptionId), -1, false, null); } } catch (URISyntaxException e) { Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show(); diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index ac8f7b334..e2dd5f3b8 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -672,8 +672,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo private void insertMissedCall(@NonNull Recipient recipient, boolean signal) { Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getAddress()); - MessageNotifier.updateNotification(this, KeyCachingService.getMasterSecret(this), - messageAndThreadId.second, signal); + MessageNotifier.updateNotification(this, messageAndThreadId.second, signal); } private void handleAnswerCall(Intent intent) { diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index b52258be5..05a206bd4 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify @@ -21,11 +21,8 @@ import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; @@ -55,15 +52,14 @@ public class MessageSender { private static final String TAG = MessageSender.class.getSimpleName(); public static long send(final Context context, - final MasterSecret masterSecret, final OutgoingTextMessage message, final long threadId, final boolean forceSms, final SmsDatabase.InsertListener insertListener) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); - Recipient recipient = message.getRecipient(); - boolean keyExchange = message.isKeyExchange(); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + Recipient recipient = message.getRecipient(); + boolean keyExchange = message.isKeyExchange(); long allocatedThreadId; @@ -73,8 +69,7 @@ public class MessageSender { allocatedThreadId = threadId; } - long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, - message, forceSms, System.currentTimeMillis(), insertListener); + long messageId = database.insertMessageOutbox(allocatedThreadId, message, forceSms, System.currentTimeMillis(), insertListener); sendTextMessage(context, recipient, forceSms, keyExchange, messageId, message.getExpiresIn()); @@ -82,7 +77,6 @@ public class MessageSender { } public static long send(final Context context, - final MasterSecret masterSecret, final OutgoingMediaMessage message, final long threadId, final boolean forceSms, @@ -101,9 +95,9 @@ public class MessageSender { } Recipient recipient = message.getRecipient(); - long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms, insertListener); + long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); - sendMediaMessage(context, masterSecret, recipient, forceSms, messageId, message.getExpiresIn()); + sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn()); return allocatedThreadId; } catch (MmsException e) { @@ -117,7 +111,7 @@ public class MessageSender { sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterAddress); } - public static void resend(Context context, MasterSecret masterSecret, MessageRecord messageRecord) { + public static void resend(Context context, MessageRecord messageRecord) { try { long messageId = messageRecord.getId(); boolean forceSms = messageRecord.isForcedSms(); @@ -126,7 +120,7 @@ public class MessageSender { Recipient recipient = messageRecord.getRecipient(); if (messageRecord.isMms()) { - sendMediaMessage(context, masterSecret, recipient, forceSms, messageId, expiresIn); + sendMediaMessage(context, recipient, forceSms, messageId, expiresIn); } else { sendTextMessage(context, recipient, forceSms, keyExchange, messageId, expiresIn); } @@ -135,13 +129,11 @@ public class MessageSender { } } - private static void sendMediaMessage(Context context, MasterSecret masterSecret, - Recipient recipient, boolean forceSms, - long messageId, long expiresIn) + private static void sendMediaMessage(Context context, Recipient recipient, boolean forceSms, long messageId, long expiresIn) throws MmsException { if (!forceSms && isSelfSend(context, recipient)) { - sendMediaSelf(context, masterSecret, messageId, expiresIn); + sendMediaSelf(context, messageId, expiresIn); } else if (isGroupPushSend(recipient)) { sendGroupPush(context, recipient, messageId, null); } else if (!forceSms && isPushMediaSend(context, recipient)) { @@ -165,7 +157,7 @@ public class MessageSender { } private static void sendTextSelf(Context context, long messageId, long expiresIn) { - EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); database.markAsSent(messageId, true); @@ -180,15 +172,14 @@ public class MessageSender { } } - private static void sendMediaSelf(Context context, MasterSecret masterSecret, - long messageId, long expiresIn) + private static void sendMediaSelf(Context context, long messageId, long expiresIn) throws MmsException { ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); database.markAsSent(messageId, true); - database.copyMessageInbox(masterSecret, messageId); + database.copyMessageInbox(messageId); if (expiresIn > 0) { database.markExpireStarted(messageId); diff --git a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java index 558f44270..9715705de 100644 --- a/src/org/thoughtcrime/securesms/util/DirectoryHelper.java +++ b/src/org/thoughtcrime/securesms/util/DirectoryHelper.java @@ -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
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
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 = 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); } } } diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index 7775846db..a1cfb8d04 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -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> getRemoteIdentityKey(final Context context, final Recipient recipient) { final SettableFuture> 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.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.absent(), 0); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0); IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming); Optional 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); } } } diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java index c887bc7e1..400448df8 100644 --- a/src/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index bb9caf99f..5bda307fe 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -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 contextReference; - private final WeakReference 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(FAILURE, null); } } @@ -85,7 +81,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask= 16) setExoViewSource(masterSecret, videoSource, autoplay); - else setVideoViewSource(masterSecret, videoSource, autoplay); + if (Build.VERSION.SDK_INT >= 16) setExoViewSource(videoSource, autoplay); + else setVideoViewSource(videoSource, autoplay); } public void pause() { @@ -124,7 +123,7 @@ public class VideoPlayer extends FrameLayout { this.window = window; } - private void setExoViewSource(@NonNull MasterSecret masterSecret, @NonNull VideoSlide videoSource, boolean autoplay) + private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) throws IOException { BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); @@ -138,7 +137,7 @@ public class VideoPlayer extends FrameLayout { exoView.setPlayer(exoPlayer); DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(getContext(), "GenericUserAgent", null); - AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(getContext(), masterSecret, defaultDataSourceFactory, null); + AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(getContext(), defaultDataSourceFactory, null); ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); MediaSource mediaSource = new ExtractorMediaSource(videoSource.getUri(), attachmentDataSourceFactory, extractorsFactory, null, null); @@ -147,7 +146,7 @@ public class VideoPlayer extends FrameLayout { exoPlayer.setPlayWhenReady(autoplay); } - private void setVideoViewSource(@NonNull MasterSecret masterSecret, @NonNull VideoSlide videoSource, boolean autoplay) + private void setVideoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) throws IOException { if (this.attachmentServer != null) { @@ -156,7 +155,7 @@ public class VideoPlayer extends FrameLayout { if (videoSource.getUri() != null && PartAuthority.isLocalUri(videoSource.getUri())) { Log.w(TAG, "Starting video attachment server for part provider Uri..."); - this.attachmentServer = new AttachmentServer(getContext(), masterSecret, videoSource.asAttachment()); + this.attachmentServer = new AttachmentServer(getContext(), videoSource.asAttachment()); this.attachmentServer.start(); //noinspection ConstantConditions diff --git a/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java index 657e56536..ec216148c 100644 --- a/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java +++ b/src/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java @@ -9,22 +9,18 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.TransferListener; -import org.thoughtcrime.securesms.crypto.MasterSecret; - public class AttachmentDataSourceFactory implements DataSource.Factory { - private final Context context; - private final MasterSecret masterSecret; + private final Context context; private final DefaultDataSourceFactory defaultDataSourceFactory; private final TransferListener listener; - public AttachmentDataSourceFactory(@NonNull Context context, @NonNull MasterSecret masterSecret, + public AttachmentDataSourceFactory(@NonNull Context context, @NonNull DefaultDataSourceFactory defaultDataSourceFactory, @Nullable TransferListener listener) { this.context = context; - this.masterSecret = masterSecret; this.defaultDataSourceFactory = defaultDataSourceFactory; this.listener = listener; } @@ -32,6 +28,6 @@ public class AttachmentDataSourceFactory implements DataSource.Factory { @Override public AttachmentDataSource createDataSource() { return new AttachmentDataSource(defaultDataSourceFactory.createDataSource(), - new PartDataSource(context, masterSecret, listener)); + new PartDataSource(context, listener)); } } diff --git a/src/org/thoughtcrime/securesms/video/exo/PartDataSource.java b/src/org/thoughtcrime/securesms/video/exo/PartDataSource.java index 92e6a7b84..d9d86ca84 100644 --- a/src/org/thoughtcrime/securesms/video/exo/PartDataSource.java +++ b/src/org/thoughtcrime/securesms/video/exo/PartDataSource.java @@ -23,19 +23,15 @@ import java.io.InputStream; public class PartDataSource implements DataSource { private final @NonNull Context context; - private final @NonNull MasterSecret masterSecret; private final @Nullable TransferListener listener; private Uri uri; private InputStream inputSteam; - public PartDataSource(@NonNull Context context, - @NonNull MasterSecret masterSecret, - @Nullable TransferListener listener) + public PartDataSource(@NonNull Context context, @Nullable TransferListener listener) { - this.context = context.getApplicationContext(); - this.masterSecret = masterSecret; - this.listener = listener; + this.context = context.getApplicationContext(); + this.listener = listener; } @Override @@ -44,11 +40,11 @@ public class PartDataSource implements DataSource { AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); PartUriParser partUri = new PartUriParser(uri); - Attachment attachment = attachmentDatabase.getAttachment(masterSecret, partUri.getPartId()); + Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId()); if (attachment == null) throw new IOException("Attachment not found"); - this.inputSteam = attachmentDatabase.getAttachmentStream(masterSecret, partUri.getPartId()); + this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId()); if (inputSteam == null) throw new IOException("InputStream not foudn");