diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index d3bf9c3d5..a76982975 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -806,6 +806,13 @@ android:taskAffinity=":Nfc" android:theme="@style/Theme.Keychain.Light.Dialog" /> + + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 85a173502..c1e1412f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -217,6 +217,28 @@ public class SecurityTokenHelper { } + public void resetPin(String newPinStr) throws IOException { + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW1 with mode 82 for decryption) + } + + byte[] newPin = newPinStr.getBytes(); + + final int MAX_PW1_LENGTH_INDEX = 1; + byte[] pwStatusBytes = getPwStatusBytes(); + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + + // Command APDU for RESET RETRY COUNTER command (page 33) + CommandAPDU changePin = new CommandAPDU(0x00, 0x2C, 0x02, 0x81, newPin); + ResponseAPDU response = communicate(changePin); + + if (response.getSW() != APDU_SW_SUCCESS) { + throw new CardException("Failed to change PIN", response.getSW()); + } + } + /** * 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. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 84c139d0b..4a0fe8069 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -1,21 +1,22 @@ package org.sufficientlysecure.keychain.service.input; -import android.os.Parcel; -import android.os.Parcelable; - -import org.sufficientlysecure.keychain.util.Passphrase; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.util.Passphrase; + public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT, - SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, + SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY } public Date mSignatureTime; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java new file mode 100644 index 000000000..2d9bf7c85 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/SecurityTokenChangePinParcel.java @@ -0,0 +1,18 @@ +package org.sufficientlysecure.keychain.service.input; + + +import android.os.Parcelable; + +import com.google.auto.value.AutoValue; + + +@AutoValue +public abstract class SecurityTokenChangePinParcel implements Parcelable { + public abstract String getAdminPin(); + public abstract String getNewPin(); + + public static SecurityTokenChangePinParcel createSecurityTokenUnlock(String adminPin, String newPin) { + return new AutoValue_SecurityTokenChangePinParcel(adminPin, newPin); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java new file mode 100644 index 000000000..c38f667de --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenChangePinOperationActivity.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * 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 . + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.io.IOException; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import nordpol.android.NfcGuideView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; +import org.sufficientlysecure.keychain.service.input.SecurityTokenChangePinParcel; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OrientationUtils; +import org.sufficientlysecure.keychain.util.Passphrase; + + +/** + * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant + * NFC devices. + * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + */ +public class SecurityTokenChangePinOperationActivity extends BaseSecurityTokenActivity { + public static final String EXTRA_CHANGE_PIN_PARCEL = "change_pin_parcel"; + + public static final String RESULT_TOKEN_INFO = "token_info"; + + public ViewAnimator vAnimator; + public TextView vErrorText; + private TextView vErrorTextPin; + public Button vErrorTryAgainButton; + public NfcGuideView nfcGuideView; + + private SecurityTokenChangePinParcel changePinInput; + + private SecurityTokenInfo resultTokenInfo; + + @Override + protected void initTheme() { + mThemeChanger = new ThemeChanger(this); + mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog, + R.style.Theme_Keychain_Dark_Dialog); + mThemeChanger.changeTheme(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(Constants.TAG, "NfcOperationActivity.onCreate"); + + nfcGuideView = (NfcGuideView) findViewById(R.id.nfc_guide_view); + + // prevent annoying orientation changes while fumbling with the device + OrientationUtils.lockOrientation(this); + // prevent close when touching outside of the dialog (happens easily when fumbling with the device) + setFinishOnTouchOutside(false); + // keep screen on + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + setTitle(R.string.security_token_nfc_text); + + vAnimator = (ViewAnimator) findViewById(R.id.view_animator); + vAnimator.setDisplayedChild(0); + + nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION); + + vErrorText = (TextView) findViewById(R.id.security_token_activity_3_error_text); + vErrorTextPin = (TextView) findViewById(R.id.security_token_activity_4_error_text); + vErrorTryAgainButton = (Button) findViewById(R.id.security_token_activity_3_error_try_again); + vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resumeTagHandling(); + + vAnimator.setDisplayedChild(0); + + nfcGuideView.setVisibility(View.VISIBLE); + nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION); + } + }); + Button vCancel = (Button) findViewById(R.id.security_token_activity_0_cancel); + vCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }); + findViewById(R.id.security_token_activity_4_back).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent result = new Intent(); + result.putExtra(RESULT_TOKEN_INFO, resultTokenInfo); + setResult(RESULT_CANCELED, result); + finish(); + } + }); + + changePinInput = getIntent().getParcelableExtra(EXTRA_CHANGE_PIN_PARCEL); + } + + @Override + protected void initLayout() { + setContentView(R.layout.security_token_operation_activity); + } + + @Override + public void onSecurityTokenPreExecute() { + // start with indeterminate progress + vAnimator.setDisplayedChild(1); + nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); + } + + @Override + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenHelper.setAdminPin(new Passphrase(changePinInput.getAdminPin())); + mSecurityTokenHelper.resetPin(changePinInput.getNewPin()); + + resultTokenInfo = mSecurityTokenHelper.getTokenInfo(); + } + + @Override + protected final void onSecurityTokenPostExecute() { + Intent result = new Intent(); + result.putExtra(RESULT_TOKEN_INFO, resultTokenInfo); + setResult(RESULT_OK, result); + + // show finish + vAnimator.setDisplayedChild(2); + + nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); + + if (mSecurityTokenHelper.isPersistentConnectionAllowed()) { + // Just close + finish(); + } else { + mSecurityTokenHelper.clearSecureMessaging(); + new AsyncTask() { + @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; + } + } + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } + } + + @Override + protected void onSecurityTokenError(String error) { + pauseTagHandling(); + + vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); + vAnimator.setDisplayedChild(3); + + nfcGuideView.setVisibility(View.GONE); + } + + @Override + public void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) { + resultTokenInfo = tokeninfo; + + pauseTagHandling(); + + vErrorTextPin.setText(error); + vAnimator.setDisplayedChild(4); + + nfcGuideView.setVisibility(View.GONE); + } +} 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 ceea84ee8..09dfa51cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.securitytoken.KeyType; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -65,6 +66,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; public static final String RESULT_CRYPTO_INPUT = "result_data"; + public static final String RESULT_TOKEN_INFO = "token_info"; public ViewAnimator vAnimator; public TextView vErrorText; @@ -74,6 +76,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { private RequiredInputParcel mRequiredInput; private CryptoInputParcel mInputParcel; + private SecurityTokenInfo mResultTokenInfo; @Override protected void initTheme() { @@ -277,6 +280,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { } case SECURITY_TOKEN_RESET_CARD: { mSecurityTokenHelper.resetAndWipeToken(); + mResultTokenInfo = mSecurityTokenHelper.getTokenInfo(); break; } @@ -334,6 +338,9 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { Intent result = new Intent(); // send back the CryptoInputParcel we received result.putExtra(RESULT_CRYPTO_INPUT, inputParcel); + if (mResultTokenInfo != null) { + result.putExtra(RESULT_TOKEN_INFO, mResultTokenInfo); + } setResult(RESULT_OK, result); } @@ -348,7 +355,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { } @Override - public void onSecurityTokenPinError(String error) { + public void onSecurityTokenPinError(String error, SecurityTokenInfo tokeninfo) { onSecurityTokenError(error); // clear (invalid) passphrase diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java index 7c67e0ad0..ba6f68f02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenContract.java @@ -68,6 +68,7 @@ class ManageSecurityTokenContract { void operationImportKey(byte[] importKeyData); void operationPromote(long masterKeyId, byte[] cardAid); void operationResetSecurityToken(); + void operationChangePinSecurityToken(String adminPin, String newPin); void finishAndShowKey(long masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java index 89c9fefc5..58b693ea6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java @@ -54,10 +54,12 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.SecurityTokenChangePinParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; +import org.sufficientlysecure.keychain.ui.SecurityTokenChangePinOperationActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; @@ -76,6 +78,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur private static final String ARG_TOKEN_INFO = "token_info"; public static final int REQUEST_CODE_OPEN_FILE = 0; public static final int REQUEST_CODE_RESET = 1; + public static final int REQUEST_CODE_CHANGE_PIN = 2; public static final int PERMISSION_READ_STORAGE = 0; ManageSecurityTokenMvpPresenter presenter; @@ -303,6 +306,15 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur startActivityForResult(intent, REQUEST_CODE_RESET); } + @Override + public void operationChangePinSecurityToken(String adminPin, String newPin) { + Intent intent = new Intent(getActivity(), SecurityTokenChangePinOperationActivity.class); + SecurityTokenChangePinParcel changePinParcel = + SecurityTokenChangePinParcel.createSecurityTokenUnlock(adminPin, newPin); + intent.putExtra(SecurityTokenChangePinOperationActivity.EXTRA_CHANGE_PIN_PARCEL, changePinParcel); + startActivityForResult(intent, REQUEST_CODE_CHANGE_PIN); + } + @Override public void showFileSelectDialog() { FileHelper.openDocument(this, null, "*/*", false, REQUEST_CODE_OPEN_FILE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java index ba2966b31..054e396d9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenPresenter.java @@ -147,7 +147,7 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter { @Override public void onClickUnlockToken() { - // TODO + view.showAdminPinDialog(); } private LoaderCallbacks loaderCallbacks = new LoaderCallbacks() {