From 0fd5b45df913d1524aa400a644c48dc91044d9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 10 Jan 2016 17:17:57 +0100 Subject: Use more generic 'Security Token' where possible, add sutitle to create key what tokens are supported --- .../org/sufficientlysecure/keychain/Constants.java | 2 +- .../operations/results/OperationResult.java | 6 +- .../keychain/pgp/PgpKeyOperation.java | 52 +- .../keychain/remote/OpenPgpService.java | 12 +- .../keychain/service/SaveKeyringParcel.java | 45 +- .../keychain/ui/CreateKeyActivity.java | 44 +- .../keychain/ui/CreateKeyEmailFragment.java | 4 +- .../keychain/ui/CreateKeyFinalFragment.java | 15 +- .../keychain/ui/CreateKeyStartFragment.java | 8 +- .../ui/CreateSecurityTokenBlankFragment.java | 90 ++ .../ui/CreateSecurityTokenImportResetFragment.java | 297 ++++++ .../ui/CreateSecurityTokenPinFragment.java | 213 ++++ .../ui/CreateSecurityTokenWaitFragment.java | 58 ++ .../keychain/ui/CreateYubiKeyBlankFragment.java | 90 -- .../ui/CreateYubiKeyImportResetFragment.java | 297 ------ .../keychain/ui/CreateYubiKeyPinFragment.java | 213 ---- .../keychain/ui/CreateYubiKeyWaitFragment.java | 58 -- .../keychain/ui/EditKeyFragment.java | 8 +- .../keychain/ui/ImportKeysActivity.java | 4 +- .../keychain/ui/MainActivity.java | 4 +- .../keychain/ui/NfcOperationActivity.java | 342 ------- .../keychain/ui/PassphraseDialogActivity.java | 4 +- .../ui/SecurityTokenOperationActivity.java | 340 +++++++ .../keychain/ui/ViewKeyActivity.java | 72 +- .../keychain/ui/ViewKeyAdvSubkeysFragment.java | 8 +- .../keychain/ui/ViewKeySecurityTokenFragment.java | 222 +++++ .../keychain/ui/ViewKeyYubiKeyFragment.java | 222 ----- .../keychain/ui/adapter/SubkeysAdapter.java | 4 +- .../keychain/ui/base/BaseNfcActivity.java | 1021 -------------------- .../ui/base/BaseSecurityTokenNfcActivity.java | 1020 +++++++++++++++++++ .../keychain/ui/base/CryptoOperationHelper.java | 10 +- .../keychain/util/Preferences.java | 4 +- 32 files changed, 2393 insertions(+), 2396 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java (limited to 'OpenKeychain/src/main/java/org') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 7bac8aa97..3e48abccb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -105,7 +105,7 @@ public final class Constants { public static final String CACHED_CONSOLIDATE = "cachedConsolidate"; public static final String SEARCH_KEYSERVER = "search_keyserver_pref"; public static final String SEARCH_KEYBASE = "search_keybase_pref"; - public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin"; + public static final String USE_NUMKEYPAD_FOR_SECURITY_TOKEN_PIN = "useNumKeypadForYubikeyPin"; public static final String ENCRYPT_FILENAMES = "encryptFilenames"; public static final String FILE_USE_COMPRESSION = "useFileCompression"; public static final String TEXT_USE_COMPRESSION = "useTextCompression"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index ed2123987..1e90c2cc3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -562,9 +562,9 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands), MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot), MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard), - MSG_MF_ERROR_BAD_NFC_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_algo), - MSG_MF_ERROR_BAD_NFC_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_size), - MSG_MF_ERROR_BAD_NFC_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_stripped), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_algo), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size), + MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 59b840054..40a17a918 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -413,8 +413,8 @@ public class PgpKeyOperation { boolean hasEncrypt = false; boolean hasAuth = false; for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) { - if (change.mMoveKeyToCard) { - // If this is a keytocard operation, see if it was completed: look for a hash + if (change.mMoveKeyToSecurityToken) { + // If this is a moveKeyToSecurityToken operation, see if it was completed: look for a hash // matching the given subkey ID in cryptoData. byte[] subKeyId = new byte[8]; ByteBuffer buf = ByteBuffer.wrap(subKeyId); @@ -422,13 +422,13 @@ public class PgpKeyOperation { byte[] serialNumber = cryptoInput.getCryptoData().get(buf); if (serialNumber != null) { - change.mMoveKeyToCard = false; - change.mDummyDivert = serialNumber; + change.mMoveKeyToSecurityToken = false; + change.mSecurityTokenSerialNo = serialNumber; } } - if (change.mMoveKeyToCard) { - // Pending keytocard operation. Need to make sure that we don't have multiple + if (change.mMoveKeyToSecurityToken) { + // Pending moveKeyToSecurityToken operation. Need to make sure that we don't have multiple // subkeys pending for the same slot. CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId); @@ -810,26 +810,26 @@ public class PgpKeyOperation { // no really, it is. this operation irrevocably removes the private key data from the key sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); - } else if (change.mMoveKeyToCard) { - if (checkSmartCardCompatibility(sKey, log, indent + 1)) { + } else if (change.mMoveKeyToSecurityToken) { + if (checkSecurityTokenCompatibility(sKey, log, indent + 1)) { log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); nfcKeyToCardOps.addSubkey(change.mKeyId); } else { - // Appropriate log message already set by checkSmartCardCompatibility + // Appropriate log message already set by checkSecurityTokenCompatibility return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null); } - } else if (change.mDummyDivert != null) { + } else if (change.mSecurityTokenSerialNo != null) { // NOTE: Does this code get executed? Or always handled in internalRestricted? - if (change.mDummyDivert.length != 16) { + if (change.mSecurityTokenSerialNo.length != 16) { log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId), - Hex.toHexString(change.mDummyDivert, 8, 6)); - sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert); + Hex.toHexString(change.mSecurityTokenSerialNo, 8, 6)); + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mSecurityTokenSerialNo); sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); } @@ -1063,22 +1063,22 @@ public class PgpKeyOperation { indent -= 1; } - // 7. if requested, change PIN and/or Admin PIN on card - if (saveParcel.mCardPin != null) { + // 7. if requested, change PIN and/or Admin PIN on security token + if (saveParcel.mSecurityTokenPin != null) { progress(R.string.progress_modify_pin, 90); log.add(LogType.MSG_MF_PIN, indent); indent += 1; - nfcKeyToCardOps.setPin(saveParcel.mCardPin); + nfcKeyToCardOps.setPin(saveParcel.mSecurityTokenPin); indent -= 1; } - if (saveParcel.mCardAdminPin != null) { + if (saveParcel.mSecurityTokenAdminPin != null) { progress(R.string.progress_modify_admin_pin, 90); log.add(LogType.MSG_MF_ADMIN_PIN, indent); indent += 1; - nfcKeyToCardOps.setAdminPin(saveParcel.mCardAdminPin); + nfcKeyToCardOps.setAdminPin(saveParcel.mSecurityTokenAdminPin); indent -= 1; } @@ -1157,22 +1157,22 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } - if (change.mDummyStrip || change.mDummyDivert != null) { + if (change.mDummyStrip || change.mSecurityTokenSerialNo != null) { // IT'S DANGEROUS~ // no really, it is. this operation irrevocably removes the private key data from the key if (change.mDummyStrip) { sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey()); } else { // the serial number must be 16 bytes in length - if (change.mDummyDivert.length != 16) { + if (change.mSecurityTokenSerialNo.length != 16) { log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId), - Hex.toHexString(change.mDummyDivert, 8, 6)); - sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert); + Hex.toHexString(change.mSecurityTokenSerialNo, 8, 6)); + sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mSecurityTokenSerialNo); } sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); } @@ -1625,26 +1625,26 @@ public class PgpKeyOperation { && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD; } - private static boolean checkSmartCardCompatibility(PGPSecretKey key, OperationLog log, int indent) { + private static boolean checkSecurityTokenCompatibility(PGPSecretKey key, OperationLog log, int indent) { PGPPublicKey publicKey = key.getPublicKey(); int algorithm = publicKey.getAlgorithm(); if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT && algorithm != PublicKeyAlgorithmTags.RSA_SIGN && algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) { - log.add(LogType.MSG_MF_ERROR_BAD_NFC_ALGO, indent + 1); + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_ALGO, indent + 1); return false; } // Key size must be 2048 int keySize = publicKey.getBitStrength(); if (keySize != 2048) { - log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1); + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE, indent + 1); return false; } // Secret key parts must be available if (isDivertToCard(key) || isDummy(key)) { - log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1); + log.add(LogType.MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED, indent + 1); return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index a6d505763..15c83d4dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -59,7 +59,7 @@ import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.ImportKeysActivity; -import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; import org.sufficientlysecure.keychain.util.InputData; @@ -191,12 +191,12 @@ public class OpenPgpService extends Service { case NFC_MOVE_KEY_TO_CARD: case NFC_DECRYPT: case NFC_SIGN: { - // build PendingIntent for YubiKey NFC operations - Intent intent = new Intent(context, NfcOperationActivity.class); + // build PendingIntent for Security Token NFC operations + Intent intent = new Intent(context, SecurityTokenOperationActivity.class); // pass params through to activity that it can be returned again later to repeat pgp operation - intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data); - intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); - intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInput); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_SERVICE_INTENT, data); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInput); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 6959fca56..472eb3b18 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -61,9 +61,9 @@ public class SaveKeyringParcel implements Parcelable { public ArrayList mRevokeUserIds; public ArrayList mRevokeSubKeys; - // if these are non-null, PINs will be changed on the card - public Passphrase mCardPin; - public Passphrase mCardAdminPin; + // if these are non-null, PINs will be changed on the token + public Passphrase mSecurityTokenPin; + public Passphrase mSecurityTokenAdminPin; // private because they have to be set together with setUpdateOptions private boolean mUpload; @@ -89,8 +89,8 @@ public class SaveKeyringParcel implements Parcelable { mChangeSubKeys = new ArrayList<>(); mRevokeUserIds = new ArrayList<>(); mRevokeSubKeys = new ArrayList<>(); - mCardPin = null; - mCardAdminPin = null; + mSecurityTokenPin = null; + mSecurityTokenAdminPin = null; mUpload = false; mUploadAtomic = false; mKeyserver = null; @@ -128,7 +128,7 @@ public class SaveKeyringParcel implements Parcelable { for (SubkeyChange change : mChangeSubKeys) { if (change.mRecertify || change.mFlags != null || change.mExpiry != null - || change.mMoveKeyToCard) { + || change.mMoveKeyToSecurityToken) { return false; } } @@ -175,11 +175,11 @@ public class SaveKeyringParcel implements Parcelable { public boolean mRecertify; // if this flag is true, the subkey should be changed to a stripped key public boolean mDummyStrip; - // if this flag is true, the subkey should be moved to a card - public boolean mMoveKeyToCard; + // if this flag is true, the subkey should be moved to a security token + public boolean mMoveKeyToSecurityToken; // if this is non-null, the subkey will be changed to a divert-to-card - // key for the given serial number - public byte[] mDummyDivert; + // (security token) key for the given serial number + public byte[] mSecurityTokenSerialNo; public SubkeyChange(long keyId) { mKeyId = keyId; @@ -196,16 +196,17 @@ public class SaveKeyringParcel implements Parcelable { mExpiry = expiry; } - public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) { + public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToSecurityToken) { this(keyId, null, null); // these flags are mutually exclusive! - if (dummyStrip && moveKeyToCard) { + if (dummyStrip && moveKeyToSecurityToken) { throw new AssertionError( - "cannot set strip and keytocard flags at the same time - this is a bug!"); + "cannot set strip and moveKeyToSecurityToken" + + " flags at the same time - this is a bug!"); } mDummyStrip = dummyStrip; - mMoveKeyToCard = moveKeyToCard; + mMoveKeyToSecurityToken = moveKeyToSecurityToken; } @Override @@ -214,8 +215,8 @@ public class SaveKeyringParcel implements Parcelable { out += "mFlags: " + mFlags + ", "; out += "mExpiry: " + mExpiry + ", "; out += "mDummyStrip: " + mDummyStrip + ", "; - out += "mMoveKeyToCard: " + mMoveKeyToCard + ", "; - out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]"; + out += "mMoveKeyToSecurityToken: " + mMoveKeyToSecurityToken + ", "; + out += "mSecurityTokenSerialNo: [" + (mSecurityTokenSerialNo == null ? 0 : mSecurityTokenSerialNo.length) + " bytes]"; return out; } @@ -259,8 +260,8 @@ public class SaveKeyringParcel implements Parcelable { mRevokeUserIds = source.createStringArrayList(); mRevokeSubKeys = (ArrayList) source.readSerializable(); - mCardPin = source.readParcelable(Passphrase.class.getClassLoader()); - mCardAdminPin = source.readParcelable(Passphrase.class.getClassLoader()); + mSecurityTokenPin = source.readParcelable(Passphrase.class.getClassLoader()); + mSecurityTokenAdminPin = source.readParcelable(Passphrase.class.getClassLoader()); mUpload = source.readByte() != 0; mUploadAtomic = source.readByte() != 0; @@ -288,8 +289,8 @@ public class SaveKeyringParcel implements Parcelable { destination.writeStringList(mRevokeUserIds); destination.writeSerializable(mRevokeSubKeys); - destination.writeParcelable(mCardPin, flags); - destination.writeParcelable(mCardAdminPin, flags); + destination.writeParcelable(mSecurityTokenPin, flags); + destination.writeParcelable(mSecurityTokenAdminPin, flags); destination.writeByte((byte) (mUpload ? 1 : 0)); destination.writeByte((byte) (mUploadAtomic ? 1 : 0)); @@ -322,8 +323,8 @@ public class SaveKeyringParcel implements Parcelable { out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; out += "mRevokeUserIds: " + mRevokeUserIds + "\n"; out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n"; - out += "mCardPin: " + mCardPin + "\n"; - out += "mCardAdminPin: " + mCardAdminPin; + out += "mSecurityTokenPin: " + mSecurityTokenPin + "\n"; + out += "mSecurityTokenAdminPin: " + mSecurityTokenAdminPin; return out; } 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 a4163d7f9..925888813 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -28,7 +28,7 @@ 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.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -36,16 +36,16 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; -public class CreateKeyActivity extends BaseNfcActivity { +public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; public static final String EXTRA_FIRST_TIME = "first_time"; public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; - public static final String EXTRA_CREATE_YUBI_KEY = "create_yubi_key"; - public static final String EXTRA_YUBI_KEY_PIN = "yubi_key_pin"; - public static final String EXTRA_YUBI_KEY_ADMIN_PIN = "yubi_key_admin_pin"; + public static final String EXTRA_CREATE_SECURITY_TOKEN = "create_yubi_key"; + 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"; @@ -58,9 +58,9 @@ public class CreateKeyActivity extends BaseNfcActivity { ArrayList mAdditionalEmails; Passphrase mPassphrase; boolean mFirstTime; - boolean mCreateYubiKey; - Passphrase mYubiKeyPin; - Passphrase mYubiKeyAdminPin; + boolean mCreateSecurityToken; + Passphrase mSecurityTokenPin; + Passphrase mSecurityTokenAdminPin; Fragment mCurrentFragment; @@ -93,9 +93,9 @@ public class CreateKeyActivity extends BaseNfcActivity { mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS); mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); - mCreateYubiKey = savedInstanceState.getBoolean(EXTRA_CREATE_YUBI_KEY); - mYubiKeyPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_PIN); - mYubiKeyAdminPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_ADMIN_PIN); + mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN); + mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN); + mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { @@ -105,7 +105,7 @@ public class CreateKeyActivity extends BaseNfcActivity { mName = intent.getStringExtra(EXTRA_NAME); mEmail = intent.getStringExtra(EXTRA_EMAIL); mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); - mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false); + mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false); if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); @@ -113,13 +113,13 @@ public class CreateKeyActivity extends BaseNfcActivity { byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); if (containsKeys(nfcFingerprints)) { - Fragment frag = CreateYubiKeyImportResetFragment.newInstance( + Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( nfcFingerprints, nfcAid, nfcUserId); loadFragment(frag, FragAction.START); setTitle(R.string.title_import_keys); } else { - Fragment frag = CreateYubiKeyBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); loadFragment(frag, FragAction.START); setTitle(R.string.title_manage_my_keys); } @@ -169,19 +169,19 @@ public class CreateKeyActivity extends BaseNfcActivity { Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mScannedFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { - Fragment frag = CreateYubiKeyImportResetFragment.newInstance( + Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( mScannedFingerprints, mNfcAid, mNfcUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { - Fragment frag = CreateYubiKeyBlankFragment.newInstance(); + Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); loadFragment(frag, FragAction.TO_RIGHT); } } @@ -209,9 +209,9 @@ public class CreateKeyActivity extends BaseNfcActivity { outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails); outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); - outState.putBoolean(EXTRA_CREATE_YUBI_KEY, mCreateYubiKey); - outState.putParcelable(EXTRA_YUBI_KEY_PIN, mYubiKeyPin); - outState.putParcelable(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin); + outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken); + outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin); + outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index acb768f55..b020a0dba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -235,10 +235,10 @@ public class CreateKeyEmailFragment extends Fragment { CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity()); - if (createKeyActivity.mCreateYubiKey) { + if (createKeyActivity.mCreateSecurityToken) { hideKeyboard(); - CreateYubiKeyPinFragment frag = CreateYubiKeyPinFragment.newInstance(); + CreateSecurityTokenPinFragment frag = CreateSecurityTokenPinFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } else { CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index b79e4454d..5c3ac999c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -24,7 +24,6 @@ import java.util.Iterator; import android.app.Activity; import android.content.Intent; import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -188,7 +187,7 @@ public class CreateKeyFinalFragment extends Fragment { if (mSaveKeyringParcel == null) { mSaveKeyringParcel = new SaveKeyringParcel(); - if (createKeyActivity.mCreateYubiKey) { + if (createKeyActivity.mCreateSecurityToken) { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, @@ -263,7 +262,7 @@ public class CreateKeyFinalFragment extends Fragment { return; } - final boolean createYubiKey = activity.mCreateYubiKey; + final boolean createSecurityToken = activity.mCreateSecurityToken; CryptoOperationHelper.Callback createKeyCallback = new CryptoOperationHelper.Callback() { @@ -275,7 +274,7 @@ public class CreateKeyFinalFragment extends Fragment { @Override public void onCryptoOperationSuccess(EditKeyResult result) { - if (createYubiKey) { + if (createSecurityToken) { moveToCard(result); return; } @@ -327,7 +326,7 @@ public class CreateKeyFinalFragment extends Fragment { try { changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "Key that should be moved to YubiKey not found in database!"); + Log.e(Constants.TAG, "Key that should be moved to Security Token not found in database!"); return; } @@ -339,7 +338,7 @@ public class CreateKeyFinalFragment extends Fragment { try { while (cursor != null && cursor.moveToNext()) { long subkeyId = cursor.getLong(0); - changeKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToCard = true; + changeKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToSecurityToken = true; } } finally { if (cursor != null) { @@ -348,8 +347,8 @@ public class CreateKeyFinalFragment extends Fragment { } // define new PIN and Admin PIN for the card - changeKeyringParcel.mCardPin = activity.mYubiKeyPin; - changeKeyringParcel.mCardAdminPin = activity.mYubiKeyAdminPin; + changeKeyringParcel.mSecurityTokenPin = activity.mSecurityTokenPin; + changeKeyringParcel.mSecurityTokenAdminPin = activity.mSecurityTokenAdminPin; CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 68ec0e8c8..c62ec97e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -38,7 +38,7 @@ public class CreateKeyStartFragment extends Fragment { View mCreateKey; View mImportKey; - View mYubiKey; + View mSecurityToken; TextView mSkipOrCancel; public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012; @@ -61,7 +61,7 @@ public class CreateKeyStartFragment extends Fragment { mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); - mYubiKey = view.findViewById(R.id.create_key_yubikey_button); + mSecurityToken = view.findViewById(R.id.create_key_security_token_button); mSkipOrCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { @@ -78,10 +78,10 @@ public class CreateKeyStartFragment extends Fragment { } }); - mYubiKey.setOnClickListener(new View.OnClickListener() { + mSecurityToken.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateYubiKeyWaitFragment frag = new CreateYubiKeyWaitFragment(); + CreateSecurityTokenWaitFragment frag = new CreateSecurityTokenWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java new file mode 100644 index 000000000..08441c199 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenBlankFragment.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +public class CreateSecurityTokenBlankFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateSecurityTokenBlankFragment newInstance() { + CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment(); + + Bundle args = new Bundle(); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getFragmentManager().getBackStackEntryCount() == 0) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } else { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void nextClicked() { + mCreateKeyActivity.mCreateSecurityToken = true; + + CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java new file mode 100644 index 000000000..81f782149 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 java.nio.ByteBuffer; +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; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.RadioButton; +import android.widget.TextView; + +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +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.base.QueueingCryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Preferences; + + +public class CreateSecurityTokenImportResetFragment + extends QueueingCryptoOperationFragment + implements NfcListenerFragment { + + private static final int REQUEST_CODE_RESET = 0x00005001; + + private static final String ARG_FINGERPRINTS = "fingerprint"; + public static final String ARG_AID = "aid"; + public static final String ARG_USER_ID = "user_ids"; + + CreateKeyActivity mCreateKeyActivity; + + private byte[] mTokenFingerprints; + private byte[] mTokenAid; + private String mTokenUserId; + private String mTokenFingerprint; + private ImportKeysListFragment mListFragment; + private TextView vSerNo; + private TextView vUserId; + private TextView mNextButton; + private RadioButton mRadioImport; + private RadioButton mRadioReset; + private View mResetWarning; + + // for CryptoOperationFragment key import + private String mKeyserver; + private ArrayList mKeyList; + + public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { + + CreateSecurityTokenImportResetFragment frag = new CreateSecurityTokenImportResetFragment(); + + Bundle args = new Bundle(); + args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints); + args.putByteArray(ARG_AID, nfcAid); + args.putString(ARG_USER_ID, userId); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + + mTokenFingerprints = args.getByteArray(ARG_FINGERPRINTS); + mTokenAid = args.getByteArray(ARG_AID); + mTokenUserId = args.getString(ARG_USER_ID); + + byte[] fp = new byte[20]; + ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); + mTokenFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_security_token_import_reset_fragment, container, false); + + vSerNo = (TextView) view.findViewById(R.id.token_serno); + vUserId = (TextView) view.findViewById(R.id.token_userid); + mNextButton = (TextView) view.findViewById(R.id.create_key_next_button); + mRadioImport = (RadioButton) view.findViewById(R.id.token_decision_import); + mRadioReset = (RadioButton) view.findViewById(R.id.token_decision_reset); + mResetWarning = view.findViewById(R.id.token_import_reset_warning); + + View mBackButton = view.findViewById(R.id.create_key_back_button); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getFragmentManager().getBackStackEntryCount() == 0) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } else { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + } + }); + + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mRadioReset.isChecked()) { + resetCard(); + } else { + importKey(); + } + } + }); + + mListFragment = ImportKeysListFragment.newInstance(null, null, + "0x" + mTokenFingerprint, true, null); + + mRadioImport.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + mNextButton.setText(R.string.btn_import); + mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_key_plus_grey600_24dp, 0); + mNextButton.setVisibility(View.VISIBLE); + mResetWarning.setVisibility(View.GONE); + + getFragmentManager().beginTransaction() + .replace(R.id.security_token_import_fragment, mListFragment, "token_import") + .commit(); + + getFragmentManager().executePendingTransactions(); + refreshSearch(); + } + } + }); + mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + mNextButton.setText(R.string.btn_reset); + mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_grey_24dp, 0); + mNextButton.setVisibility(View.VISIBLE); + mResetWarning.setVisibility(View.VISIBLE); + + getFragmentManager().beginTransaction() + .remove(mListFragment) + .commit(); + } + } + }); + + setData(); + + + return view; + } + + @Override + public void onSaveInstanceState(Bundle args) { + super.onSaveInstanceState(args); + + args.putByteArray(ARG_FINGERPRINTS, mTokenFingerprints); + args.putByteArray(ARG_AID, mTokenAid); + args.putString(ARG_USER_ID, mTokenUserId); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + public void setData() { + String serno = Hex.toHexString(mTokenAid, 10, 4); + vSerNo.setText(getString(R.string.security_token_serial_no, serno)); + + if (!mTokenUserId.isEmpty()) { + vUserId.setText(getString(R.string.security_token_key_holder, mTokenUserId)); + } else { + vUserId.setText(getString(R.string.security_token_key_holder_not_set)); + } + } + + public void refreshSearch() { + mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mTokenFingerprint, + Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); + } + + public void importKey() { + + ArrayList keyList = new ArrayList<>(); + keyList.add(new ParcelableKeyRing(mTokenFingerprint, null)); + mKeyList = keyList; + + mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); + + super.setProgressMessageResource(R.string.progress_importing); + + super.cryptoOperation(); + + } + + public void resetCard() { + Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_SERVICE_INTENT, (Parcelable[]) null); + RequiredInputParcel resetP = RequiredInputParcel.createNfcReset(); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP); + intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel()); + startActivityForResult(intent, REQUEST_CODE_RESET); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_RESET && resultCode == Activity.RESULT_OK) { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + return; + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void doNfcInBackground() throws IOException { + + mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints(); + mTokenAid = mCreateKeyActivity.nfcGetAid(); + mTokenUserId = mCreateKeyActivity.nfcGetUserId(); + + byte[] fp = new byte[20]; + ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); + mTokenFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + } + + @Override + public void onNfcPostExecute() { + + setData(); + + } + + @Override + public ImportKeyringParcel createOperationInput() { + return new ImportKeyringParcel(mKeyList, mKeyserver); + } + + @Override + public void onQueuedOperationSuccess(ImportKeyResult result) { + long[] masterKeyIds = result.getImportedMasterKeyIds(); + if (masterKeyIds.length == 0) { + super.onCryptoOperationError(result); + return; + } + + // null-protected from Queueing*Fragment + Activity activity = getActivity(); + + Intent intent = new Intent(activity, ViewKeyActivity.class); + // use the imported masterKeyId, not the one from the token, because + // that one might* just have been a subkey of the imported key + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); + intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints); + startActivity(intent); + activity.finish(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java new file mode 100644 index 000000000..45cf7a665 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenPinFragment.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 android.app.Activity; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashSet; + +public class CreateSecurityTokenPinFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + EditText mPin; + EditText mPinRepeat; + TextView mAdminPin; + View mBackButton; + View mNextButton; + + private static HashSet sPinBlacklist = new HashSet<>(Arrays.asList( + "000000", + "111111", + "222222", + "333333", + "444444", + "555555", + "666666", + "777777", + "888888", + "999999", + "123456", + "XXXXXX" + )); + + /** + * Creates new instance of this fragment + */ + public static CreateSecurityTokenPinFragment newInstance() { + CreateSecurityTokenPinFragment frag = new CreateSecurityTokenPinFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + /** + * Checks if text of given EditText is not empty. If it is empty an error is + * set and the EditText gets the focus. + * + * @return true if EditText is not empty + */ + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().length() == 0) { + editText.setError(context.getString(R.string.create_key_empty)); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { + Passphrase p1 = new Passphrase(editText1); + Passphrase p2 = new Passphrase(editText2); + return (p1.equals(p2)); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); + + mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin); + mPinRepeat = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); + mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + if (mCreateKeyActivity.mSecurityTokenPin == null) { + new AsyncTask() { + @Override + protected Passphrase doInBackground(Void... unused) { + SecureRandom secureRandom = new SecureRandom(); + // min = 8, we choose 8 + String adminPin = "" + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9); + + return new Passphrase(adminPin); + } + + @Override + protected void onPostExecute(Passphrase adminPin) { + mCreateKeyActivity.mSecurityTokenAdminPin = adminPin; + + mAdminPin.setText(mCreateKeyActivity.mSecurityTokenAdminPin.toStringUnsafe()); + } + }.execute(); + } else { + mAdminPin.setText(mCreateKeyActivity.mSecurityTokenAdminPin.toStringUnsafe()); + } + + mPin.requestFocus(); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void back() { + hideKeyboard(); + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + + private void nextClicked() { + if (isEditTextNotEmpty(getActivity(), mPin)) { + + if (!areEditTextsEqual(mPin, mPinRepeat)) { + mPinRepeat.setError(getString(R.string.create_key_passphrases_not_equal)); + mPinRepeat.requestFocus(); + return; + } + + if (mPin.getText().toString().length() < 6) { + mPin.setError(getString(R.string.create_key_yubi_key_pin_too_short)); + mPin.requestFocus(); + return; + } + + if (sPinBlacklist.contains(mPin.getText().toString())) { + mPin.setError(getString(R.string.create_key_yubi_key_pin_insecure)); + mPin.requestFocus(); + return; + } + + mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString()); + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + hideKeyboard(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + } + + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java new file mode 100644 index 000000000..a3ea38e40 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * 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 android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + + +public class CreateSecurityTokenWaitFragment extends Fragment { + + CreateKeyActivity mCreateKeyActivity; + View mBackButton; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false); + + mBackButton = view.findViewById(R.id.create_key_back_button); + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java deleted file mode 100644 index 5b13dc88e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; - -public class CreateYubiKeyBlankFragment extends Fragment { - - CreateKeyActivity mCreateKeyActivity; - View mBackButton; - View mNextButton; - - /** - * Creates new instance of this fragment - */ - public static CreateYubiKeyBlankFragment newInstance() { - CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment(); - - Bundle args = new Bundle(); - - frag.setArguments(args); - - return frag; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false); - - mBackButton = view.findViewById(R.id.create_key_back_button); - mNextButton = view.findViewById(R.id.create_key_next_button); - - mBackButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (getFragmentManager().getBackStackEntryCount() == 0) { - getActivity().setResult(Activity.RESULT_CANCELED); - getActivity().finish(); - } else { - mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); - } - } - }); - mNextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - nextClicked(); - } - }); - - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCreateKeyActivity = (CreateKeyActivity) getActivity(); - } - - private void nextClicked() { - mCreateKeyActivity.mCreateYubiKey = true; - - CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java deleted file mode 100644 index 5712f4452..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 java.nio.ByteBuffer; -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; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.RadioButton; -import android.widget.TextView; - -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -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.base.QueueingCryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Preferences; - - -public class CreateYubiKeyImportResetFragment - extends QueueingCryptoOperationFragment - implements NfcListenerFragment { - - private static final int REQUEST_CODE_RESET = 0x00005001; - - private static final String ARG_FINGERPRINTS = "fingerprint"; - public static final String ARG_AID = "aid"; - public static final String ARG_USER_ID = "user_ids"; - - CreateKeyActivity mCreateKeyActivity; - - private byte[] mNfcFingerprints; - private byte[] mNfcAid; - private String mNfcUserId; - private String mNfcFingerprint; - private ImportKeysListFragment mListFragment; - private TextView vSerNo; - private TextView vUserId; - private TextView mNextButton; - private RadioButton mRadioImport; - private RadioButton mRadioReset; - private View mResetWarning; - - // for CryptoOperationFragment key import - private String mKeyserver; - private ArrayList mKeyList; - - public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { - - CreateYubiKeyImportResetFragment frag = new CreateYubiKeyImportResetFragment(); - - Bundle args = new Bundle(); - args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints); - args.putByteArray(ARG_AID, nfcAid); - args.putString(ARG_USER_ID, userId); - frag.setArguments(args); - - return frag; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); - - mNfcFingerprints = args.getByteArray(ARG_FINGERPRINTS); - mNfcAid = args.getByteArray(ARG_AID); - mNfcUserId = args.getString(ARG_USER_ID); - - byte[] fp = new byte[20]; - ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20); - mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubi_key_import_reset_fragment, container, false); - - vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); - vUserId = (TextView) view.findViewById(R.id.yubikey_userid); - mNextButton = (TextView) view.findViewById(R.id.create_key_next_button); - mRadioImport = (RadioButton) view.findViewById(R.id.yubikey_decision_import); - mRadioReset = (RadioButton) view.findViewById(R.id.yubikey_decision_reset); - mResetWarning = view.findViewById(R.id.yubikey_import_reset_warning); - - View mBackButton = view.findViewById(R.id.create_key_back_button); - mBackButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (getFragmentManager().getBackStackEntryCount() == 0) { - getActivity().setResult(Activity.RESULT_CANCELED); - getActivity().finish(); - } else { - mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); - } - } - }); - - mNextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mRadioReset.isChecked()) { - resetCard(); - } else { - importKey(); - } - } - }); - - mListFragment = ImportKeysListFragment.newInstance(null, null, - "0x" + mNfcFingerprint, true, null); - - mRadioImport.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - mNextButton.setText(R.string.btn_import); - mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_key_plus_grey600_24dp, 0); - mNextButton.setVisibility(View.VISIBLE); - mResetWarning.setVisibility(View.GONE); - - getFragmentManager().beginTransaction() - .replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import") - .commit(); - - getFragmentManager().executePendingTransactions(); - refreshSearch(); - } - } - }); - mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - mNextButton.setText(R.string.btn_reset); - mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_grey_24dp, 0); - mNextButton.setVisibility(View.VISIBLE); - mResetWarning.setVisibility(View.VISIBLE); - - getFragmentManager().beginTransaction() - .remove(mListFragment) - .commit(); - } - } - }); - - setData(); - - - return view; - } - - @Override - public void onSaveInstanceState(Bundle args) { - super.onSaveInstanceState(args); - - args.putByteArray(ARG_FINGERPRINTS, mNfcFingerprints); - args.putByteArray(ARG_AID, mNfcAid); - args.putString(ARG_USER_ID, mNfcUserId); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCreateKeyActivity = (CreateKeyActivity) getActivity(); - } - - public void setData() { - String serno = Hex.toHexString(mNfcAid, 10, 4); - vSerNo.setText(getString(R.string.yubikey_serno, serno)); - - if (!mNfcUserId.isEmpty()) { - vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId)); - } else { - vUserId.setText(getString(R.string.yubikey_key_holder_not_set)); - } - } - - public void refreshSearch() { - mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint, - Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); - } - - public void importKey() { - - ArrayList keyList = new ArrayList<>(); - keyList.add(new ParcelableKeyRing(mNfcFingerprint, null)); - mKeyList = keyList; - - mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); - - super.setProgressMessageResource(R.string.progress_importing); - - super.cryptoOperation(); - - } - - public void resetCard() { - Intent intent = new Intent(getActivity(), NfcOperationActivity.class); - intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, (Parcelable[]) null); - RequiredInputParcel resetP = RequiredInputParcel.createNfcReset(); - intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, resetP); - intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel()); - startActivityForResult(intent, REQUEST_CODE_RESET); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_RESET && resultCode == Activity.RESULT_OK) { - mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); - return; - } - - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void doNfcInBackground() throws IOException { - - mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints(); - mNfcAid = mCreateKeyActivity.nfcGetAid(); - mNfcUserId = mCreateKeyActivity.nfcGetUserId(); - - byte[] fp = new byte[20]; - ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20); - mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - } - - @Override - public void onNfcPostExecute() { - - setData(); - - } - - @Override - public ImportKeyringParcel createOperationInput() { - return new ImportKeyringParcel(mKeyList, mKeyserver); - } - - @Override - public void onQueuedOperationSuccess(ImportKeyResult result) { - long[] masterKeyIds = result.getImportedMasterKeyIds(); - if (masterKeyIds.length == 0) { - super.onCryptoOperationError(result); - return; - } - - // null-protected from Queueing*Fragment - Activity activity = getActivity(); - - Intent intent = new Intent(activity, ViewKeyActivity.class); - // use the imported masterKeyId, not the one from the yubikey, because - // that one might* just have been a subkey of the imported key - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); - intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); - startActivity(intent); - activity.finish(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java deleted file mode 100644 index 128383d6d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * - * 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 android.app.Activity; -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.HashSet; - -public class CreateYubiKeyPinFragment extends Fragment { - - // view - CreateKeyActivity mCreateKeyActivity; - EditText mPin; - EditText mPinRepeat; - TextView mAdminPin; - View mBackButton; - View mNextButton; - - private static HashSet sPinBlacklist = new HashSet<>(Arrays.asList( - "000000", - "111111", - "222222", - "333333", - "444444", - "555555", - "666666", - "777777", - "888888", - "999999", - "123456", - "XXXXXX" - )); - - /** - * Creates new instance of this fragment - */ - public static CreateYubiKeyPinFragment newInstance() { - CreateYubiKeyPinFragment frag = new CreateYubiKeyPinFragment(); - - Bundle args = new Bundle(); - frag.setArguments(args); - - return frag; - } - - /** - * Checks if text of given EditText is not empty. If it is empty an error is - * set and the EditText gets the focus. - * - * @return true if EditText is not empty - */ - private static boolean isEditTextNotEmpty(Context context, EditText editText) { - boolean output = true; - if (editText.getText().length() == 0) { - editText.setError(context.getString(R.string.create_key_empty)); - editText.requestFocus(); - output = false; - } else { - editText.setError(null); - } - - return output; - } - - private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { - Passphrase p1 = new Passphrase(editText1); - Passphrase p2 = new Passphrase(editText2); - return (p1.equals(p2)); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); - - mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin); - mPinRepeat = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); - mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); - mBackButton = view.findViewById(R.id.create_key_back_button); - mNextButton = view.findViewById(R.id.create_key_next_button); - - if (mCreateKeyActivity.mYubiKeyPin == null) { - new AsyncTask() { - @Override - protected Passphrase doInBackground(Void... unused) { - SecureRandom secureRandom = new SecureRandom(); - // min = 8, we choose 8 - String adminPin = "" + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9) - + secureRandom.nextInt(9); - - return new Passphrase(adminPin); - } - - @Override - protected void onPostExecute(Passphrase adminPin) { - mCreateKeyActivity.mYubiKeyAdminPin = adminPin; - - mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); - } - }.execute(); - } else { - mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); - } - - mPin.requestFocus(); - mBackButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - back(); - } - }); - mNextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - nextClicked(); - } - }); - - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCreateKeyActivity = (CreateKeyActivity) getActivity(); - } - - private void back() { - hideKeyboard(); - mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); - } - - private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mPin)) { - - if (!areEditTextsEqual(mPin, mPinRepeat)) { - mPinRepeat.setError(getString(R.string.create_key_passphrases_not_equal)); - mPinRepeat.requestFocus(); - return; - } - - if (mPin.getText().toString().length() < 6) { - mPin.setError(getString(R.string.create_key_yubi_key_pin_too_short)); - mPin.requestFocus(); - return; - } - - if (sPinBlacklist.contains(mPin.getText().toString())) { - mPin.setError(getString(R.string.create_key_yubi_key_pin_insecure)); - mPin.requestFocus(); - return; - } - - mCreateKeyActivity.mYubiKeyPin = new Passphrase(mPin.getText().toString()); - - CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); - hideKeyboard(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); - } - } - - private void hideKeyboard() { - if (getActivity() == null) { - return; - } - InputMethodManager inputManager = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - - // check if no view has focus - View v = getActivity().getCurrentFocus(); - if (v == null) - return; - - inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java deleted file mode 100644 index d45195512..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann - * - * 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 android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; - - -public class CreateYubiKeyWaitFragment extends Fragment { - - CreateKeyActivity mCreateKeyActivity; - View mBackButton; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.create_yubi_key_wait_fragment, container, false); - - mBackButton = view.findViewById(R.id.create_key_back_button); - - mBackButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); - } - }); - - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCreateKeyActivity = (CreateKeyActivity) getActivity(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1c72cdf41..2d94d0d93 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -435,9 +435,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment { public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; 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 7bd7bafcc..af60a1d9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -40,11 +40,11 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment; -import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; -public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener { +public class MainActivity extends BaseSecurityTokenNfcActivity implements FabContainer, OnBackStackChangedListener { static final int ID_KEYS = 1; static final int ID_ENCRYPT_DECRYPT = 2; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java deleted file mode 100644 index 86b0a36d0..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2013-2015 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * Copyright (C) 2013-2014 Signe Rüsch - * Copyright (C) 2013-2014 Philipp Jakubeit - * - * 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 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.Toast; -import android.widget.ViewAnimator; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -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.remote.CryptoInputParcelCacheService; -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.BaseNfcActivity; -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; -import org.sufficientlysecure.keychain.util.Preferences; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * 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 NfcOperationActivity extends BaseNfcActivity { - - public static final String EXTRA_REQUIRED_INPUT = "required_input"; - public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; - - // passthrough for OpenPgpService - public static final String EXTRA_SERVICE_INTENT = "data"; - - public static final String RESULT_CRYPTO_INPUT = "result_data"; - - public ViewAnimator vAnimator; - public TextView vErrorText; - public Button vErrorTryAgainButton; - - private RequiredInputParcel mRequiredInput; - private Intent mServiceIntent; - - 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 - 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"); - - // 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); - - mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); - - setTitle(R.string.nfc_text); - - vAnimator = (ViewAnimator) findViewById(R.id.view_animator); - vAnimator.setDisplayedChild(0); - vErrorText = (TextView) findViewById(R.id.nfc_activity_3_error_text); - vErrorTryAgainButton = (Button) findViewById(R.id.nfc_activity_3_error_try_again); - vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - resumeTagHandling(); - - obtainPassphraseIfRequired(); - vAnimator.setDisplayedChild(0); - } - }); - Button vCancel = (Button) findViewById(R.id.nfc_activity_0_cancel); - vCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(RESULT_CANCELED); - finish(); - } - }); - - Intent intent = getIntent(); - Bundle data = intent.getExtras(); - - mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); - mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT); - - obtainPassphraseIfRequired(); - } - - 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) { - obtainYubiKeyPin(mRequiredInput); - } - } - - @Override - protected void initLayout() { - setContentView(R.layout.nfc_operation_activity); - } - - @Override - public void onNfcPreExecute() { - // start with indeterminate progress - vAnimator.setDisplayedChild(1); - } - - @Override - protected void doNfcInBackground() throws IOException { - - switch (mRequiredInput.mType) { - case NFC_DECRYPT: { - for (int i = 0; i < mRequiredInput.mInputData.length; i++) { - byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); - mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); - } - break; - } - case NFC_SIGN: { - 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); - mInputParcel.addCryptoData(hash, signedHash); - } - break; - } - case NFC_MOVE_KEY_TO_CARD: { - // TODO: assume PIN and Admin PIN to be default for this operation - mPin = new Passphrase("123456"); - mAdminPin = new Passphrase("12345678"); - - ProviderHelper providerHelper = new ProviderHelper(this); - CanonicalizedSecretKeyRing secretKeyRing; - try { - secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()) - ); - } catch (ProviderHelper.NotFoundException e) { - throw new IOException("Couldn't find subkey for key to card operation."); - } - - byte[] newPin = mRequiredInput.mInputData[0]; - byte[] newAdminPin = mRequiredInput.mInputData[1]; - - for (int i = 2; i < mRequiredInput.mInputData.length; i++) { - byte[] subkeyBytes = mRequiredInput.mInputData[i]; - ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); - 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[] cardSerialNumber = Arrays.copyOf(nfcGetAid(), 16); - - Passphrase passphrase; - try { - passphrase = PassphraseCacheService.getCachedPassphrase(this, - mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); - } catch (PassphraseCacheService.KeyNotFoundException e) { - 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; card 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; card 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; card must be reset to put new authentication key."); - } - } else { - throw new IOException("Inappropriate key flags for smart card key."); - } - - // TODO: Is this really used anywhere? - mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber); - } - - // change PINs afterwards - nfcModifyPIN(0x81, newPin); - nfcModifyPIN(0x83, newAdminPin); - - break; - } - case NFC_RESET_CARD: { - nfcResetCard(); - - break; - } - default: { - throw new AssertionError("Unhandled mRequiredInput.mType"); - } - } - - } - - @Override - protected void onNfcPostExecute() { - if (mServiceIntent != null) { - // if we're triggered by OpenPgpService - // save updated cryptoInputParcel in cache - CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel); - setResult(RESULT_OK, mServiceIntent); - } else { - Intent result = new Intent(); - // send back the CryptoInputParcel we received - result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel); - setResult(RESULT_OK, result); - } - - // show finish - vAnimator.setDisplayedChild(2); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - // check all 200ms if YubiKey has been taken away - while (true) { - if (isNfcConnected()) { - try { - Thread.sleep(200); - } catch (InterruptedException ignored) { - } - } else { - return null; - } - } - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - finish(); - } - }.execute(); - } - - @Override - protected void onNfcError(String error) { - pauseTagHandling(); - - vErrorText.setText(error + "\n\n" + getString(R.string.nfc_try_again_text)); - vAnimator.setDisplayedChild(3); - } - - @Override - public void onNfcPinError(String error) { - onNfcError(error); - - // clear (invalid) passphrase - PassphraseCacheService.clearCachedPassphrase( - this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); - } - - private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] cardFingerprint = nfcGetMasterKeyFingerprint(idx); - - // Note: special case: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (cardFingerprint == null) { - return true; - } - - // Slot is empty, or contains this key already. PUT KEY operation is safe - if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) || - Arrays.equals(cardFingerprint, 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/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 2fcbbf6c2..b934f3d5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -261,7 +261,7 @@ public class PassphraseDialogActivity extends FragmentActivity { hint = getString(R.string.label_pin); break; case DIVERT_TO_CARD: - message = getString(R.string.yubikey_pin_for, userId); + message = getString(R.string.security_token_pin_for, userId); hint = getString(R.string.label_pin); break; // special case: empty passphrase just returns the empty passphrase @@ -313,7 +313,7 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); - if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) + if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForSecurityTokenPin()) || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java new file mode 100644 index 000000000..130dd6a79 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * + * 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 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 org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +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.remote.CryptoInputParcelCacheService; +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.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OrientationUtils; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * 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 SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity { + + public static final String EXTRA_REQUIRED_INPUT = "required_input"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; + + // passthrough for OpenPgpService + public static final String EXTRA_SERVICE_INTENT = "data"; + + public static final String RESULT_CRYPTO_INPUT = "result_data"; + + public ViewAnimator vAnimator; + public TextView vErrorText; + public Button vErrorTryAgainButton; + + private RequiredInputParcel mRequiredInput; + private Intent mServiceIntent; + + 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 + 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"); + + // 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); + + mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + + setTitle(R.string.security_token_nfc_text); + + vAnimator = (ViewAnimator) findViewById(R.id.view_animator); + vAnimator.setDisplayedChild(0); + vErrorText = (TextView) findViewById(R.id.security_token_activity_3_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(); + + obtainPassphraseIfRequired(); + vAnimator.setDisplayedChild(0); + } + }); + 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(); + } + }); + + Intent intent = getIntent(); + Bundle data = intent.getExtras(); + + mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); + mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT); + + obtainPassphraseIfRequired(); + } + + 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) { + obtainSecurityTokenPin(mRequiredInput); + } + } + + @Override + protected void initLayout() { + setContentView(R.layout.security_token_operation_activity); + } + + @Override + public void onNfcPreExecute() { + // start with indeterminate progress + vAnimator.setDisplayedChild(1); + } + + @Override + protected void doNfcInBackground() throws IOException { + + switch (mRequiredInput.mType) { + case NFC_DECRYPT: { + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { + byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; + byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); + } + break; + } + case NFC_SIGN: { + 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); + mInputParcel.addCryptoData(hash, signedHash); + } + break; + } + case NFC_MOVE_KEY_TO_CARD: { + // TODO: assume PIN and Admin PIN to be default for this operation + mPin = new Passphrase("123456"); + mAdminPin = new Passphrase("12345678"); + + ProviderHelper providerHelper = new ProviderHelper(this); + CanonicalizedSecretKeyRing secretKeyRing; + try { + secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing( + KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()) + ); + } catch (ProviderHelper.NotFoundException e) { + throw new IOException("Couldn't find subkey for key to token operation."); + } + + byte[] newPin = mRequiredInput.mInputData[0]; + byte[] newAdminPin = mRequiredInput.mInputData[1]; + + for (int i = 2; i < mRequiredInput.mInputData.length; i++) { + byte[] subkeyBytes = mRequiredInput.mInputData[i]; + ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); + 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); + + Passphrase passphrase; + try { + passphrase = PassphraseCacheService.getCachedPassphrase(this, + mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); + } catch (PassphraseCacheService.KeyNotFoundException e) { + 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."); + } + + // TODO: Is this really used anywhere? + mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); + } + + // change PINs afterwards + nfcModifyPIN(0x81, newPin); + nfcModifyPIN(0x83, newAdminPin); + + break; + } + case NFC_RESET_CARD: { + nfcResetCard(); + + break; + } + default: { + throw new AssertionError("Unhandled mRequiredInput.mType"); + } + } + + } + + @Override + protected void onNfcPostExecute() { + if (mServiceIntent != null) { + // if we're triggered by OpenPgpService + // save updated cryptoInputParcel in cache + CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel); + setResult(RESULT_OK, mServiceIntent); + } else { + Intent result = new Intent(); + // send back the CryptoInputParcel we received + result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel); + setResult(RESULT_OK, result); + } + + // show finish + vAnimator.setDisplayedChild(2); + + new AsyncTask() { + @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) { + } + } else { + return null; + } + } + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } + + @Override + protected void onNfcError(String error) { + pauseTagHandling(); + + vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); + vAnimator.setDisplayedChild(3); + } + + @Override + public void onNfcPinError(String error) { + onNfcError(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/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 67487f2ca..03fc07936 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -84,7 +84,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; -import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -102,13 +102,13 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -public class ViewKeyActivity extends BaseNfcActivity implements +public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { - 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 = "security_token_user_id"; + public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid"; + public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints"; @Retention(RetentionPolicy.SOURCE) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) @@ -159,7 +159,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements private boolean mIsRevoked = false; private boolean mIsExpired = false; - private boolean mShowYubikeyAfterCreation = false; + private boolean mShowSecurityTokenAfterCreation = false; private MenuItem mRefreshItem; private boolean mIsRefreshing; @@ -345,9 +345,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements .commit(); } - // need to postpone loading of the yubikey fragment until after mMasterKeyId + // need to postpone loading of the security token fragment until after mMasterKeyId // is available, but we mark here that this should be done - mShowYubikeyAfterCreation = true; + mShowSecurityTokenAfterCreation = true; } @@ -656,69 +656,69 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override protected void onNfcPostExecute() { - long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); try { - // if the yubikey matches a subkey in any key + // if the security token matches a subkey in any key CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(yubiKeyId)); + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(tokenId)); byte[] candidateFp = ring.getFingerprint(); - // if the master key of that key matches this one, just show the yubikey dialog + // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { - showYubiKeyFragment(mNfcFingerprints, mNfcUserId, mNfcAid); + showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid); return; } // otherwise, offer to go to that key final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(candidateFp); - Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG, + Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG, Style.WARN, new ActionListener() { @Override public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + 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); startActivity(intent); finish(); } - }, R.string.snack_yubikey_view).show(); + }, R.string.snack_security_token_view).show(); // and if it's not found, offer import } catch (PgpKeyNotFoundException e) { - Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG, + Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG, Style.WARN, new ActionListener() { @Override public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + 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); startActivity(intent); finish(); } - }, R.string.snack_yubikey_import).show(); + }, R.string.snack_security_token_import).show(); } } - public void showYubiKeyFragment( - final byte[] nfcFingerprints, final String nfcUserId, final byte[] nfcAid) { + public void showSecurityTokenFragment( + final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid) { new Handler().post(new Runnable() { @Override public void run() { - ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance( - mMasterKeyId, nfcFingerprints, nfcUserId, nfcAid); + ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance( + mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid); FragmentManager manager = getSupportFragmentManager(); - manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE); + manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE); manager.beginTransaction() - .addToBackStack("yubikey") + .addToBackStack("security_token") .replace(R.id.view_key_fragment, frag) // if this is called while the activity wasn't resumed, just forget it happened .commitAllowingStateLoss(); @@ -888,14 +888,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements mFingerprint = data.getBlob(INDEX_FINGERPRINT); mFingerprintString = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); - // if it wasn't shown yet, display yubikey fragment - if (mShowYubikeyAfterCreation && getIntent().hasExtra(EXTRA_NFC_AID)) { - mShowYubikeyAfterCreation = false; + // if it wasn't shown yet, display token fragment + if (mShowSecurityTokenAfterCreation && getIntent().hasExtra(EXTRA_SECURITY_TOKEN_AID)) { + mShowSecurityTokenAfterCreation = false; Intent intent = getIntent(); - byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); - String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); - byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); - showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid); + byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS); + String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); + byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); + showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid); } mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index 14477723e..fc6db1b92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -340,9 +340,9 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements } // toggle change.mDummyStrip = !change.mDummyStrip; - if (change.mDummyStrip && change.mMoveKeyToCard) { + if (change.mDummyStrip && change.mMoveKeyToSecurityToken) { // User had chosen to divert key, but now wants to strip it instead. - change.mMoveKeyToCard = false; + change.mMoveKeyToSecurityToken = false; } break; } @@ -384,8 +384,8 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements // break; // } // // toggle -// change.mMoveKeyToCard = !change.mMoveKeyToCard; -// if (change.mMoveKeyToCard && change.mDummyStrip) { +// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; +// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { // // User had chosen to strip key, but now wants to divert it. // change.mDummyStrip = false; // } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java new file mode 100644 index 000000000..48e41c7bb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeySecurityTokenFragment.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 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.nio.ByteBuffer; +import java.util.Arrays; + +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + + +public class ViewKeySecurityTokenFragment + extends QueueingCryptoOperationFragment + implements LoaderCallbacks { + + public static final String ARG_MASTER_KEY_ID = "master_key_id"; + public static final String ARG_FINGERPRINT = "fingerprint"; + public static final String ARG_USER_ID = "user_id"; + public static final String ARG_CARD_AID = "aid"; + + private byte[][] mFingerprints; + private String mUserId; + private byte[] mCardAid; + private long mMasterKeyId; + private long[] mSubKeyIds; + + private Button vButton; + private TextView vStatus; + + public static ViewKeySecurityTokenFragment newInstance(long masterKeyId, + byte[] fingerprints, String userId, byte[] aid) { + ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment(); + + Bundle args = new Bundle(); + args.putLong(ARG_MASTER_KEY_ID, masterKeyId); + args.putByteArray(ARG_FINGERPRINT, fingerprints); + args.putString(ARG_USER_ID, userId); + args.putByteArray(ARG_CARD_AID, aid); + frag.setArguments(args); + + return frag; + } + + public ViewKeySecurityTokenFragment() { + super(null); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT)); + mFingerprints = new byte[buf.remaining()/20][]; + for (int i = 0; i < mFingerprints.length; i++) { + mFingerprints[i] = new byte[20]; + buf.get(mFingerprints[i]); + } + mUserId = args.getString(ARG_USER_ID); + mCardAid = args.getByteArray(ARG_CARD_AID); + + mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID); + + getLoaderManager().initLoader(0, null, this); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_security_token, null); + + TextView vSerNo = (TextView) view.findViewById(R.id.token_serno); + TextView vUserId = (TextView) view.findViewById(R.id.token_userid); + + String serno = Hex.toHexString(mCardAid, 10, 4); + vSerNo.setText(getString(R.string.security_token_serial_no, serno)); + + if (!mUserId.isEmpty()) { + vUserId.setText(getString(R.string.security_token_key_holder, mUserId)); + } else { + vUserId.setText(getString(R.string.security_token_key_holder_not_set)); + } + + vButton = (Button) view.findViewById(R.id.button_bind); + vButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + promoteToSecretKey(); + } + }); + + vStatus = (TextView) view.findViewById(R.id.token_status); + + return view; + } + + public void promoteToSecretKey() { + long[] subKeyIds = new long[mFingerprints.length]; + for (int i = 0; i < subKeyIds.length; i++) { + subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]); + } + + // mMasterKeyId and mCardAid are already set + mSubKeyIds = subKeyIds; + + cryptoOperation(); + } + + public static final String[] PROJECTION = new String[]{ + Keys._ID, + Keys.KEY_ID, + Keys.RANK, + Keys.HAS_SECRET, + Keys.FINGERPRINT + }; + // private static final int INDEX_KEY_ID = 1; + // private static final int INDEX_RANK = 2; + private static final int INDEX_HAS_SECRET = 3; + private static final int INDEX_FINGERPRINT = 4; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId), + PROJECTION, null, null, null); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (!data.moveToFirst()) { + // wut? + return; + } + + boolean allBound = true; + boolean noneBound = true; + + do { + SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET)); + byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); + Integer index = naiveIndexOf(mFingerprints, fingerprint); + if (index == null) { + continue; + } + if (keyType == SecretKeyType.DIVERT_TO_CARD) { + noneBound = false; + } else { + allBound = false; + } + } while (data.moveToNext()); + + if (allBound) { + vButton.setVisibility(View.GONE); + vStatus.setText(R.string.security_token_status_bound); + } else { + vButton.setVisibility(View.VISIBLE); + vStatus.setText(noneBound + ? R.string.security_token_status_unbound + : R.string.security_token_status_partly); + } + + } + + static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) { + for (int i = 0; i < haystack.length; i++) { + if (Arrays.equals(needle, haystack[i])) { + return i; + } + } + return null; + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + @Override + public PromoteKeyringParcel createOperationInput() { + return new PromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds); + } + + @Override + public void onQueuedOperationSuccess(PromoteKeyResult result) { + result.createNotify(getActivity()).show(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java deleted file mode 100644 index f980f297b..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * Copyright (C) 2015 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.nio.ByteBuffer; -import java.util.Arrays; - -import android.database.Cursor; -import android.os.Bundle; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import org.spongycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; - - -public class ViewKeyYubiKeyFragment - extends QueueingCryptoOperationFragment - implements LoaderCallbacks { - - public static final String ARG_MASTER_KEY_ID = "master_key_id"; - public static final String ARG_FINGERPRINT = "fingerprint"; - public static final String ARG_USER_ID = "user_id"; - public static final String ARG_CARD_AID = "aid"; - - private byte[][] mFingerprints; - private String mUserId; - private byte[] mCardAid; - private long mMasterKeyId; - private long[] mSubKeyIds; - - private Button vButton; - private TextView vStatus; - - public static ViewKeyYubiKeyFragment newInstance(long masterKeyId, - byte[] fingerprints, String userId, byte[] aid) { - ViewKeyYubiKeyFragment frag = new ViewKeyYubiKeyFragment(); - - Bundle args = new Bundle(); - args.putLong(ARG_MASTER_KEY_ID, masterKeyId); - args.putByteArray(ARG_FINGERPRINT, fingerprints); - args.putString(ARG_USER_ID, userId); - args.putByteArray(ARG_CARD_AID, aid); - frag.setArguments(args); - - return frag; - } - - public ViewKeyYubiKeyFragment() { - super(null); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Bundle args = getArguments(); - ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT)); - mFingerprints = new byte[buf.remaining()/20][]; - for (int i = 0; i < mFingerprints.length; i++) { - mFingerprints[i] = new byte[20]; - buf.get(mFingerprints[i]); - } - mUserId = args.getString(ARG_USER_ID); - mCardAid = args.getByteArray(ARG_CARD_AID); - - mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID); - - getLoaderManager().initLoader(0, null, this); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.view_key_yubikey, null); - - TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno); - TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid); - - String serno = Hex.toHexString(mCardAid, 10, 4); - vSerNo.setText(getString(R.string.yubikey_serno, serno)); - - if (!mUserId.isEmpty()) { - vUserId.setText(getString(R.string.yubikey_key_holder, mUserId)); - } else { - vUserId.setText(getString(R.string.yubikey_key_holder_not_set)); - } - - vButton = (Button) view.findViewById(R.id.button_bind); - vButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - promoteToSecretKey(); - } - }); - - vStatus = (TextView) view.findViewById(R.id.yubikey_status); - - return view; - } - - public void promoteToSecretKey() { - long[] subKeyIds = new long[mFingerprints.length]; - for (int i = 0; i < subKeyIds.length; i++) { - subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]); - } - - // mMasterKeyId and mCardAid are already set - mSubKeyIds = subKeyIds; - - cryptoOperation(); - } - - public static final String[] PROJECTION = new String[]{ - Keys._ID, - Keys.KEY_ID, - Keys.RANK, - Keys.HAS_SECRET, - Keys.FINGERPRINT - }; - // private static final int INDEX_KEY_ID = 1; - // private static final int INDEX_RANK = 2; - private static final int INDEX_HAS_SECRET = 3; - private static final int INDEX_FINGERPRINT = 4; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId), - PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (!data.moveToFirst()) { - // wut? - return; - } - - boolean allBound = true; - boolean noneBound = true; - - do { - SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET)); - byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); - Integer index = naiveIndexOf(mFingerprints, fingerprint); - if (index == null) { - continue; - } - if (keyType == SecretKeyType.DIVERT_TO_CARD) { - noneBound = false; - } else { - allBound = false; - } - } while (data.moveToNext()); - - if (allBound) { - vButton.setVisibility(View.GONE); - vStatus.setText(R.string.yubikey_status_bound); - } else { - vButton.setVisibility(View.VISIBLE); - vStatus.setText(noneBound - ? R.string.yubikey_status_unbound - : R.string.yubikey_status_partly); - } - - } - - static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) { - for (int i = 0; i < haystack.length; i++) { - if (Arrays.equals(needle, haystack[i])) { - return i; - } - } - return null; - } - - @Override - public void onLoaderReset(Loader loader) { - - } - - @Override - public PromoteKeyringParcel createOperationInput() { - return new PromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds); - } - - @Override - public void onQueuedOperationSuccess(PromoteKeyResult result) { - result.createNotify(getActivity()).show(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 84608f2dc..b5ef1d5e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -174,7 +174,7 @@ public class SubkeysAdapter extends CursorAdapter { ? mSaveKeyringParcel.getSubkeyChange(keyId) : null; - if (change != null && (change.mDummyStrip || change.mMoveKeyToCard)) { + if (change != null && (change.mDummyStrip || change.mMoveKeyToSecurityToken)) { if (change.mDummyStrip) { algorithmStr.append(", "); final SpannableString boldStripped = new SpannableString( @@ -183,7 +183,7 @@ public class SubkeysAdapter extends CursorAdapter { boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); algorithmStr.append(boldStripped); } - if (change.mMoveKeyToCard) { + if (change.mMoveKeyToSecurityToken) { algorithmStr.append(", "); final SpannableString boldDivert = new SpannableString( context.getString(R.string.key_divert) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java deleted file mode 100644 index 3e0bc7890..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * Copyright (C) 2013-2014 Signe Rüsch - * Copyright (C) 2013-2014 Philipp Jakubeit - * - * 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.base; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.interfaces.RSAPrivateCrtKey; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.IntentFilter; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.TagLostException; -import android.nfc.tech.IsoDep; -import android.os.AsyncTask; -import android.os.Bundle; - -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.util.Arrays; -import org.spongycastle.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.ui.CreateKeyActivity; -import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; -import org.sufficientlysecure.keychain.ui.ViewKeyActivity; -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; -import org.sufficientlysecure.keychain.util.Preferences; - -public abstract class BaseNfcActivity extends BaseActivity { - - public static final int REQUEST_CODE_PIN = 1; - - public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - - 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; - private NfcAdapter mNfcAdapter; - private IsoDep mIsoDep; - private boolean mTagHandlingEnabled; - - private static final int TIMEOUT = 100000; - - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; - - /** - * Override to change UI before NFC handling (UI thread) - */ - protected void onNfcPreExecute() { - } - - /** - * Override to implement NFC operations (background thread) - */ - protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); - } - - /** - * Override to handle result of NFC operations (UI thread) - */ - protected void onNfcPostExecute() { - - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); - - try { - CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - long masterKeyId = ring.getMasterKeyId(); - - Intent intent = new Intent(this, ViewKeyActivity.class); - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); - 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); - startActivity(intent); - } - } - - /** - * Override to use something different than Notify (UI thread) - */ - protected void onNfcError(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); - } - - public void handleIntentInBackground(final Intent intent) { - // Actual NFC operations are executed in doInBackground to not block the UI thread - new AsyncTask() { - @Override - protected void onPreExecute() { - super.onPreExecute(); - onNfcPreExecute(); - } - - @Override - protected IOException doInBackground(Void... params) { - try { - handleTagDiscoveredIntent(intent); - } catch (IOException e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(IOException exception) { - super.onPostExecute(exception); - - if (exception != null) { - handleNfcError(exception); - return; - } - - onNfcPostExecute(); - } - }.execute(); - } - - protected void pauseTagHandling() { - mTagHandlingEnabled = false; - } - - protected void resumeTagHandling() { - mTagHandlingEnabled = true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Check whether we're recreating a previously destroyed instance - if (savedInstanceState != null) { - // Restore value of members from saved state - mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); - } else { - mTagHandlingEnabled = true; - } - - Intent intent = getIntent(); - String action = intent.getAction(); - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { - throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); - } - - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); - } - - /** - * This activity is started as a singleTop activity. - * All new NFC Intents which are delivered to this activity are handled here - */ - @Override - public void onNewIntent(final Intent intent) { - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()) - && mTagHandlingEnabled) { - handleIntentInBackground(intent); - } - } - - private void handleNfcError(IOException e) { - - if (e instanceof TagLostException) { - onNfcError(getString(R.string.error_nfc_tag_lost)); - return; - } - - if (e instanceof IsoDepNotSupportedException) { - onNfcError(getString(R.string.error_nfc_iso_dep_not_supported)); - return; - } - - short status; - if (e instanceof CardException) { - status = ((CardException) e).getResponseCode(); - } else { - status = -1; - } - - // Wrong PIN, a status of 63CX indicates X attempts remaining. - if ((status & (short) 0xFFF0) == 0x63C0) { - int tries = status & 0x000F; - // hook to do something different when PIN is wrong - onNfcPinError(getResources().getQuantityString(R.plurals.error_pin, tries, tries)); - return; - } - - // Otherwise, all status codes are fixed values. - switch (status) { - // These errors should not occur in everyday use; if they are returned, it means we - // made a mistake sending data to the card, or the card is misbehaving. - case 0x6A80: { - onNfcError(getString(R.string.error_nfc_bad_data)); - break; - } - case 0x6883: { - onNfcError(getString(R.string.error_nfc_chaining_error)); - break; - } - case 0x6B00: { - onNfcError(getString(R.string.error_nfc_header, "P1/P2")); - break; - } - case 0x6D00: { - onNfcError(getString(R.string.error_nfc_header, "INS")); - break; - } - case 0x6E00: { - onNfcError(getString(R.string.error_nfc_header, "CLA")); - break; - } - // These error conditions are more likely to be experienced by an end user. - case 0x6285: { - onNfcError(getString(R.string.error_nfc_terminated)); - break; - } - case 0x6700: { - onNfcPinError(getString(R.string.error_nfc_wrong_length)); - break; - } - case 0x6982: { - onNfcError(getString(R.string.error_nfc_security_not_satisfied)); - break; - } - case 0x6983: { - onNfcError(getString(R.string.error_nfc_authentication_blocked)); - break; - } - case 0x6985: { - onNfcError(getString(R.string.error_nfc_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.error_nfc_data_not_found)); - break; - } - // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an - // unhandled exception on the smart card. - case 0x6F00: { - onNfcError(getString(R.string.error_nfc_unknown)); - break; - } - default: { - onNfcError(getString(R.string.error_nfc, e.getMessage())); - break; - } - } - - } - - /** - * Called when the system is about to start resuming a previous activity, - * disables NFC Foreground Dispatch - */ - public void onPause() { - super.onPause(); - Log.d(Constants.TAG, "BaseNfcActivity.onPause"); - - disableNfcForegroundDispatch(); - } - - /** - * Called when the activity will start interacting with the user, - * enables NFC Foreground Dispatch - */ - public void onResume() { - super.onResume(); - Log.d(Constants.TAG, "BaseNfcActivity.onResume"); - - enableNfcForegroundDispatch(); - } - - protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) { - - try { - Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, - requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); - if (passphrase != null) { - mPin = passphrase; - return; - } - - Intent intent = new Intent(this, PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, - RequiredInputParcel.createRequiredPassphrase(requiredInput)); - startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (KeyNotFoundException e) { - throw new AssertionError( - "tried to find passphrase for non-existing key. this is a programming error!"); - } - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PIN: { - if (resultCode != Activity.RESULT_OK) { - setResult(resultCode); - finish(); - return; - } - CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mPin = input.getPassphrase(); - break; - } - - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - /** 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 handleTagDiscoveredIntent(Intent intent) throws IOException { - - Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - - // Connect to the detected tag, setting a couple of settings - mIsoDep = IsoDep.get(detectedTag); - if (mIsoDep == null) { - throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation - mIsoDep.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 mIsoDep.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 = mIsoDep.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 card. 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 mIsoDep.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 mIsoDep.transceive(Hex.decode(info)); - } - - public String nfcGetUserId() throws IOException { - String info = "00CA006500"; - return nfcGetHolderName(nfcCommunicate(info)); - } - - /** - * Calls to calculate the signature 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); - } - - /** - * Calls to calculate the signature and returns the MPI value - * - * @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) - } - - 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]; - } - - String first = nfcCommunicate(firstApdu + getHex(one)); - String second = nfcCommunicate(secondApdu + getHex(two) + le); - - String decryptedSessionKey = nfcGetDataField(second); - - return Hex.decode(decryptedSessionKey); - } - - /** 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 = tryPin(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 void nfcResetCard() 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 = tryPin(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 = tryPin(0x83, adminPin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); - } - } - - // reactivate card! - 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 tryPin(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 card'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 card. 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 card in the given slot. - * - * @param slot The slot on the card 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 smart card."); - } - - // 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 card key."); - } - - 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 card. - 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 card failed", parseCardStatus(response)); - } - } - - // Clear array with secret data before we return. - Arrays.fill(dataToSend, (byte) 0); - } - - /** - * Parses out the status word from a JavaCard response string. - * - * @param response A hex string with the response from the card - * @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; - } - } - - /** - * Receive new NFC Intents to this activity only by enabling foreground dispatch. - * This can only be done in onResume! - */ - public void enableNfcForegroundDispatch() { - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (mNfcAdapter == null) { - return; - } - Intent nfcI = new Intent(this, getClass()) - .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); - IntentFilter[] writeTagFilters = new IntentFilter[]{ - new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) - }; - - try { - mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); - } catch (IllegalStateException e) { - Log.i(Constants.TAG, "NfcForegroundDispatch Exception: Activity is not currently in the foreground?", e); - } - Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); - } - - /** - * Disable foreground dispatch in onPause! - */ - public void disableNfcForegroundDispatch() { - if (mNfcAdapter == null) { - return; - } - mNfcAdapter.disableForegroundDispatch(this); - Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); - } - - public String nfcGetHolderName(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 nfcGetDataField(String output) { - return output.substring(0, output.length() - 4); - } - - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoDep.transceive(Hex.decode(apdu))); - } - - public static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - - public class IsoDepNotSupportedException extends IOException { - - public IsoDepNotSupportedException(String detailMessage) { - super(detailMessage); - } - - } - - public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - - } - -} 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 new file mode 100644 index 000000000..0f00fcd92 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -0,0 +1,1020 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * + * 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.base; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.TagLostException; +import android.nfc.tech.IsoDep; +import android.os.AsyncTask; +import android.os.Bundle; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.util.Arrays; +import org.spongycastle.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.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; +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 { + + public static final int REQUEST_CODE_PIN = 1; + + public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; + + 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; + private NfcAdapter mNfcAdapter; + private IsoDep mIsoDep; + private boolean mTagHandlingEnabled; + + private static final int TIMEOUT = 100000; + + private byte[] mNfcFingerprints; + private String mNfcUserId; + private byte[] mNfcAid; + + /** + * Override to change UI before NFC handling (UI thread) + */ + protected void onNfcPreExecute() { + } + + /** + * Override to implement NFC operations (background thread) + */ + protected void doNfcInBackground() throws IOException { + mNfcFingerprints = nfcGetFingerprints(); + mNfcUserId = nfcGetUserId(); + mNfcAid = nfcGetAid(); + } + + /** + * Override to handle result of NFC operations (UI thread) + */ + protected void onNfcPostExecute() { + + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + + try { + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + long masterKeyId = ring.getMasterKeyId(); + + 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); + 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); + startActivity(intent); + } + } + + /** + * Override to use something different than Notify (UI thread) + */ + protected void onNfcError(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); + } + + public void handleIntentInBackground(final Intent intent) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onNfcPreExecute(); + } + + @Override + protected IOException doInBackground(Void... params) { + try { + handleTagDiscoveredIntent(intent); + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(IOException exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleNfcError(exception); + return; + } + + onNfcPostExecute(); + } + }.execute(); + } + + protected void pauseTagHandling() { + mTagHandlingEnabled = false; + } + + protected void resumeTagHandling() { + mTagHandlingEnabled = true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Check whether we're recreating a previously destroyed instance + if (savedInstanceState != null) { + // Restore value of members from saved state + mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); + } else { + mTagHandlingEnabled = true; + } + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); + } + + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); + } + + /** + * This activity is started as a singleTop activity. + * All new NFC Intents which are delivered to this activity are handled here + */ + @Override + public void onNewIntent(final Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()) + && mTagHandlingEnabled) { + handleIntentInBackground(intent); + } + } + + private void handleNfcError(IOException e) { + + if (e instanceof TagLostException) { + onNfcError(getString(R.string.security_token_error_tag_lost)); + return; + } + + if (e instanceof IsoDepNotSupportedException) { + onNfcError(getString(R.string.security_token_error_iso_dep_not_supported)); + return; + } + + short status; + if (e instanceof CardException) { + status = ((CardException) e).getResponseCode(); + } else { + status = -1; + } + + // Wrong PIN, a status of 63CX indicates X attempts remaining. + 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)); + return; + } + + // Otherwise, all status codes are fixed values. + switch (status) { + // 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)); + break; + } + case 0x6883: { + onNfcError(getString(R.string.security_token_error_chaining_error)); + break; + } + case 0x6B00: { + onNfcError(getString(R.string.security_token_error_header, "P1/P2")); + break; + } + case 0x6D00: { + onNfcError(getString(R.string.security_token_error_header, "INS")); + break; + } + case 0x6E00: { + onNfcError(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)); + break; + } + case 0x6700: { + onNfcPinError(getString(R.string.security_token_error_wrong_length)); + break; + } + case 0x6982: { + onNfcError(getString(R.string.security_token_error_security_not_satisfied)); + break; + } + case 0x6983: { + onNfcError(getString(R.string.security_token_error_authentication_blocked)); + break; + } + case 0x6985: { + onNfcError(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)); + 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)); + break; + } + default: { + onNfcError(getString(R.string.security_token_error, e.getMessage())); + break; + } + } + + } + + /** + * Called when the system is about to start resuming a previous activity, + * disables NFC Foreground Dispatch + */ + public void onPause() { + super.onPause(); + Log.d(Constants.TAG, "BaseNfcActivity.onPause"); + + disableNfcForegroundDispatch(); + } + + /** + * Called when the activity will start interacting with the user, + * enables NFC Foreground Dispatch + */ + public void onResume() { + super.onResume(); + Log.d(Constants.TAG, "BaseNfcActivity.onResume"); + + enableNfcForegroundDispatch(); + } + + protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { + + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); + if (passphrase != null) { + mPin = passphrase; + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(requiredInput)); + startActivityForResult(intent, REQUEST_CODE_PIN); + } catch (KeyNotFoundException e) { + throw new AssertionError( + "tried to find passphrase for non-existing key. this is a programming error!"); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PIN: { + if (resultCode != Activity.RESULT_OK) { + setResult(resultCode); + finish(); + return; + } + CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); + mPin = input.getPassphrase(); + break; + } + + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** 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 handleTagDiscoveredIntent(Intent intent) throws IOException { + + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + // Connect to the detected tag, setting a couple of settings + mIsoDep = IsoDep.get(detectedTag); + if (mIsoDep == null) { + throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + } + mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation + mIsoDep.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 mIsoDep.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 = mIsoDep.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 mIsoDep.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 mIsoDep.transceive(Hex.decode(info)); + } + + public String nfcGetUserId() throws IOException { + String info = "00CA006500"; + return nfcGetHolderName(nfcCommunicate(info)); + } + + /** + * Calls to calculate the signature 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); + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @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) + } + + 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]; + } + + String first = nfcCommunicate(firstApdu + getHex(one)); + String second = nfcCommunicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = nfcGetDataField(second); + + return Hex.decode(decryptedSessionKey); + } + + /** 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 = tryPin(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 void nfcResetCard() 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 = tryPin(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 = tryPin(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 tryPin(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); + } + + /** + * 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; + } + } + + /** + * Receive new NFC Intents to this activity only by enabling foreground dispatch. + * This can only be done in onResume! + */ + public void enableNfcForegroundDispatch() { + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (mNfcAdapter == null) { + return; + } + Intent nfcI = new Intent(this, getClass()) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); + IntentFilter[] writeTagFilters = new IntentFilter[]{ + new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + }; + + try { + mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null); + } catch (IllegalStateException e) { + Log.i(Constants.TAG, "NfcForegroundDispatch Exception: Activity is not currently in the foreground?", e); + } + Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!"); + } + + /** + * Disable foreground dispatch in onPause! + */ + public void disableNfcForegroundDispatch() { + if (mNfcAdapter == null) { + return; + } + mNfcAdapter.disableForegroundDispatch(this); + Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); + } + + public String nfcGetHolderName(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 nfcGetDataField(String output) { + return output.substring(0, output.length() - 4); + } + + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoDep.transceive(Hex.decode(apdu))); + } + + public static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + public class IsoDepNotSupportedException extends IOException { + + public IsoDepNotSupportedException(String detailMessage) { + super(detailMessage); + } + + } + + public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + + } + +} 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 7ab9c7237..451065d6b 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 @@ -39,7 +39,7 @@ import org.sufficientlysecure.keychain.service.KeychainService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.SecurityTokenOperationActivity; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.RetryUploadDialogActivity; @@ -133,9 +133,9 @@ public class CryptoOperationHelper