Add ability to disable local encryption passphrase.

master
Moxie Marlinspike 2013-07-01 10:15:36 -07:00
parent 68b82c168e
commit d97252d8d6
8 changed files with 186 additions and 90 deletions

View File

@ -126,7 +126,6 @@
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:launchMode="singleInstance"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".VerifyKeysActivity"

View File

@ -21,6 +21,7 @@
android:orientation="vertical">
<TextView style="@style/Registration.Label"
android:id="@+id/old_passphrase_label"
android:layout_width="fill_parent"
android:textAllCaps="true"
android:text="@string/change_passphrase_activity__old_passphrase" />

View File

@ -14,6 +14,13 @@
<string name="ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages">Are you sure you would like to immediately trim all conversation threads to the %s most recent messages?</string>
<string name="ApplicationPreferencesActivity_delete">Delete</string>
<string name="ApplicationPreferencesActivity_my">My</string>
<string name="ApplicationPreferencesActivity_disable_storage_encryption">Disable storage encryption?</string>
<string name="ApplicationPreferencesActivity_warning_this_will_disable_storage_encryption_for_all_messages">
Warning, this will disable storage encryption for all messages and keys. Your encrypted
sessions will continue to function, but anyone with physical access to your device will be
able to access them.
</string>
<string name="ApplicationPreferencesActivity_disable">Disable</string>
<!-- AttachmentTypeSelectorAdapter -->

View File

@ -115,23 +115,30 @@
<PreferenceCategory android:title="@string/preferences__passphrase">
<Preference android:key="pref_change_passphrase"
android:title="@string/preferences__change_passphrase"
android:summary="@string/preferences__change_my_passphrase"/>
<Preference android:key="pref_change_passphrase"
android:title="@string/preferences__change_passphrase"
android:summary="@string/preferences__change_my_passphrase"
android:dependency="pref_disable_passphrase"/>
<CheckBoxPreference android:defaultValue="false"
android:key="pref_timeout_passphrase"
android:summary="@string/preferences__forget_passphrase_from_memory_after_some_interval"
android:title="@string/preferences__timeout_passphrase" />
<CheckBoxPreference android:key="pref_disable_passphrase"
android:defaultValue="false"
android:title="Disable Passphrase"
android:disableDependentsState="true"
android:summary="Disable local encryption of messages and keys"/>
<CheckBoxPreference android:defaultValue="false"
android:key="pref_timeout_passphrase"
android:summary="@string/preferences__forget_passphrase_from_memory_after_some_interval"
android:title="@string/preferences__timeout_passphrase"
android:dependency="pref_disable_passphrase"/>
<org.thoughtcrime.securesms.preferences.PassphraseTimeoutPreference
android:key="pref_timeout_interval"
android:defaultValue="300"
android:title="@string/preferences__pref_timeout_interval_title"
android:summary="@string/preferences__the_amount_of_time_to_wait_before_forgetting_passphrase"
android:dependency="pref_timeout_passphrase"
android:dialogTitle="@string/preferences__pref_timeout_interval_dialogtitle" />
<org.thoughtcrime.securesms.preferences.PassphraseTimeoutPreference
android:key="pref_timeout_interval"
android:defaultValue="300"
android:title="@string/preferences__pref_timeout_interval_title"
android:summary="@string/preferences__the_amount_of_time_to_wait_before_forgetting_passphrase"
android:dependency="pref_timeout_passphrase"
android:dialogTitle="@string/preferences__pref_timeout_interval_dialogtitle" />
</PreferenceCategory>
@ -142,16 +149,6 @@
android:title="@string/preferences__complete_key_exchanges"
android:summary="@string/preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key" />
<PreferenceScreen android:title="@string/preferences__identity_key_settings">
<Preference android:key="pref_view_identity"
android:title="@string/preferences__view_my_identity_key"
android:summary="@string/preferences__view_my_identity_key"/>
<Preference android:key="pref_manage_identity"
android:title="@string/preferences__manage_identity_keys"
android:summary="@string/preferences__manage_configured_identity_keys"/>
</PreferenceScreen>
<PreferenceScreen android:title="@string/preferences__advanced_mms_access_point_names">
<CheckBoxPreference android:key="pref_use_local_apns"
android:defaultValue="false"

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
@ -34,9 +35,9 @@ import android.widget.Toast;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
@ -53,7 +54,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static final int PICK_IDENTITY_CONTACT = 1;
private static final int PICK_IDENTITY_CONTACT = 1;
private static final int ENABLE_PASSPHRASE_ACTIVITY = 2;
public static final String RINGTONE_PREF = "pref_key_ringtone";
public static final String VIBRATE_PREF = "pref_key_vibrate";
@ -73,9 +75,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
private static final String DISPLAY_CATEGORY_PREF = "pref_display_category";
private static final String VIEW_MY_IDENTITY_PREF = "pref_view_identity";
private static final String MANAGE_IDENTITIES_PREF = "pref_manage_identity";
private static final String CHANGE_PASSPHRASE_PREF = "pref_change_passphrase";
public static final String DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase";
public static final String USE_LOCAL_MMS_APNS_PREF = "pref_use_local_apns";
public static final String MMSC_HOST_PREF = "pref_apn_mmsc_host";
@ -83,7 +84,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
public static final String MMSC_PROXY_PORT_PREF = "pref_apn_mms_proxy_port";
public static final String SMS_DELIVERY_REPORT_PREF = "pref_delivery_report_sms";
public static final String MMS_DELIVERY_REPORT_PREF = "pref_delivery_report_mms";
public static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
@ -110,16 +110,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
initializeIdentitySelection();
initializeEditTextSummaries();
this.findPreference(VIEW_MY_IDENTITY_PREF)
.setOnPreferenceClickListener(new ViewMyIdentityClickListener());
this.findPreference(MANAGE_IDENTITIES_PREF)
.setOnPreferenceClickListener(new ManageIdentitiesClickListener());
this.findPreference(CHANGE_PASSPHRASE_PREF)
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
this.findPreference(THREAD_TRIM_NOW)
.setOnPreferenceClickListener(new TrimNowClickListener());
this.findPreference(THREAD_TRIM_LENGTH)
.setOnPreferenceChangeListener(new TrimLengthValidationListener());
this.findPreference(DISABLE_PASSPHRASE_PREF)
.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
}
@Override
@ -151,9 +149,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
public void onActivityResult(int reqCode, int resultCode, Intent data) {
super.onActivityResult(reqCode, resultCode, data);
Log.w("ApplicationPreferencesActivity", "Got result: " + resultCode + " for req: " + reqCode);
if (resultCode == Activity.RESULT_OK) {
switch (reqCode) {
case PICK_IDENTITY_CONTACT: handleIdentitySelection(data); break;
case PICK_IDENTITY_CONTACT: handleIdentitySelection(data); break;
case ENABLE_PASSPHRASE_ACTIVITY: finish(); break;
}
}
}
@ -247,37 +248,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
}
}
private class ViewMyIdentityClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent viewIdentityIntent = new Intent(ApplicationPreferencesActivity.this, ViewIdentityActivity.class);
viewIdentityIntent.putExtra("identity_key", IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this));
viewIdentityIntent.putExtra("title", getString(R.string.ApplicationPreferencesActivity_my) + " " +
getString(R.string.ViewIdentityActivity_identity_fingerprint));
startActivity(viewIdentityIntent);
return true;
}
}
private class ManageIdentitiesClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
if (masterSecret != null) {
Intent manageIntent = new Intent(ApplicationPreferencesActivity.this, ReviewIdentitiesActivity.class);
manageIntent.putExtra("master_secret", masterSecret);
startActivity(manageIntent);
} else {
Toast.makeText(ApplicationPreferencesActivity.this,
R.string.ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys,
Toast.LENGTH_LONG).show();
}
return true;
}
}
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
@ -319,6 +289,48 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
}
}
private class DisablePassphraseClickListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (!((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(ApplicationPreferencesActivity.this);
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_storage_encryption);
builder.setMessage(R.string.ApplicationPreferencesActivity_warning_this_will_disable_storage_encryption_for_all_messages);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(R.string.ApplicationPreferencesActivity_disable, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MasterSecret masterSecret = getIntent().getParcelableExtra("master_secret");
MasterSecretUtil.changeMasterSecretPassphrase(ApplicationPreferencesActivity.this,
masterSecret,
MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
PreferenceManager.getDefaultSharedPreferences(ApplicationPreferencesActivity.this)
.edit()
.putBoolean(DISABLE_PASSPHRASE_PREF, true)
.commit();
((CheckBoxPreference)preference).setChecked(true);
Intent intent = new Intent(ApplicationPreferencesActivity.this, KeyCachingService.class);
intent.setAction(KeyCachingService.DISABLE_ACTION);
startService(intent);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
} else {
Intent intent = new Intent(ApplicationPreferencesActivity.this,
PassphraseChangeActivity.class);
startActivityForResult(intent, ENABLE_PASSPHRASE_ACTIVITY);
}
return false;
}
}
private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
public TrimLengthValidationListener() {
@ -343,7 +355,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
return false;
}
preference.setSummary((String)newValue + " " +
preference.setSummary(newValue + " " +
getString(R.string.ApplicationPreferencesActivity_messages_per_conversation));
return true;
}

View File

@ -17,11 +17,13 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
@ -36,11 +38,13 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
*/
public class PassphraseChangeActivity extends PassphraseActivity {
private EditText originalPassphrase;
private EditText newPassphrase;
private EditText repeatPassphrase;
private Button okButton;
private Button cancelButton;
private EditText originalPassphrase;
private EditText newPassphrase;
private EditText repeatPassphrase;
private TextView originalPassphraseLabel;
private Button okButton;
private Button cancelButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -52,15 +56,24 @@ public class PassphraseChangeActivity extends PassphraseActivity {
}
private void initializeResources() {
this.originalPassphrase = (EditText) findViewById(R.id.old_passphrase);
this.newPassphrase = (EditText) findViewById(R.id.new_passphrase);
this.repeatPassphrase = (EditText) findViewById(R.id.repeat_passphrase);
this.originalPassphraseLabel = (TextView) findViewById(R.id.old_passphrase_label);
this.originalPassphrase = (EditText) findViewById(R.id.old_passphrase );
this.newPassphrase = (EditText) findViewById(R.id.new_passphrase );
this.repeatPassphrase = (EditText) findViewById(R.id.repeat_passphrase );
this.okButton = (Button) findViewById(R.id.ok_button);
this.cancelButton = (Button) findViewById(R.id.cancel_button);
this.okButton = (Button ) findViewById(R.id.ok_button );
this.cancelButton = (Button ) findViewById(R.id.cancel_button );
this.okButton.setOnClickListener(new OkButtonClickListener());
this.cancelButton.setOnClickListener(new CancelButtonClickListener());
if (isPassphraseDisabled()) {
this.originalPassphrase.setVisibility(View.GONE);
this.originalPassphraseLabel.setVisibility(View.GONE);
} else {
this.originalPassphrase.setVisibility(View.VISIBLE);
this.originalPassphraseLabel.setVisibility(View.VISIBLE);
}
}
private void verifyAndSavePassphrases() {
@ -72,6 +85,10 @@ public class PassphraseChangeActivity extends PassphraseActivity {
String passphrase = (newText == null ? "" : newText.toString());
String passphraseRepeat = (repeatText == null ? "" : repeatText.toString());
if (isPassphraseDisabled()) {
original = MasterSecretUtil.UNENCRYPTED_PASSPHRASE;
}
try {
if (!passphrase.equals(passphraseRepeat)) {
Toast.makeText(getApplicationContext(),
@ -81,6 +98,12 @@ public class PassphraseChangeActivity extends PassphraseActivity {
this.repeatPassphrase.setText("");
} else {
MasterSecret masterSecret = MasterSecretUtil.changeMasterSecretPassphrase(this, original, passphrase);
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putBoolean(ApplicationPreferencesActivity.DISABLE_PASSPHRASE_PREF, false)
.commit();
MemoryCleaner.clean(original);
MemoryCleaner.clean(passphrase);
MemoryCleaner.clean(passphraseRepeat);
@ -94,6 +117,11 @@ public class PassphraseChangeActivity extends PassphraseActivity {
}
}
private boolean isPassphraseDisabled() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(ApplicationPreferencesActivity.DISABLE_PASSPHRASE_PREF, false);
}
private class CancelButtonClickListener implements OnClickListener {
public void onClick(View v) {
finish();

View File

@ -48,13 +48,18 @@ import javax.crypto.spec.SecretKeySpec;
public class MasterSecretUtil {
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
public static final String ASYMMETRIC_LOCAL_PUBLIC = "asymmetric_master_secret_public";
public static MasterSecret changeMasterSecretPassphrase(Context context, String originalPassphrase, String newPassphrase) throws InvalidPassphraseException {
public static MasterSecret changeMasterSecretPassphrase(Context context,
MasterSecret masterSecret,
String newPassphrase)
{
try {
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
byte[] combinedSecrets = combineSecrets(masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
byte[] combinedSecrets = combineSecrets(masterSecret.getEncryptionKey().getEncoded(),
masterSecret.getMacKey().getEncoded());
encryptWithPassphraseAndSave(context, combinedSecrets, newPassphrase);
return masterSecret;
@ -63,6 +68,17 @@ public class MasterSecretUtil {
}
}
public static MasterSecret changeMasterSecretPassphrase(Context context,
String originalPassphrase,
String newPassphrase)
throws InvalidPassphraseException
{
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
changeMasterSecretPassphrase(context, masterSecret, newPassphrase);
return masterSecret;
}
public static MasterSecret getMasterSecret(Context context, String passphrase) throws InvalidPassphraseException {
try {
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");

View File

@ -23,11 +23,13 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;
@ -36,7 +38,9 @@ import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.RoutingActivity;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
/**
@ -54,6 +58,7 @@ public class KeyCachingService extends Service {
public static final String CLEAR_KEY_EVENT = "org.thoughtcrime.securesms.service.action.CLEAR_KEY_EVENT";
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
public static final String ACTIVITY_START_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_START_EVENT";
public static final String ACTIVITY_STOP_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_STOP_EVENT";
@ -76,20 +81,21 @@ public class KeyCachingService extends Service {
broadcastNewSecret();
startTimeoutIfAppropriate();
new Thread() {
new AsyncTask<Void, Void, Void>() {
@Override
public void run() {
protected Void doInBackground(Void... params) {
if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) {
DecryptingQueue.schedulePendingDecrypts(KeyCachingService.this, masterSecret);
MessageNotifier.updateNotification(KeyCachingService.this, masterSecret);
}
return null;
}
}.start();
}.execute();
}
@Override
public void onStart(Intent intent, int startId) {
if (intent == null) return;
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_NOT_STICKY;
if (intent.getAction() != null && intent.getAction().equals(CLEAR_KEY_ACTION))
handleClearKey();
@ -99,12 +105,26 @@ public class KeyCachingService extends Service {
handleActivityStopped();
else if (intent.getAction() != null && intent.getAction().equals(PASSPHRASE_EXPIRED_EVENT))
handleClearKey();
else if (intent.getAction() != null && intent.getAction().equals(DISABLE_ACTION))
handleDisableService();
return START_NOT_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
pending = PendingIntent.getService(this, 0, new Intent(PASSPHRASE_EXPIRED_EVENT, null, this, KeyCachingService.class), 0);
this.pending = PendingIntent.getService(this, 0, new Intent(PASSPHRASE_EXPIRED_EVENT, null,
this, KeyCachingService.class), 0);
if (isPassphraseDisabled()) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
Log.w("KeyCachingService", e);
}
}
}
@Override
@ -138,19 +158,25 @@ public class KeyCachingService extends Service {
sendBroadcast(intent, KEY_PERMISSION);
new Thread() {
new AsyncTask<Void, Void, Void>() {
@Override
public void run() {
protected Void doInBackground(Void... params) {
MessageNotifier.updateNotification(KeyCachingService.this, null);
return null;
}
}.start();
}.execute();
}
private void handleDisableService() {
if (isPassphraseDisabled())
stopForeground(true);
}
private void startTimeoutIfAppropriate() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean timeoutEnabled = sharedPreferences.getBoolean(ApplicationPreferencesActivity.PASSPHRASE_TIMEOUT_PREF, false);
if ((activitiesRunning == 0) && (this.masterSecret != null) && timeoutEnabled) {
if ((activitiesRunning == 0) && (this.masterSecret != null) && timeoutEnabled && !isPassphraseDisabled()) {
long timeoutMinutes = sharedPreferences.getInt(ApplicationPreferencesActivity.PASSPHRASE_TIMEOUT_INTERVAL_PREF, 60 * 5);
long timeoutMillis = timeoutMinutes * 60 * 1000;
@ -195,6 +221,11 @@ public class KeyCachingService extends Service {
}
private void foregroundService() {
if (isPassphraseDisabled()) {
stopForeground(true);
return;
}
if (Build.VERSION.SDK_INT >= 11) foregroundServiceModern();
else foregroundServiceLegacy();
}
@ -209,6 +240,11 @@ public class KeyCachingService extends Service {
sendBroadcast(intent, KEY_PERMISSION);
}
private boolean isPassphraseDisabled() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(ApplicationPreferencesActivity.DISABLE_PASSPHRASE_PREF, false);
}
@Override
public IBinder onBind(Intent arg0) {