token-import: add ui for pin change and unlock

This commit is contained in:
Vincent Breitmoser
2017-09-07 16:05:59 +02:00
parent 36bec236f4
commit 646940eb44
12 changed files with 413 additions and 34 deletions

View File

@@ -108,7 +108,7 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
/**
* Override to do something when PIN is wrong, e.g., clear passphrases (UI thread)
*/
protected void onSecurityTokenPinError(String error) {
protected void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) {
onSecurityTokenError(error);
}
@@ -242,8 +242,16 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
// https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410
if ((status & (short) 0xFFF0) == 0x63C0) {
int tries = status & 0x000F;
SecurityTokenInfo tokeninfo = null;
try {
tokeninfo = mSecurityTokenHelper.getTokenInfo();
} catch (IOException e2) {
// don't care
}
// hook to do something different when PIN is wrong
onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries));
onSecurityTokenPinError(
getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries), tokeninfo);
return;
}
@@ -256,8 +264,15 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */
// NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN
case 0x6982: {
SecurityTokenInfo tokeninfo = null;
try {
tokeninfo = mSecurityTokenHelper.getTokenInfo();
} catch (IOException e2) {
// don't care
}
// hook to do something different when PIN is wrong
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied));
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied), tokeninfo);
break;
}
/* OpenPGP Card Spec: Selected file in termination state */
@@ -270,14 +285,14 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
// https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f
case 0x6700: {
// hook to do something different when PIN is wrong
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length));
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length), null);
break;
}
/* OpenPGP Card Spec: Incorrect parameters in the data field */
// NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN
case 0x6A80: {
// hook to do something different when PIN is wrong
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data));
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data), null);
break;
}
/* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */

View File

@@ -0,0 +1,76 @@
package org.sufficientlysecure.keychain.ui.token;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.support.annotation.CheckResult;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
class ChangePinDialogHelper {
@CheckResult
static AlertDialog createAdminPinDialog(Context context, final ManageSecurityTokenMvpPresenter presenter) {
ContextThemeWrapper themedContext = ThemeChanger.getDialogThemeWrapper(context);
@SuppressLint("InflateParams") // it's a dialog, no root element
View view = LayoutInflater.from(themedContext).inflate(R.layout.admin_pin_dialog, null, false);
final EditText adminPin = (EditText) view.findViewById(R.id.admin_pin_current);
final EditText newPin = (EditText) view.findViewById(R.id.pin_new);
final EditText newPinRepeat = (EditText) view.findViewById(R.id.pin_new_repeat);
AlertDialog dialog = new Builder(themedContext)
.setView(view)
.setNegativeButton(R.string.button_cancel, null)
.setPositiveButton(R.string.token_unlock_ok, null).create();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(final DialogInterface dialog) {
((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
checkAndHandleInput(adminPin, newPin, newPinRepeat, dialog, presenter);
}
});
}
});
return dialog;
}
private static void checkAndHandleInput(EditText adminPinView, EditText newPinView, EditText newPinRepeatView,
DialogInterface dialog, ManageSecurityTokenMvpPresenter presenter) {
String adminPin = adminPinView.getText().toString();
String newPin = newPinView.getText().toString();
String newPinRepeat = newPinRepeatView.getText().toString();
if (adminPin.length() < 8) {
adminPinView.setError(adminPinView.getContext().getString(R.string.token_error_admin_min8));
return;
}
if (newPin.length() < 6) {
newPinView.setError(newPinView.getContext().getString(R.string.token_error_pin_min6));
return;
}
if (!newPin.equals(newPinRepeat)) {
newPinRepeatView.setError(newPinRepeatView.getContext().getString(R.string.token_error_pin_repeat));
return;
}
dialog.dismiss();
presenter.onInputAdminPin(adminPin, newPin);
}
}

View File

@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui.token;
import android.net.Uri;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment.StatusLine;
@@ -31,7 +32,7 @@ class ManageSecurityTokenContract {
void onClickRetry();
void onClickViewKey();
void onClickViewLog();
void onMenuClickViewLog();
void onClickImport();
void onImportSuccess(OperationResult result);
@@ -41,6 +42,10 @@ class ManageSecurityTokenContract {
void onPromoteError(OperationResult result);
void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo);
void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo);
void onClickLoadFile();
void onFileSelected(Uri fileUri);
void onStoragePermissionGranted();
@@ -48,9 +53,14 @@ class ManageSecurityTokenContract {
void onClickResetToken();
void onClickConfirmReset();
void onSecurityTokenResetSuccess();
void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo);
void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo);
void onClickUnlockToken();
void onMenuClickChangePin();
void onInputAdminPin(String adminPin, String newPin);
void onClickUnlockTokenImpossible();
}
interface ManageSecurityTokenMvpView {
@@ -74,9 +84,12 @@ class ManageSecurityTokenContract {
void showFileSelectDialog();
void showConfirmResetDialog();
void showAdminPinDialog();
void showDisplayLogActivity(OperationResult result);
void requestStoragePermission();
void showErrorCannotUnlock();
}
}

View File

@@ -31,6 +31,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -41,8 +42,6 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
@@ -65,7 +64,8 @@ import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCal
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpView;
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.ui.util.ThemeChanger;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
@@ -149,7 +149,9 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
view.findViewById(R.id.button_reset_token_2).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_3).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_4).setOnClickListener(this);
view.findViewById(R.id.button_reset_token_5).setOnClickListener(this);
view.findViewById(R.id.button_unlock).setOnClickListener(this);
view.findViewById(R.id.button_unlock_impossible).setOnClickListener(this);
view.findViewById(R.id.button_load_file).setOnClickListener(this);
setHasOptionsMenu(true);
@@ -175,7 +177,11 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.view_log: {
presenter.onClickViewLog();
presenter.onMenuClickViewLog();
return true;
}
case R.id.change_pin: {
presenter.onMenuClickChangePin();
return true;
}
default: {
@@ -259,13 +265,14 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
@Override
public void showActionLocked(int attemptsLeft) {
actionAnimator.setDisplayedChildId(R.id.token_layout_locked);
if (attemptsLeft > 0) {
actionAnimator.setDisplayedChildId(R.id.token_layout_locked);
String unlockAttemptsText = getResources().getQuantityString(
R.plurals.token_unlock_attempts, attemptsLeft, attemptsLeft);
unlockSubtitle.setText(unlockAttemptsText);
} else {
unlockSubtitle.setText(R.string.token_unlock_attempts_none);
actionAnimator.setDisplayedChildId(R.id.token_layout_locked_hard);
}
}
@@ -329,12 +336,23 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
.setPositiveButton(R.string.token_reset_confirm_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
presenter.onClickConfirmReset();
}
}).show();
}
@Override
public void showAdminPinDialog() {
AlertDialog adminPinDialog = ChangePinDialogHelper.createAdminPinDialog(getContext(), presenter);
adminPinDialog.show();
}
@Override
public void showErrorCannotUnlock() {
Notify.create(getActivity(), R.string.token_error_locked_indefinitely, Style.ERROR).show();
}
@Override
public void showDisplayLogActivity(OperationResult result) {
Intent intent = new Intent(getActivity(), LogDisplayActivity.class);
@@ -374,8 +392,22 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
break;
}
case REQUEST_CODE_RESET: {
SecurityTokenInfo tokenInfo = data == null ? null :
data.<SecurityTokenInfo>getParcelableExtra(SecurityTokenOperationActivity.RESULT_TOKEN_INFO);
if (resultCode == Activity.RESULT_OK) {
presenter.onSecurityTokenResetSuccess();
presenter.onSecurityTokenResetSuccess(tokenInfo);
} else {
presenter.onSecurityTokenResetCanceled(tokenInfo);
}
break;
}
case REQUEST_CODE_CHANGE_PIN: {
SecurityTokenInfo tokenInfo = data == null ? null :
data.<SecurityTokenInfo>getParcelableExtra(SecurityTokenOperationActivity.RESULT_TOKEN_INFO);
if (resultCode == Activity.RESULT_OK) {
presenter.onSecurityTokenChangePinSuccess(tokenInfo);
} else {
presenter.onSecurityTokenChangePinCanceled(tokenInfo);
}
break;
}
@@ -407,7 +439,8 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
case R.id.button_reset_token_1:
case R.id.button_reset_token_2:
case R.id.button_reset_token_3:
case R.id.button_reset_token_4: {
case R.id.button_reset_token_4:
case R.id.button_reset_token_5: {
presenter.onClickResetToken();
break;
}
@@ -416,6 +449,10 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
presenter.onClickUnlockToken();
break;
}
case R.id.button_unlock_impossible: {
presenter.onClickUnlockTokenImpossible();
break;
}
}
}

View File

@@ -51,7 +51,8 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
private final Context context;
private final LoaderManager loaderManager;
private final SecurityTokenInfo tokenInfo;
private SecurityTokenInfo tokenInfo;
private ManageSecurityTokenMvpView view;
@@ -92,11 +93,28 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
continueSearch();
}
private void resetAndContinueSearch() {
checkedKeyStatus = false;
searchedLocally = false;
searchedAtUri = false;
searchedKeyservers = false;
view.hideAction();
view.resetStatusLines();
continueSearch();
}
private void continueSearch() {
if (!checkedKeyStatus) {
view.statusLineAdd(StatusLine.CHECK_KEY);
delayPerformKeyCheck();
return;
boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0;
if (keyIsLocked) {
// the "checking key status" is fake: we only do it if we already know the key is locked
view.statusLineAdd(StatusLine.CHECK_KEY);
delayPerformKeyCheck();
return;
} else {
checkedKeyStatus = true;
}
}
if (!searchedLocally) {
@@ -150,6 +168,30 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
view.showAdminPinDialog();
}
@Override
public void onMenuClickChangePin() {
if (!checkedKeyStatus) {
return;
}
if (tokenInfo.getVerifyAdminRetries() == 0) {
view.showErrorCannotUnlock();
return;
}
view.showAdminPinDialog();
}
@Override
public void onInputAdminPin(String adminPin, String newPin) {
view.operationChangePinSecurityToken(adminPin, newPin);
}
@Override
public void onClickUnlockTokenImpossible() {
view.showErrorCannotUnlock();
}
private LoaderCallbacks<KeyRetrievalResult> loaderCallbacks = new LoaderCallbacks<KeyRetrievalResult>() {
@Override
public Loader<KeyRetrievalResult> onCreateLoader(int id, Bundle args) {
@@ -272,13 +314,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
@Override
public void onClickRetry() {
searchedLocally = false;
searchedAtUri = false;
searchedKeyservers = false;
view.hideAction();
view.resetStatusLines();
continueSearch();
resetAndContinueSearch();
}
@Override
@@ -297,8 +333,31 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
}
@Override
public void onSecurityTokenResetSuccess() {
// TODO
public void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo) {
this.tokenInfo = tokenInfo;
resetAndContinueSearch();
}
@Override
public void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo) {
if (tokenInfo != null) {
this.tokenInfo = tokenInfo;
resetAndContinueSearch();
}
}
@Override
public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) {
this.tokenInfo = tokenInfo;
resetAndContinueSearch();
}
@Override
public void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo) {
if (tokenInfo != null) {
this.tokenInfo = tokenInfo;
resetAndContinueSearch();
}
}
@Override
@@ -340,7 +399,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
}
@Override
public void onClickViewLog() {
public void onMenuClickViewLog() {
OperationResult result = new GenericOperationResult(GenericOperationResult.RESULT_OK, log);
view.showDisplayLogActivity(result);
}