diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
16 files changed, 630 insertions, 1022 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index e7ff6ce46..65c51969e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -18,14 +18,6 @@ package org.sufficientlysecure.keychain.ui; -import java.io.File; -import java.io.IOException; -import java.security.SecureRandom; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; - import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -41,9 +33,8 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.text.Editable; -import android.text.InputType; +import android.text.TextUtils; import android.text.TextWatcher; -import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -52,12 +43,9 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; -import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.TextView; -import com.github.pinball83.maskededittext.MaskedEditText; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ExportResult; @@ -71,6 +59,14 @@ import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Passphrase; +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Random; + public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult> implements OnBackStackChangedListener { @@ -96,7 +92,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar private long[] mMasterKeyIds; String mBackupCode; - private MaskedEditText mCodeEditText; + private EditText[] mCodeEditText; private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator; private Integer mBackStackLevel; @@ -146,8 +142,13 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar boolean newCheckedState = !item.isChecked(); item.setChecked(newCheckedState); mDebugModeAcceptAnyCode = newCheckedState; - if (newCheckedState) { - mCodeEditText.setText("ABCD-EFGH-IJKL-MNOP-QRST-UVWX"); + if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) { + mCodeEditText[0].setText("ABCD"); + mCodeEditText[1].setText("EFGH"); + mCodeEditText[2].setText("IJKL"); + mCodeEditText[3].setText("MNOP"); + mCodeEditText[4].setText("QRST"); + mCodeEditText[5].setText("UVWX"); Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show(); } return true; @@ -171,7 +172,9 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar mTitleAnimator.setDisplayedChild(1, animate); mStatusAnimator.setDisplayedChild(1, animate); mCodeFieldsAnimator.setDisplayedChild(1, animate); - mCodeEditText.setText(" - - - - - "); + for (EditText editText : mCodeEditText) { + editText.setText(""); + } pushBackStackEntry(); @@ -185,7 +188,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar hideKeyboard(); if (animate) { - @ColorInt int black = mCodeEditText.getCurrentTextColor(); + @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); @ColorInt int red = getResources().getColor(R.color.android_red_dark); animateFlashText(mCodeEditText, black, red, false); } @@ -200,14 +203,18 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar hideKeyboard(); - mCodeEditText.setEnabled(false); + for (EditText editText : mCodeEditText) { + editText.setEnabled(false); + } @ColorInt int green = getResources().getColor(R.color.android_green_dark); if (animate) { - @ColorInt int black = mCodeEditText.getCurrentTextColor(); + @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); animateFlashText(mCodeEditText, black, green, true); } else { - mCodeEditText.setTextColor(green); + for (TextView textView : mCodeEditText) { + textView.setTextColor(green); + } } popBackStackNoAction(); @@ -230,46 +237,38 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS); mExportSecret = args.getBoolean(ARG_EXPORT_SECRET); - // NOTE: order of these method calls matter, see setupAutomaticLinebreak() - mCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code_input); - mCodeEditText.setInputType( - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); - setupAutomaticLinebreak(mCodeEditText); - mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); - setupEditTextSuccessListener(mCodeEditText); - - // prevent selection action mode, partially circumventing text selection bug - mCodeEditText.setCustomSelectionActionModeCallback(new ActionMode.Callback() { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; + mCodeEditText = new EditText[6]; + mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); + mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); + mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); + mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); + mCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5); + mCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6); + + { + TextView[] codeDisplayText = new TextView[6]; + codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1); + codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2); + codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3); + codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4); + codeDisplayText[4] = (TextView) view.findViewById(R.id.backup_code_display_5); + codeDisplayText[5] = (TextView) view.findViewById(R.id.backup_code_display_6); + + // set backup code in code TextViews + char[] backupCode = mBackupCode.toCharArray(); + for (int i = 0; i < codeDisplayText.length; i++) { + codeDisplayText[i].setText(backupCode, i * 5, 4); } - @Override - public void onDestroyActionMode(ActionMode mode) { - + // set background to null in TextViews - this will retain padding from EditText style! + for (TextView textView : codeDisplayText) { + // noinspection deprecation, setBackground(Drawable) is API level >=16 + textView.setBackgroundDrawable(null); } - }); - - - TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display); - setupAutomaticLinebreak(codeDisplayText); - - // set background to null in TextViews - this will retain padding from EditText style! - // noinspection deprecation, setBackground(Drawable) is API level >=16 - codeDisplayText.setBackgroundDrawable(null); + } - codeDisplayText.setText(mBackupCode); + setupEditTextFocusNext(mCodeEditText); + setupEditTextSuccessListener(mCodeEditText); mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator); mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator); @@ -347,67 +346,76 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel); } - /** - * Automatic line break with max 6 lines for smaller displays - * <p/> - * NOTE: I was not able to get this behaviour using XML! - * Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307 - */ - private void setupAutomaticLinebreak(TextView textview) { - textview.setSingleLine(true); - textview.setMaxLines(6); - textview.setHorizontallyScrolling(false); - } + private void setupEditTextSuccessListener(final EditText[] backupCodes) { + for (EditText backupCode : backupCodes) { - private void setupEditTextSuccessListener(final MaskedEditText backupCode) { - backupCode.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + backupCode.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } - @Override - public void afterTextChanged(Editable s) { - String currentBackupCode = backupCode.getText().toString(); - boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT - || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; - boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1); - if (!inInputState || !partIsComplete) { - return; + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 4) { + throw new AssertionError("max length of each field is 4!"); + } + + boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT + || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; + boolean partIsComplete = s.length() == 4; + if (!inInputState || !partIsComplete) { + return; + } + + checkIfCodeIsCorrect(); } + }); - checkIfCodeIsCorrect(currentBackupCode); - } - }); + } } - private void checkIfCodeIsCorrect(String currentBackupCode) { + private void checkIfCodeIsCorrect() { if (Constants.DEBUG && mDebugModeAcceptAnyCode) { switchState(BackupCodeState.STATE_OK, true); return; } - if (currentBackupCode.equals(mBackupCode)) { + StringBuilder backupCodeInput = new StringBuilder(26); + for (EditText editText : mCodeEditText) { + if (editText.getText().length() < 4) { + return; + } + backupCodeInput.append(editText.getText()); + backupCodeInput.append('-'); + } + backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); + + // if they don't match, do nothing + if (backupCodeInput.toString().equals(mBackupCode)) { switchState(BackupCodeState.STATE_OK, true); return; } switchState(BackupCodeState.STATE_INPUT_ERROR, true); + } private static void animateFlashText( - final TextView textView, int color1, int color2, boolean staySecondColor) { + final TextView[] textViews, int color1, int color2, boolean staySecondColor) { ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { - textView.setTextColor((Integer) animator.getAnimatedValue()); + for (TextView textView : textViews) { + textView.setTextColor((Integer) animator.getAnimatedValue()); + } } }); anim.setRepeatMode(ValueAnimator.REVERSE); @@ -418,6 +426,34 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar } + private static void setupEditTextFocusNext(final EditText[] backupCodes) { + for (int i = 0; i < backupCodes.length - 1; i++) { + + final int next = i + 1; + + backupCodes[i].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + boolean inserting = before < count; + boolean cursorAtEnd = (start + count) == 4; + + if (inserting && cursorAtEnd) { + backupCodes[next].requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + } + } + private void pushBackStackEntry() { if (mBackStackLevel != null) { return; 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/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/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/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 72e42eec3..7d2d30c35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -416,11 +416,18 @@ public class ImportKeysActivity extends BaseActivity intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); setResult(RESULT_OK, intent); finish(); - return; + } else if (result.isOkNew() || result.isOkUpdated()) { + // User has successfully imported a key, hide first time dialog + Preferences.getPreferences(this).setFirstTime(false); + + // Close activities opened for importing keys and go to the list of keys + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } else { + result.createNotify(ImportKeysActivity.this) + .show((ViewGroup) findViewById(R.id.import_snackbar)); } - - result.createNotify(ImportKeysActivity.this) - .show((ViewGroup) findViewById(R.id.import_snackbar)); } // methods from CryptoOperationHelper.Callback diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 8de60dfd3..133cf299f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -17,29 +17,46 @@ package org.sufficientlysecure.keychain.ui; +import android.Manifest; import android.app.Activity; +import android.content.ContentResolver; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; public class ImportKeysFileFragment extends Fragment { private ImportKeysActivity mImportActivity; private View mBrowse; private View mClipboardButton; - public static final int REQUEST_CODE_FILE = 0x00007003; + private Uri mCurrentUri; + + private static final int REQUEST_CODE_FILE = 0x00007003; + private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; /** * Creates new instance of this fragment @@ -83,10 +100,10 @@ public class ImportKeysFileFragment extends Fragment { sendText = clipboardText.toString(); sendText = PgpHelper.getPgpKeyContent(sendText); if (sendText == null) { - Notify.create(mImportActivity, "Bad data!", Style.ERROR).show(); + Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); return; } - mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null)); + mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null)); } } }); @@ -106,11 +123,12 @@ public class ImportKeysFileFragment extends Fragment { switch (requestCode) { case REQUEST_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { + mCurrentUri = data.getData(); - // load data - mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(null, data.getData())); + if (checkAndRequestReadPermission(mCurrentUri)) { + startImportingKeys(); + } } - break; } @@ -121,4 +139,77 @@ public class ImportKeysFileFragment extends Fragment { } } + private void startImportingKeys() { + boolean isEncrypted; + try { + isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri); + } catch (IOException e) { + Log.e(Constants.TAG, "Error opening file", e); + + Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); + return; + } + + if (isEncrypted) { + Intent intent = new Intent(mImportActivity, DecryptActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(mCurrentUri); + startActivity(intent); + } else { + mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri)); + } + } + + /** + * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. + * <p/> + * This method returns true on Android < 6, or if permission is already granted. It + * requests the permission and returns false otherwise. + * <p/> + * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html + */ + private boolean checkAndRequestReadPermission(final Uri uri) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + return true; + } + + // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + requestPermissions( + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); + + return false; + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + + if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + return; + } + + boolean permissionWasGranted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (permissionWasGranted) { + startImportingKeys(); + } else { + Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } + } + } 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/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index e7f91a07e..2c562c30e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -46,8 +46,6 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.ViewAnimator; -import com.github.pinball83.maskededittext.MaskedEditText; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; @@ -60,7 +58,6 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; -import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -157,7 +154,7 @@ public class PassphraseDialogActivity extends FragmentActivity { public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { private EditText mPassphraseEditText; private TextView mPassphraseText; - private MaskedEditText mBackupCodeEditText; + private EditText[] mBackupCodeEditText; private boolean mIsCancelled = false; private RequiredInputParcel mRequiredInput; @@ -184,15 +181,15 @@ public class PassphraseDialogActivity extends FragmentActivity { View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null); alert.setView(view); - mBackupCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code); - // NOTE: order of these method calls matter, see setupAutomaticLinebreak() - mBackupCodeEditText.setInputType( - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); - setupAutomaticLinebreak(mBackupCodeEditText); - mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); - mBackupCodeEditText.setOnEditorActionListener(this); + mBackupCodeEditText = new EditText[6]; + mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); + mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); + mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); + mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); + mBackupCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5); + mBackupCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6); - openKeyboard(mBackupCodeEditText); + setupEditTextFocusNext(mBackupCodeEditText); AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, @@ -307,11 +304,11 @@ public class PassphraseDialogActivity extends FragmentActivity { /** * Hack to open keyboard. - * * This is the only method that I found to work across all Android versions * http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ - * Notes: * onCreateView can't be used because we want to add buttons to the dialog - * * opening in onActivityCreated does not work on Android 4.4 + * Notes: + * * onCreateView can't be used because we want to add buttons to the dialog + * * opening in onActivityCreated does not work on Android 4.4 */ private void openKeyboard(final TextView textView) { textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { @@ -333,18 +330,35 @@ public class PassphraseDialogActivity extends FragmentActivity { textView.requestFocus(); } - /** - * Automatic line break with max 6 lines for smaller displays - * <p/> - * NOTE: I was not able to get this behaviour using XML! - * Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307 - */ - private void setupAutomaticLinebreak(TextView textview) { - textview.setSingleLine(true); - textview.setMaxLines(6); - textview.setHorizontallyScrolling(false); + private static void setupEditTextFocusNext(final EditText[] backupCodes) { + for (int i = 0; i < backupCodes.length - 1; i++) { + + final int next = i + 1; + + backupCodes[i].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + boolean inserting = before < count; + boolean cursorAtEnd = (start + count) == 4; + + if (inserting && cursorAtEnd) { + backupCodes[next].requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + } } + @Override public void onStart() { super.onStart(); @@ -356,8 +370,17 @@ public class PassphraseDialogActivity extends FragmentActivity { public void onClick(View v) { if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { - Passphrase passphrase = - new Passphrase(mBackupCodeEditText.getText().toString()); + StringBuilder backupCodeInput = new StringBuilder(26); + for (EditText editText : mBackupCodeEditText) { + if (editText.getText().length() < 4) { + return; + } + backupCodeInput.append(editText.getText()); + backupCodeInput.append('-'); + } + backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); + + Passphrase passphrase = new Passphrase(backupCodeInput.toString()); finishCaching(passphrase); return; 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/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/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 3d96f3c6d..3dcc2f58b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -50,14 +50,13 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; +import okhttp3.OkHttpClient; +import okhttp3.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; @@ -354,19 +353,15 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On Log.d("Converted URL", newKeyserver.toString()); - OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); - - // don't follow any redirects - client.setFollowRedirects(false); - client.setFollowSslRedirects(false); - if (onlyTrustedKeyserver - && !TlsHelper.usePinnedCertificateIfAvailable(client, newKeyserver.toURL())) { + && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL()) == null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); reason = FailureReason.NO_PINNED_CERTIFICATE; return reason; } + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(newKeyserver.toURL(), proxy); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java index c25f775b0..334a7361b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -119,7 +119,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { private static Boolean checkHandle(String handle) { try { HttpURLConnection nection = - (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection(); + (HttpURLConnection) new URL("https://twitter.com/" + handle).getUrlResponse(); nection.setRequestMethod("HEAD"); nection.setRequestProperty("User-Agent", "OpenKeychain"); return nection.getResponseCode() == 200; |