diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
21 files changed, 555 insertions, 1046 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b1fec3aae..44b185c52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -47,9 +47,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin"; public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin"; - public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; - public static final String EXTRA_NFC_AID = "nfc_aid"; - public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id"; + public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid"; + public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints"; public static final String FRAGMENT_TAG = "currentFragment"; @@ -66,8 +66,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { byte[] mScannedFingerprints; - byte[] mNfcAid; - String mNfcUserId; + byte[] mSecurityTokenAid; + String mSecurityTokenUserId; @Override public void onCreate(Bundle savedInstanceState) { @@ -107,10 +107,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false); - if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { - byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); - String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); - byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) { + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); if (containsKeys(nfcFingerprints)) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( @@ -143,24 +143,29 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void doNfcInBackground() throws IOException { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); + protected void doSecurityTokenInBackground() throws IOException { + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground(); return; } - mScannedFingerprints = nfcGetFingerprints(); - mNfcAid = nfcGetAid(); - mNfcUserId = nfcGetUserId(); + mScannedFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); } @Override - protected void onNfcPostExecute() { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); + protected void onSecurityTokenPostExecute() { + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute(); return; } + // We don't want get back to wait activity mainly because it looks weird with otg token + if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { + getSupportFragmentManager().popBackStackImmediate(); + } + if (containsKeys(mScannedFingerprints)) { try { long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); @@ -169,15 +174,15 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( - mScannedFingerprints, mNfcAid, mNfcUserId); + mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { @@ -255,9 +260,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } - interface NfcListenerFragment { - void doNfcInBackground() throws IOException; - void onNfcPostExecute(); + interface SecurityTokenListenerFragment { + void doSecurityTokenInBackground() throws IOException; + void onSecurityTokenPostExecute(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index b020a0dba..b871f471c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { private CreateKeyActivity mCreateKeyActivity; @@ -52,10 +51,6 @@ public class CreateKeyEmailFragment extends Fragment { private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>(); private EmailAdapter mEmailAdapter; - // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS - // EMAIL_ADDRESS fails for mails with umlauts for example - private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); - /** * Creates new instance of this fragment */ @@ -76,16 +71,15 @@ public class CreateKeyEmailFragment extends Fragment { * @return true if EditText is not empty */ private boolean isMainEmailValid(EditText editText) { - boolean output = true; - if (!checkEmail(editText.getText().toString(), false)) { + if (editText.getText().length() == 0) { editText.setError(getString(R.string.create_key_empty)); editText.requestFocus(); - output = false; - } else { - editText.setError(null); + return false; + } else if (!checkEmail(editText.getText().toString(), false)){ + return false; } - - return output; + editText.setError(null); + return true; } @Override @@ -146,10 +140,9 @@ public class CreateKeyEmailFragment extends Fragment { * @return */ private boolean checkEmail(String email, boolean additionalEmail) { - // check for email format or if the user did any input - if (!isEmailFormatValid(email)) { + if (email.isEmpty()) { Notify.create(getActivity(), - getString(R.string.create_key_email_invalid_email), + getString(R.string.create_key_email_empty_email), Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } @@ -167,18 +160,6 @@ public class CreateKeyEmailFragment extends Fragment { } /** - * Checks the email format - * Uses the default Android Email Pattern - * - * @param email - * @return - */ - private boolean isEmailFormatValid(String email) { - // check for email format or if the user did any input - return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches()); - } - - /** * Checks for duplicated emails inside the additional email adapter. * * @param email 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 cbf862074..896df0ad2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -57,6 +57,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.util.Date; import java.util.Iterator; +import java.util.regex.Pattern; public class CreateKeyFinalFragment extends Fragment { @@ -81,6 +82,10 @@ public class CreateKeyFinalFragment extends Fragment { private OperationResult mQueuedFinishResult; private EditKeyResult mQueuedDisplayResult; + // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS + // EMAIL_ADDRESS fails for mails with umlauts for example + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); + public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); frag.setRetainInstance(true); @@ -106,7 +111,11 @@ public class CreateKeyFinalFragment extends Fragment { CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); // set values - mNameEdit.setText(createKeyActivity.mName); + if (createKeyActivity.mName != null) { + mNameEdit.setText(createKeyActivity.mName); + } else { + mNameEdit.setText(getString(R.string.user_id_no_name)); + } if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { String emailText = createKeyActivity.mEmail + ", "; Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator(); @@ -122,6 +131,8 @@ public class CreateKeyFinalFragment extends Fragment { mEmailEdit.setText(createKeyActivity.mEmail); } + checkEmailValidity(); + mCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -309,6 +320,31 @@ public class CreateKeyFinalFragment extends Fragment { return saveKeyringParcel; } + private void checkEmailValidity() { + CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); + + boolean emailsValid = true; + if (!EMAIL_PATTERN.matcher(createKeyActivity.mEmail).matches()) { + emailsValid = false; + } + if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { + for (Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator(); it.hasNext(); ) { + if (!EMAIL_PATTERN.matcher(it.next().toString()).matches()) { + emailsValid = false; + } + } + } + if (!emailsValid) { + mEmailEdit.setError(getString(R.string.create_key_final_email_valid_warning)); + mEmailEdit.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mNameEdit.requestFocus(); // Workaround to remove focus from email + } + }); + } + } + private void createKey() { CreateKeyActivity activity = (CreateKeyActivity) getActivity(); if (activity == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java index 7480367bb..3332b9cf9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java @@ -18,13 +18,11 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; @@ -50,27 +48,6 @@ public class CreateKeyNameFragment extends Fragment { return frag; } - /** - * Checks if text of given EditText is not empty. If it is empty an error is - * set and the EditText gets the focus. - * - * @param context - * @param editText - * @return true if EditText is not empty - */ - private static boolean isEditTextNotEmpty(Context context, EditText editText) { - boolean output = true; - if (editText.getText().length() == 0) { - editText.setError(context.getString(R.string.create_key_empty)); - editText.requestFocus(); - output = false; - } else { - editText.setError(null); - } - - return output; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_name_fragment, container, false); @@ -109,13 +86,11 @@ public class CreateKeyNameFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mNameEdit)) { - // save state - mCreateKeyActivity.mName = mNameEdit.getText().toString(); + // save state + mCreateKeyActivity.mName = mNameEdit.getText().length() == 0 ? null : mNameEdit.getText().toString(); - CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); - } + CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index ea57fe558..6f35fdd38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -43,7 +42,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; @@ -51,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Preferences; public class CreateSecurityTokenImportResetFragment extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> - implements NfcListenerFragment { + implements SecurityTokenListenerFragment { private static final int REQUEST_CODE_RESET = 0x00005001; @@ -231,7 +230,7 @@ public class CreateSecurityTokenImportResetFragment public void resetCard() { Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class); - RequiredInputParcel resetP = RequiredInputParcel.createNfcReset(); + RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset(); intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP); intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel()); startActivityForResult(intent, REQUEST_CODE_RESET); @@ -248,11 +247,11 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void doNfcInBackground() throws IOException { + public void doSecurityTokenInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints(); - mTokenAid = mCreateKeyActivity.nfcGetAid(); - mTokenUserId = mCreateKeyActivity.nfcGetUserId(); + mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); + mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); + mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); @@ -260,7 +259,7 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void onNfcPostExecute() { + public void onSecurityTokenPostExecute() { setData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index a3ea38e40..5dc2c478b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -26,6 +27,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; public class CreateSecurityTokenWaitFragment extends Fragment { @@ -34,6 +36,15 @@ public class CreateSecurityTokenWaitFragment extends Fragment { View mBackButton; @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) { + ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection(); + } + } + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false); 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 4cbb4724e..9ed8e369d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -35,6 +35,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; @@ -440,50 +441,45 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring } break; } - case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: { - // TODO: enable later when Admin PIN handling is resolved - Notify.create(getActivity(), - "This feature will be available in an upcoming OpenKeychain version.", - Notify.Style.WARN).show(); - break; + case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: { + SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); + if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || + secretKeyType == SecretKeyType.GNU_DUMMY) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR) + .show(); + break; + } + + int algorithm = mSubkeysAdapter.getAlgorithm(position); + if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL + && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT + && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) + .show(); + break; + } + + if (mSubkeysAdapter.getKeySize(position) != 2048) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) + .show(); + break; + } -// Activity activity = EditKeyFragment.this.getActivity(); -// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); -// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || -// secretKeyType == SecretKeyType.GNU_DUMMY) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// int algorithm = mSubkeysAdapter.getAlgorithm(position); -// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN -// if (algorithm != 1 && algorithm != 2 && algorithm != 3) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// if (mSubkeysAdapter.getKeySize(position) != 2048) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// -// -// SubkeyChange change; -// change = mSaveKeyringParcel.getSubkeyChange(keyId); -// if (change == null) { -// mSaveKeyringParcel.mChangeSubKeys.add( -// new SubkeyChange(keyId, false, true) -// ); -// break; -// } -// // toggle -// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; -// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { -// // User had chosen to strip key, but now wants to divert it. -// change.mDummyStrip = false; -// } -// break; + SubkeyChange change; + change = mSaveKeyringParcel.getSubkeyChange(keyId); + if (change == null) { + mSaveKeyringParcel.mChangeSubKeys.add( + new SubkeyChange(keyId, false, true) + ); + break; + } + // toggle + change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; + if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { + // User had chosen to strip key, but now wants to divert it. + change.mDummyStrip = false; + } + break; } } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); 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 22202a35f..d5c540856 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -729,6 +729,8 @@ public class EncryptFilesFragment // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(new CryptoInputParcel(new Date())); + } else if (resultCode == Activity.RESULT_CANCELED) { + onCryptoOperationCancelled(); } return; } 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 b2b85ec14..51022094b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -21,6 +21,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.ViewAnimator; import com.tokenautocomplete.TokenCompleteTextView; @@ -109,6 +110,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { } }); + ImageView addRecipientImgView = (ImageView) view.findViewById(R.id.add_recipient); + addRecipientImgView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mEncryptKeyView.showAllKeys(); + } + }); + return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index af60a1d9b..37e01e98f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -90,8 +90,9 @@ public class MainActivity extends BaseSecurityTokenNfcActivity implements FabCon @Override public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { if (drawerItem != null) { + PrimaryDrawerItem item = (PrimaryDrawerItem) drawerItem; Intent intent = null; - switch (drawerItem.getIdentifier()) { + switch ((int) item.getIdentifier()) { case ID_KEYS: onKeysSelected(); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java new file mode 100644 index 000000000..5cb680a57 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de> + * + * 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.sufficientlysecure.keychain.ui; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + +public class RedirectImportKeysActivity extends BaseActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + startQrCodeCaptureActivity(); + } + + private void startQrCodeCaptureActivity() { + final Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); + scanQrCode.setAction(ImportKeysProxyActivity.ACTION_QR_CODE_API); + + new AlertDialog.Builder(this) + .setTitle(R.string.redirect_import_key_title) + .setMessage(R.string.redirect_import_key_message) + .setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // directly scan with OpenKeychain + startActivity(scanQrCode); + finish(); + } + }) + .setNegativeButton(R.string.redirect_import_key_no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // close window + finish(); + } + }) + .show(); + } +}
\ No newline at end of file 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 d2ecce242..925ad19d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com> * * 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 @@ -35,10 +36,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.OrientationUtils; @@ -69,8 +72,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private RequiredInputParcel mRequiredInput; - private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - private CryptoInputParcel mInputParcel; @Override @@ -137,9 +138,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD - && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD + && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); + checkPinAvailability(); + } else { + checkDeviceConnection(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (REQUEST_CODE_PIN == requestCode) { + checkPinAvailability(); + } + } + + private void checkPinAvailability() { + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); + if (passphrase != null) { + checkDeviceConnection(); + } + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new AssertionError( + "tried to find passphrase for non-existing key. this is a programming error!"); } } @@ -149,39 +174,53 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPreExecute() { + public void onSecurityTokenPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); } @Override - protected void doNfcInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { - case NFC_DECRYPT: { + case SECURITY_TOKEN_DECRYPT: { + long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + mSecurityTokenHelper.getKeyFingerprint(KeyType.ENCRYPT)); + + if (tokenKeyId != mRequiredInput.getSubKeyId()) { + throw new IOException(getString(R.string.error_wrong_security_token)); + } + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; } - case NFC_SIGN: { + case SECURITY_TOKEN_SIGN: { + long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN)); + + if (tokenKeyId != mRequiredInput.getSubKeyId()) { + throw new IOException(getString(R.string.error_wrong_security_token)); + } + mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = nfcCalculateSignature(hash, algo); + byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } - case NFC_MOVE_KEY_TO_CARD: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mPin = new Passphrase("123456"); - mAdminPin = new Passphrase("12345678"); + mSecurityTokenHelper.setPin(new Passphrase("123456")); + mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -202,11 +241,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); - - long keyGenerationTimestampMillis = key.getCreationTime().getTime(); - long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; - byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(nfcGetAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16); Passphrase passphrase; try { @@ -216,46 +251,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity throw new IOException("Unable to get cached passphrase!"); } - if (key.canSign() || key.canCertify()) { - if (shouldPutKey(key.getFingerprint(), 0)) { - nfcPutKey(0xB6, key, passphrase); - nfcPutData(0xCE, timestampBytes); - nfcPutData(0xC7, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new signature key."); - } - } else if (key.canEncrypt()) { - if (shouldPutKey(key.getFingerprint(), 1)) { - nfcPutKey(0xB8, key, passphrase); - nfcPutData(0xCF, timestampBytes); - nfcPutData(0xC8, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new decryption key."); - } - } else if (key.canAuthenticate()) { - if (shouldPutKey(key.getFingerprint(), 2)) { - nfcPutKey(0xA4, key, passphrase); - nfcPutData(0xD0, timestampBytes); - nfcPutData(0xC9, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new authentication key."); - } - } else { - throw new IOException("Inappropriate key flags for Security Token key."); - } + mSecurityTokenHelper.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); } // change PINs afterwards - nfcModifyPin(0x81, newPin); - nfcModifyPin(0x83, newAdminPin); + mSecurityTokenHelper.modifyPin(0x81, newPin); + mSecurityTokenHelper.modifyPin(0x83, newAdminPin); break; } - case NFC_RESET_CARD: { - nfcReset(); + case SECURITY_TOKEN_RESET_CARD: { + mSecurityTokenHelper.resetAndWipeToken(); break; } @@ -267,7 +276,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected final void onNfcPostExecute() { + protected final void onSecurityTokenPostExecute() { handleResult(mInputParcel); // show finish @@ -275,28 +284,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - // check all 200ms if Security Token has been taken away - while (true) { - if (isNfcConnected()) { - try { - Thread.sleep(200); - } catch (InterruptedException ignored) { + if (mSecurityTokenHelper.isPersistentConnectionAllowed()) { + // Just close + finish(); + } else { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + // check all 200ms if Security Token has been taken away + while (true) { + if (isSecurityTokenConnected()) { + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + } else { + return null; } - } else { - return null; } } - } - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - finish(); - } - }.execute(); + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } } /** @@ -311,7 +325,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void onNfcError(String error) { + protected void onSecurityTokenError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); @@ -321,31 +335,11 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPinError(String error) { - onNfcError(error); + public void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); } - - private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = nfcGetMasterKeyFingerprint(idx); - - // Note: special case: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (tokenFingerprint == null) { - return true; - } - - // Slot is empty, or contains this key already. PUT KEY operation is safe - if (Arrays.equals(tokenFingerprint, BLANK_FINGERPRINT) || - Arrays.equals(tokenFingerprint, fingerprint)) { - return true; - } - - // Slot already contains a different key; don't overwrite it. - return false; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java new file mode 100644 index 000000000..05b30b1ae --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com> + * + * 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.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class UsbEventReceiverActivity extends Activity { + public static final String ACTION_USB_PERMISSION = + "org.sufficientlysecure.keychain.ui.USB_PERMISSION"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + + Intent intent = getIntent(); + if (intent != null) { + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + + Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName()); + usbManager.requestPermission(usbDevice, + PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0)); + } + } + + // Close the activity + finish(); + } +}
\ No newline at end of file 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 d81797454..e47ca1db9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,7 +32,6 @@ import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; @@ -172,9 +171,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements private byte[] mFingerprint; private String mFingerprintString; - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; @SuppressLint("InflateParams") @Override @@ -647,17 +646,17 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void doNfcInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } @Override - protected void onNfcPostExecute() { + protected void onSecurityTokenPostExecute() { - long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { @@ -668,7 +667,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { - showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid); + showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid); return; } @@ -681,9 +680,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } @@ -696,9 +695,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index fc6db1b92..93b38af9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -39,6 +39,7 @@ import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; @@ -346,50 +347,45 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements } break; } - case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: { - // TODO: enable later when Admin PIN handling is resolved - Notify.create(getActivity(), - "This feature will be available in an upcoming OpenKeychain version.", - Notify.Style.WARN).show(); - break; + case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: { + SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); + if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || + secretKeyType == SecretKeyType.GNU_DUMMY) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR) + .show(); + break; + } + + int algorithm = mSubkeysAdapter.getAlgorithm(position); + if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL + && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT + && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) + .show(); + break; + } + + if (mSubkeysAdapter.getKeySize(position) != 2048) { + Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) + .show(); + break; + } -// Activity activity = EditKeyFragment.this.getActivity(); -// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); -// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || -// secretKeyType == SecretKeyType.GNU_DUMMY) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// int algorithm = mSubkeysAdapter.getAlgorithm(position); -// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN -// if (algorithm != 1 && algorithm != 2 && algorithm != 3) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// if (mSubkeysAdapter.getKeySize(position) != 2048) { -// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) -// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); -// break; -// } -// -// -// SubkeyChange change; -// change = mSaveKeyringParcel.getSubkeyChange(keyId); -// if (change == null) { -// mSaveKeyringParcel.mChangeSubKeys.add( -// new SubkeyChange(keyId, false, true) -// ); -// break; -// } -// // toggle -// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; -// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { -// // User had chosen to strip key, but now wants to divert it. -// change.mDummyStrip = false; -// } -// break; + SubkeyChange change; + change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId); + if (change == null) { + mEditModeSaveKeyringParcel.mChangeSubKeys.add( + new SubkeyChange(keyId, false, true) + ); + break; + } + // toggle + change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; + if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { + // User had chosen to strip key, but now wants to divert it. + change.mDummyStrip = false; + } + break; } } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); 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 77044cd2b..f4c0a9365 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 @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com> * * 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 @@ -20,41 +21,33 @@ package org.sufficientlysecure.keychain.ui.base; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.interfaces.RSAPrivateCrtKey; - import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.AsyncTask; import android.os.Bundle; -import nordpol.Apdu; -import nordpol.android.TagDispatcher; -import nordpol.android.AndroidCard; -import nordpol.android.OnDiscoveredTagListener; -import nordpol.IsoCard; - -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.service.PassphraseCacheService.KeyNotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.securitytoken.CardException; +import org.sufficientlysecure.keychain.securitytoken.NfcTransport; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.securitytoken.Transport; +import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -63,56 +56,52 @@ import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implements OnDiscoveredTagListener { +import java.io.IOException; + +import nordpol.android.OnDiscoveredTagListener; +import nordpol.android.TagDispatcher; + +public abstract class BaseSecurityTokenNfcActivity extends BaseActivity + implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - // Fidesmo constants - private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - protected Passphrase mPin; - protected Passphrase mAdminPin; - protected boolean mPw1ValidForMultipleSignatures; - protected boolean mPw1ValidatedForSignature; - protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - protected boolean mPw3Validated; + protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); protected TagDispatcher mTagDispatcher; - private IsoCard mIsoCard; + protected UsbConnectionDispatcher mUsbDispatcher; private boolean mTagHandlingEnabled; - private static final int TIMEOUT = 100000; - - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; /** - * Override to change UI before NFC handling (UI thread) + * Override to change UI before SecurityToken handling (UI thread) */ - protected void onNfcPreExecute() { + protected void onSecurityTokenPreExecute() { } /** - * Override to implement NFC operations (background thread) + * Override to implement SecurityToken operations (background thread) */ - protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } /** - * Override to handle result of NFC operations (UI thread) + * Override to handle result of SecurityToken operations (UI thread) */ - protected void onNfcPostExecute() { + protected void onSecurityTokenPostExecute() { - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( @@ -121,15 +110,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } catch (PgpKeyNotFoundException e) { Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } } @@ -137,32 +126,49 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen /** * Override to use something different than Notify (UI thread) */ - protected void onNfcError(String error) { + protected void onSecurityTokenError(String error) { Notify.create(this, error, Style.WARN).show(); } /** * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) */ - protected void onNfcPinError(String error) { - onNfcError(error); + protected void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); } public void tagDiscovered(final Tag tag) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) + return; + + securityTokenDiscovered(new NfcTransport(tag)); + } + + public void usbDeviceDiscovered(final UsbDevice usbDevice) { + // Actual USB operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); + } + + public void securityTokenDiscovered(final Transport transport) { + // Actual Security Token operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) return; new AsyncTask<Void, Void, IOException>() { @Override protected void onPreExecute() { super.onPreExecute(); - onNfcPreExecute(); + onSecurityTokenPreExecute(); } @Override protected IOException doInBackground(Void... params) { try { - handleTagDiscovered(tag); + handleSecurityToken(transport); } catch (IOException e) { return e; } @@ -175,11 +181,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onPostExecute(exception); if (exception != null) { - handleNfcError(exception); + handleSecurityTokenError(exception); return; } - onNfcPostExecute(); + onSecurityTokenPostExecute(); } }.execute(); } @@ -197,6 +203,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onCreate(savedInstanceState); mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); + mUsbDispatcher = new UsbConnectionDispatcher(this, this); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -230,15 +237,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen mTagDispatcher.interceptIntent(intent); } - private void handleNfcError(IOException e) { + private void handleSecurityTokenError(IOException e) { if (e instanceof TagLostException) { - onNfcError(getString(R.string.security_token_error_tag_lost)); + onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); return; } if (e instanceof IsoDepNotSupportedException) { - onNfcError(getString(R.string.security_token_error_iso_dep_not_supported)); + onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported)); return; } @@ -253,7 +260,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen if ((status & (short) 0xFFF0) == 0x63C0) { int tries = status & 0x000F; // hook to do something different when PIN is wrong - onNfcPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); + onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); return; } @@ -262,61 +269,61 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // These errors should not occur in everyday use; if they are returned, it means we // made a mistake sending data to the token, or the token is misbehaving. case 0x6A80: { - onNfcError(getString(R.string.security_token_error_bad_data)); + onSecurityTokenError(getString(R.string.security_token_error_bad_data)); break; } case 0x6883: { - onNfcError(getString(R.string.security_token_error_chaining_error)); + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); break; } case 0x6B00: { - onNfcError(getString(R.string.security_token_error_header, "P1/P2")); + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); break; } case 0x6D00: { - onNfcError(getString(R.string.security_token_error_header, "INS")); + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); break; } case 0x6E00: { - onNfcError(getString(R.string.security_token_error_header, "CLA")); + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - onNfcError(getString(R.string.security_token_error_terminated)); + onSecurityTokenError(getString(R.string.security_token_error_terminated)); break; } case 0x6700: { - onNfcPinError(getString(R.string.security_token_error_wrong_length)); + onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); break; } case 0x6982: { - onNfcError(getString(R.string.security_token_error_security_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); break; } case 0x6983: { - onNfcError(getString(R.string.security_token_error_authentication_blocked)); + onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); break; } case 0x6985: { - onNfcError(getString(R.string.security_token_error_conditions_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - onNfcError(getString(R.string.security_token_error_data_not_found)); + onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the security token. case 0x6F00: { - onNfcError(getString(R.string.security_token_error_unknown)); + onSecurityTokenError(getString(R.string.security_token_error_unknown)); break; } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoToken()) { + if (mSecurityTokenHelper.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -324,12 +331,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen promptFidesmoAppInstall(); } } else { // Other (possibly) compatible hardware - onNfcError(getString(R.string.security_token_error_pgp_app_not_installed)); + onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); } break; } default: { - onNfcError(getString(R.string.security_token_error, e.getMessage())); + onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); break; } } @@ -363,7 +370,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mPin = passphrase; + mSecurityTokenHelper.setPin(passphrase); return; } @@ -371,7 +378,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, RequiredInputParcel.createRequiredPassphrase(requiredInput)); startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (KeyNotFoundException e) { + } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( "tried to find passphrase for non-existing key. this is a programming error!"); } @@ -388,7 +395,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mPin = input.getPassphrase(); + mSecurityTokenHelper.setPin(input.getPassphrase()); break; } default: @@ -396,639 +403,22 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - /** Handle NFC communication and return a result. - * - * This method is called by onNewIntent above upon discovery of an NFC tag. - * It handles initialization and login to the application, subsequently - * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then - * finishes the activity with an appropriate result. - * - * On general communication, see also - * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - * - * References to pages are generally related to the OpenPGP Application - * on ISO SmartCard Systems specification. - * - */ - protected void handleTagDiscovered(Tag tag) throws IOException { - - // Connect to the detected tag, setting a couple of settings - mIsoCard = AndroidCard.get(tag); - if (mIsoCard == null) { - throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - mIsoCard.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation - mIsoCard.connect(); - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - - // Command APDU (page 51) for SELECT FILE command (page 29) - String opening = - "00" // CLA - + "A4" // INS - + "04" // P1 - + "00" // P2 - + "06" // Lc (number of bytes) - + "D27600012401" // Data (6 bytes) - + "00"; // Le - String response = nfcCommunicate(opening); // activate connection - if ( ! response.endsWith(accepted) ) { - throw new CardException("Initialization failed!", parseCardStatus(response)); - } - - byte[] pwStatusBytes = nfcGetPwStatusBytes(); - mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); - mPw1ValidatedForSignature = false; - mPw1ValidatedForDecrypt = false; - mPw3Validated = false; - - doNfcInBackground(); - - } - - public boolean isNfcConnected() { - return mIsoCard.isConnected(); - } - - /** Return the key id from application specific data stored on tag, or null - * if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The long key id of the requested key, or null if not found. - */ - public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = nfcGetMasterKeyFingerprint(idx); - if (fp == null) { - return null; - } - ByteBuffer buf = ByteBuffer.wrap(fp); - // skip first 12 bytes of the fingerprint - buf.position(12); - // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) - return buf.getLong(); - } - - /** Return fingerprints of all keys from application specific data stored - * on tag, or null if data not available. - * - * @return The fingerprints of all subkeys in a contiguous byte array. - */ - public byte[] nfcGetFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mIsoCard.transceive(Hex.decode(data)); - - Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); - Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); - - Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); - if (fptlv == null) { - return null; - } - - return fptlv.mV; - } - - /** Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed. - * - * @return Seven bytes in fixed format, plus 0x9000 status word at the end. - */ - public byte[] nfcGetPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mIsoCard.transceive(Hex.decode(data)); - } - - /** Return the fingerprint from application specific data stored on tag, or - * null if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The fingerprint of the requested key, or null if not found. - */ - public byte[] nfcGetMasterKeyFingerprint(int idx) throws IOException { - byte[] data = nfcGetFingerprints(); - if (data == null) { - return null; - } - - // return the master key fingerprint - ByteBuffer fpbuf = ByteBuffer.wrap(data); - byte[] fp = new byte[20]; - fpbuf.position(idx * 20); - fpbuf.get(fp, 0, 20); - - return fp; - } - - public byte[] nfcGetAid() throws IOException { - String info = "00CA004F00"; - return mIsoCard.transceive(Hex.decode(info)); - } - - public String nfcGetUserId() throws IOException { - String info = "00CA006500"; - return getHolderName(nfcCommunicate(info)); - } - - /** - * 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) - } - - // dsi, including Lc - String dsi; - - Log.i(Constants.TAG, "Hash: " + hashAlgo); - switch (hashAlgo) { - case HashAlgorithmTags.SHA1: - if (hash.length != 20) { - throw new IOException("Bad hash length (" + hash.length + ", expected 10!"); - } - dsi = "23" // Lc - + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes - + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes - + "0605" + "2B0E03021A" // OID of SHA1 - + "0500" // TLV coding of ZERO - + "0414" + getHex(hash); // 0x14 are 20 hash bytes - break; - case HashAlgorithmTags.RIPEMD160: - if (hash.length != 20) { - throw new IOException("Bad hash length (" + hash.length + ", expected 20!"); - } - dsi = "233021300906052B2403020105000414" + getHex(hash); - break; - case HashAlgorithmTags.SHA224: - if (hash.length != 28) { - throw new IOException("Bad hash length (" + hash.length + ", expected 28!"); - } - dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); - break; - case HashAlgorithmTags.SHA256: - if (hash.length != 32) { - throw new IOException("Bad hash length (" + hash.length + ", expected 32!"); - } - dsi = "333031300D060960864801650304020105000420" + getHex(hash); - break; - case HashAlgorithmTags.SHA384: - if (hash.length != 48) { - throw new IOException("Bad hash length (" + hash.length + ", expected 48!"); - } - dsi = "433041300D060960864801650304020205000430" + getHex(hash); - break; - case HashAlgorithmTags.SHA512: - if (hash.length != 64) { - throw new IOException("Bad hash length (" + hash.length + ", expected 64!"); - } - dsi = "533051300D060960864801650304020305000440" + getHex(hash); - break; - default: - throw new IOException("Not supported hash algo!"); - } - - // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) - String apdu = - "002A9E9A" // CLA, INS, P1, P2 - + dsi // digital signature input - + "00"; // Le - - String response = nfcCommunicate(apdu); - - // split up response into signature and status - String status = response.substring(response.length()-4); - String signature = response.substring(0, response.length() - 4); - - // while we are getting 0x61 status codes, retrieve more data - while (status.substring(0, 2).equals("61")) { - Log.d(Constants.TAG, "requesting more data, status " + status); - // Send GET RESPONSE command - response = nfcCommunicate("00C00000" + status.substring(2)); - status = response.substring(response.length()-4); - signature += response.substring(0, response.length()-4); - } - - Log.d(Constants.TAG, "final response:" + status); - - if (!mPw1ValidForMultipleSignatures) { - mPw1ValidatedForSignature = false; - } - - if ( ! "9000".equals(status)) { - throw new CardException("Bad NFC response code: " + status, parseCardStatus(response)); - } - - // Make sure the signature we received is actually the expected number of bytes long! - if (signature.length() != 256 && signature.length() != 512) { - throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); - } - - return Hex.decode(signature); - } - - /** - * 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) + protected void handleSecurityToken(Transport transport) throws IOException { + // Don't reconnect if device was already connected + if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() + && mSecurityTokenHelper.isConnected() + && mSecurityTokenHelper.getTransport().equals(transport))) { + mSecurityTokenHelper.setTransport(transport); + mSecurityTokenHelper.connectToDevice(); } - - String firstApdu = "102a8086fe"; - String secondApdu = "002a808603"; - String le = "00"; - - byte[] one = new byte[254]; - // leave out first byte: - System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); - - byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; - for (int i = 0; i < two.length; i++) { - two[i] = encryptedSessionKey[i + one.length + 1]; - } - - nfcCommunicate(firstApdu + getHex(one)); - String second = nfcCommunicate(secondApdu + getHex(two) + le); - - String decryptedSessionKey = getDataField(second); - - return Hex.decode(decryptedSessionKey); + doSecurityTokenInBackground(); } - /** Verifies the user's PW1 or PW3 with the appropriate mode. - * - * @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 { - if (mPin != null || mode == 0x83) { - - byte[] pin; - if (mode == 0x83) { - pin = mAdminPin.toStringUnsafe().getBytes(); - } else { - pin = mPin.toStringUnsafe().getBytes(); - } - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - String response = nfcTryPin(mode, pin); // login - if (!response.equals(accepted)) { - throw new CardException("Bad PIN!", parseCardStatus(response)); - } - - if (mode == 0x81) { - mPw1ValidatedForSignature = true; - } else if (mode == 0x82) { - mPw1ValidatedForDecrypt = true; - } else if (mode == 0x83) { - mPw3Validated = true; - } - } + public boolean isSecurityTokenConnected() { + return mSecurityTokenHelper.isConnected(); } - /** - * 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 = nfcTryPin(0x81, pin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); - } - } - - // try wrong Admin PIN 4 times until counter goes to C0 - byte[] adminPin = "XXXXXXXX".getBytes(); - for (int i = 0; i <= 4; i++) { - String response = nfcTryPin(0x83, adminPin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); - } - } - - // reactivate token! - String reactivate1 = "00" + "e6" + "00" + "00"; - String reactivate2 = "00" + "44" + "00" + "00"; - String response1 = nfcCommunicate(reactivate1); - String response2 = nfcCommunicate(reactivate2); - if (!response1.equals(accepted) || !response2.equals(accepted)) { - throw new CardException("Reactivating failed!", parseCardStatus(response1)); - } - - } - - private String nfcTryPin(int mode, byte[] pin) throws IOException { - // Command APDU for VERIFY command (page 32) - String login = - "00" // CLA - + "20" // INS - + "00" // P1 - + String.format("%02x", mode) // P2 - + String.format("%02x", pin.length) // Lc - + Hex.toHexString(pin); - - return nfcCommunicate(login); - } - - /** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for - * conformance to the token's requirements for key length. - * - * @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 { - final int MAX_PW1_LENGTH_INDEX = 1; - final int MAX_PW3_LENGTH_INDEX = 3; - - byte[] pwStatusBytes = nfcGetPwStatusBytes(); - - if (pw == 0x81) { - if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else if (pw == 0x83) { - if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else { - throw new IOException("Invalid PW index for modify PIN operation"); - } - - byte[] pin; - if (pw == 0x83) { - pin = mAdminPin.toStringUnsafe().getBytes(); - } else { - pin = mPin.toStringUnsafe().getBytes(); - } - - // Command APDU for CHANGE REFERENCE DATA command (page 32) - String changeReferenceDataApdu = "00" // CLA - + "24" // INS - + "00" // P1 - + String.format("%02x", pw) // P2 - + String.format("%02x", pin.length + newPin.length) // Lc - + getHex(pin) - + getHex(newPin); - String response = nfcCommunicate(changeReferenceDataApdu); // change PIN - if (!response.equals("9000")) { - throw new CardException("Failed to change PIN", parseCardStatus(response)); - } - } - - /** - * Stores a data object on the token. Automatically validates the proper PIN for the operation. - * Supported for all data objects < 255 bytes in length. Only the cardholder certificate - * (0x7F21) can exceed this length. - * - * @param dataObject The data object to be stored. - * @param data The data to store in the object - */ - public void nfcPutData(int dataObject, byte[] data) throws IOException { - if (data.length > 254) { - throw new IOException("Cannot PUT DATA with length > 254"); - } - if (dataObject == 0x0101 || dataObject == 0x0103) { - if (!mPw1ValidatedForDecrypt) { - nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) - } - } else if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3) - } - - String putDataApdu = "00" // CLA - + "DA" // INS - + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1 - + String.format("%02x", dataObject & 0xFF) // P2 - + String.format("%02x", data.length) // Lc - + getHex(data); - - String response = nfcCommunicate(putDataApdu); // put data - if (!response.equals("9000")) { - throw new CardException("Failed to put data.", parseCardStatus(response)); - } - } - - /** - * Puts a key on the token in the given slot. - * - * @param slot The slot on the token where the key should be stored: - * 0xB6: Signature Key - * 0xB8: Decipherment Key - * 0xA4: Authentication Key - */ - public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) - throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - RSAPrivateCrtKey crtSecretKey; - try { - secretKey.unlock(passphrase); - crtSecretKey = secretKey.getCrtSecretKey(); - } catch (PgpGeneralException e) { - throw new IOException(e.getMessage()); - } - - // Shouldn't happen; the UI should block the user from getting an incompatible key this far. - if (crtSecretKey.getModulus().bitLength() > 2048) { - throw new IOException("Key too large to export to Security Token."); - } - - // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. - if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { - throw new IOException("Invalid public exponent for smart Security Token."); - } - - if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3 with mode 83) - } - - byte[] header= Hex.decode( - "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23) - + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length - + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex) - + "9103" // Public modulus, length 3 - + "928180" // Prime P, length 128 - + "938180" // Prime Q, length 128 - + "948180" // Coefficient (1/q mod p), length 128 - + "958180" // Prime exponent P (d mod (p - 1)), length 128 - + "968180" // Prime exponent Q (d mod (1 - 1)), length 128 - + "97820100" // Modulus, length 256, last item in private key template - + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow - byte[] dataToSend = new byte[934]; - byte[] currentKeyObject; - int offset = 0; - - System.arraycopy(header, 0, dataToSend, offset, header.length); - offset += header.length; - currentKeyObject = crtSecretKey.getPublicExponent().toByteArray(); - System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3); - offset += 3; - // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0 - // in the array to represent sign, so we take care to set the offset to 1 if necessary. - currentKeyObject = crtSecretKey.getPrimeP().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeQ().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getModulus().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256); - - String putKeyCommand = "10DB3FFF"; - String lastPutKeyCommand = "00DB3FFF"; - - // Now we're ready to communicate with the token. - offset = 0; - String response; - while(offset < dataToSend.length) { - int dataRemaining = dataToSend.length - offset; - if (dataRemaining > 254) { - response = nfcCommunicate( - putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) - ); - offset += 254; - } else { - int length = dataToSend.length - offset; - response = nfcCommunicate( - lastPutKeyCommand + String.format("%02x", length) - + Hex.toHexString(dataToSend, offset, length)); - offset += length; - } - - if (!response.endsWith("9000")) { - throw new CardException("Key export to Security Token failed", parseCardStatus(response)); - } - } - - // Clear array with secret data before we return. - Arrays.fill(dataToSend, (byte) 0); - } - - /** - * 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 - * @return A short indicating the SW1/SW2, or 0 if a status could not be determined. - */ - short parseCardStatus(String response) { - if (response.length() < 4) { - return 0; // invalid input - } - - try { - return Short.parseShort(response.substring(response.length() - 4), 16); - } catch (NumberFormatException e) { - return 0; - } - } - - public String getHolderName(String name) { - try { - String slength; - int ilength; - name = name.substring(6); - slength = name.substring(0, 2); - ilength = Integer.parseInt(slength, 16) * 2; - name = name.substring(2, ilength + 2); - name = (new String(Hex.decode(name))).replace('<', ' '); - return name; - } catch (IndexOutOfBoundsException e) { - // try-catch for https://github.com/FluffyKaon/OpenPGP-Card - // Note: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! - - Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); - return ""; - } - } - - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - - public static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - - public class IsoDepNotSupportedException extends IOException { + public static class IsoDepNotSupportedException extends IOException { public IsoDepNotSupportedException(String detailMessage) { super(detailMessage); @@ -1036,38 +426,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - - } - - 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 - // see if it is a Fidesmo device or not - byte[] mSelectResponse = mIsoCard.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); - // Compare the status returned by our select with the OK status code - return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); - } catch (IOException e) { - Log.e(Constants.TAG, "Card communication failed!", e); - } - } - return false; - } - /** * Ask user if she wants to install PGP onto her Fidesmo token - */ + */ private void promptFidesmoPgpInstall() { FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); @@ -1084,6 +445,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen /** * Use the package manager to detect if an application is installed on the phone + * * @param uri an URI identifying the application's package * @return 'true' if the app is installed */ @@ -1099,4 +461,28 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } return mAppInstalled; } + + @Override + protected void onStop() { + super.onStop(); + mUsbDispatcher.onStop(); + } + + @Override + protected void onStart() { + super.onStart(); + mUsbDispatcher.onStart(); + } + + public SecurityTokenHelper getSecurityTokenHelper() { + return mSecurityTokenHelper; + } + + /** + * Run Security Token routines if last used token is connected and supports + * persistent connections + */ + public void checkDeviceConnection() { + mUsbDispatcher.rescanDevices(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 451065d6b..ad15c8f68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -130,9 +130,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu switch (requiredInput.mType) { // always use CryptoOperationHelper.startActivityForResult! - case NFC_MOVE_KEY_TO_CARD: - case NFC_DECRYPT: - case NFC_SIGN: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: + case SECURITY_TOKEN_DECRYPT: + case SECURITY_TOKEN_SIGN: { Intent intent = new Intent(activity, SecurityTokenOperationActivity.class); intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index 9f5ffc358..0c0877bae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -35,7 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { public static final int MESSAGE_CHANGE_EXPIRY = 1; public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_STRIP = 3; - public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; + public static final int MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN = 4; public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0; public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1; public static final int SUBKEY_MENU_STRIP_SUBKEY = 2; @@ -82,7 +82,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { showAlertDialog(); break; case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN: - sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); + sendMessageToHandler(MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN, null); break; default: break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 5fcc3d58b..4dc0ebaa0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -64,8 +64,8 @@ public class KeyFormattingUtils { String algorithmStr; switch (algorithm) { - case PublicKeyAlgorithmTags.RSA_ENCRYPT: case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_ENCRYPT: case PublicKeyAlgorithmTags.RSA_SIGN: { algorithmStr = "RSA"; break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java index 49b37692c..55d5aec0c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java @@ -23,15 +23,11 @@ import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.util.AttributeSet; -import android.util.Patterns; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.ContactHelper; -import java.util.regex.Matcher; - public class EmailEditText extends AppCompatAutoCompleteTextView { public EmailEditText(Context context) { @@ -70,20 +66,7 @@ public class EmailEditText extends AppCompatAutoCompleteTextView { @Override public void afterTextChanged(Editable editable) { - String email = editable.toString(); - if (email.length() > 0) { - Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); - if (emailMatcher.matches()) { - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.ic_stat_retyped_ok, 0); - } else { - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.ic_stat_retyped_bad, 0); - } - } else { - // remove drawable if email is empty - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } + } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 78f9f431b..4074d391b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -177,4 +177,22 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem> mLoaderManager.restartLoader(0, args, this); } + @Override + public boolean enoughToFilter() { + return true; + } + + public void showAllKeys(){ + Bundle args = new Bundle(); + args.putString(ARG_QUERY, ""); + mLoaderManager.restartLoader(0, args, this); + super.showDropDown(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // increase width to include add button + this.setDropDownWidth(this.getRight()); + } } |