mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
Switch to a more heavily TOFU model for identity keys.
1) There is no longer a concept of "verified" or "unverified." Only "what we saw last time" and "different from last time." 2) Let's eliminate "verify session," since we're all about identity keys now. 3) Mark manually processed key exchanges as processed.
This commit is contained in:
parent
ef7977128b
commit
24fc93e9ae
BIN
res/drawable-hdpi/ic_menu_unlock_holo_dark.png
Normal file
BIN
res/drawable-hdpi/ic_menu_unlock_holo_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-mdpi/ic_menu_unlock_holo_dark.png
Normal file
BIN
res/drawable-mdpi/ic_menu_unlock_holo_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 596 B |
BIN
res/drawable-xhdpi/ic_menu_unlock_holo_dark.png
Normal file
BIN
res/drawable-xhdpi/ic_menu_unlock_holo_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -1,59 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView android:id="@+id/ScrollView"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
android:fillViewport="true"
|
||||
android:background="@drawable/background_pattern_repeat">
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout android:paddingRight="16dip"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingTop="10dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dip">
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:id="@+id/description_text"
|
||||
<TextView style="@style/Registration.Description"
|
||||
android:id="@+id/description_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="17dip"
|
||||
android:layout_marginBottom="5dip" />
|
||||
android:layout_marginBottom="16dip"
|
||||
android:layout_marginTop="16dip"
|
||||
android:text="@string/receive_key_activity__the_signature_on_this_key_exchange_is_different"/>
|
||||
|
||||
<TextView android:id="@+id/signature_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="17dip"
|
||||
android:textStyle="italic"
|
||||
android:layout_marginBottom="10dip" />
|
||||
|
||||
<LinearLayout android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
<LinearLayout android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="7dip"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button android:id="@+id/verify_session_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/receive_key_activity__session"
|
||||
android:visibility="gone"
|
||||
android:gravity="center" />
|
||||
|
||||
<Button android:id="@+id/verify_identity_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/receive_key_activity__identities"
|
||||
android:visibility="gone"
|
||||
android:gravity="center" />
|
||||
|
||||
<Button android:id="@+id/ok_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/receive_key_activity__complete_exchange"
|
||||
android:gravity="center"/>
|
||||
|
||||
<Button android:id="@+id/cancel_button"
|
||||
android:text="@android:string/cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"/>
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<Button android:id="@+id/ok_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/receive_key_activity__complete"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
@ -1,24 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip">
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:drawSelectorOnTop="false"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:fadingEdgeLength="16dip" />
|
||||
|
||||
<TextView android:id="@id/android:empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/review_identities__you_don_t_currently_have_any_identity_keys_in_your_trust_database"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:padding="20dip" />
|
||||
|
||||
</LinearLayout>
|
||||
<fragment android:id="@+id/fragment_content"
|
||||
android:name="org.thoughtcrime.securesms.ReviewIdentitiesFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
||||
|
24
res/layout/review_identities_fragment.xml
Normal file
24
res/layout/review_identities_fragment.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:drawSelectorOnTop="false"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:fadingEdgeLength="16dip" />
|
||||
|
||||
<TextView android:id="@id/android:empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/review_identities__you_don_t_currently_have_any_identity_keys_in_your_trust_database"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:padding="20dip" />
|
||||
|
||||
</LinearLayout>
|
@ -2,7 +2,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="Security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_holo_dark"
|
||||
android:icon="@drawable/ic_menu_unlock_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="@string/conversation_insecure__menu_start_secure_session"
|
||||
|
@ -2,12 +2,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="@string/conversation_secure_verified__menu_security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_verified_holo_dark"
|
||||
android:icon="@drawable/ic_menu_lock_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="@string/conversation_secure_verified__menu_verify_session"
|
||||
android:id="@+id/menu_verify_session" />
|
||||
|
||||
<item android:title="@string/conversation_secure_verified__menu_verify_recipient"
|
||||
android:id="@+id/menu_verify_recipient"/>
|
||||
|
@ -2,15 +2,12 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="@string/conversation_secure_verified__menu_security"
|
||||
android:id="@+id/menu_security"
|
||||
android:icon="@drawable/ic_menu_lock_unverified_holo_dark"
|
||||
android:icon="@drawable/ic_menu_lock_holo_dark"
|
||||
android:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item android:title="@string/conversation_secure_verified__menu_verify_session"
|
||||
android:id="@+id/menu_verify_session" />
|
||||
|
||||
<item android:title="@string/conversation_secure_verified__menu_verify_recipient"
|
||||
android:id="@+id/menu_verify_recipient"/>
|
||||
|
||||
<item android:title="@string/conversation_secure_verified__menu_abort_secure_session"
|
||||
android:id="@+id/menu_abort_session"/>
|
||||
|
@ -316,7 +316,12 @@
|
||||
<!-- receive_key_activity -->
|
||||
<string name="receive_key_activity__session">Session</string>
|
||||
<string name="receive_key_activity__identities">Identities</string>
|
||||
<string name="receive_key_activity__complete_exchange">Complete Exchange</string>
|
||||
<string name="receive_key_activity__complete">Complete</string>
|
||||
<string name="receive_key_activity__the_signature_on_this_key_exchange_is_different">The
|
||||
signature on this key exchange is different than what you\'ve previously received from this
|
||||
contact. This could either mean that someone is trying to intercept your communication, or
|
||||
that this contact simply re-installed TextSecure and now has a new identity key.
|
||||
</string>
|
||||
|
||||
<!-- recipients_panel -->
|
||||
<string name="recipients_panel__to">To</string>
|
||||
@ -473,7 +478,6 @@
|
||||
<string name="conversation_list__menu_search">Search</string>
|
||||
|
||||
<!-- conversation_secure_verified -->
|
||||
<!-- conversation_secure_unverified -->
|
||||
<string name="conversation_secure_verified__menu_security">Security</string>
|
||||
<string name="conversation_secure_verified__menu_verify_session">Verify Session</string>
|
||||
<string name="conversation_secure_verified__menu_verify_recipient">Verify Recipient</string>
|
||||
@ -508,6 +512,9 @@
|
||||
|
||||
<!-- verify_keys -->
|
||||
<string name="verify_keys__menu_verified">Verified</string>
|
||||
<string name="ReceiveKeyActivity_you_may_wish_to_verify_this_contact">You may wish to verify
|
||||
this contact.
|
||||
</string>
|
||||
|
||||
<!-- Misc. piggybacking -->
|
||||
|
||||
|
@ -123,14 +123,6 @@
|
||||
android:title="@string/preferences__view_my_identity_key"
|
||||
android:summary="@string/preferences__view_my_identity_key"/>
|
||||
|
||||
<Preference android:key="pref_export_identity"
|
||||
android:title="@string/preferences__export_my_identity_key"
|
||||
android:summary="@string/preferences__export_my_identity_key"/>
|
||||
|
||||
<Preference android:key="pref_import_identity"
|
||||
android:title="@string/preferences__import_contacts_key"
|
||||
android:summary="@string/preferences__import_an_identity_key_from_a_contact"/>
|
||||
|
||||
<Preference android:key="pref_manage_identity"
|
||||
android:title="@string/preferences__manage_identity_keys"
|
||||
android:summary="@string/preferences__manage_configured_identity_keys"/>
|
||||
|
@ -55,7 +55,6 @@ import java.util.List;
|
||||
public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPreferenceActivity {
|
||||
|
||||
private static final int PICK_IDENTITY_CONTACT = 1;
|
||||
private static final int IMPORT_IDENTITY_ID = 2;
|
||||
|
||||
public static final String RINGTONE_PREF = "pref_key_ringtone";
|
||||
public static final String VIBRATE_PREF = "pref_key_vibrate";
|
||||
@ -107,10 +106,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
|
||||
this.findPreference(VIEW_MY_IDENTITY_PREF)
|
||||
.setOnPreferenceClickListener(new ViewMyIdentityClickListener());
|
||||
this.findPreference(EXPORT_MY_IDENTITY_PREF)
|
||||
.setOnPreferenceClickListener(new ExportMyIdentityClickListener());
|
||||
this.findPreference(IMPORT_CONTACT_IDENTITY_PREF)
|
||||
.setOnPreferenceClickListener(new ImportContactIdentityClickListener());
|
||||
this.findPreference(MANAGE_IDENTITIES_PREF)
|
||||
.setOnPreferenceClickListener(new ManageIdentitiesClickListener());
|
||||
this.findPreference(CHANGE_PASSPHRASE_PREF)
|
||||
@ -133,8 +128,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
switch (reqCode) {
|
||||
case (PICK_IDENTITY_CONTACT) : handleIdentitySelection(data); break;
|
||||
case IMPORT_IDENTITY_ID: importIdentityKey(data.getData()); break;
|
||||
case PICK_IDENTITY_CONTACT: handleIdentitySelection(data); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,25 +203,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
}
|
||||
}
|
||||
|
||||
private void importIdentityKey(Uri uri) {
|
||||
IdentityKey identityKey = ContactAccessor.getInstance().importIdentityKey(this, uri);
|
||||
String contactName = ContactAccessor.getInstance().getNameFromContact(this, uri);
|
||||
|
||||
if (identityKey == null) {
|
||||
Dialogs.displayAlert(this,
|
||||
getString(R.string.ApplicationPreferenceActivity_not_found_exclamation),
|
||||
getString(R.string.ApplicationPreferenceActivity_no_valid_identity_key_was_found_in_the_specified_contact),
|
||||
android.R.drawable.ic_dialog_alert);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent verifyImportedKeyIntent = new Intent(this, VerifyImportedIdentityActivity.class);
|
||||
verifyImportedKeyIntent.putExtra("master_secret", getIntent().getParcelableExtra("master_secret"));
|
||||
verifyImportedKeyIntent.putExtra("identity_key", identityKey);
|
||||
verifyImportedKeyIntent.putExtra("contact_name", contactName);
|
||||
startActivity(verifyImportedKeyIntent);
|
||||
}
|
||||
|
||||
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
@ -249,57 +224,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
|
||||
}
|
||||
}
|
||||
|
||||
private class ExportMyIdentityClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (!IdentityKeyUtil.hasIdentityKey(ApplicationPreferencesActivity.this)) {
|
||||
Toast.makeText(ApplicationPreferencesActivity.this,
|
||||
R.string.ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Long> rawContactIds = ContactIdentityManager
|
||||
.getInstance(ApplicationPreferencesActivity.this)
|
||||
.getSelfIdentityRawContactIds();
|
||||
|
||||
if (rawContactIds== null) {
|
||||
Toast.makeText(ApplicationPreferencesActivity.this,
|
||||
R.string.ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, rawContactIds,
|
||||
IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this));
|
||||
|
||||
Toast.makeText(ApplicationPreferencesActivity.this,
|
||||
R.string.ApplicationPreferenceActivity_exported_to_contacts_database,
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ImportContactIdentityClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
|
||||
if (masterSecret != null) {
|
||||
Intent importIntent = new Intent(Intent.ACTION_PICK);
|
||||
importIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
|
||||
startActivityForResult(importIntent, IMPORT_IDENTITY_ID);
|
||||
} else {
|
||||
Toast.makeText(ApplicationPreferencesActivity.this,
|
||||
R.string.ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ManageIdentitiesClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
|
@ -48,7 +48,6 @@ import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import org.thoughtcrime.securesms.components.RecipientsPanel;
|
||||
import org.thoughtcrime.securesms.crypto.AuthenticityCalculator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.crypto.KeyUtil;
|
||||
@ -125,6 +124,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
private long threadId;
|
||||
private int distributionType;
|
||||
private boolean isEncryptedConversation;
|
||||
private boolean isAuthenticatedConversation;
|
||||
private boolean isMmsEnabled = true;
|
||||
|
||||
private CharacterCalculator characterCalculator = new CharacterCalculator();
|
||||
@ -211,10 +211,10 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
|
||||
if (isSingleConversation() && isEncryptedConversation)
|
||||
{
|
||||
if (isAuthenticatedSession()) {
|
||||
inflater.inflate(R.menu.conversation_secure_verified, menu);
|
||||
if (isAuthenticatedConversation) {
|
||||
inflater.inflate(R.menu.conversation_secure_identity, menu);
|
||||
} else {
|
||||
inflater.inflate(R.menu.conversation_secure_unverified, menu);
|
||||
inflater.inflate(R.menu.conversation_secure_no_identity, menu);
|
||||
}
|
||||
} else if (isSingleConversation()) {
|
||||
inflater.inflate(R.menu.conversation_insecure, menu);
|
||||
@ -435,16 +435,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
String subtitle = null;
|
||||
|
||||
if (isSingleConversation()) {
|
||||
|
||||
if (isEncryptedConversation) {
|
||||
title = AuthenticityCalculator.getAuthenticatedName(this,
|
||||
getRecipients().getPrimaryRecipient(),
|
||||
masterSecret);
|
||||
}
|
||||
|
||||
if (title == null || title.trim().length() == 0) {
|
||||
title = getRecipients().getPrimaryRecipient().getName();
|
||||
}
|
||||
|
||||
if (title == null || title.trim().length() == 0) {
|
||||
title = getRecipients().getPrimaryRecipient().getNumber();
|
||||
@ -513,10 +504,12 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
{
|
||||
sendButton.setImageResource(R.drawable.ic_send_encrypted_holo_light);
|
||||
this.isEncryptedConversation = true;
|
||||
this.isAuthenticatedConversation = KeyUtil.isIdentityKeyFor(this, masterSecret, getRecipients().getPrimaryRecipient());
|
||||
this.characterCalculator = new EncryptedCharacterCalculator();
|
||||
} else {
|
||||
sendButton.setImageResource(R.drawable.ic_send_holo_light);
|
||||
this.isEncryptedConversation = false;
|
||||
this.isAuthenticatedConversation = false;
|
||||
this.characterCalculator = new CharacterCalculator();
|
||||
}
|
||||
|
||||
@ -728,12 +721,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
return getRecipients() != null && !getRecipients().isSingleRecipient();
|
||||
}
|
||||
|
||||
private boolean isAuthenticatedSession() {
|
||||
return AuthenticityCalculator.isAuthenticated(this,
|
||||
getRecipients().getPrimaryRecipient(),
|
||||
masterSecret);
|
||||
}
|
||||
|
||||
private Recipients getRecipients() {
|
||||
try {
|
||||
if (isExistingConversation()) return this.recipients;
|
||||
|
@ -322,6 +322,7 @@ public class ConversationItem extends LinearLayout {
|
||||
intent.putExtra("recipient", messageRecord.getIndividualRecipient());
|
||||
intent.putExtra("body", messageRecord.getBody().getBody());
|
||||
intent.putExtra("thread_id", messageRecord.getThreadId());
|
||||
intent.putExtra("message_id", messageRecord.getId());
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
intent.putExtra("sent", messageRecord.isOutgoing());
|
||||
context.startActivity(intent);
|
||||
|
@ -21,10 +21,11 @@ public class DatabaseUpgradeActivity extends Activity {
|
||||
|
||||
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
|
||||
public static final int MMS_BODY_VERSION = 46;
|
||||
|
||||
public static final int TOFU_IDENTITIES_VERSION = 50;
|
||||
|
||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||
add(TOFU_IDENTITIES_VERSION);
|
||||
}};
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
|
@ -17,20 +17,32 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
/**
|
||||
* List item view for displaying user identity keys.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class IdentityKeyView extends RelativeLayout {
|
||||
public class IdentityKeyView extends RelativeLayout
|
||||
implements Recipient.RecipientModifiedListener
|
||||
{
|
||||
|
||||
private TextView identityName;
|
||||
private String identityKeyString;
|
||||
|
||||
private Recipients recipients;
|
||||
private IdentityKey identityKey;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
public IdentityKeyView(Context context) {
|
||||
super(context);
|
||||
@ -45,17 +57,30 @@ public class IdentityKeyView extends RelativeLayout {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
public void set(String name, String identityKeyString) {
|
||||
identityName.setText(name);
|
||||
this.identityKeyString = identityKeyString;
|
||||
public void set(IdentityDatabase.Identity identity) {
|
||||
this.recipients = identity.getRecipients();
|
||||
this.identityKey = identity.getIdentityKey();
|
||||
|
||||
this.recipients.addListener(this);
|
||||
|
||||
identityName.setText(recipients.toShortString());
|
||||
}
|
||||
|
||||
public String getIdentityKeyString() {
|
||||
return this.identityKeyString;
|
||||
public IdentityKey getIdentityKey() {
|
||||
return this.identityKey;
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.identityName = (TextView)findViewById(R.id.identity_name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
IdentityKeyView.this.identityName.setText(recipients.toShortString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuInflater;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
public abstract class KeyVerifyingActivity extends KeyScanningActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = this.getSupportMenuInflater();
|
||||
inflater.inflate(R.menu.verify_keys, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_session_verified: handleVerified(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void handleVerified();
|
||||
|
||||
}
|
@ -16,14 +16,19 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidVersionException;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeMessage;
|
||||
@ -42,36 +47,32 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
public class ReceiveKeyActivity extends PassphraseRequiredSherlockActivity {
|
||||
|
||||
private TextView descriptionText;
|
||||
private TextView signatureText;
|
||||
|
||||
private Button confirmButton;
|
||||
private Button cancelButton;
|
||||
private Button verifySessionButton;
|
||||
private Button verifyIdentityButton;
|
||||
|
||||
private Recipient recipient;
|
||||
private long threadId;
|
||||
private long messageId;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private KeyExchangeMessage keyExchangeMessage;
|
||||
private KeyExchangeProcessor keyExchangeProcessor;
|
||||
|
||||
private boolean sent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.receive_key_activity);
|
||||
|
||||
initializeResources();
|
||||
|
||||
try {
|
||||
initializeKey();
|
||||
initializeText();
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("ReceiveKeyActivity", ike);
|
||||
initializeCorruptedKeyText();
|
||||
} catch (InvalidVersionException ive) {
|
||||
initializeBadVersionText();
|
||||
Log.w("ReceiveKeyActivity", ive);
|
||||
}
|
||||
initializeListeners();
|
||||
}
|
||||
@ -83,64 +84,20 @@ public class ReceiveKeyActivity extends PassphraseRequiredSherlockActivity {
|
||||
}
|
||||
|
||||
private void initializeText() {
|
||||
if (keyExchangeProcessor.hasCompletedSession()) initializeTextForExistingSession();
|
||||
else initializeTextForNewSession();
|
||||
|
||||
initializeSignatureText();
|
||||
SpannableString spannableString = new SpannableString(descriptionText.getText() + " " +
|
||||
getString(R.string.ReceiveKeyActivity_you_may_wish_to_verify_this_contact));
|
||||
spannableString.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class);
|
||||
intent.putExtra("recipient", recipient);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
}
|
||||
}, descriptionText.getText().length()+1, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
private void initializeCorruptedKeyText() {
|
||||
descriptionText.setText(R.string.ReceiveKeyActivity_error_you_have_received_a_corrupted_public_key);
|
||||
confirmButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initializeBadVersionText() {
|
||||
descriptionText.setText(R.string.ReceiveKeyActivity_error_you_have_received_a_public_key_from_an_unsupported_version_of_the_protocol);
|
||||
confirmButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initializeSignatureText() {
|
||||
if (!keyExchangeMessage.hasIdentityKey()) {
|
||||
signatureText.setText(R.string.ReceiveKeyActivity_this_key_exchange_message_does_not_include_an_identity_signature);
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityKey identityKey = keyExchangeMessage.getIdentityKey();
|
||||
String identityName = DatabaseFactory.getIdentityDatabase(this).getNameForIdentity(masterSecret, identityKey);
|
||||
|
||||
if (identityName == null) {
|
||||
signatureText.setText(R.string.ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_but_you_do_not_yet_trust_it);
|
||||
} else {
|
||||
signatureText.setText(String.format(getString(R.string.ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_which_you_trust_for_s), identityName));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeTextForExistingSession() {
|
||||
if (keyExchangeProcessor.isRemoteKeyExchangeForExistingSession(keyExchangeMessage)) {
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_this_is_the_key_that_you_sent_to_start_your_current_encrypted_session_with_s), recipient.toShortString()));
|
||||
this.confirmButton.setVisibility(View.GONE);
|
||||
this.verifySessionButton.setVisibility(View.VISIBLE);
|
||||
this.verifyIdentityButton.setVisibility(View.VISIBLE);
|
||||
} else if (keyExchangeProcessor.isLocalKeyExchangeForExistingSession(keyExchangeMessage)) {
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_this_is_the_key_that_you_received_to_start_your_current_encrypted_session_with_s), recipient.toShortString()));
|
||||
this.confirmButton.setVisibility(View.GONE);
|
||||
this.verifySessionButton.setVisibility(View.VISIBLE);
|
||||
this.verifyIdentityButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_warning_you_already_have_an_encrypted_session), recipient.toShortString()));
|
||||
this.confirmButton.setVisibility(View.VISIBLE);
|
||||
this.verifyIdentityButton.setVisibility(View.GONE);
|
||||
this.verifySessionButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeTextForNewSession() {
|
||||
if (keyExchangeProcessor.hasInitiatedSession() && !this.sent)
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_previously_initiated), recipient.toShortString()));
|
||||
else if (keyExchangeProcessor.hasInitiatedSession() && this.sent)
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_you_have_initiated_a_key_exchange_message_with_s_but_have_not_yet_received_a_reply), recipient.toShortString()));
|
||||
else if (!keyExchangeProcessor.hasInitiatedSession() && !this.sent)
|
||||
descriptionText.setText(String.format(getString(R.string.ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_no_existing_session), recipient.toShortString()));
|
||||
descriptionText.setText(spannableString);
|
||||
descriptionText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private void initializeKey() throws InvalidKeyException, InvalidVersionException {
|
||||
@ -150,53 +107,47 @@ public class ReceiveKeyActivity extends PassphraseRequiredSherlockActivity {
|
||||
|
||||
private void initializeResources() {
|
||||
this.descriptionText = (TextView) findViewById(R.id.description_text);
|
||||
this.signatureText = (TextView) findViewById(R.id.signature_text);
|
||||
this.confirmButton = (Button) findViewById(R.id.ok_button);
|
||||
this.cancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
this.verifyIdentityButton = (Button) findViewById(R.id.verify_identity_button);
|
||||
this.verifySessionButton = (Button) findViewById(R.id.verify_session_button);
|
||||
this.recipient = getIntent().getParcelableExtra("recipient");
|
||||
this.threadId = getIntent().getLongExtra("thread_id", -1);
|
||||
this.messageId = getIntent().getLongExtra("message_id", -1);
|
||||
this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
this.sent = getIntent().getBooleanExtra("sent", false);
|
||||
this.keyExchangeProcessor = new KeyExchangeProcessor(this, masterSecret, recipient);
|
||||
}
|
||||
|
||||
private void initializeListeners() {
|
||||
this.confirmButton.setOnClickListener(new OkListener());
|
||||
this.cancelButton.setOnClickListener(new CancelListener());
|
||||
this.verifyIdentityButton.setOnClickListener(new VerifyIdentityListener());
|
||||
this.verifySessionButton.setOnClickListener(new VerifySessionListener());
|
||||
}
|
||||
|
||||
private class VerifyIdentityListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class);
|
||||
intent.putExtra("recipient", recipient);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class VerifySessionListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(ReceiveKeyActivity.this, VerifyKeysActivity.class);
|
||||
intent.putExtra("recipient", recipient);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class OkListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AsyncTask<Void, Void, Void> () {
|
||||
private ProgressDialog dialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(ReceiveKeyActivity.this, "Processing",
|
||||
"Processing key exchange...", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||
.markAsProcessedKeyExchange(messageId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private class CancelListener implements View.OnClickListener {
|
||||
@ -205,5 +156,4 @@ public class ReceiveKeyActivity extends PassphraseRequiredSherlockActivity {
|
||||
ReceiveKeyActivity.this.finish();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,89 +16,16 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Activity for reviewing/managing saved identity keys.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ReviewIdentitiesActivity extends SherlockListActivity {
|
||||
|
||||
private static final int MENU_OPTION_DELETE = 2;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private MasterCipher masterCipher;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
this.setContentView(R.layout.review_identities);
|
||||
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
initializeResources();
|
||||
registerForContextMenu(this.getListView());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
masterCipher = null;
|
||||
System.gc();
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
viewIdentity(((IdentityKeyView)view).getIdentityKeyString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
menu.add(0, MENU_OPTION_DELETE, Menu.NONE, R.string.delete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(android.view.MenuItem item) {
|
||||
Cursor cursor = ((CursorAdapter)this.getListAdapter()).getCursor();
|
||||
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_KEY));
|
||||
String identityName = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_NAME));
|
||||
|
||||
|
||||
switch(item.getItemId()) {
|
||||
case MENU_OPTION_DELETE:
|
||||
deleteIdentity(identityName, identityKeyString);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public class ReviewIdentitiesActivity extends SherlockFragmentActivity {
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.review_identities);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,99 +36,4 @@ public class ReviewIdentitiesActivity extends SherlockListActivity {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void initializeResources() {
|
||||
this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
|
||||
Cursor cursor = DatabaseFactory.getIdentityDatabase(this).getIdentities();
|
||||
this.startManagingCursor(cursor);
|
||||
this.setListAdapter(new IdentitiesListAdapter(this, cursor));
|
||||
}
|
||||
|
||||
private void viewIdentity(String identityKeyString) {
|
||||
try {
|
||||
Intent viewIntent = new Intent(this, ViewIdentityActivity.class);
|
||||
viewIntent.putExtra("identity_key", new IdentityKey(Base64.decode(identityKeyString), 0));
|
||||
startActivity(viewIntent);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("ReviewIdentitiesActivity", ike);
|
||||
Toast.makeText(this, R.string.ReviewIdentitiesActivity_unable_to_view_corrupted_identity_key_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
} catch (IOException e) {
|
||||
Log.w("ReviewIdentitiesActivity", e);
|
||||
Toast.makeText(this, R.string.ReviewIdentitiesActivity_unable_to_view_corrupted_identity_key_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteIdentity(String name, String keyString) {
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
|
||||
alertDialog.setTitle(R.string.ReviewIdentitiesActivity_delete_identity);
|
||||
alertDialog.setMessage(R.string.ReviewIdentitiesActivity_delete_identity_are_you_sure_you_want_to_delete_this_identity_key);
|
||||
alertDialog.setCancelable(true);
|
||||
alertDialog.setNegativeButton(R.string.no, null);
|
||||
alertDialog.setPositiveButton(R.string.yes, new DeleteIdentityListener(name, keyString));
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private class DeleteIdentityListener implements OnClickListener {
|
||||
private final String name;
|
||||
private final String keyString;
|
||||
|
||||
public DeleteIdentityListener(String name, String keyString) {
|
||||
this.name = name;
|
||||
this.keyString = keyString;
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface arg0, int arg1) {
|
||||
DatabaseFactory.getIdentityDatabase(ReviewIdentitiesActivity.this)
|
||||
.deleteIdentity(name, keyString);
|
||||
}
|
||||
}
|
||||
|
||||
private class IdentitiesListAdapter extends CursorAdapter {
|
||||
|
||||
public IdentitiesListAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
}
|
||||
|
||||
public IdentitiesListAdapter(Context context, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
IdentityKey identityKey;
|
||||
boolean valid;
|
||||
|
||||
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_KEY));
|
||||
String identityName = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_NAME));
|
||||
|
||||
try {
|
||||
String mac = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.MAC));
|
||||
valid = masterCipher.verifyMacFor(identityName + identityKeyString, Base64.decode(mac));
|
||||
identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("ReviewIdentitiesActivity",ike);
|
||||
valid = false;
|
||||
} catch (IOException e) {
|
||||
Log.w("ReviewIdentitiesActivity",e);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
identityName = getString(R.string.ReviewIdentitiesActivity_invalid_identity);
|
||||
|
||||
((IdentityKeyView)view).set(identityName, identityKeyString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
IdentityKeyView identityKeyView = new IdentityKeyView(context);
|
||||
bindView(identityKeyView, context, cursor);
|
||||
return identityKeyView;
|
||||
}
|
||||
}
|
||||
}
|
90
src/org/thoughtcrime/securesms/ReviewIdentitiesFragment.java
Normal file
90
src/org/thoughtcrime/securesms/ReviewIdentitiesFragment.java
Normal file
@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.loaders.IdentityLoader;
|
||||
|
||||
public class ReviewIdentitiesFragment extends SherlockListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>
|
||||
{
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
return inflater.inflate(R.layout.review_identities_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
this.masterSecret = getSherlockActivity().getIntent().getParcelableExtra("master_secret");
|
||||
|
||||
initializeListAdapter();
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
Intent viewIntent = new Intent(getActivity(), ViewIdentityActivity.class);
|
||||
viewIntent.putExtra("identity_key", ((IdentityKeyView)view).getIdentityKey());
|
||||
startActivity(viewIntent);
|
||||
}
|
||||
|
||||
private void initializeListAdapter() {
|
||||
this.setListAdapter(new IdentitiesListAdapter(getActivity(), null, masterSecret));
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new IdentityLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
((CursorAdapter)getListAdapter()).changeCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
((CursorAdapter)getListAdapter()).changeCursor(null);
|
||||
}
|
||||
|
||||
private class IdentitiesListAdapter extends CursorAdapter {
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public IdentitiesListAdapter(Context context, Cursor cursor, MasterSecret masterSecret) {
|
||||
super(context, cursor);
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
IdentityDatabase.Reader reader = DatabaseFactory.getIdentityDatabase(context)
|
||||
.readerFor(masterSecret, cursor);
|
||||
|
||||
((IdentityKeyView)view).set(reader.getCurrent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
IdentityKeyView identityKeyView = new IdentityKeyView(context);
|
||||
bindView(identityKeyView, context, cursor);
|
||||
return identityKeyView;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
/**
|
||||
* Activity that provides interface for users to save
|
||||
* identity keys they receive.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SaveIdentityActivity extends PassphraseRequiredSherlockActivity {
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private IdentityKey identityKey;
|
||||
|
||||
private EditText identityName;
|
||||
private Button okButton;
|
||||
private Button cancelButton;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.save_identity_activity);
|
||||
|
||||
initializeResources();
|
||||
initializeListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
String nameSuggestion = getIntent().getStringExtra("name_suggestion");
|
||||
|
||||
this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
|
||||
this.identityKey = (IdentityKey)getIntent().getParcelableExtra("identity_key");
|
||||
this.identityName = (EditText)findViewById(R.id.identity_name);
|
||||
this.okButton = (Button)findViewById(R.id.ok_button);
|
||||
this.cancelButton = (Button)findViewById(R.id.cancel_button);
|
||||
|
||||
if ((nameSuggestion != null) && (nameSuggestion.trim().length() > 0)) {
|
||||
this.identityName.setText(nameSuggestion);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeListeners() {
|
||||
this.okButton.setOnClickListener(new OkListener());
|
||||
this.cancelButton.setOnClickListener(new CancelListener());
|
||||
}
|
||||
|
||||
private class OkListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (identityName.getText() == null || identityName.getText().toString().trim().length() == 0) {
|
||||
Toast.makeText(SaveIdentityActivity.this,
|
||||
R.string.SaveIdentityActivity_you_must_specify_a_name_for_this_identity_exclamation,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DatabaseFactory.getIdentityDatabase(SaveIdentityActivity.this).saveIdentity(masterSecret, identityKey, identityName.getText().toString());
|
||||
} catch (InvalidKeyException e) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(SaveIdentityActivity.this);
|
||||
builder.setTitle(R.string.SaveIdentityActivity_identity_name_exists_exclamation);
|
||||
builder.setMessage(R.string.SaveIdentityActivity_an_identity_key_with_the_specified_name_already_exists);
|
||||
builder.setPositiveButton(R.string.SaveIdentityActivity_manage_identities,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(SaveIdentityActivity.this, ReviewIdentitiesActivity.class);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class CancelListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,13 +16,11 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
@ -35,7 +33,7 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class VerifyIdentityActivity extends KeyVerifyingActivity {
|
||||
public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
|
||||
private Recipient recipient;
|
||||
private MasterSecret masterSecret;
|
||||
@ -46,6 +44,7 @@ public class VerifyIdentityActivity extends KeyVerifyingActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.verify_identity_activity);
|
||||
|
||||
initializeResources();
|
||||
@ -59,35 +58,12 @@ public class VerifyIdentityActivity extends KeyVerifyingActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleVerified() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(R.string.VerifyIdentityActivity_mark_identity_verified_question);
|
||||
builder.setMessage(R.string.VerifyIdentityActivity_are_you_sure_you_have_validated_the_recipients_identity_fingerprint_and_would_like_to_mark_it_as_verified);
|
||||
|
||||
builder.setPositiveButton(R.string.VerifyIdentityActivity_mark_verified,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
SessionRecord sessionRecord = new SessionRecord(VerifyIdentityActivity.this,
|
||||
masterSecret, recipient);
|
||||
IdentityKey identityKey = sessionRecord.getIdentityKey();
|
||||
String recipientName = recipient.getName();
|
||||
|
||||
Intent intent = new Intent(VerifyIdentityActivity.this,
|
||||
SaveIdentityActivity.class);
|
||||
|
||||
intent.putExtra("name_suggestion", recipientName);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
intent.putExtra("identity_key", identityKey);
|
||||
|
||||
startActivity(intent);
|
||||
finish();
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeLocalIdentityKey() {
|
||||
|
@ -1,180 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.util.Dialogs;
|
||||
import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
|
||||
/**
|
||||
* Activity for verifying identities keys as they are imported.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class VerifyImportedIdentityActivity extends KeyScanningActivity {
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private String contactName;
|
||||
private IdentityKey identityKey;
|
||||
private EditText identityName;
|
||||
private TextView identityFingerprint;
|
||||
|
||||
private Button compareButton;
|
||||
private Button verifiedButton;
|
||||
private Button cancelButton;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.verify_imported_identity_activity);
|
||||
|
||||
initializeResources();
|
||||
initializeFingerprints();
|
||||
initializeListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
MemoryCleaner.clean(masterSecret);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void initializeListeners() {
|
||||
verifiedButton.setOnClickListener(new VerifiedButtonListener());
|
||||
cancelButton.setOnClickListener(new CancelButtonListener());
|
||||
compareButton.setOnClickListener(new CompareButtonListener());
|
||||
}
|
||||
|
||||
private void initializeFingerprints() {
|
||||
if (contactName != null)
|
||||
identityName.setText(contactName);
|
||||
identityFingerprint.setText(identityKey.getFingerprint());
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret");
|
||||
identityFingerprint = (TextView)findViewById(R.id.imported_identity);
|
||||
identityName = (EditText)findViewById(R.id.identity_name);
|
||||
identityKey = (IdentityKey)this.getIntent().getParcelableExtra("identity_key");
|
||||
contactName = (String)this.getIntent().getStringExtra("contact_name");
|
||||
verifiedButton = (Button)findViewById(R.id.verified_button);
|
||||
cancelButton = (Button)findViewById(R.id.cancel_button);
|
||||
compareButton = (Button)findViewById(R.id.compare_button);
|
||||
}
|
||||
|
||||
private class CancelButtonListener implements View.OnClickListener {
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private class CompareButtonListener implements View.OnClickListener {
|
||||
public void onClick(View v) {
|
||||
registerForContextMenu(compareButton);
|
||||
compareButton.showContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private class VerifiedButtonListener implements View.OnClickListener {
|
||||
public void onClick(View v) {
|
||||
if (identityName.getText() == null || identityName.getText().length() == 0) {
|
||||
Toast.makeText(VerifyImportedIdentityActivity.this,
|
||||
R.string.VerifyImportedIdentityActivity_you_must_specify_a_name_for_this_contact_exclamation,
|
||||
Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(VerifyImportedIdentityActivity.this);
|
||||
dialogBuilder.setTitle(R.string.VerifyImportedIdentityActivity_save_identity_key_question);
|
||||
dialogBuilder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
dialogBuilder.setMessage(String.format(getString(R.string.VerifyImportedIdentityActivity_are_you_sure_that_you_would_like_to_mark_this_as_a_valid_identity_key_for_all_future_correspondence_with_s), identityName.getText()));
|
||||
dialogBuilder.setCancelable(true);
|
||||
dialogBuilder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface arg0, int arg1) {
|
||||
try {
|
||||
DatabaseFactory.getIdentityDatabase(VerifyImportedIdentityActivity.this).saveIdentity(masterSecret, identityKey, identityName.getText().toString());
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w("VerifiedButtonListener", ike);
|
||||
Dialogs.displayAlert(VerifyImportedIdentityActivity.this,
|
||||
getString(R.string.VerifyImportedIdentityActivity_error_saving_identity_key_exclamation),
|
||||
getString(R.string.VerifyImportedIdentityActivity_this_identity_key_or_an_identity_key_with_the_same_name_already_exists_please_edit_your_key_database),
|
||||
android.R.drawable.ic_dialog_alert);
|
||||
return;
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
dialogBuilder.setNegativeButton(R.string.no, null);
|
||||
dialogBuilder.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getScanString() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_scan_to_compare);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayString() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_get_scanned_to_compare);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IdentityKey getIdentityKeyToCompare() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IdentityKey getIdentityKeyToDisplay() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNotVerifiedMessage() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_warning_the_scanned_key_does_not_match_exclamation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNotVerifiedTitle() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_not_verified_exclamation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVerifiedMessage() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_the_scanned_key_matches_exclamation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVerifiedTitle() {
|
||||
return getString(R.string.VerifyImportedIdentityActivity_verified_exclamation);
|
||||
}
|
||||
}
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -34,7 +32,7 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class VerifyKeysActivity extends KeyVerifyingActivity {
|
||||
public class VerifyKeysActivity extends KeyScanningActivity {
|
||||
|
||||
private byte[] yourFingerprintBytes;
|
||||
private byte[] theirFingerprintBytes;
|
||||
@ -60,28 +58,6 @@ public class VerifyKeysActivity extends KeyVerifyingActivity {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleVerified() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(R.string.VerifyKeysActivity_mark_session_verified_question);
|
||||
builder.setMessage(R.string.VerifyKeysActivity_are_you_sure_that_you_have_validated_these_fingerprints_and_would_like_to_mark_this_session_as_verified);
|
||||
builder.setPositiveButton(R.string.VerifyKeysActivity_mark_verified,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
SessionRecord sessionRecord = new SessionRecord(VerifyKeysActivity.this, masterSecret,
|
||||
recipient);
|
||||
sessionRecord.setVerifiedSessionKey(true);
|
||||
sessionRecord.save();
|
||||
VerifyKeysActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.recipient = (Recipient)this.getIntent().getParcelableExtra("recipient");
|
||||
this.masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret");
|
||||
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.keys.SessionRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
public class AuthenticityCalculator {
|
||||
|
||||
private static boolean isAuthenticatedIdentity(Context context,
|
||||
MasterSecret masterSecret,
|
||||
IdentityKey identityKey)
|
||||
{
|
||||
String identityName = DatabaseFactory.getIdentityDatabase(context)
|
||||
.getNameForIdentity(masterSecret, identityKey);
|
||||
|
||||
if (identityName == null) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
public static String getAuthenticatedName(Context context,
|
||||
Recipient recipient,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
SessionRecord session = new SessionRecord(context, masterSecret, recipient);
|
||||
return DatabaseFactory.getIdentityDatabase(context)
|
||||
.getNameForIdentity(masterSecret, session.getIdentityKey());
|
||||
}
|
||||
|
||||
public static boolean isAuthenticated(Context context,
|
||||
Recipient recipient,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
SessionRecord session = new SessionRecord(context, masterSecret, recipient);
|
||||
|
||||
if (session.isVerifiedSession()) {
|
||||
return true;
|
||||
} else if (session.getIdentityKey() != null) {
|
||||
return isAuthenticatedIdentity(context, masterSecret, session.getIdentityKey());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -307,9 +307,7 @@ public class DecryptingQueue {
|
||||
|
||||
if (processor.isStale(keyExchangeMessage)) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
|
||||
} else if (!processor.hasCompletedSession() ||
|
||||
processor.hasSameSessionIdentity(keyExchangeMessage))
|
||||
{
|
||||
} else if (processor.isTrusted(keyExchangeMessage)) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId);
|
||||
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.keys.LocalKeyRecord;
|
||||
import org.thoughtcrime.securesms.database.keys.RemoteKeyRecord;
|
||||
import org.thoughtcrime.securesms.database.keys.SessionRecord;
|
||||
@ -58,16 +59,13 @@ public class KeyExchangeProcessor {
|
||||
this.sessionRecord = new SessionRecord(context, masterSecret, recipient);
|
||||
}
|
||||
|
||||
public boolean hasCompletedSession() {
|
||||
return sessionRecord.getLocalFingerprint() != null;
|
||||
public boolean isTrusted(KeyExchangeMessage message) {
|
||||
if (!message.hasIdentityKey()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasSameSessionIdentity(KeyExchangeMessage message) {
|
||||
return
|
||||
(this.sessionRecord.getIdentityKey() != null) &&
|
||||
(message.getIdentityKey() != null) &&
|
||||
(this.sessionRecord.getIdentityKey().equals(message.getIdentityKey()) &&
|
||||
!isRemoteKeyExchangeForExistingSession(message));
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean hasInitiatedSession() {
|
||||
@ -78,14 +76,6 @@ public class KeyExchangeProcessor {
|
||||
return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null;
|
||||
}
|
||||
|
||||
public boolean isRemoteKeyExchangeForExistingSession(KeyExchangeMessage message) {
|
||||
return Arrays.areEqual(message.getPublicKey().getFingerprintBytes(), sessionRecord.getRemoteFingerprint());
|
||||
}
|
||||
|
||||
public boolean isLocalKeyExchangeForExistingSession(KeyExchangeMessage message) {
|
||||
return Arrays.areEqual(message.getPublicKey().getFingerprintBytes(), sessionRecord.getLocalFingerprint());
|
||||
}
|
||||
|
||||
public boolean isStale(KeyExchangeMessage message) {
|
||||
int responseKeyId = Conversions.highBitsToMedium(message.getPublicKey().getId());
|
||||
|
||||
@ -121,6 +111,9 @@ public class KeyExchangeProcessor {
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, message.getIdentityKey());
|
||||
|
||||
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
||||
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
|
@ -92,6 +92,14 @@ public class KeyUtil {
|
||||
(SessionRecord.hasSession(context, recipient));
|
||||
}
|
||||
|
||||
public static boolean isIdentityKeyFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
Recipient recipient)
|
||||
{
|
||||
return isSessionFor(context, recipient) &&
|
||||
new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null;
|
||||
}
|
||||
|
||||
public static LocalKeyRecord initializeRecordFor(Recipient recipient, Context context, MasterSecret masterSecret) {
|
||||
Log.w("KeyUtil", "Initializing local key pairs...");
|
||||
try {
|
||||
|
@ -26,9 +26,12 @@ import android.util.Log;
|
||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.keys.SessionRecord;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@ -47,7 +50,8 @@ public class DatabaseFactory {
|
||||
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 DATABASE_VERSION = 8;
|
||||
private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9;
|
||||
private static final int DATABASE_VERSION = 9;
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
@ -357,6 +361,36 @@ public class DatabaseFactory {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipientId);
|
||||
IdentityKey identityKey = sessionRecord.getIdentityKey();
|
||||
|
||||
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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
|
||||
@ -566,6 +600,11 @@ public class DatabaseFactory {
|
||||
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);");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -38,12 +41,14 @@ public class IdentityDatabase extends Database {
|
||||
|
||||
private static final String TABLE_NAME = "identities";
|
||||
private static final String ID = "_id";
|
||||
public static final String RECIPIENT = "recipient";
|
||||
public static final String IDENTITY_KEY = "key";
|
||||
public static final String IDENTITY_NAME = "name";
|
||||
public static final String MAC = "mac";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
IDENTITY_KEY + " TEXT UNIQUE, " + IDENTITY_NAME + " TEXT UNIQUE, " +
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
RECIPIENT + " INTEGER UNIQUE, " +
|
||||
IDENTITY_KEY + " TEXT, " +
|
||||
MAC + " TEXT);";
|
||||
|
||||
public IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
@ -60,69 +65,127 @@ public class IdentityDatabase extends Database {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public String getNameForIdentity(MasterSecret masterSecret, IdentityKey identityKey) {
|
||||
if (identityKey == null)
|
||||
return null;
|
||||
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
public boolean isValidIdentity(MasterSecret masterSecret,
|
||||
Recipient recipient,
|
||||
IdentityKey theirIdentity)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
String number = recipient.getNumber();
|
||||
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Cursor cursor = null;
|
||||
|
||||
Log.w("IdentityDatabase", "Querying for: " + Base64.encodeBytes(identityKey.serialize()));
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, IDENTITY_KEY + " = ?", new String[] {Base64.encodeBytes(identityKey.serialize())}, null, null, null);
|
||||
cursor = database.query(TABLE_NAME, null, RECIPIENT + " = ?",
|
||||
new String[] {recipientId+""}, null, null,null);
|
||||
|
||||
if (cursor == null || !cursor.moveToFirst())
|
||||
return null;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
|
||||
|
||||
String identityName = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_NAME));
|
||||
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
byte[] mac = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(MAC)));
|
||||
|
||||
if (!masterCipher.verifyMacFor(identityName + identityKeyString, mac)) {
|
||||
Log.w("IdentityDatabase", "Mac check failed!");
|
||||
return null;
|
||||
if (!masterCipher.verifyMacFor(recipientId + serializedIdentity, Base64.decode(mac))) {
|
||||
Log.w("IdentityDatabase", "MAC failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.w("IdentityDatabase", "Returning identity name: " + identityName);
|
||||
return identityName;
|
||||
IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
||||
return ourIdentity.equals(theirIdentity);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("IdentityDatabase", e);
|
||||
return null;
|
||||
return false;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("IdentityDatabase", e);
|
||||
return false;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void saveIdentity(MasterSecret masterSecret, IdentityKey identityKey, String tagName) throws InvalidKeyException {
|
||||
public void saveIdentity(MasterSecret masterSecret, Recipient recipient, IdentityKey identityKey)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String number = recipient.getNumber();
|
||||
long recipientId = DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number);
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
||||
String macString = Base64.encodeBytes(masterCipher.getMacFor(tagName + identityKeyString));
|
||||
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
|
||||
identityKeyString));
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(RECIPIENT, recipientId);
|
||||
contentValues.put(IDENTITY_KEY, identityKeyString);
|
||||
contentValues.put(IDENTITY_NAME, tagName);
|
||||
contentValues.put(MAC, macString);
|
||||
|
||||
long id = database.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
if (id == -1)
|
||||
throw new InvalidKeyException("Error inserting key!");
|
||||
database.replace(TABLE_NAME, null, contentValues);
|
||||
|
||||
context.getContentResolver().notifyChange(CHANGE_URI, null);
|
||||
}
|
||||
|
||||
public void deleteIdentity(String name, String keyString) {
|
||||
public void deleteIdentity(long id) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String where = IDENTITY_NAME + " = ? AND " + IDENTITY_KEY + " = ?";
|
||||
String[] args = new String[] {name, keyString};
|
||||
|
||||
database.delete(TABLE_NAME, where, args);
|
||||
database.delete(TABLE_NAME, ID_WHERE, new String[] {id+""});
|
||||
|
||||
context.getContentResolver().notifyChange(CHANGE_URI, null);
|
||||
}
|
||||
|
||||
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
|
||||
return new Reader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public class Reader {
|
||||
private final Cursor cursor;
|
||||
private final MasterCipher cipher;
|
||||
|
||||
public Reader(MasterSecret masterSecret, Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
this.cipher = new MasterCipher(masterSecret);
|
||||
}
|
||||
|
||||
public Identity getCurrent() {
|
||||
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT));
|
||||
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId + "", true);
|
||||
|
||||
try {
|
||||
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
|
||||
|
||||
if (!cipher.verifyMacFor(recipientId + identityKeyString, Base64.decode(mac))) {
|
||||
return new Identity(recipients, null);
|
||||
}
|
||||
|
||||
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
|
||||
return new Identity(recipients, identityKey);
|
||||
} catch (IOException e) {
|
||||
Log.w("IdentityDatabase", e);
|
||||
return new Identity(recipients, null);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("IdentityDatabase", e);
|
||||
return new Identity(recipients, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Identity {
|
||||
private final Recipients recipients;
|
||||
private final IdentityKey identityKey;
|
||||
|
||||
public Identity(Recipients recipients, IdentityKey identityKey) {
|
||||
this.recipients = recipients;
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
public Recipients getRecipients() {
|
||||
return recipients;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ import org.thoughtcrime.securesms.crypto.IdentityKey;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.database.keys.Record;
|
||||
import org.thoughtcrime.securesms.database.keys.SessionKey;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
@ -56,23 +54,27 @@ public class SessionRecord extends Record {
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public SessionRecord(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
super(context, getFileNameForRecipient(context, recipient));
|
||||
this(context, masterSecret, getRecipientId(context, recipient));
|
||||
}
|
||||
|
||||
public SessionRecord(Context context, MasterSecret masterSecret, long recipientId) {
|
||||
super(context, recipientId+"");
|
||||
this.masterSecret = masterSecret;
|
||||
this.sessionVersion = 31337;
|
||||
loadData();
|
||||
}
|
||||
|
||||
public static void delete(Context context, Recipient recipient) {
|
||||
Record.delete(context, getFileNameForRecipient(context, recipient));
|
||||
Record.delete(context, getRecipientId(context, recipient)+"");
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, Recipient recipient) {
|
||||
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
|
||||
return Record.hasRecord(context, getFileNameForRecipient(context, recipient));
|
||||
Log.w("LocalKeyRecord", "Checking: " + getRecipientId(context, recipient));
|
||||
return Record.hasRecord(context, getRecipientId(context, recipient)+"");
|
||||
}
|
||||
|
||||
private static String getFileNameForRecipient(Context context, Recipient recipient) {
|
||||
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(recipient.getNumber()) + "";
|
||||
private static long getRecipientId(Context context, Recipient recipient) {
|
||||
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(recipient.getNumber());
|
||||
}
|
||||
|
||||
public void setSessionKey(SessionKey sessionKeyRecord) {
|
||||
@ -116,9 +118,9 @@ public class SessionRecord extends Record {
|
||||
return this.identityKey;
|
||||
}
|
||||
|
||||
public void setVerifiedSessionKey(boolean verifiedSessionKey) {
|
||||
this.verifiedSessionKey = verifiedSessionKey;
|
||||
}
|
||||
// public void setVerifiedSessionKey(boolean verifiedSessionKey) {
|
||||
// this.verifiedSessionKey = verifiedSessionKey;
|
||||
// }
|
||||
|
||||
public boolean isVerifiedSession() {
|
||||
return this.verifiedSessionKey;
|
||||
@ -130,8 +132,8 @@ public class SessionRecord extends Record {
|
||||
}
|
||||
|
||||
private boolean isValidVersionMarker(int versionMarker) {
|
||||
for (int i=0;i<VALID_VERSION_MARKERS.length;i++)
|
||||
if (versionMarker == VALID_VERSION_MARKERS[i])
|
||||
for (int VALID_VERSION_MARKER : VALID_VERSION_MARKERS)
|
||||
if (versionMarker == VALID_VERSION_MARKER)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@ -199,7 +201,7 @@ public class SessionRecord extends Record {
|
||||
|
||||
if (versionMarker >= 0X55555556) {
|
||||
readIdentityKey(in);
|
||||
this.verifiedSessionKey = (readInteger(in) == 1) ? true : false;
|
||||
this.verifiedSessionKey = (readInteger(in) == 1);
|
||||
}
|
||||
|
||||
if (in.available() != 0)
|
||||
@ -209,7 +211,7 @@ public class SessionRecord extends Record {
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w("SessionRecord", "No session information found.");
|
||||
return;
|
||||
// XXX
|
||||
} catch (IOException ioe) {
|
||||
Log.w("keyrecord", ioe);
|
||||
// XXX
|
||||
|
@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.database.loaders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
|
||||
public class IdentityLoader extends CursorLoader {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public IdentityLoader(Context context) {
|
||||
super(context);
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
return DatabaseFactory.getIdentityDatabase(context).getIdentities();
|
||||
}
|
||||
|
||||
}
|
@ -108,7 +108,7 @@ public class SmsReceiver {
|
||||
|
||||
if (processor.isStale(keyExchangeMessage)) {
|
||||
message.setStale(true);
|
||||
} else if (!processor.hasCompletedSession() || processor.hasSameSessionIdentity(keyExchangeMessage)) {
|
||||
} else if (processor.isTrusted(keyExchangeMessage)) {
|
||||
message.setProcessed(true);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = storeStandardMessage(masterSecret, message);
|
||||
|
Loading…
x
Reference in New Issue
Block a user