aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java218
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java53
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java15
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java103
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java77
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java176
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java860
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java2
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;