token-import: add entrypoint for blank token setup flow

This commit is contained in:
Vincent Breitmoser
2017-09-11 01:42:04 +02:00
parent 19bb4ff83c
commit e0b5d97356
9 changed files with 116 additions and 214 deletions

View File

@@ -1,6 +1,8 @@
package org.sufficientlysecure.keychain.securitytoken; package org.sufficientlysecure.keychain.securitytoken;
import java.util.Arrays;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -12,6 +14,8 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@AutoValue @AutoValue
public abstract class SecurityTokenInfo implements Parcelable { public abstract class SecurityTokenInfo implements Parcelable {
private static final byte[] EMPTY_ARRAY = new byte[20];
@Nullable @Nullable
public abstract byte[] getFingerprintSign(); public abstract byte[] getFingerprintSign();
@Nullable @Nullable
@@ -36,7 +40,8 @@ public abstract class SecurityTokenInfo implements Parcelable {
} }
public boolean isEmpty() { public boolean isEmpty() {
return getFingerprintSign() == null && getFingerprintDecrypt() == null && getFingerprintAuth() == null; return Arrays.equals(EMPTY_ARRAY, getFingerprintSign()) && Arrays.equals(EMPTY_ARRAY, getFingerprintDecrypt()) &&
Arrays.equals(EMPTY_ARRAY, getFingerprintAuth());
} }
public static SecurityTokenInfo create(byte[] fpSign, byte[] fpDecrypt, byte[] fpAuth, public static SecurityTokenInfo create(byte[] fpSign, byte[] fpDecrypt, byte[] fpAuth,
@@ -45,10 +50,6 @@ public abstract class SecurityTokenInfo implements Parcelable {
userId, url, verifyRetries, verifyAdminRetries); userId, url, verifyRetries, verifyAdminRetries);
} }
public static SecurityTokenInfo createBlank(byte[] aid) {
return new AutoValue_SecurityTokenInfo(null, null, null, aid, null, null, 0, 0);
}
public static SecurityTokenInfo newInstanceDebugKeyserver() { public static SecurityTokenInfo newInstanceDebugKeyserver() {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
throw new UnsupportedOperationException("This operation is only available in debug builds!"); throw new UnsupportedOperationException("This operation is only available in debug builds!");

View File

@@ -110,16 +110,9 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
if (intent.hasExtra(EXTRA_SECURITY_TOKEN_INFO)) { if (intent.hasExtra(EXTRA_SECURITY_TOKEN_INFO)) {
SecurityTokenInfo tokenInfo = intent.getParcelableExtra(EXTRA_SECURITY_TOKEN_INFO); SecurityTokenInfo tokenInfo = intent.getParcelableExtra(EXTRA_SECURITY_TOKEN_INFO);
if (!tokenInfo.isEmpty()) { Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo);
Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo); loadFragment(frag, FragAction.START);
loadFragment(frag, FragAction.START); setTitle(R.string.title_manage_my_keys);
setTitle(R.string.title_import_keys);
} else {
Fragment frag = CreateSecurityTokenBlankFragment.newInstance();
loadFragment(frag, FragAction.START);
setTitle(R.string.title_manage_my_keys);
}
// done // done
return; return;
@@ -164,15 +157,10 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false; CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false;
} }
if (!tokenInfo.isEmpty()) { Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo);
Fragment frag = ManageSecurityTokenFragment.newInstance(tokenInfo); if (mCurrentFragment instanceof ManageSecurityTokenFragment) {
if (mCurrentFragment instanceof ManageSecurityTokenFragment) { loadFragment(frag, FragAction.REPLACE);
loadFragment(frag, FragAction.REPLACE);
} else {
loadFragment(frag, FragAction.TO_RIGHT);
}
} else { } else {
Fragment frag = CreateSecurityTokenBlankFragment.newInstance();
loadFragment(frag, FragAction.TO_RIGHT); loadFragment(frag, FragAction.TO_RIGHT);
} }
} }
@@ -196,6 +184,14 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity {
setContentView(R.layout.create_key_activity); setContentView(R.layout.create_key_activity);
} }
public void startCreateKeyForSecurityToken(SecurityTokenInfo tokenInfo) {
mCreateSecurityToken = true;
this.tokenInfo = tokenInfo;
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
loadFragment(frag, FragAction.TO_RIGHT);
}
public enum FragAction { public enum FragAction {
START, START,
TO_RIGHT, TO_RIGHT,

View File

@@ -1,86 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
public class CreateSecurityTokenBlankFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity;
View mBackButton;
View mNextButton;
/**
* Creates new instance of this fragment
*/
public static CreateSecurityTokenBlankFragment newInstance() {
return new CreateSecurityTokenBlankFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false);
mBackButton = view.findViewById(R.id.create_key_back_button);
mNextButton = view.findViewById(R.id.create_key_next_button);
mBackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getFragmentManager().getBackStackEntryCount() == 0) {
getActivity().setResult(Activity.RESULT_CANCELED);
getActivity().finish();
} else {
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
}
}
});
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nextClicked();
}
});
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mCreateKeyActivity = (CreateKeyActivity) getActivity();
}
private void nextClicked() {
mCreateKeyActivity.mCreateSecurityToken = true;
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
}
}

View File

@@ -56,6 +56,8 @@ class ManageSecurityTokenContract {
void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo); void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo);
void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo); void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo);
void onClickSetupToken();
void onClickUnlockToken(); void onClickUnlockToken();
void onMenuClickChangePin(); void onMenuClickChangePin();
void onInputAdminPin(String adminPin, String newPin); void onInputAdminPin(String adminPin, String newPin);
@@ -73,6 +75,7 @@ class ManageSecurityTokenContract {
void showActionViewKey(); void showActionViewKey();
void showActionRetryOrFromFile(); void showActionRetryOrFromFile();
void showActionLocked(int unlockAttempts); void showActionLocked(int unlockAttempts);
void showActionEmptyToken();
void hideAction(); void hideAction();
void operationImportKey(byte[] importKeyData); void operationImportKey(byte[] importKeyData);
@@ -85,6 +88,7 @@ class ManageSecurityTokenContract {
void showFileSelectDialog(); void showFileSelectDialog();
void showConfirmResetDialog(); void showConfirmResetDialog();
void showAdminPinDialog(); void showAdminPinDialog();
void startCreateKeyForToken(SecurityTokenInfo tokenInfo);
void showDisplayLogActivity(OperationResult result); void showDisplayLogActivity(OperationResult result);

View File

@@ -130,6 +130,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
view.findViewById(R.id.button_unlock).setOnClickListener(this); view.findViewById(R.id.button_unlock).setOnClickListener(this);
view.findViewById(R.id.button_unlock_impossible).setOnClickListener(this); view.findViewById(R.id.button_unlock_impossible).setOnClickListener(this);
view.findViewById(R.id.button_load_file).setOnClickListener(this); view.findViewById(R.id.button_load_file).setOnClickListener(this);
view.findViewById(R.id.button_setup).setOnClickListener(this);
setHasOptionsMenu(true); setHasOptionsMenu(true);
@@ -253,6 +254,11 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
} }
} }
@Override
public void showActionEmptyToken() {
actionAnimator.setDisplayedChildId(R.id.token_layout_empty);
}
@Override @Override
public void hideAction() { public void hideAction() {
actionAnimator.setDisplayedChild(0); actionAnimator.setDisplayedChild(0);
@@ -325,6 +331,12 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
adminPinDialog.show(); adminPinDialog.show();
} }
@Override
public void startCreateKeyForToken(SecurityTokenInfo tokenInfo) {
CreateKeyActivity activity = (CreateKeyActivity) getActivity();
activity.startCreateKeyForSecurityToken(tokenInfo);
}
@Override @Override
public void showErrorCannotUnlock() { public void showErrorCannotUnlock() {
Notify.create(getActivity(), R.string.token_error_locked_indefinitely, Style.ERROR).show(); Notify.create(getActivity(), R.string.token_error_locked_indefinitely, Style.ERROR).show();
@@ -430,6 +442,11 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
presenter.onClickUnlockTokenImpossible(); presenter.onClickUnlockTokenImpossible();
break; break;
} }
case R.id.button_setup: {
presenter.onClickSetupToken();
break;
}
} }
} }

View File

@@ -107,7 +107,8 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
private void continueSearch() { private void continueSearch() {
if (!checkedKeyStatus) { if (!checkedKeyStatus) {
boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0; boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0;
if (keyIsLocked) { boolean keyIsEmpty = tokenInfo.isEmpty();
if (keyIsLocked || keyIsEmpty) {
// the "checking key status" is fake: we only do it if we already know the key is locked // the "checking key status" is fake: we only do it if we already know the key is locked
view.statusLineAdd(StatusLine.CHECK_KEY); view.statusLineAdd(StatusLine.CHECK_KEY);
delayPerformKeyCheck(); delayPerformKeyCheck();
@@ -148,19 +149,27 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
} }
private void performKeyCheck() { private void performKeyCheck() {
boolean isLocked = tokenInfo.getVerifyRetries() == 0; boolean keyIsEmpty = tokenInfo.isEmpty();
if (!isLocked) { if (keyIsEmpty) {
view.statusLineOk(); view.statusLineOk();
checkedKeyStatus = true; view.showActionEmptyToken();
continueSearch();
return; return;
} }
view.statusLineError(); boolean keyIsLocked = tokenInfo.getVerifyRetries() == 0;
if (keyIsLocked) {
view.statusLineError();
int unlockAttemptsLeft = tokenInfo.getVerifyAdminRetries(); int unlockAttemptsLeft = tokenInfo.getVerifyAdminRetries();
view.showActionLocked(unlockAttemptsLeft); view.showActionLocked(unlockAttemptsLeft);
return;
}
view.statusLineOk();
checkedKeyStatus = true;
continueSearch();
} }
@Override @Override
@@ -346,6 +355,11 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
} }
} }
@Override
public void onClickSetupToken() {
view.startCreateKeyForToken(tokenInfo);
}
@Override @Override
public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) { public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) {
this.tokenInfo = tokenInfo; this.tokenInfo = tokenInfo;

View File

@@ -50,7 +50,7 @@
android:inAnimation="@anim/fade_in_delayed" android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out" android:outAnimation="@anim/fade_out"
android:clipChildren="false" android:clipChildren="false"
custom:initialView="05"> custom:initialView="06">
<Space <Space
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -67,7 +67,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?listPreferredItemHeight" android:minHeight="?listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/token_status_not_found" android:text="@string/token_result_not_found"
style="?android:textAppearanceLarge" style="?android:textAppearanceLarge"
/> />
@@ -79,7 +79,7 @@
android:drawableStart="@drawable/ic_repeat_grey_24dp" android:drawableStart="@drawable/ic_repeat_grey_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/card_view_button" android:textColor="@color/card_view_button"
android:text="@string/token_status_retry" android:text="@string/token_action_retry"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -91,7 +91,7 @@
android:drawableStart="@drawable/ic_folder_grey_24dp" android:drawableStart="@drawable/ic_folder_grey_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/card_view_button" android:textColor="@color/card_view_button"
android:text="@string/token_status_load_from_file" android:text="@string/token_action_load_from_file"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -103,7 +103,7 @@
android:drawableStart="@drawable/ic_bomb_24dp" android:drawableStart="@drawable/ic_bomb_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/android_red_dark" android:textColor="@color/android_red_dark"
android:text="@string/token_status_reset" android:text="@string/token_action_reset"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -120,7 +120,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?listPreferredItemHeight" android:minHeight="?listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/token_status_key_found" android:text="@string/token_result_key_found"
style="?android:textAppearanceLarge" style="?android:textAppearanceLarge"
/> />
@@ -132,7 +132,7 @@
android:drawableStart="@drawable/ic_key_plus_grey600_24dp" android:drawableStart="@drawable/ic_key_plus_grey600_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/card_view_button" android:textColor="@color/card_view_button"
android:text="@string/token_status_import" android:text="@string/token_action_import"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -144,7 +144,7 @@
android:drawableStart="@drawable/ic_bomb_24dp" android:drawableStart="@drawable/ic_bomb_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/android_red_dark" android:textColor="@color/android_red_dark"
android:text="@string/token_status_reset" android:text="@string/token_action_reset"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -161,7 +161,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?listPreferredItemHeight" android:minHeight="?listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/token_status_token_ok" android:text="@string/token_result_token_ok"
style="?android:textAppearanceLarge" style="?android:textAppearanceLarge"
/> />
@@ -173,7 +173,7 @@
android:drawableStart="@drawable/ic_vpn_key_grey_24dp" android:drawableStart="@drawable/ic_vpn_key_grey_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/card_view_button" android:textColor="@color/card_view_button"
android:text="@string/token_status_view_key" android:text="@string/token_action_view_key"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -185,7 +185,7 @@
android:drawableStart="@drawable/ic_bomb_24dp" android:drawableStart="@drawable/ic_bomb_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/android_red_dark" android:textColor="@color/android_red_dark"
android:text="@string/token_status_reset" android:text="@string/token_action_reset"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -259,7 +259,7 @@
android:drawableStart="@drawable/ic_bomb_24dp" android:drawableStart="@drawable/ic_bomb_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/android_red_dark" android:textColor="@color/android_red_dark"
android:text="@string/token_status_reset" android:text="@string/token_action_reset"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />
@@ -334,7 +334,37 @@
android:drawableStart="@drawable/ic_bomb_24dp" android:drawableStart="@drawable/ic_bomb_24dp"
android:drawablePadding="12dp" android:drawablePadding="12dp"
android:textColor="@color/android_red_dark" android:textColor="@color/android_red_dark"
android:text="@string/token_status_reset" android:text="@string/token_action_reset"
style="?borderlessButtonStyle"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/token_layout_empty">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?listPreferredItemHeight"
android:gravity="center_vertical"
android:text="@string/token_result_empty"
style="?android:textAppearanceLarge"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button_setup"
android:drawableLeft="@drawable/ic_person_add_grey_24dp"
android:drawableStart="@drawable/ic_person_add_grey_24dp"
android:drawableTint="@color/md_black_1000"
android:drawablePadding="12dp"
android:text="@string/token_action_setup"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
/> />

View File

@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_above="@+id/create_key_buttons">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/first_time_blank_security_token" />
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:background="?attr/colorButtonRow"
android:id="@+id/create_key_buttons">
<TextView
android:id="@+id/create_key_back_button"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:text="@string/btn_back"
android:textAllCaps="true"
android:minHeight="?android:attr/listPreferredItemHeight"
android:drawableLeft="@drawable/ic_chevron_left_grey_24dp"
android:drawablePadding="8dp"
android:gravity="left|center_vertical"
android:clickable="true"
style="?android:attr/borderlessButtonStyle" />
<TextView
android:id="@+id/create_key_next_button"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:text="@string/first_time_blank_security_token_yes"
android:textAllCaps="true"
android:minHeight="?android:attr/listPreferredItemHeight"
android:drawableRight="@drawable/ic_chevron_right_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical|right"
android:clickable="true"
style="?android:attr/borderlessButtonStyle" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1931,14 +1931,16 @@
<string name="token_reset_confirm_message">This will irrecoverably delete the key stored on this Security Token. You will no longer be able to use this key for decryption! Are you sure?</string> <string name="token_reset_confirm_message">This will irrecoverably delete the key stored on this Security Token. You will no longer be able to use this key for decryption! Are you sure?</string>
<string name="token_reset_confirm_ok">Reset</string> <string name="token_reset_confirm_ok">Reset</string>
<string name="token_status_title">Gathering information for Security Token…</string> <string name="token_status_title">Gathering information for Security Token…</string>
<string name="token_status_not_found">Key not found!</string> <string name="token_result_not_found">Key not found!</string>
<string name="token_status_retry">Retry Search</string> <string name="token_result_key_found">Key found!</string>
<string name="token_status_load_from_file">Load from File</string> <string name="token_result_token_ok">Ready for use!</string>
<string name="token_status_reset">Reset Security Token</string> <string name="token_result_empty">Token is empty</string>
<string name="token_status_key_found">Key found!</string> <string name="token_action_retry">Retry Search</string>
<string name="token_status_import">Import</string> <string name="token_action_load_from_file">Load from File</string>
<string name="token_status_token_ok">Ready for use!</string> <string name="token_action_reset">Reset Security Token</string>
<string name="token_status_view_key">View Key</string> <string name="token_action_import">Import</string>
<string name="token_action_view_key">View Key</string>
<string name="token_action_setup">"Set up with new key"</string>
<string name="token_action_unlock">Unlock using admin pin</string> <string name="token_action_unlock">Unlock using admin pin</string>
<string name="token_unlock_attempts_none">"No unlock attempts left"</string> <string name="token_unlock_attempts_none">"No unlock attempts left"</string>
<plurals name="token_unlock_attempts"> <plurals name="token_unlock_attempts">