diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
16 files changed, 195 insertions, 103 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index a9dfaa2c5..e7ff6ce46 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -43,6 +43,7 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -170,11 +171,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar mTitleAnimator.setDisplayedChild(1, animate); mStatusAnimator.setDisplayedChild(1, animate); mCodeFieldsAnimator.setDisplayedChild(1, animate); - // use non-breaking spaces to enlarge the empty EditText appropriately - String empty = "\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" + - "-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" + - "-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0"; - mCodeEditText.setText(empty); + mCodeEditText.setText(" - - - - - "); pushBackStackEntry(); @@ -241,6 +238,30 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); setupEditTextSuccessListener(mCodeEditText); + // prevent selection action mode, partially circumventing text selection bug + mCodeEditText.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + + } + }); + + TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display); setupAutomaticLinebreak(codeDisplayText); @@ -351,27 +372,27 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar @Override public void afterTextChanged(Editable s) { + String currentBackupCode = backupCode.getText().toString(); boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; - boolean partIsComplete = (backupCode.getText().toString().indexOf(' ') == -1) - && (backupCode.getText().toString().indexOf('\u00a0') == -1); + boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1); if (!inInputState || !partIsComplete) { return; } - checkIfCodeIsCorrect(backupCode); + checkIfCodeIsCorrect(currentBackupCode); } }); } - private void checkIfCodeIsCorrect(EditText backupCode) { + private void checkIfCodeIsCorrect(String currentBackupCode) { if (Constants.DEBUG && mDebugModeAcceptAnyCode) { switchState(BackupCodeState.STATE_OK, true); return; } - if (backupCode.toString().equals(mBackupCode)) { + if (currentBackupCode.equals(mBackupCode)) { switchState(BackupCodeState.STATE_OK, true); return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index b53bfc1d0..58a28da51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -278,7 +278,7 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase - saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); + saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -288,7 +288,7 @@ public class CreateKeyFinalFragment extends Fragment { 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) + ? new ChangeUnlockParcel(createKeyActivity.mPassphrase) : null; } String userId = KeyRing.createUserId( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 2d94d0d93..2184f91f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -339,8 +339,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring // cache new returned passphrase! mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel( - (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), - null + (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index be08f6a53..22202a35f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -247,10 +247,10 @@ public class EncryptFilesFragment try { mFilesAdapter.add(inputUri); } catch (IOException e) { + String fileName = FileHelper.getFilename(getActivity(), inputUri); Notify.create(getActivity(), - getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), + getActivity().getString(R.string.error_file_added_already, fileName), Notify.Style.ERROR).show(this); - return; } // remove from pending input uris diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index ca5d20fb9..b2b85ec14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -79,9 +79,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - mEncryptKeyView.setDeletionStyle(TokenCompleteTextView.TokenDeleteStyle.ToString); final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon); mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index f67c6a724..72e42eec3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -131,8 +131,11 @@ public class ImportKeysActivity extends BaseActivity if (Intent.ACTION_VIEW.equals(action)) { if (FacebookKeyserver.isFacebookHost(dataUri)) { action = ACTION_IMPORT_KEY_FROM_FACEBOOK; - } else if ("http".equals(scheme) || "https".equals(scheme)) { + } else if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { action = ACTION_SEARCH_KEYSERVER_FROM_URL; + } else if ("openpgp4fpr".equalsIgnoreCase(scheme)) { + action = ACTION_IMPORT_KEY_FROM_KEYSERVER; + extras.putString(EXTRA_FINGERPRINT, dataUri.getSchemeSpecificPart()); } else { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) // delegate action to ACTION_IMPORT_KEY diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index b399af950..4d4219f56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -330,9 +330,16 @@ public class ImportKeysListFragment extends ListFragment implements } public void loadNew(LoaderState loaderState) { - mLoaderState = loaderState; + if (mLoaderState instanceof BytesLoaderState) { + BytesLoaderState ls = (BytesLoaderState) mLoaderState; + + if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { + return; + } + } + restartLoaders(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index fd4f27176..e7f91a07e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -192,6 +192,8 @@ public class PassphraseDialogActivity extends FragmentActivity { mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mBackupCodeEditText.setOnEditorActionListener(this); + openKeyboard(mBackupCodeEditText); + AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); @@ -281,28 +283,7 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseText.setText(message); mPassphraseEditText.setHint(hint); - // Hack to open keyboard. - // This is the only method that I found to work across all Android versions - // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ - // Notes: * onCreateView can't be used because we want to add buttons to the dialog - // * opening in onActivityCreated does not work on Android 4.4 - mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPassphraseEditText.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || mPassphraseEditText == null) { - return; - } - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); - } - }); - } - }); - mPassphraseEditText.requestFocus(); + openKeyboard(mPassphraseEditText); mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); @@ -325,6 +306,34 @@ public class PassphraseDialogActivity extends FragmentActivity { } /** + * Hack to open keyboard. + * + * This is the only method that I found to work across all Android versions + * http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ + * Notes: * onCreateView can't be used because we want to add buttons to the dialog + * * opening in onActivityCreated does not work on Android 4.4 + */ + private void openKeyboard(final TextView textView) { + textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + textView.post(new Runnable() { + @Override + public void run() { + if (getActivity() == null || textView == null) { + return; + } + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(textView, InputMethodManager.SHOW_IMPLICIT); + } + }); + } + }); + textView.requestFocus(); + } + + /** * Automatic line break with max 6 lines for smaller displays * <p/> * NOTE: I was not able to get this behaviour using XML! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 78d82d436..d2ecce242 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - nfcModifyPIN(0x81, newPin); - nfcModifyPIN(0x83, newAdminPin); + nfcModifyPin(0x81, newPin); + nfcModifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - nfcResetCard(); + nfcReset(); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index ea70cde2a..4fd327c8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.List; - import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -49,6 +47,7 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; @@ -59,6 +58,8 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.util.List; + public class SettingsActivity extends AppCompatPreferenceActivity { public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; @@ -405,7 +406,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } /** - * This fragment shows the keyserver/contacts sync preferences + * This fragment shows the keyserver/wifi-only-sync/contacts sync preferences */ public static class SyncPrefsFragment extends PresetPreferenceFragment { @@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { super.onResume(); // this needs to be done in onResume since the user can change sync values from Android // settings and we need to reflect that change when the user navigates back - AccountManager manager = AccountManager.get(getActivity()); - final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + final Account account = KeychainApplication.createAccountIfNecessary(getActivity()); // for keyserver sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), @@ -441,8 +441,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, final Account account, final String authority) { - boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) - && checkContactsPermission(authority); + // account is null if it could not be created for some reason + boolean syncEnabled = + account != null + && ContentResolver.getSyncAutomatically(account, authority) + && checkContactsPermission(authority); syncCheckBox.setChecked(syncEnabled); setSummary(syncCheckBox, authority, syncEnabled); @@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return false; } } else { + if (account == null) { + // if account could not be created for some reason, + // we can't have our sync + return false; + } // disable syncs ContentResolver.setSyncAutomatically(account, authority, false); // immediately delete any linked contacts diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java index 5a8ab36bc..488558aa3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -40,6 +40,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; @@ -312,19 +313,19 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC public void showAsSelectedKeyserver() { isSelectedKeyserver = true; selectedServerLabel.setVisibility(View.VISIBLE); - outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + outerLayout.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorPrimaryDark)); } public void showAsUnselectedKeyserver() { isSelectedKeyserver = false; selectedServerLabel.setVisibility(View.GONE); - outerLayout.setBackgroundColor(Color.WHITE); + outerLayout.setBackgroundColor(0); } @Override public void onItemSelected() { selectedServerLabel.setVisibility(View.GONE); - itemView.setBackgroundColor(Color.LTGRAY); + itemView.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorBrightToolbar)); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 03fc07936..35c1615c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -469,8 +469,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements // use new passphrase! mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel( - (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), - null + (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); mEditOpHelper.cryptoOperation(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index ce2f2def8..0aa8330ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // let user choose application Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.setType("text/plain"); + sendIntent.setType(Constants.MIME_TYPE_KEYS); // NOTE: Don't use Intent.EXTRA_TEXT to send the key // better send it via a Uri! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index fb72a263e..4a68c55fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -333,14 +333,6 @@ public class KeyAdapter extends CursorAdapter { return mUserId.email; } } - - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - @Override - public String toString() { - return " "; - } - } public static String[] getProjectionWith(String[] projection) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 107c63e0b..063181dfe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -45,8 +46,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - initTheme(); super.onCreate(savedInstanceState); + initTheme(); initLayout(); initToolbar(); } @@ -65,6 +66,16 @@ public abstract class BaseActivity extends AppCompatActivity { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home : + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + public static void onResumeChecks(Context context) { KeyserverSyncAdapterService.cancelUpdates(context); // in case user has disabled sync from Android account settings diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index ae1944ced..77044cd2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -26,14 +26,11 @@ import java.nio.ByteBuffer; import java.security.interfaces.RSAPrivateCrtKey; import android.app.Activity; -import android.app.PendingIntent; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; -import android.nfc.tech.IsoDep; import android.os.AsyncTask; import android.os.Bundle; @@ -199,7 +196,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mTagDispatcher = TagDispatcher.get(this, this, false, false); + mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -319,7 +316,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoDevice()) { + if (isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -530,18 +527,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen public String nfcGetUserId() throws IOException { String info = "00CA006500"; - return nfcGetHolderName(nfcCommunicate(info)); + return getHolderName(nfcCommunicate(info)); } /** - * Calls to calculate the signature and returns the MPI value + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value * * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { if (!mPw1ValidatedForSignature) { - nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) } // dsi, including Lc @@ -634,14 +631,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Calls to calculate the signature and returns the MPI value + * Call DECIPHER command * * @param encryptedSessionKey the encoded session key * @return the decoded session key */ public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } String firstApdu = "102a8086fe"; @@ -657,10 +654,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen two[i] = encryptedSessionKey[i + one.length + 1]; } - String first = nfcCommunicate(firstApdu + getHex(one)); + nfcCommunicate(firstApdu + getHex(one)); String second = nfcCommunicate(secondApdu + getHex(two) + le); - String decryptedSessionKey = nfcGetDataField(second); + String decryptedSessionKey = getDataField(second); return Hex.decode(decryptedSessionKey); } @@ -670,7 +667,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. * For PW3 (Admin PIN), mode is 0x83. */ - public void nfcVerifyPIN(int mode) throws IOException { + public void nfcVerifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { byte[] pin; @@ -683,7 +680,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; - String response = tryPin(mode, pin); // login + String response = nfcTryPin(mode, pin); // login if (!response.equals(accepted)) { throw new CardException("Bad PIN!", parseCardStatus(response)); } @@ -698,13 +695,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public void nfcResetCard() throws IOException { + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + public void nfcReset() throws IOException { String accepted = "9000"; // try wrong PIN 4 times until counter goes to C0 byte[] pin = "XXXXXX".getBytes(); for (int i = 0; i <= 4; i++) { - String response = tryPin(0x81, pin); + String response = nfcTryPin(0x81, pin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); } @@ -713,7 +715,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // try wrong Admin PIN 4 times until counter goes to C0 byte[] adminPin = "XXXXXXXX".getBytes(); for (int i = 0; i <= 4; i++) { - String response = tryPin(0x83, adminPin); + String response = nfcTryPin(0x83, adminPin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); } @@ -730,7 +732,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private String tryPin(int mode, byte[] pin) throws IOException { + private String nfcTryPin(int mode, byte[] pin) throws IOException { // Command APDU for VERIFY command (page 32) String login = "00" // CLA @@ -749,7 +751,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(int pw, byte[] newPin) throws IOException { + public void nfcModifyPin(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; @@ -802,10 +804,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (dataObject == 0x0101 || dataObject == 0x0103) { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations) + nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) } } else if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3) + nfcVerifyPin(0x83); // (Verify PW3) } String putDataApdu = "00" // CLA @@ -854,7 +856,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } byte[] header= Hex.decode( @@ -934,6 +936,53 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: nfcPutData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + public byte[] nfcGenerateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = nfcCommunicate(generateKeyApdu); + String second = nfcCommunicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + /** + * Transceive data via NFC encoded as Hex + */ + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoCard.transceive(Hex.decode(apdu))); + } + + /** * Parses out the status word from a JavaCard response string. * * @param response A hex string with the response from the token @@ -951,7 +1000,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public String nfcGetHolderName(String name) { + public String getHolderName(String name) { try { String slength; int ilength; @@ -971,14 +1020,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - private String nfcGetDataField(String output) { + private String getDataField(String output) { return output.substring(0, output.length() - 4); } - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoCard.transceive(Hex.decode(apdu))); - } - public static String getHex(byte[] raw) { return new String(Hex.encode(raw)); } @@ -1005,7 +1050,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private boolean isFidesmoDevice() { + private boolean isFidesmoToken() { if (isNfcConnected()) { // Check if we can still talk to the card try { // By trying to select any apps that have the Fidesmo AID prefix we can @@ -1021,11 +1066,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Ask user if she wants to install PGP onto her Fidesmo device + * Ask user if she wants to install PGP onto her Fidesmo token */ private void promptFidesmoPgpInstall() { - FidesmoPgpInstallDialog mFidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); - mFidesmoPgpInstallDialog.show(getSupportFragmentManager(), "mFidesmoPgpInstallDialog"); + FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); + fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); } /** @@ -1033,8 +1078,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * to launch the Google Play store. */ private void promptFidesmoAppInstall() { - FidesmoInstallDialog mFidesmoInstallDialog = new FidesmoInstallDialog(); - mFidesmoInstallDialog.show(getSupportFragmentManager(), "mFidesmoInstallDialog"); + FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); + fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); } /** @@ -1044,7 +1089,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen */ private boolean isAndroidAppInstalled(String uri) { PackageManager mPackageManager = getPackageManager(); - boolean mAppInstalled = false; + boolean mAppInstalled; try { mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); mAppInstalled = true; |