From b1a978d573763aa2b867eadf4e3ee8597bd8f0a0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 5 Feb 2016 19:24:22 +0100 Subject: split off ApiDataAccessObject from ProviderHelper --- .../keychain/provider/ApiDataAccessObject.java | 243 +++++++++++++++++++++ .../keychain/provider/ProviderHelper.java | 230 ++----------------- .../provider/SimpleContentResolverInterface.java | 22 ++ .../keychain/remote/ApiPermissionHelper.java | 17 +- .../keychain/remote/OpenPgpService.java | 11 +- .../remote/ui/AccountSettingsActivity.java | 6 +- .../keychain/remote/ui/AppSettingsActivity.java | 4 +- .../ui/AppSettingsAllowedKeysListFragment.java | 10 +- .../remote/ui/RemoteCreateAccountActivity.java | 11 +- .../keychain/remote/ui/RemoteRegisterActivity.java | 8 +- .../remote/ui/SelectSignKeyIdListFragment.java | 9 +- 11 files changed, 324 insertions(+), 247 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java new file mode 100644 index 000000000..0d3d4dcbd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -0,0 +1,243 @@ +package org.sufficientlysecure.keychain.provider; + + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; + +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.remote.AppSettings; + + +public class ApiDataAccessObject { + + private final SimpleContentResolverInterface mQueryInterface; + + public ApiDataAccessObject(Context context) { + final ContentResolver contentResolver = context.getContentResolver(); + mQueryInterface = new SimpleContentResolverInterface() { + @Override + public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); + } + + @Override + public Uri insert(Uri contentUri, ContentValues values) { + return contentResolver.insert(contentUri, values); + } + + @Override + public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { + return contentResolver.update(contentUri, values, where, selectionArgs); + } + + @Override + public int delete(Uri contentUri, String where, String[] selectionArgs) { + return contentResolver.delete(contentUri, where, selectionArgs); + } + }; + } + + public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) { + mQueryInterface = queryInterface; + } + + public ArrayList getRegisteredApiApps() { + Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null); + + ArrayList packageNames = new ArrayList<>(); + try { + if (cursor != null) { + int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); + if (cursor.moveToFirst()) { + do { + packageNames.add(cursor.getString(packageNameCol)); + } while (cursor.moveToNext()); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return packageNames; + } + + private ContentValues contentValueForApiApps(AppSettings appSettings) { + ContentValues values = new ContentValues(); + values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); + values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); + return values; + } + + private ContentValues contentValueForApiAccounts(AccountSettings accSettings) { + ContentValues values = new ContentValues(); + values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName()); + values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId()); + + // DEPRECATED and thus hardcoded + values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB); + values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, + PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); + values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, + PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT); + return values; + } + + public void insertApiApp(AppSettings appSettings) { + mQueryInterface.insert(ApiApps.CONTENT_URI, + contentValueForApiApps(appSettings)); + } + + public void insertApiAccount(Uri uri, AccountSettings accSettings) { + mQueryInterface.insert(uri, contentValueForApiAccounts(accSettings)); + } + + public void updateApiAccount(Uri uri, AccountSettings accSettings) { + if (mQueryInterface.update(uri, contentValueForApiAccounts(accSettings), null, + null) <= 0) { + throw new RuntimeException(); + } + } + + /** + * Must be an uri pointing to an account + */ + public AppSettings getApiAppSettings(Uri uri) { + AppSettings settings = null; + + Cursor cursor = mQueryInterface.query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + settings = new AppSettings(); + settings.setPackageName(cursor.getString( + cursor.getColumnIndex(ApiApps.PACKAGE_NAME))); + settings.setPackageCertificate(cursor.getBlob( + cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE))); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return settings; + } + + public AccountSettings getApiAccountSettings(Uri accountUri) { + AccountSettings settings = null; + + Cursor cursor = mQueryInterface.query(accountUri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + settings = new AccountSettings(); + + settings.setAccountName(cursor.getString( + cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); + settings.setKeyId(cursor.getLong( + cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return settings; + } + + public Set getAllKeyIdsForApp(Uri uri) { + Set keyIds = new HashSet<>(); + + Cursor cursor = mQueryInterface.query(uri, null, null, null, null); + try { + if (cursor != null) { + int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); + while (cursor.moveToNext()) { + keyIds.add(cursor.getLong(keyIdColumn)); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return keyIds; + } + + public HashSet getAllowedKeyIdsForApp(Uri uri) { + HashSet keyIds = new HashSet<>(); + + Cursor cursor = mQueryInterface.query(uri, null, null, null, null); + try { + if (cursor != null) { + int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID); + while (cursor.moveToNext()) { + keyIds.add(cursor.getLong(keyIdColumn)); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return keyIds; + } + + public void saveAllowedKeyIdsForApp(Uri uri, Set allowedKeyIds) + throws RemoteException, OperationApplicationException { + // wipe whole table of allowed keys for this account + mQueryInterface.delete(uri, null, null); + + // re-insert allowed key ids + for (Long keyId : allowedKeyIds) { + ContentValues values = new ContentValues(); + values.put(ApiAllowedKeys.KEY_ID, keyId); + mQueryInterface.insert(uri, values); + } + } + + public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { + ContentValues values = new ContentValues(); + values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); + mQueryInterface.insert(uri, values); + } + + public byte[] getApiAppCertificate(String packageName) { + Uri queryUri = ApiApps.buildByPackageNameUri(packageName); + + String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; + + Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null); + try { + byte[] signature = null; + if (cursor != null && cursor.moveToFirst()) { + int signatureCol = 0; + + signature = cursor.getBlob(signatureCol); + } + return signature; + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 83e2baf9a..72a3e2ff5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -18,6 +18,18 @@ package org.sufficientlysecure.keychain.provider; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; + import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; @@ -29,19 +41,12 @@ import android.os.RemoteException; import android.support.annotation.NonNull; import android.support.v4.util.LongSparseArray; -import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -51,41 +56,29 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; -import org.sufficientlysecure.keychain.remote.AccountSettings; -import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - /** * This class contains high level methods for database access. Despite its * name, it is not only a helper but actually the main interface for all @@ -1481,191 +1474,6 @@ public class ProviderHelper { return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values); } - public ArrayList getRegisteredApiApps() { - Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); - - ArrayList packageNames = new ArrayList<>(); - try { - if (cursor != null) { - int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); - if (cursor.moveToFirst()) { - do { - packageNames.add(cursor.getString(packageNameCol)); - } while (cursor.moveToNext()); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return packageNames; - } - - private ContentValues contentValueForApiApps(AppSettings appSettings) { - ContentValues values = new ContentValues(); - values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); - values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); - return values; - } - - private ContentValues contentValueForApiAccounts(AccountSettings accSettings) { - ContentValues values = new ContentValues(); - values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName()); - values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId()); - - // DEPRECATED and thus hardcoded - values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB); - values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, - PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); - values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, - PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT); - return values; - } - - public void insertApiApp(AppSettings appSettings) { - mContentResolver.insert(KeychainContract.ApiApps.CONTENT_URI, - contentValueForApiApps(appSettings)); - } - - public void insertApiAccount(Uri uri, AccountSettings accSettings) { - mContentResolver.insert(uri, contentValueForApiAccounts(accSettings)); - } - - public void updateApiAccount(Uri uri, AccountSettings accSettings) { - if (mContentResolver.update(uri, contentValueForApiAccounts(accSettings), null, - null) <= 0) { - throw new RuntimeException(); - } - } - - /** - * Must be an uri pointing to an account - */ - public AppSettings getApiAppSettings(Uri uri) { - AppSettings settings = null; - - Cursor cursor = mContentResolver.query(uri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - settings = new AppSettings(); - settings.setPackageName(cursor.getString( - cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); - settings.setPackageCertificate(cursor.getBlob( - cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE))); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return settings; - } - - public AccountSettings getApiAccountSettings(Uri accountUri) { - AccountSettings settings = null; - - Cursor cursor = mContentResolver.query(accountUri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - settings = new AccountSettings(); - - settings.setAccountName(cursor.getString( - cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); - settings.setKeyId(cursor.getLong( - cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return settings; - } - - public Set getAllKeyIdsForApp(Uri uri) { - Set keyIds = new HashSet<>(); - - Cursor cursor = mContentResolver.query(uri, null, null, null, null); - try { - if (cursor != null) { - int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); - while (cursor.moveToNext()) { - keyIds.add(cursor.getLong(keyIdColumn)); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return keyIds; - } - - public HashSet getAllowedKeyIdsForApp(Uri uri) { - HashSet keyIds = new HashSet<>(); - - Cursor cursor = mContentResolver.query(uri, null, null, null, null); - try { - if (cursor != null) { - int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAllowedKeys.KEY_ID); - while (cursor.moveToNext()) { - keyIds.add(cursor.getLong(keyIdColumn)); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return keyIds; - } - - public void saveAllowedKeyIdsForApp(Uri uri, Set allowedKeyIds) - throws RemoteException, OperationApplicationException { - // wipe whole table of allowed keys for this account - mContentResolver.delete(uri, null, null); - - // re-insert allowed key ids - for (Long keyId : allowedKeyIds) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, keyId); - mContentResolver.insert(uri, values); - } - } - - public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); - mContentResolver.insert(uri, values); - } - - public byte[] getApiAppCertificate(String packageName) { - Uri queryUri = ApiApps.buildByPackageNameUri(packageName); - - String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; - - Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null); - try { - byte[] signature = null; - if (cursor != null && cursor.moveToFirst()) { - int signatureCol = 0; - - signature = cursor.getBlob(signatureCol); - } - return signature; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - public ContentResolver getContentResolver() { return mContentResolver; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java new file mode 100644 index 000000000..c47d210a3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java @@ -0,0 +1,22 @@ +package org.sufficientlysecure.keychain.provider; + + +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** This interface contains the principal methods for database access + * from {#android.content.ContentResolver}. It is used to allow substitution + * of a ContentResolver in DAOs. + * + * @see ApiDataAccessObject + */ +public interface SimpleContentResolverInterface { + Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder); + + Uri insert(Uri contentUri, ContentValues values); + + int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs); + + int delete(Uri contentUri, String where, String[] selectionArgs); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index 7edd8b2b0..1a6638bd9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -33,8 +33,8 @@ import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; @@ -49,13 +49,13 @@ import java.util.Arrays; public class ApiPermissionHelper { private final Context mContext; - private final ProviderHelper mProviderHelper; + private final ApiDataAccessObject mApiDao; private PackageManager mPackageManager; - public ApiPermissionHelper(Context context) { + public ApiPermissionHelper(Context context, ApiDataAccessObject apiDao) { mContext = context; mPackageManager = context.getPackageManager(); - mProviderHelper = new ProviderHelper(context); + mApiDao = apiDao; } public static class WrongPackageCertificateException extends Exception { @@ -71,9 +71,8 @@ public class ApiPermissionHelper { * * @return null if caller is allowed, or a Bundle with a PendingIntent */ - protected Intent isAllowed(Intent data) { + protected Intent isAllowedOrReturnIntent(Intent data) { ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(mContext); - try { if (isCallerAllowed()) { return null; @@ -168,7 +167,7 @@ public class ApiPermissionHelper { Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName); - return mProviderHelper.getApiAccountSettings(uri); // can be null! + return mApiDao.getApiAccountSettings(uri); // can be null! } @Deprecated @@ -224,7 +223,7 @@ public class ApiPermissionHelper { private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException { Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); - ArrayList allowedPkgs = mProviderHelper.getRegisteredApiApps(); + ArrayList allowedPkgs = mApiDao.getRegisteredApiApps(); Log.d(Constants.TAG, "allowed: " + allowedPkgs); // check if package is allowed to use our service @@ -239,7 +238,7 @@ public class ApiPermissionHelper { throw new WrongPackageCertificateException(e.getMessage()); } - byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName); + byte[] storedCert = mApiDao.getApiAppCertificate(packageName); if (Arrays.equals(currentCert, storedCert)) { Log.d(Constants.TAG, "Package certificate is correct! (equals certificate from database)"); 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 2e14099de..2bf14f876 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -47,6 +47,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -82,12 +83,14 @@ public class OpenPgpService extends Service { private ApiPermissionHelper mApiPermissionHelper; private ProviderHelper mProviderHelper; + private ApiDataAccessObject mApiDao; @Override public void onCreate() { super.onCreate(); - mApiPermissionHelper = new ApiPermissionHelper(this); + mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this)); mProviderHelper = new ProviderHelper(this); + mApiDao = new ApiDataAccessObject(this); } /** @@ -402,11 +405,11 @@ public class OpenPgpService extends Service { } String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - HashSet allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( + HashSet allowedKeyIds = mApiDao.getAllowedKeyIdsForApp( KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { - allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp( + allowedKeyIds.addAll(mApiDao.getAllKeyIdsForApp( ApiAccounts.buildBaseUri(currentPkg))); } @@ -732,7 +735,7 @@ public class OpenPgpService extends Service { } // check if caller is allowed to access OpenKeychain - Intent result = mApiPermissionHelper.isAllowed(data); + Intent result = mApiPermissionHelper.isAllowedOrReturnIntent(data); if (result != null) { return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index e19757d65..27700b5e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; @@ -98,7 +98,7 @@ public class AccountSettingsActivity extends BaseActivity { } private void loadData(Uri accountUri) { - AccountSettings settings = new ProviderHelper(this).getApiAccountSettings(accountUri); + AccountSettings settings = new ApiDataAccessObject(this).getApiAccountSettings(accountUri); mAccountSettingsFragment.setAccSettings(settings); } @@ -110,7 +110,7 @@ public class AccountSettingsActivity extends BaseActivity { } private void save() { - new ProviderHelper(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings()); + new ApiDataAccessObject(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings()); SingletonResult result = new SingletonResult( SingletonResult.RESULT_OK, LogType.MSG_ACC_SAVED); Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 3b5fdfd8a..249c0c208 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -37,8 +37,8 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; @@ -182,7 +182,7 @@ public class AppSettingsActivity extends BaseActivity { } private void loadData(Bundle savedInstanceState, Uri appUri) { - mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri); + mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri); // get application name and icon from package manager String appName; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index caa173f03..22a03a7c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -36,8 +36,8 @@ import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; import org.sufficientlysecure.keychain.ui.widget.FixedListView; @@ -47,7 +47,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i private static final String ARG_DATA_URI = "uri"; private KeySelectableAdapter mAdapter; - private ProviderHelper mProviderHelper; + private ApiDataAccessObject mApiDao; private Uri mDataUri; @@ -69,7 +69,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mProviderHelper = new ProviderHelper(getActivity()); + mApiDao = new ApiDataAccessObject(getActivity()); } @Override @@ -107,7 +107,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mProviderHelper.getAllKeyIdsForApp(mDataUri); + Set checked = mApiDao.getAllKeyIdsForApp(mDataUri); mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); setListAdapter(mAdapter); getListView().setOnItemClickListener(mAdapter); @@ -141,7 +141,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void saveAllowedKeys() { try { - mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); + mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); } catch (RemoteException | OperationApplicationException e) { Log.e(Constants.TAG, "Problem saving allowed key ids!", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java index adc1f5abf..a4a0b242c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.remote.ui; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -25,8 +26,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -56,7 +57,7 @@ public class RemoteCreateAccountActivity extends BaseActivity { final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final String accName = extras.getString(EXTRA_ACC_NAME); - final ProviderHelper providerHelper = new ProviderHelper(this); + final ApiDataAccessObject apiDao = new ApiDataAccessObject(this); mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( R.id.api_account_settings_fragment); @@ -65,7 +66,7 @@ public class RemoteCreateAccountActivity extends BaseActivity { // update existing? Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName); - AccountSettings settings = providerHelper.getApiAccountSettings(uri); + AccountSettings settings = apiDao.getApiAccountSettings(uri); if (settings == null) { // create new account settings = new AccountSettings(accName); @@ -94,11 +95,11 @@ public class RemoteCreateAccountActivity extends BaseActivity { if (mUpdateExistingAccount) { Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName); Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build(); - providerHelper.updateApiAccount( + apiDao.updateApiAccount( accountUri, mAccSettingsFragment.getAccSettings()); } else { - providerHelper.insertApiAccount( + apiDao.insertApiAccount( KeychainContract.ApiAccounts.buildBaseUri(packageName), mAccSettingsFragment.getAccSettings()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java index d31f9e35a..0ce9e66c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java @@ -17,13 +17,14 @@ package org.sufficientlysecure.keychain.remote.ui; + import android.content.Intent; import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; @@ -52,7 +53,7 @@ public class RemoteRegisterActivity extends BaseActivity { final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); - final ProviderHelper providerHelper = new ProviderHelper(this); + final ApiDataAccessObject apiDao = new ApiDataAccessObject(this); mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById( R.id.api_app_settings_fragment); @@ -67,8 +68,7 @@ public class RemoteRegisterActivity extends BaseActivity { @Override public void onClick(View v) { // Allow - - providerHelper.insertApiApp(mAppSettingsHeaderFragment.getAppSettings()); + apiDao.insertApiApp(mAppSettingsHeaderFragment.getAppSettings()); // give data through for new service call Intent resultData = extras.getParcelable(EXTRA_DATA); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 852e8bb81..73ce5fe47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.remote.ui; + import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -36,9 +37,9 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; import org.sufficientlysecure.keychain.ui.widget.FixedListView; import org.sufficientlysecure.keychain.util.Log; @@ -49,7 +50,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public static final String ARG_DATA = "data"; private SelectKeyCursorAdapter mAdapter; - private ProviderHelper mProviderHelper; + private ApiDataAccessObject mApiDao; private Uri mDataUri; @@ -72,7 +73,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mProviderHelper = new ProviderHelper(getActivity()); + mApiDao = new ApiDataAccessObject(getActivity()); } @Override @@ -116,7 +117,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); - mProviderHelper.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); + mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); -- cgit v1.2.3 From da3167476b7e30ba653fc706a4a023ad1e07ea38 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 2 Dec 2015 02:18:14 +0100 Subject: external-provider: add (experimental) external provider --- .../provider/KeychainExternalContract.java | 35 ++++ .../keychain/remote/KeychainExternalProvider.java | 231 +++++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java new file mode 100644 index 000000000..07a28d710 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -0,0 +1,35 @@ +package org.sufficientlysecure.keychain.provider; + + +import android.net.Uri; +import android.provider.BaseColumns; + +import org.sufficientlysecure.keychain.Constants; + + +public class KeychainExternalContract { + + // this is in KeychainExternalContract already, but we want to be double + // sure this isn't mixed up with the internal one! + public static final String CONTENT_AUTHORITY_EXTERNAL = Constants.PROVIDER_AUTHORITY + ".exported"; + + private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri + .parse("content://" + CONTENT_AUTHORITY_EXTERNAL); + + public static final String BASE_EMAIL_STATUS = "email_status"; + + public static class EmailStatus implements BaseColumns { + public static final String EMAIL_ADDRESS = "email_address"; + public static final String EMAIL_STATUS = "email_status"; + + public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() + .appendPath(BASE_EMAIL_STATUS).build(); + + public static final String CONTENT_TYPE + = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; + } + + private KeychainExternalContract() { + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java new file mode 100644 index 000000000..9ab4be08a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010-2014 Thialfihar + * Copyright (C) 2014 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.remote; + + +import java.util.Arrays; +import java.util.HashMap; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.KeychainExternalContract; +import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; +import org.sufficientlysecure.keychain.util.Log; + + +public class KeychainExternalProvider extends ContentProvider { + private static final int EMAIL_STATUS = 101; + + + protected UriMatcher mUriMatcher; + + + /** + * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by + * this {@link ContentProvider}. + */ + protected UriMatcher buildUriMatcher() { + final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); + + String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL; + + /** + * list email_status + * + *
+         * email_status/
+         * 
+ */ + matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); + + return matcher; + } + + private KeychainDatabase mKeychainDatabase; + + /** {@inheritDoc} */ + @Override + public boolean onCreate() { + mUriMatcher = buildUriMatcher(); + return true; + } + + public KeychainDatabase getDb() { + if(mKeychainDatabase == null) + mKeychainDatabase = new KeychainDatabase(getContext()); + return mKeychainDatabase; + } + + /** + * {@inheritDoc} + */ + @Override + public String getType(@NonNull Uri uri) { + final int match = mUriMatcher.match(uri); + switch (match) { + case EMAIL_STATUS: + return EmailStatus.CONTENT_TYPE; + + default: + throw new UnsupportedOperationException("Unknown uri: " + uri); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); + + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + int match = mUriMatcher.match(uri); + + String groupBy; + + switch (match) { + case EMAIL_STATUS: { + HashMap projectionMap = new HashMap<>(); + projectionMap.put(EmailStatus._ID, "email AS _id"); + projectionMap.put(EmailStatus.EMAIL_ADDRESS, + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.EMAIL_ADDRESS); + // we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified" + projectionMap.put(EmailStatus.EMAIL_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) " + // remap to keep this provider contract independent from our internal representation + + " WHEN " + Certs.VERIFIED_SELF + " THEN 1" + + " WHEN " + Certs.VERIFIED_SECRET + " THEN 2" + + " END AS " + EmailStatus.EMAIL_STATUS); + qb.setProjectionMap(projectionMap); + + if (projection == null) { + throw new IllegalArgumentException("Please provide a projection!"); + } + + qb.setTables( + Tables.USER_PACKETS + + " INNER JOIN " + Tables.CERTS + " ON (" + + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + + Tables.CERTS + "." + Certs.MASTER_KEY_ID + + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + + Tables.CERTS + "." + Certs.RANK + // verified == 0 has no self-cert, which is basically an error case. never return that! + + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + + ")" + ); + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL"); + // in case there are multiple verifying certificates + groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ", " + + Tables.USER_PACKETS + "." + UserPackets.USER_ID; + + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = EmailStatus.EMAIL_ADDRESS + " ASC, " + EmailStatus.EMAIL_STATUS + " DESC"; + } + + // uri to watch is all /key_rings/ + uri = KeyRings.CONTENT_URI; + + boolean gotCondition = false; + String emailWhere = ""; + // JAVA ♥ + for (int i = 0; i < selectionArgs.length; ++i) { + if (selectionArgs[i].length() == 0) { + continue; + } + if (i != 0) { + emailWhere += " OR "; + } + emailWhere += UserPackets.USER_ID + " LIKE "; + // match '*', so it has to be at the *end* of the user id + emailWhere += DatabaseUtils.sqlEscapeString("%<" + selectionArgs[i] + ">"); + gotCondition = true; + } + + if (gotCondition) { + qb.appendWhere(" AND (" + emailWhere + ")"); + } else { + // TODO better way to do this? + Log.e(Constants.TAG, "Malformed find by email query!"); + qb.appendWhere(" AND 0"); + } + + break; + } + + default: { + throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); + } + + } + + // If no sort order is specified use the default + String orderBy; + if (TextUtils.isEmpty(sortOrder)) { + orderBy = null; + } else { + orderBy = sortOrder; + } + + SQLiteDatabase db = getDb().getReadableDatabase(); + + Cursor cursor = qb.query(db, projection, selection, null, groupBy, null, orderBy); + if (cursor != null) { + // Tell the cursor what uri to watch, so it knows when its source data changes + cursor.setNotificationUri(getContext().getContentResolver(), uri); + } + + Log.d(Constants.TAG, + "Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null)); + + return cursor; + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + throw new UnsupportedOperationException(); + } + + @Override + public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + +} -- cgit v1.2.3 From 1c256e9e50547f92f7e468fadd442157155d57c8 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 5 Feb 2016 19:24:30 +0100 Subject: external-provider: add permission check for status query --- .../keychain/provider/KeychainProvider.java | 2 +- .../keychain/remote/ApiPermissionHelper.java | 11 +++++ .../keychain/remote/KeychainExternalProvider.java | 47 +++++++++++++++++++--- .../keychain/remote/OpenPgpService.java | 1 + 4 files changed, 54 insertions(+), 7 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 87b8a3b65..0cb8e4675 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar - * Copyright (C) 2014 Vincent Breitmoser + * Copyright (C) 2014-2016 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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index 1a6638bd9..3af8e70dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -66,6 +66,17 @@ public class ApiPermissionHelper { } } + /** Returns true iff the caller is allowed, or false on any type of problem. + * This method should only be used in cases where error handling is dealt with separately. + */ + protected boolean isAllowedIgnoreErrors() { + try { + return isCallerAllowed(); + } catch (WrongPackageCertificateException e) { + return false; + } + } + /** * Checks if caller is allowed to access the API * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 9ab4be08a..7eb5d558f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -1,7 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * Copyright (C) 2014 Vincent Breitmoser + * Copyright (C) 2016 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 @@ -20,6 +18,7 @@ package org.sufficientlysecure.keychain.remote; +import java.security.AccessControlException; import java.util.Arrays; import java.util.HashMap; @@ -35,6 +34,9 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; @@ -42,14 +44,18 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; +import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.util.Log; -public class KeychainExternalProvider extends ContentProvider { +public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface { private static final int EMAIL_STATUS = 101; + private static final int API_APPS = 301; + private static final int API_APPS_BY_PACKAGE_NAME = 302; - protected UriMatcher mUriMatcher; + private UriMatcher mUriMatcher; + private ApiPermissionHelper mApiPermissionHelper; /** @@ -70,6 +76,9 @@ public class KeychainExternalProvider extends ContentProvider { */ matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); + matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS, API_APPS); + matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); + return matcher; } @@ -79,6 +88,7 @@ public class KeychainExternalProvider extends ContentProvider { @Override public boolean onCreate() { mUriMatcher = buildUriMatcher(); + mApiPermissionHelper = new ApiPermissionHelper(getContext(), new ApiDataAccessObject(this)); return true; } @@ -98,6 +108,12 @@ public class KeychainExternalProvider extends ContentProvider { case EMAIL_STATUS: return EmailStatus.CONTENT_TYPE; + case API_APPS: + return ApiApps.CONTENT_TYPE; + + case API_APPS_BY_PACKAGE_NAME: + return ApiApps.CONTENT_ITEM_TYPE; + default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -115,10 +131,15 @@ public class KeychainExternalProvider extends ContentProvider { int match = mUriMatcher.match(uri); - String groupBy; + String groupBy = null; switch (match) { case EMAIL_STATUS: { + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + HashMap projectionMap = new HashMap<>(); projectionMap.put(EmailStatus._ID, "email AS _id"); projectionMap.put(EmailStatus.EMAIL_ADDRESS, @@ -185,6 +206,20 @@ public class KeychainExternalProvider extends ContentProvider { break; } + case API_APPS: { + qb.setTables(Tables.API_APPS); + + break; + } + + case API_APPS_BY_PACKAGE_NAME: { + qb.setTables(Tables.API_APPS); + qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); + qb.appendWhereEscapeString(uri.getLastPathSegment()); + + break; + } + default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } 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 2bf14f876..d92393e64 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-2015 Dominik Schürmann + * Copyright (C) 2016 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 -- cgit v1.2.3 From 31b27e59ee8f578be35df1e4ece3c4381c5dfae5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Feb 2016 16:29:41 +0100 Subject: add ACTION_CHECK_PERMISSION for a simple permission check --- .../sufficientlysecure/keychain/remote/OpenPgpService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 d92393e64..91d3cda7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -674,6 +674,16 @@ public class OpenPgpService extends Service { } } + private Intent checkPermissionImpl(@NonNull Intent data) { + Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data); + if (permissionIntent != null) { + return permissionIntent; + } + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + return result; + } + private Intent getSignKeyMasterId(Intent data) { // NOTE: Accounts are deprecated on API version >= 7 if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { @@ -803,6 +813,9 @@ public class OpenPgpService extends Service { String action = data.getAction(); switch (action) { + case OpenPgpApi.ACTION_CHECK_PERMISSION: { + return checkPermissionImpl(data); + } case OpenPgpApi.ACTION_CLEARTEXT_SIGN: { return signImpl(data, inputStream, outputStream, true); } -- cgit v1.2.3 From 6a853f4c84e66aee70e3e1cd784ddbb8c1deac00 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 23 Feb 2016 17:08:17 +0100 Subject: wip --- .../keychain/remote/OpenPgpService.java | 130 ++++++++++++++------- 1 file changed, 88 insertions(+), 42 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 91d3cda7c..10b137b4f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -18,6 +18,17 @@ package org.sufficientlysecure.keychain.remote; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + import android.app.PendingIntent; import android.app.Service; import android.content.Intent; @@ -42,6 +53,8 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing.UserId; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; @@ -60,18 +73,9 @@ import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; - public class OpenPgpService extends Service { - static final String[] EMAIL_SEARCH_PROJECTION = new String[]{ + static final String[] KEY_SEARCH_PROJECTION = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.IS_EXPIRED, @@ -79,7 +83,7 @@ public class OpenPgpService extends Service { }; // do not pre-select revoked or expired keys - static final String EMAIL_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED + static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0"; private ApiPermissionHelper mApiPermissionHelper; @@ -94,22 +98,35 @@ public class OpenPgpService extends Service { mApiDao = new ApiDataAccessObject(this); } - /** - * Search database for key ids based on emails. - */ - private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { + private static class KeyIdResult { + final Intent mRequiredUserInteraction; + final HashSet mKeyIds; + + KeyIdResult(Intent requiredUserInteraction) { + mRequiredUserInteraction = requiredUserInteraction; + mKeyIds = null; + } + KeyIdResult(HashSet keyIds) { + mRequiredUserInteraction = null; + mKeyIds = keyIds; + } + } + + private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0); boolean missingUserIdsCheck = false; boolean duplicateUserIdsCheck = false; - ArrayList keyIds = new ArrayList<>(); + HashSet keyIds = new HashSet<>(); ArrayList missingEmails = new ArrayList<>(); ArrayList duplicateEmails = new ArrayList<>(); if (!noUserIdsCheck) { - for (String email : encryptionUserIds) { + for (String rawUserId : encryptionUserIds) { + UserId userId = KeyRing.splitUserId(rawUserId); + String email = userId.email != null ? userId.email : rawUserId; // try to find the key for this specific email Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); - Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null); + Cursor cursor = getContentResolver().query(uri, KEY_SEARCH_PROJECTION, KEY_SEARCH_WHERE, null, null); try { // result should be one entry containing the key id if (cursor != null && cursor.moveToFirst()) { @@ -138,15 +155,11 @@ public class OpenPgpService extends Service { } } - // convert ArrayList to long[] - long[] keyIdsArray = new long[keyIds.size()]; - for (int i = 0; i < keyIdsArray.length; i++) { - keyIdsArray[i] = keyIds.get(i); - } - if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { // allow the user to verify pub key selection + // convert ArrayList to long[] + long[] keyIdsArray = getUnboxedLongArray(keyIds); ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext()); PendingIntent pi = piFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray, missingEmails, duplicateEmails, noUserIdsCheck); @@ -155,18 +168,15 @@ public class OpenPgpService extends Service { Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); - return result; + return new KeyIdResult(result); } else { // everything was easy, we have exactly one key for every email - if (keyIdsArray.length == 0) { + if (keyIds.isEmpty()) { Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); } - Intent result = new Intent(); - result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray); - result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); - return result; + return new KeyIdResult(keyIds); } } @@ -281,20 +291,31 @@ public class OpenPgpService extends Service { compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED; } - // first try to get key ids from non-ambiguous key id extra - long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); - if (keyIds == null) { + long[] keyIds; + { + HashSet encryptKeyIds = new HashSet<>(); + // get key ids based on given user ids - String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - // give params through to activity... - Intent result = returnKeyIdsFromEmails(data, userIds); + if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { + String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); + data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); + // give params through to activity... + KeyIdResult result = returnKeyIdsFromEmails(data, userIds); + + if (result.mRequiredUserInteraction != null) { + return result.mRequiredUserInteraction; + } + encryptKeyIds.addAll(result.mKeyIds); + } - if (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0) == OpenPgpApi.RESULT_CODE_SUCCESS) { - keyIds = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS); - } else { - // if not success -> result contains a PendingIntent for user interaction - return result; + // add key ids from non-ambiguous key id extra + if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) { + for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) { + encryptKeyIds.add(keyId); + } } + + keyIds = getUnboxedLongArray(encryptKeyIds); } // TODO this is not correct! @@ -670,8 +691,33 @@ public class OpenPgpService extends Service { } else { // get key ids based on given user ids String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - return returnKeyIdsFromEmails(data, userIds); + data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); + KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds); + if (keyResult.mRequiredUserInteraction != null) { + return keyResult.mRequiredUserInteraction; + } + + if (keyResult.mKeyIds == null) { + throw new AssertionError("one of requiredUserInteraction and keyIds must be non-null, this is a bug!"); + } + + long[] keyIds = getUnboxedLongArray(keyResult.mKeyIds); + + Intent resultIntent = new Intent(); + resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); + resultIntent.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds); + return resultIntent; + } + } + + @NonNull + private static long[] getUnboxedLongArray(@NonNull Collection arrayList) { + long[] result = new long[arrayList.size()]; + int i = 0; + for (Long e : arrayList) { + result[i++] = e; } + return result; } private Intent checkPermissionImpl(@NonNull Intent data) { -- cgit v1.2.3 From 5989b7b4b82c4d645fab1793de32c051fa68428d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 24 Feb 2016 19:08:20 +0100 Subject: add some license headers --- .../keychain/provider/ApiDataAccessObject.java | 18 ++++++++++++++++++ .../keychain/provider/KeychainExternalContract.java | 17 +++++++++++++++++ .../provider/SimpleContentResolverInterface.java | 17 +++++++++++++++++ 3 files changed, 52 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java index 0d3d4dcbd..7c8295ad5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2014-2016 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.provider; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index 07a28d710..a4d35f168 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 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.provider; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java index c47d210a3..0e4d76aa4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 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.provider; -- cgit v1.2.3 From 05accb49066a2693cfc1c92e29ad10112f957495 Mon Sep 17 00:00:00 2001 From: "tdjogi010@gmail.com" Date: Wed, 2 Mar 2016 02:51:25 +0530 Subject: Alert for Stripping Subkeys. --- .../keychain/ui/dialog/EditSubkeyDialogFragment.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index b51648740..c34312e79 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -75,7 +75,22 @@ public class EditSubkeyDialogFragment extends DialogFragment { sendMessageToHandler(MESSAGE_REVOKE, null); break; case 2: - sendMessageToHandler(MESSAGE_STRIP, null); + CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); + stripAlertDialog.setTitle(getResources().getString(R.string.title_alert_strip)). + setMessage(R.string.alert_strip).setCancelable(true); + stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + sendMessageToHandler(MESSAGE_STRIP, null); + } + }); + stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dismiss(); + } + }); + stripAlertDialog.show(); break; case 3: sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); -- cgit v1.2.3 From ece848dee87c4e068189d511a51e28a7f2525f38 Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Wed, 2 Mar 2016 00:07:34 +0100 Subject: Fix backup code comparison --- .../sufficientlysecure/keychain/ui/BackupCodeFragment.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index a9dfaa2c5..47552bf13 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -351,27 +351,28 @@ public class BackupCodeFragment extends CryptoOperationFragment Date: Wed, 2 Mar 2016 14:42:44 +0100 Subject: nfc: disable broadcomWorkaround for TagDispatcher to reduce delay (see #1734) --- .../keychain/ui/base/BaseSecurityTokenNfcActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index ae1944ced..c3352363a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -199,7 +199,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mTagDispatcher = TagDispatcher.get(this, this, false, false); + mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { -- cgit v1.2.3 From 2e69952326a034bb9f1bdba4573adc9af9be3c2b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 2 Mar 2016 15:08:57 +0100 Subject: small nullpointer fix --- .../sufficientlysecure/keychain/keyimport/FacebookKeyserver.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java index d87a82a24..faa2a1848 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.keyimport; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; @@ -37,7 +38,6 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.net.Proxy; -import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -190,8 +190,11 @@ public class FacebookKeyserver extends Keyserver { return uri.getPathSegments().get(0); } - public static boolean isFacebookHost(Uri uri) { + public static boolean isFacebookHost(@Nullable Uri uri) { + if (uri == null) { + return false; + } String host = uri.getHost(); - return host.equalsIgnoreCase(FB_HOST) || host.equalsIgnoreCase(FB_HOST_WWW); + return FB_HOST.equalsIgnoreCase(host) || FB_HOST_WWW.equalsIgnoreCase(host); } } -- cgit v1.2.3 From c7761d09cbd5a148d16760afeddefb89ee469364 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 2 Mar 2016 15:12:14 +0100 Subject: handle openpgp4fpr intent filter as import rather than certify (see #1661) --- .../java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index f67c6a724..72e42eec3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -131,8 +131,11 @@ public class ImportKeysActivity extends BaseActivity if (Intent.ACTION_VIEW.equals(action)) { if (FacebookKeyserver.isFacebookHost(dataUri)) { action = ACTION_IMPORT_KEY_FROM_FACEBOOK; - } else if ("http".equals(scheme) || "https".equals(scheme)) { + } else if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { action = ACTION_SEARCH_KEYSERVER_FROM_URL; + } else if ("openpgp4fpr".equalsIgnoreCase(scheme)) { + action = ACTION_IMPORT_KEY_FROM_KEYSERVER; + extras.putString(EXTRA_FINGERPRINT, dataUri.getSchemeSpecificPart()); } else { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) // delegate action to ACTION_IMPORT_KEY -- cgit v1.2.3 From 4b3d584d1e3328565b918e437e073700ca797202 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 2 Mar 2016 16:05:52 +0100 Subject: add v11 to supported api versions --- .../org/sufficientlysecure/keychain/remote/OpenPgpService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 10b137b4f..2d70f3681 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -25,6 +25,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -75,6 +76,9 @@ import org.sufficientlysecure.keychain.util.Passphrase; public class OpenPgpService extends Service { + public static final List SUPPORTED_VERSIONS = + Collections.unmodifiableList(Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10, 11)); + static final String[] KEY_SEARCH_PROJECTION = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, @@ -779,13 +783,12 @@ public class OpenPgpService extends Service { // version code is required and needs to correspond to version code of service! // History of versions in openpgp-api's CHANGELOG.md - List supportedVersions = Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10); - if (!supportedVersions.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) { + if (!SUPPORTED_VERSIONS.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) { Intent result = new Intent(); OpenPgpError error = new OpenPgpError (OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n" + "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n" - + "supported API versions: " + supportedVersions); + + "supported API versions: " + SUPPORTED_VERSIONS); result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); return result; -- cgit v1.2.3 From 81ba0ff108310d70dfbec30261276c75d2286121 Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Wed, 2 Mar 2016 17:09:12 +0100 Subject: Always check read permission --- .../sufficientlysecure/keychain/ui/ImportKeysListFragment.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index b399af950..18063ed1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -330,9 +330,16 @@ public class ImportKeysListFragment extends ListFragment implements } public void loadNew(LoaderState loaderState) { - mLoaderState = loaderState; + if (mLoaderState instanceof BytesLoaderState) { + BytesLoaderState ls = (BytesLoaderState) mLoaderState; + + if ( ! checkAndRequestReadPermission(ls.mDataUri)) { + return; + } + } + restartLoaders(); } -- cgit v1.2.3 From e422b3af045768e64c555e33ea333e3712adc378 Mon Sep 17 00:00:00 2001 From: Advaita Date: Thu, 3 Mar 2016 01:52:21 +0530 Subject: Update ContactSyncAdapterService.java --- .../keychain/service/ContactSyncAdapterService.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index cdfe2f1ce..15f8a47db 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -143,6 +143,13 @@ public class ContactSyncAdapterService extends Service { } public static void requestContactsSync() { + // if user has disabled automatic sync, do nothing + if (!ContentResolver.getSyncAutomatically( + new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), + ContactsContract.AUTHORITY)) { + return; + } + Bundle extras = new Bundle(); // no need to wait, do it immediately extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); -- cgit v1.2.3 From 73bbfc54d9a250e93d57dc55793633d6c1a74b44 Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Fri, 4 Mar 2016 09:16:09 +0100 Subject: Remove uri from pending even when already added --- .../java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index be08f6a53..22202a35f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -247,10 +247,10 @@ public class EncryptFilesFragment try { mFilesAdapter.add(inputUri); } catch (IOException e) { + String fileName = FileHelper.getFilename(getActivity(), inputUri); Notify.create(getActivity(), - getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), + getActivity().getString(R.string.error_file_added_already, fileName), Notify.Style.ERROR).show(this); - return; } // remove from pending input uris -- cgit v1.2.3 From 78a30ed207d2dc7ec6823ffb6675478a01c5dbba Mon Sep 17 00:00:00 2001 From: Michal Kepkowski Date: Fri, 4 Mar 2016 11:22:14 +0100 Subject: okHttp --- .../keychain/linked/LinkedTokenResource.java | 61 ++++++++-------------- .../linked/resources/GenericHttpsResource.java | 10 ++-- .../keychain/linked/resources/GithubResource.java | 34 +++++++----- .../keychain/linked/resources/TwitterResource.java | 58 +++++++++++--------- 4 files changed, 81 insertions(+), 82 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index e5a128e32..6a584a939 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -2,12 +2,10 @@ package org.sufficientlysecure.keychain.linked; import android.content.Context; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; +import com.squareup.okhttp.CertificatePinner; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; import org.json.JSONException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; @@ -18,12 +16,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; -import org.thoughtcrime.ssl.pinning.util.PinningHelper; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URI; import java.util.HashMap; @@ -233,46 +227,35 @@ public abstract class LinkedTokenResource extends LinkedResource { } - @SuppressWarnings("deprecation") // HttpRequestBase is deprecated - public static String getResponseBody(Context context, HttpRequestBase request) - throws IOException, HttpStatusException { - return getResponseBody(context, request, null); + + private static CertificatePinner getCertificatePinner(String hostname, String[] pins){ + CertificatePinner.Builder builder = new CertificatePinner.Builder(); + for(String pin : pins){ + builder.add(hostname,pin); + } + return builder.build(); } - @SuppressWarnings("deprecation") // HttpRequestBase is deprecated - public static String getResponseBody(Context context, HttpRequestBase request, String[] pins) - throws IOException, HttpStatusException { - StringBuilder sb = new StringBuilder(); + public static String getResponseBody(Request request, String... pins) + throws IOException, HttpStatusException { - request.setHeader("User-Agent", "Open Keychain"); + Log.d("Connection to: "+request.url().getHost(),""); + OkHttpClient client = new OkHttpClient(); + if(pins !=null){ + client.setCertificatePinner(getCertificatePinner(request.url().getHost(),pins)); + } + Response response = client.newCall(request).execute(); - HttpClient httpClient; - if (pins == null) { - httpClient = new DefaultHttpClient(new BasicHttpParams()); - } else { - httpClient = PinningHelper.getPinnedHttpClient(context, pins); - } - HttpResponse response = httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - String reason = response.getStatusLine().getReasonPhrase(); + int statusCode = response.code(); + String reason = response.message(); if (statusCode != 200) { throw new HttpStatusException(statusCode, reason); } - HttpEntity entity = response.getEntity(); - InputStream inputStream = entity.getContent(); - - BufferedReader bReader = new BufferedReader( - new InputStreamReader(inputStream, "UTF-8"), 8); - String line; - while ((line = bReader.readLine()) != null) { - sb.append(line); - } - - return sb.toString(); + return response.body().string(); } public static class HttpStatusException extends Throwable { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java index 82240c405..d6b8640e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import org.apache.http.client.methods.HttpGet; +import com.squareup.okhttp.Request; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -32,14 +32,16 @@ public class GenericHttpsResource extends LinkedTokenResource { token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24)); } - @SuppressWarnings("deprecation") // HttpGet is deprecated @Override protected String fetchResource (Context context, OperationLog log, int indent) throws HttpStatusException, IOException { log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); - HttpGet httpGet = new HttpGet(mSubUri); - return getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url(mSubUri.toURL()) + .addHeader("User-Agent", "OpenKeychain") + .build(); + return getResponseBody(request); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java index 7a97ffd96..134582ae1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import org.apache.http.client.methods.HttpGet; +import com.squareup.okhttp.Request; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -47,7 +47,7 @@ public class GithubResource extends LinkedTokenResource { return String.format(context.getResources().getString(R.string.linked_id_github_text), token); } - @SuppressWarnings("deprecation") // HttpGet is deprecated + @Override protected String fetchResource (Context context, OperationLog log, int indent) throws HttpStatusException, IOException, JSONException { @@ -55,8 +55,11 @@ public class GithubResource extends LinkedTokenResource { log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); indent += 1; - HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + mGistId); - String response = getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url("https://api.github.com/gists/" + mGistId) + .addHeader("User-Agent", "OpenKeychain") + .build(); + String response = getResponseBody(request); JSONObject obj = new JSONObject(response); @@ -79,7 +82,7 @@ public class GithubResource extends LinkedTokenResource { } - @Deprecated // not used for now, but could be used to pick up earlier posted gist if already present? + @SuppressWarnings({ "deprecation", "unused" }) public static GithubResource searchInGithubStream( Context context, String screenName, String needle, OperationLog log) { @@ -94,12 +97,12 @@ public class GithubResource extends LinkedTokenResource { try { JSONArray array; { - HttpGet httpGet = - new HttpGet("https://api.github.com/users/" + screenName + "/gists"); - httpGet.setHeader("Content-Type", "application/json"); - httpGet.setHeader("User-Agent", "OpenKeychain"); - - String response = getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url("https://api.github.com/users/" + screenName + "/gists") + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); + String response = getResponseBody(request); array = new JSONArray(response); } @@ -116,10 +119,13 @@ public class GithubResource extends LinkedTokenResource { continue; } String id = obj.getString("id"); - HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + id); - httpGet.setHeader("User-Agent", "OpenKeychain"); - JSONObject gistObj = new JSONObject(getResponseBody(context, httpGet)); + Request request = new Request.Builder() + .url("https://api.github.com/gists/" + id) + .addHeader("User-Agent", "OpenKeychain") + .build(); + + JSONObject gistObj = new JSONObject(getResponseBody(request)); JSONObject gistFiles = gistObj.getJSONObject("files"); Iterator gistIt = gistFiles.keys(); if (!gistIt.hasNext()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java index 73e3d3643..b0f02c43c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -7,11 +7,11 @@ import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; import android.util.Log; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; import com.textuality.keybase.lib.JWalk; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -84,18 +84,19 @@ public class TwitterResource extends LinkedTokenResource { return null; } - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/show.json" - + "?id=" + mTweetId - + "&include_entities=false"); - // construct a normal HTTPS request and include an Authorization // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + authToken); - httpGet.setHeader("Content-Type", "application/json"); + Request request = new Request.Builder() + .url("https://api.twitter.com/1.1/statuses/show.json" + + "?id=" + mTweetId + + "&include_entities=false") + .addHeader("Authorization", "Bearer " + authToken) + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); try { - String response = getResponseBody(context, httpGet, CERT_PINS); + String response = getResponseBody(request, CERT_PINS); JSONObject obj = new JSONObject(response); JSONObject user = obj.getJSONObject("user"); if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) { @@ -157,21 +158,20 @@ public class TwitterResource extends LinkedTokenResource { return null; } - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json" + Request request = new Request.Builder() + .url("https://api.twitter.com/1.1/statuses/user_timeline.json" + "?screen_name=" + screenName + "&count=15" + "&include_rts=false" + "&trim_user=true" - + "&exclude_replies=true"); - - // construct a normal HTTPS request and include an Authorization - // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + authToken); - httpGet.setHeader("Content-Type", "application/json"); + + "&exclude_replies=true") + .addHeader("Authorization", "Bearer " + authToken) + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); try { - String response = getResponseBody(context, httpGet, CERT_PINS); + String response = getResponseBody(request, CERT_PINS); JSONArray array = new JSONArray(response); for (int i = 0; i < array.length(); i++) { @@ -216,12 +216,20 @@ public class TwitterResource extends LinkedTokenResource { String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c" + "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "=="; + RequestBody requestBody = RequestBody.create( + MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), + "grant_type=client_credentials"); + // Step 2: Obtain a bearer token - HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); - httpPost.setHeader("Authorization", "Basic " + base64Encoded); - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); - httpPost.setEntity(new StringEntity("grant_type=client_credentials")); - JSONObject rawAuthorization = new JSONObject(getResponseBody(context, httpPost, CERT_PINS)); + Request request = new Request.Builder() + .url("https://api.twitter.com/oauth2/token") + .addHeader("Authorization", "Basic " + base64Encoded) + .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") + .addHeader("User-Agent", "OpenKeychain") + .post(requestBody) + .build(); + + JSONObject rawAuthorization = new JSONObject(getResponseBody(request, CERT_PINS)); // Applications should verify that the value associated with the // token_type key of the returned object is bearer -- cgit v1.2.3 From d352ccf8aeb15b8858f95924c34dfbf0bb755a65 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Sat, 5 Mar 2016 17:27:22 +0530 Subject: Fix Dark theme no longer works in main screen on android 5, Issue#1746 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 107c63e0b..beaf68372 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -45,8 +45,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - initTheme(); super.onCreate(savedInstanceState); + initTheme(); initLayout(); initToolbar(); } -- cgit v1.2.3 From 26f8a9db9c182d266708781e2c242253a57dd28f Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Sat, 5 Mar 2016 13:17:15 +0100 Subject: Extend FileProvider to support name update --- .../keychain/provider/TemporaryFileProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java index 68963d595..bb44314d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java @@ -99,6 +99,12 @@ public class TemporaryFileProvider extends ContentProvider { return context.getContentResolver().insert(CONTENT_URI, contentValues); } + public static int setName(Context context, Uri uri, String name) { + ContentValues values = new ContentValues(); + values.put(TemporaryFileColumns.COLUMN_NAME, name); + return context.getContentResolver().update(uri, values, null, null); + } + public static int setMimeType(Context context, Uri uri, String mimetype) { ContentValues values = new ContentValues(); values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype); @@ -283,8 +289,11 @@ public class TemporaryFileProvider extends ContentProvider { @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - if (values.size() != 1 || !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) { - throw new UnsupportedOperationException("Update supported only for type field!"); + if (values.size() != 1) { + throw new UnsupportedOperationException("Update supported only for one field at a time!"); + } + if (!values.containsKey(TemporaryFileColumns.COLUMN_NAME) && !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) { + throw new UnsupportedOperationException("Update supported only for name and type field!"); } if (selection != null || selectionArgs != null) { throw new UnsupportedOperationException("Update supported only for plain uri!"); -- cgit v1.2.3 From bcef5b66921984b9b403c9b022750ada402f77ef Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Sat, 5 Mar 2016 13:24:18 +0100 Subject: Set filename after decryption --- .../sufficientlysecure/keychain/operations/InputDataOperation.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 43fc11b84..6682cc6e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -119,8 +119,9 @@ public class InputDataOperation extends BaseOperation { // inform the storage provider about the mime type for this uri if (decryptResult.getDecryptionMetadata() != null) { - TemporaryFileProvider.setMimeType(mContext, currentInputUri, - decryptResult.getDecryptionMetadata().getMimeType()); + OpenPgpMetadata meta = decryptResult.getDecryptionMetadata(); + TemporaryFileProvider.setName(mContext, currentInputUri, meta.getFilename()); + TemporaryFileProvider.setMimeType(mContext, currentInputUri, meta.getMimeType()); } } else { -- cgit v1.2.3 From 8b8b91e24722f254ad84916d3e1b6c6210906924 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Sun, 6 Mar 2016 18:38:45 +0530 Subject: Fix K-9 stable: OpenPGP: Unknown compression algorithm #1752 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 009876045..328daa7ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -470,7 +470,13 @@ public class PgpSignEncryptOperation extends BaseOperation { InputStream in = new BufferedInputStream(inputData.getInputStream()); if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm()); + // Use preferred compression algo + int algo = input.getCompressionAlgorithm(); + if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) { + algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM; + } + + compressGen = new PGPCompressedDataGenerator(algo); bcpgOut = new BCPGOutputStream(compressGen.open(out)); } else { bcpgOut = new BCPGOutputStream(out); -- cgit v1.2.3 From b8a73427bd23f1d2c33afe5dc9f4459471637946 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 7 Mar 2016 00:12:17 +0100 Subject: fix bug from leftover line in openpgpservice --- .../main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 2d70f3681..9f75d8e86 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -302,7 +302,6 @@ public class OpenPgpService extends Service { // get key ids based on given user ids if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); // give params through to activity... KeyIdResult result = returnKeyIdsFromEmails(data, userIds); -- cgit v1.2.3 From 67178ef89398bcc6fc28f0959a5066109531cde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 7 Mar 2016 00:23:43 +0100 Subject: fix get key ids by removing another leftover --- .../main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 9f75d8e86..0e807e9ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -694,7 +694,6 @@ public class OpenPgpService extends Service { } else { // get key ids based on given user ids String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds); if (keyResult.mRequiredUserInteraction != null) { return keyResult.mRequiredUserInteraction; -- cgit v1.2.3 From 1a202b028df0632a12399f7257e4a1fa1c90fbf6 Mon Sep 17 00:00:00 2001 From: fjodor Date: Sun, 6 Mar 2016 08:51:54 +0200 Subject: Save split user ids in database #1745 Save split user ids in database --- .../keychain/provider/KeychainContract.java | 6 ++++ .../keychain/provider/KeychainDatabase.java | 3 ++ .../keychain/provider/KeychainProvider.java | 3 ++ .../keychain/provider/ProviderHelper.java | 34 +++++++++++++--------- 4 files changed, 33 insertions(+), 13 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 177f07344..90a695547 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -60,6 +60,9 @@ public class KeychainContract { String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String TYPE = "type"; // not a database id String USER_ID = "user_id"; // not a database id + String NAME = "name"; + String EMAIL = "email"; + String COMMENT = "comment"; String ATTRIBUTE_DATA = "attribute_data"; // not a database id String RANK = "rank"; // ONLY used for sorting! no key, no nothing! String IS_PRIMARY = "is_primary"; @@ -359,6 +362,9 @@ public class KeychainContract { public static class Certs implements CertsColumns, BaseColumns { public static final String USER_ID = UserPacketsColumns.USER_ID; + public static final String NAME = UserPacketsColumns.NAME; + public static final String EMAIL = UserPacketsColumns.EMAIL; + public static final String COMMENT = UserPacketsColumns.COMMENT; public static final String SIGNER_UID = "signer_user_id"; public static final int UNVERIFIED = 0; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 752c13007..8cc57e81d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -115,6 +115,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { + UserPacketsColumns.MASTER_KEY_ID + " INTEGER, " + UserPacketsColumns.TYPE + " INT, " + UserPacketsColumns.USER_ID + " TEXT, " + + UserPacketsColumns.NAME + " TEXT, " + + UserPacketsColumns.EMAIL + " TEXT, " + + UserPacketsColumns.COMMENT + " TEXT, " + UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, " + UserPacketsColumns.IS_PRIMARY + " INTEGER, " diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 0cb8e4675..9fbee0a67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -308,6 +308,9 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); + projectionMap.put(KeyRings.NAME, Tables.USER_PACKETS + "." + UserPackets.NAME); + projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL); + projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT); projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups" + " WHERE dups." + UserPackets.MASTER_KEY_ID diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 72a3e2ff5..a0ebc691d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -19,17 +19,6 @@ package org.sufficientlysecure.keychain.provider; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.TimeUnit; - import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; @@ -79,6 +68,17 @@ import org.sufficientlysecure.keychain.util.ProgressFixedScaler; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.Utf8Util; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; + /** * This class contains high level methods for database access. Despite its * name, it is not only a helper but actually the main interface for all @@ -452,11 +452,13 @@ public class ProviderHelper { mIndent += 1; for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); - UserPacketItem item = new UserPacketItem(); uids.add(item); + KeyRing.UserId splitUserId = KeyRing.splitUserId(userId); item.userId = userId; - + item.name = splitUserId.name; + item.email = splitUserId.email; + item.comment = splitUserId.comment; int unknownCerts = 0; log(LogType.MSG_IP_UID_PROCESSING, userId); @@ -746,6 +748,9 @@ public class ProviderHelper { private static class UserPacketItem implements Comparable { Integer type; String userId; + String name; + String email; + String comment; byte[] attributeData; boolean isPrimary = false; WrappedSignature selfCert; @@ -1437,6 +1442,9 @@ public class ProviderHelper { values.put(UserPackets.MASTER_KEY_ID, masterKeyId); values.put(UserPackets.TYPE, item.type); values.put(UserPackets.USER_ID, item.userId); + values.put(UserPackets.NAME, item.name); + values.put(UserPackets.EMAIL, item.email); + values.put(UserPackets.COMMENT, item.comment); values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData); values.put(UserPackets.IS_PRIMARY, item.isPrimary); values.put(UserPackets.IS_REVOKED, item.selfRevocation != null); -- cgit v1.2.3 From b4ea59bf7cfec8f505622dd80903d2f8a4461570 Mon Sep 17 00:00:00 2001 From: fjodor Date: Mon, 7 Mar 2016 16:40:26 +0200 Subject: Increased version DB and added a migration path from an old database format --- .../org/sufficientlysecure/keychain/provider/KeychainDatabase.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 8cc57e81d..2a4d898bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -54,7 +54,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 14; + private static final int DATABASE_VERSION = 15; static Boolean apgHack = false; private Context mContext; @@ -309,7 +309,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX verified_certs ON certs (" + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); - + case 15: + db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT"); + db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT"); + db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT"); } // always do consolidate after upgrade -- cgit v1.2.3 From 3d1d26899707e453147e3c1b27894d367516b23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 8 Mar 2016 02:27:56 +0100 Subject: Add nfcGenerateOnCardKey by Joey Castillo before it gets lost --- .../ui/base/BaseSecurityTokenNfcActivity.java | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index c3352363a..dc5e583af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -933,6 +933,48 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Arrays.fill(dataToSend, (byte) 0); } + /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using the nfcSetFingerprint method. + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + * + * TODO: nfcSetFingerprint missing. + */ + public byte[] nfcGenerateOnCardKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + nfcVerifyPIN(0x83); // (Verify PW1 with mode 82 for decryption) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = nfcCommunicate(generateKeyApdu); + String second = nfcCommunicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = nfcGetDataField(first) + nfcGetDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + /** * Parses out the status word from a JavaCard response string. * -- cgit v1.2.3 From e45d671ce9c62e15a8189157e054b61b10e23a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 8 Mar 2016 02:34:57 +0100 Subject: Fix comment in nfcGenerateOnCardKey --- .../keychain/ui/base/BaseSecurityTokenNfcActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index dc5e583af..d23b15908 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -955,7 +955,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW1 with mode 82 for decryption) + nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) } String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; -- cgit v1.2.3 From 84fb5e45b140f7a0ebec1b181bc909cf80668823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 8 Mar 2016 03:01:32 +0100 Subject: Fix documentation of nfcGenerateOnCardKey --- .../keychain/ui/base/BaseSecurityTokenNfcActivity.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index d23b15908..d3000e565 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -938,7 +938,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * this command also has the effect of resetting the digital signature counter. * NOTE: This does not set the key fingerprint data object! After calling this command, you * must construct a public key packet using the returned public key data objects, compute the - * key fingerprint, and store it on the card using the nfcSetFingerprint method. + * key fingerprint, and store it on the card using: nfcPutData(0xC8, key.getFingerprint()) * * @param slot The slot on the card where the key should be generated: * 0xB6: Signature Key @@ -946,8 +946,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * 0xA4: Authentication Key * @return the public key data objects, in TLV format. For RSA this will be the public modulus * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. - * - * TODO: nfcSetFingerprint missing. */ public byte[] nfcGenerateOnCardKey(int slot) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { -- cgit v1.2.3 From d6520cde04a27f5a43543f0ccee5a1e02ade69e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 8 Mar 2016 04:04:18 +0100 Subject: Fix code style and documentation in BaseSecurityTokenNfcActivity --- .../ui/SecurityTokenOperationActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 79 ++++++++++++---------- 2 files changed, 45 insertions(+), 40 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 78d82d436..d2ecce242 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - nfcModifyPIN(0x81, newPin); - nfcModifyPIN(0x83, newAdminPin); + nfcModifyPin(0x81, newPin); + nfcModifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - nfcResetCard(); + nfcReset(); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index d3000e565..77044cd2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -26,14 +26,11 @@ 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.content.pm.PackageManager; 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; @@ -319,7 +316,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoDevice()) { + if (isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -530,18 +527,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen public String nfcGetUserId() throws IOException { String info = "00CA006500"; - return nfcGetHolderName(nfcCommunicate(info)); + return getHolderName(nfcCommunicate(info)); } /** - * Calls to calculate the signature and returns the MPI value + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value * * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { if (!mPw1ValidatedForSignature) { - nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) } // dsi, including Lc @@ -634,14 +631,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Calls to calculate the signature and returns the MPI value + * Call DECIPHER command * * @param encryptedSessionKey the encoded session key * @return the decoded session key */ public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } String firstApdu = "102a8086fe"; @@ -657,10 +654,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen two[i] = encryptedSessionKey[i + one.length + 1]; } - String first = nfcCommunicate(firstApdu + getHex(one)); + nfcCommunicate(firstApdu + getHex(one)); String second = nfcCommunicate(secondApdu + getHex(two) + le); - String decryptedSessionKey = nfcGetDataField(second); + String decryptedSessionKey = getDataField(second); return Hex.decode(decryptedSessionKey); } @@ -670,7 +667,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @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 { + public void nfcVerifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { byte[] pin; @@ -683,7 +680,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // 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 + String response = nfcTryPin(mode, pin); // login if (!response.equals(accepted)) { throw new CardException("Bad PIN!", parseCardStatus(response)); } @@ -698,13 +695,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public void nfcResetCard() throws IOException { + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + public void nfcReset() throws IOException { String accepted = "9000"; // try wrong PIN 4 times until counter goes to C0 byte[] pin = "XXXXXX".getBytes(); for (int i = 0; i <= 4; i++) { - String response = tryPin(0x81, pin); + String response = nfcTryPin(0x81, pin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); } @@ -713,7 +715,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // 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); + String response = nfcTryPin(0x83, adminPin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); } @@ -730,7 +732,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private String tryPin(int mode, byte[] pin) throws IOException { + private String nfcTryPin(int mode, byte[] pin) throws IOException { // Command APDU for VERIFY command (page 32) String login = "00" // CLA @@ -749,7 +751,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @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 { + public void nfcModifyPin(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; @@ -802,10 +804,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (dataObject == 0x0101 || dataObject == 0x0103) { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations) + nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) } } else if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3) + nfcVerifyPin(0x83); // (Verify PW3) } String putDataApdu = "00" // CLA @@ -854,7 +856,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } byte[] header= Hex.decode( @@ -947,13 +949,13 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @return the public key data objects, in TLV format. For RSA this will be the public modulus * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. */ - public byte[] nfcGenerateOnCardKey(int slot) throws IOException { + public byte[] nfcGenerateKey(int slot) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; @@ -966,13 +968,20 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen throw new IOException("On-card key generation failed"); } - String publicKeyData = nfcGetDataField(first) + nfcGetDataField(second); + String publicKeyData = getDataField(first) + getDataField(second); Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); return Hex.decode(publicKeyData); } + /** + * Transceive data via NFC encoded as Hex + */ + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoCard.transceive(Hex.decode(apdu))); + } + /** * Parses out the status word from a JavaCard response string. * @@ -991,7 +1000,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public String nfcGetHolderName(String name) { + public String getHolderName(String name) { try { String slength; int ilength; @@ -1011,14 +1020,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - private String nfcGetDataField(String output) { + private String getDataField(String output) { return output.substring(0, output.length() - 4); } - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoCard.transceive(Hex.decode(apdu))); - } - public static String getHex(byte[] raw) { return new String(Hex.encode(raw)); } @@ -1045,7 +1050,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private boolean isFidesmoDevice() { + private boolean isFidesmoToken() { if (isNfcConnected()) { // Check if we can still talk to the card try { // By trying to select any apps that have the Fidesmo AID prefix we can @@ -1061,11 +1066,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Ask user if she wants to install PGP onto her Fidesmo device + * Ask user if she wants to install PGP onto her Fidesmo token */ private void promptFidesmoPgpInstall() { - FidesmoPgpInstallDialog mFidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); - mFidesmoPgpInstallDialog.show(getSupportFragmentManager(), "mFidesmoPgpInstallDialog"); + FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); + fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); } /** @@ -1073,8 +1078,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * to launch the Google Play store. */ private void promptFidesmoAppInstall() { - FidesmoInstallDialog mFidesmoInstallDialog = new FidesmoInstallDialog(); - mFidesmoInstallDialog.show(getSupportFragmentManager(), "mFidesmoInstallDialog"); + FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); + fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); } /** @@ -1084,7 +1089,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen */ private boolean isAndroidAppInstalled(String uri) { PackageManager mPackageManager = getPackageManager(); - boolean mAppInstalled = false; + boolean mAppInstalled; try { mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); mAppInstalled = true; -- cgit v1.2.3 From 5379f64d08ad22e2f7fbc38de9fa79b093c0c7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 8 Mar 2016 15:01:49 +0100 Subject: Revert "Use non-breaking spaces for backup code MaskedEditText" This reverts commit 0feb4d074ce284bfed17e82a3cbc218209aff14a. Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java --- .../org/sufficientlysecure/keychain/ui/BackupCodeFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index 47552bf13..f1d9b270d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -170,11 +170,7 @@ public class BackupCodeFragment extends CryptoOperationFragment Date: Tue, 8 Mar 2016 16:20:48 +0100 Subject: Open keyboard for backup code --- .../keychain/ui/PassphraseDialogActivity.java | 53 +++++++++++++--------- 1 file changed, 31 insertions(+), 22 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 fd4f27176..e7f91a07e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -192,6 +192,8 @@ public class PassphraseDialogActivity extends FragmentActivity { mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mBackupCodeEditText.setOnEditorActionListener(this); + openKeyboard(mBackupCodeEditText); + AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); @@ -281,28 +283,7 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseText.setText(message); mPassphraseEditText.setHint(hint); - // Hack to open keyboard. - // This is the only method that I found to work across all Android versions - // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ - // Notes: * onCreateView can't be used because we want to add buttons to the dialog - // * opening in onActivityCreated does not work on Android 4.4 - mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPassphraseEditText.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || mPassphraseEditText == null) { - return; - } - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); - } - }); - } - }); - mPassphraseEditText.requestFocus(); + openKeyboard(mPassphraseEditText); mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); @@ -324,6 +305,34 @@ public class PassphraseDialogActivity extends FragmentActivity { return dialog; } + /** + * Hack to open keyboard. + * + * This is the only method that I found to work across all Android versions + * http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ + * Notes: * onCreateView can't be used because we want to add buttons to the dialog + * * opening in onActivityCreated does not work on Android 4.4 + */ + private void openKeyboard(final TextView textView) { + textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + textView.post(new Runnable() { + @Override + public void run() { + if (getActivity() == null || textView == null) { + return; + } + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(textView, InputMethodManager.SHOW_IMPLICIT); + } + }); + } + }); + textView.requestFocus(); + } + /** * Automatic line break with max 6 lines for smaller displays *

-- cgit v1.2.3 From 4a33cc83171cbb868a9bca61638ee230de19f30e Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Tue, 8 Mar 2016 21:14:50 +0530 Subject: Fix NullPointerException in ImportKeysListFragment.checkAndRequestReadPermission, #1766 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 18063ed1a..4d4219f56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -335,7 +335,7 @@ public class ImportKeysListFragment extends ListFragment implements if (mLoaderState instanceof BytesLoaderState) { BytesLoaderState ls = (BytesLoaderState) mLoaderState; - if ( ! checkAndRequestReadPermission(ls.mDataUri)) { + if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { return; } } -- cgit v1.2.3 From 4d347f1a8e8736a988974abf74dee1734f3b22f7 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 10 Mar 2016 01:05:19 +0100 Subject: Fix display of keyserver item in dark theme --- .../sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java index 5a8ab36bc..488558aa3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -40,6 +40,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; @@ -312,19 +313,19 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC public void showAsSelectedKeyserver() { isSelectedKeyserver = true; selectedServerLabel.setVisibility(View.VISIBLE); - outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + outerLayout.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorPrimaryDark)); } public void showAsUnselectedKeyserver() { isSelectedKeyserver = false; selectedServerLabel.setVisibility(View.GONE); - outerLayout.setBackgroundColor(Color.WHITE); + outerLayout.setBackgroundColor(0); } @Override public void onItemSelected() { selectedServerLabel.setVisibility(View.GONE); - itemView.setBackgroundColor(Color.LTGRAY); + itemView.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorBrightToolbar)); } @Override -- cgit v1.2.3 From 69e6e404bf77305c80511bbc904734655cd8865c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 10 Mar 2016 18:34:15 +0100 Subject: service: add opportunistic mode to encryption --- .../keychain/remote/OpenPgpService.java | 46 ++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 0e807e9ba..02d9ba62d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -103,20 +103,20 @@ public class OpenPgpService extends Service { } private static class KeyIdResult { - final Intent mRequiredUserInteraction; + final Intent mResultIntent; final HashSet mKeyIds; - KeyIdResult(Intent requiredUserInteraction) { - mRequiredUserInteraction = requiredUserInteraction; + KeyIdResult(Intent resultIntent) { + mResultIntent = resultIntent; mKeyIds = null; } KeyIdResult(HashSet keyIds) { - mRequiredUserInteraction = null; + mResultIntent = null; mKeyIds = keyIds; } } - private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { + private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) { boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0); boolean missingUserIdsCheck = false; boolean duplicateUserIdsCheck = false; @@ -159,9 +159,15 @@ public class OpenPgpService extends Service { } } - if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { - // allow the user to verify pub key selection + if (isOpportunistic && (noUserIdsCheck || missingUserIdsCheck)) { + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_ERROR, + new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode")); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + return new KeyIdResult(result); + } + if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { // convert ArrayList to long[] long[] keyIdsArray = getUnboxedLongArray(keyIds); ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext()); @@ -173,15 +179,14 @@ public class OpenPgpService extends Service { result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); return new KeyIdResult(result); - } else { - // everything was easy, we have exactly one key for every email - - if (keyIds.isEmpty()) { - Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); - } + } - return new KeyIdResult(keyIds); + // everything was easy, we have exactly one key for every email + if (keyIds.isEmpty()) { + Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); } + + return new KeyIdResult(keyIds); } private Intent signImpl(Intent data, InputStream inputStream, @@ -302,11 +307,12 @@ public class OpenPgpService extends Service { // get key ids based on given user ids if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); + boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); // give params through to activity... - KeyIdResult result = returnKeyIdsFromEmails(data, userIds); + KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic); - if (result.mRequiredUserInteraction != null) { - return result.mRequiredUserInteraction; + if (result.mResultIntent != null) { + return result.mResultIntent; } encryptKeyIds.addAll(result.mKeyIds); } @@ -694,9 +700,9 @@ public class OpenPgpService extends Service { } else { // get key ids based on given user ids String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds); - if (keyResult.mRequiredUserInteraction != null) { - return keyResult.mRequiredUserInteraction; + KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds, false); + if (keyResult.mResultIntent != null) { + return keyResult.mResultIntent; } if (keyResult.mKeyIds == null) { -- cgit v1.2.3 From 6087b0a6e048dbc4cfeae6d71e825ece6253994e Mon Sep 17 00:00:00 2001 From: fjodor Date: Thu, 10 Mar 2016 21:09:28 +0200 Subject: Updated HAS_DUPLICATE_USER_ID in KeychainProvider to use name and email address instead of user id #1745 --- .../org/sufficientlysecure/keychain/provider/KeychainProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 9fbee0a67..e6789bc7b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -316,8 +316,8 @@ public class KeychainProvider extends ContentProvider { + " WHERE dups." + UserPackets.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND dups." + UserPackets.RANK + " = 0" - + " AND dups." + UserPackets.USER_ID - + " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID + + " AND dups." + UserPackets.NAME + " = "+ Tables.USER_PACKETS + "." + UserPackets.NAME + + " AND dups." + UserPackets.EMAIL + " = "+ Tables.USER_PACKETS + "." + UserPackets.EMAIL + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, -- cgit v1.2.3 From b5211b33de683f5e903a2ad2d5028e2176c88eb8 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sat, 12 Mar 2016 10:32:38 +0800 Subject: Provisional backup-code text selection fix For issue #1779, a provisional fix where selection is still possible, but more difficult. Preventing selection altogether is not possible due to the implementation in the Masked-Edittext dependency. To be updated when Masked-Edittext fixes this bug. --- .../keychain/ui/BackupCodeFragment.java | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index f1d9b270d..e7ff6ce46 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -43,6 +43,7 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -237,6 +238,30 @@ public class BackupCodeFragment extends CryptoOperationFragment Date: Sat, 12 Mar 2016 19:32:36 +0100 Subject: okhttp3 --- .../keychain/keyimport/FacebookKeyserver.java | 14 ++++-- .../keychain/keyimport/HkpKeyserver.java | 55 +++++++--------------- .../keychain/linked/LinkedTokenResource.java | 18 ++++--- .../linked/resources/GenericHttpsResource.java | 2 +- .../keychain/linked/resources/GithubResource.java | 2 +- .../keychain/linked/resources/TwitterResource.java | 6 +-- .../ui/dialog/AddEditKeyserverDialogFragment.java | 15 ++---- .../keychain/util/OkHttpClientFactory.java | 55 ++++++++++++++++++++++ .../keychain/util/OkHttpKeybaseClient.java | 45 +++++------------- .../keychain/util/TlsHelper.java | 24 +++++----- 10 files changed, 127 insertions(+), 109 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java index faa2a1848..1c592003c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -23,10 +23,10 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -34,6 +34,8 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; +import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.IOException; import java.net.Proxy; @@ -104,8 +106,7 @@ public class FacebookKeyserver extends Keyserver { String request = String.format(FB_KEY_URL_FORMAT, fbUsername); Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy); - OkHttpClient client = new OkHttpClient(); - client.setProxy(mProxy); + OkHttpClient client = OkHttpClientFactory.getClient(mProxy); URL url = new URL(request); @@ -126,6 +127,9 @@ public class FacebookKeyserver extends Keyserver { throw new QueryFailedException("Cannot connect to Facebook. " + "Check your Internet connection!" + (mProxy == Proxy.NO_PROXY ? "" : " Using proxy " + mProxy)); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in cert pinning", e); + throw new QueryFailedException("Exception in cert pinning. "); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index c2190318b..bdf43b6da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -18,16 +18,18 @@ package org.sufficientlysecure.keychain.keyimport; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; -import com.squareup.okhttp.Response; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.TlsHelper; import java.io.IOException; @@ -42,7 +44,6 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -199,43 +200,12 @@ public class HkpKeyserver extends Keyserver { return mSecure ? "https://" : "http://"; } - /** - * returns a client with pinned certificate if necessary - * - * @param url url to be queried by client - * @param proxy proxy to be used by client - * @return client with a pinned certificate if necessary - */ - public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException { - OkHttpClient client = new OkHttpClient(); - - try { - TlsHelper.usePinnedCertificateIfAvailable(client, url); - } catch (TlsHelper.TlsHelperException e) { - Log.w(Constants.TAG, e); - } - - // don't follow any redirects - client.setFollowRedirects(false); - client.setFollowSslRedirects(false); - - if (proxy != null) { - client.setProxy(proxy); - client.setConnectTimeout(30000, TimeUnit.MILLISECONDS); - } else { - client.setProxy(Proxy.NO_PROXY); - client.setConnectTimeout(5000, TimeUnit.MILLISECONDS); - } - client.setReadTimeout(45000, TimeUnit.MILLISECONDS); - - return client; - } private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError { try { URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); - OkHttpClient client = getClient(url, proxy); + OkHttpClient client = OkHttpClientFactory.getPinnedClient(url, proxy); Response response = client.newCall(new Request.Builder().url(url).build()).execute(); String responseBody = response.body().string(); // contains body both in case of success or failure @@ -249,6 +219,9 @@ public class HkpKeyserver extends Keyserver { Log.e(Constants.TAG, "IOException at HkpKeyserver", e); throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" + (proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy)); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in pinning certs", e); + throw new QueryFailedException("Exception in pinning certs"); } } @@ -413,6 +386,7 @@ public class HkpKeyserver extends Keyserver { Log.d(Constants.TAG, "hkp keyserver add: " + url); Log.d(Constants.TAG, "params: " + params); + RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params); Request request = new Request.Builder() @@ -422,7 +396,7 @@ public class HkpKeyserver extends Keyserver { .post(body) .build(); - Response response = getClient(url, mProxy).newCall(request).execute(); + Response response = OkHttpClientFactory.getPinnedClient(url, mProxy).newCall(request).execute(); Log.d(Constants.TAG, "response code: " + response.code()); Log.d(Constants.TAG, "answer: " + response.body().string()); @@ -434,6 +408,9 @@ public class HkpKeyserver extends Keyserver { } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); throw new AddKeyException(); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "Exception in pinning certs", e); + throw new AddKeyException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index 6a584a939..24fa3bd67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -2,10 +2,10 @@ package org.sufficientlysecure.keychain.linked; import android.content.Context; -import com.squareup.okhttp.CertificatePinner; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; +import okhttp3.CertificatePinner; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.json.JSONException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; @@ -16,6 +16,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import java.io.IOException; import java.net.MalformedURLException; @@ -236,13 +237,16 @@ public abstract class LinkedTokenResource extends LinkedResource { return builder.build(); } + public static String getResponseBody(Request request, String... pins) throws IOException, HttpStatusException { - Log.d("Connection to: "+request.url().getHost(),""); - OkHttpClient client = new OkHttpClient(); + Log.d("Connection to: "+request.url().url().getHost(),""); + OkHttpClient client; if(pins !=null){ - client.setCertificatePinner(getCertificatePinner(request.url().getHost(),pins)); + client = OkHttpClientFactory.getPinnedSimpleClient(getCertificatePinner(request.url().url().getHost(),pins)); + }else{ + client = OkHttpClientFactory.getSimpleClient(); } Response response = client.newCall(request).execute(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java index d6b8640e3..da531e8fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import com.squareup.okhttp.Request; +import okhttp3.Request; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java index 134582ae1..0e87ca6e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import com.squareup.okhttp.Request; +import okhttp3.Request; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java index b0f02c43c..db3b64225 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -7,11 +7,11 @@ import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; import android.util.Log; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; import com.textuality.keybase.lib.JWalk; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 3d96f3c6d..7abef97d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -50,14 +50,13 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; +import okhttp3.OkHttpClient; +import okhttp3.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.OkHttpClientFactory; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; @@ -354,14 +353,10 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On Log.d("Converted URL", newKeyserver.toString()); - OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); - - // don't follow any redirects - client.setFollowRedirects(false); - client.setFollowSslRedirects(false); + OkHttpClient client = OkHttpClientFactory.getPinnedClient(newKeyserver.toURL(), proxy); if (onlyTrustedKeyserver - && !TlsHelper.usePinnedCertificateIfAvailable(client, newKeyserver.toURL())) { + && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL())==null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); reason = FailureReason.NO_PINNED_CERTIFICATE; return reason; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java new file mode 100644 index 000000000..2bf3b7e14 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -0,0 +1,55 @@ +package org.sufficientlysecure.keychain.util; + +import okhttp3.CertificatePinner; +import okhttp3.OkHttpClient; +import org.sufficientlysecure.keychain.Constants; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +/** + * Created by Michał Kępkowski on 11/03/16. + */ +public class OkHttpClientFactory { + private static OkHttpClient client; + + public static OkHttpClient getSimpleClient(){ + if(client == null){ + client = new OkHttpClient.Builder().build(); + } + return client; + } + + public static OkHttpClient getPinnedSimpleClient(CertificatePinner pinner){ + return new OkHttpClient.Builder() + .certificatePinner(pinner) + .build(); + } + + + public static OkHttpClient getPinnedClient(URL url, Proxy proxy) throws IOException, TlsHelper.TlsHelperException { + + return new OkHttpClient.Builder() + .followRedirects(false) + .followSslRedirects(false) + .proxy(proxy) + .connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS) + .sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)) + .build(); + } + + public static OkHttpClient getClient( Proxy proxy) throws IOException, TlsHelper.TlsHelperException { + + return new OkHttpClient.Builder() + .followRedirects(false) + .followSslRedirects(false) + .proxy(proxy) + .connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS) + .build(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index d2c90cfcd..04527c730 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -17,55 +17,36 @@ package org.sufficientlysecure.keychain.util; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.OkUrlFactory; + import com.textuality.keybase.lib.KeybaseUrlConnectionClient; +import okhttp3.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.IOException; import java.net.Proxy; import java.net.URL; -import java.net.URLConnection; -import java.util.concurrent.TimeUnit; /** * Wrapper for Keybase Lib */ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { - private OkUrlFactory generateUrlFactory() { - OkHttpClient client = new OkHttpClient(); - return new OkUrlFactory(client); - } @Override - public URLConnection openConnection(URL url, Proxy proxy, boolean isKeybase) throws IOException { - OkUrlFactory factory = generateUrlFactory(); - if (proxy != null) { - factory.client().setProxy(proxy); - factory.client().setConnectTimeout(30000, TimeUnit.MILLISECONDS); - factory.client().setReadTimeout(40000, TimeUnit.MILLISECONDS); - } else { - factory.client().setConnectTimeout(5000, TimeUnit.MILLISECONDS); - factory.client().setReadTimeout(25000, TimeUnit.MILLISECONDS); - } - - factory.client().setFollowSslRedirects(false); - - // forced the usage of api.keybase.io pinned certificate - if (isKeybase) { - try { - if (!TlsHelper.usePinnedCertificateIfAvailable(factory.client(), url)) { - throw new IOException("no pinned certificate found for URL!"); - } - } catch (TlsHelper.TlsHelperException e) { - Log.e(Constants.TAG, "TlsHelper failed", e); - throw new IOException("TlsHelper failed"); + public OkHttpClient getClient(URL url, Proxy proxy, boolean pin) throws IOException { + try { + if(pin) { + return OkHttpClientFactory.getPinnedClient(url, proxy); + }else{ + return OkHttpClientFactory.getClient( proxy); } + } catch (IOException e) { + throw new IOException("no pinned certificate found for URL!"); + } catch (TlsHelper.TlsHelperException e) { + Log.e(Constants.TAG, "TlsHelper failed", e); + throw new IOException("TlsHelper failed"); } - - return factory.open(url); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 1492abdeb..d1b8f768b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -19,8 +19,8 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; -import com.squareup.okhttp.OkHttpClient; +import okhttp3.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; public class TlsHelper { @@ -80,30 +81,30 @@ public class TlsHelper { * @throws TlsHelperException * @throws IOException */ - public static boolean usePinnedCertificateIfAvailable(OkHttpClient client, URL url) throws TlsHelperException, IOException { + public static SSLSocketFactory getPinnedSslSocketFactory(URL url) throws TlsHelperException, IOException { if (url.getProtocol().equals("https")) { // use certificate PIN from assets if we have one for (String host : sPinnedCertificates.keySet()) { if (url.getHost().endsWith(host)) { - pinCertificate(sPinnedCertificates.get(host), client); - return true; + return pinCertificate(sPinnedCertificates.get(host)); + //return true; } } } - return false; + return null; } /** - * Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the - * client. - * Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate. + * Modifies the builder to accept only requests with a given certificate. Applies to all URLs requested by the + * builder. + * Therefore a builder that is pinned this way should be used to only make requests to URLs with passed certificate. * * @param certificate certificate to pin - * @param client OkHttpClient to enforce pinning on + * @param builder OkHttpBuilder to enforce pinning on * @throws TlsHelperException * @throws IOException */ - private static void pinCertificate(byte[] certificate, OkHttpClient client) + private static SSLSocketFactory pinCertificate(byte[] certificate) throws TlsHelperException, IOException { // We don't use OkHttp's CertificatePinner since it can not be used to pin self-signed // certificate if such certificate is not accepted by TrustManager. @@ -130,7 +131,8 @@ public class TlsHelper { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); - client.setSslSocketFactory(context.getSocketFactory()); + return context.getSocketFactory(); + //builder.sslSocketFactory(context.getSocketFactory()); } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { throw new TlsHelperException(e); } -- cgit v1.2.3 From 966948d79b8e791a2ae95fc1593a43adb97bc0eb Mon Sep 17 00:00:00 2001 From: fiaxh Date: Mon, 14 Mar 2016 23:06:49 +0100 Subject: Set MIME application/pgp-keys when sending a key --- .../org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index ce2f2def8..0aa8330ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // let user choose application Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.setType("text/plain"); + sendIntent.setType(Constants.MIME_TYPE_KEYS); // NOTE: Don't use Intent.EXTRA_TEXT to send the key // better send it via a Uri! -- cgit v1.2.3 From eb5eb95a9ffb0f340c85c0710b451a5895988e21 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 15 Mar 2016 23:30:50 +0100 Subject: fix ridiculous database migration bug introduced by d6e4936fa54dc3577296dbadaeb556178dcad2c3 --- .../keychain/provider/KeychainDatabase.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 2a4d898bc..c3697997f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -279,37 +279,35 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER"); db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB"); case 7: - // consolidate - case 8: // new table for allowed key ids in API try { db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); } catch (Exception e) { // never mind, the column probably already existed } - case 9: + case 8: // tbale name for user_ids changed to user_packets db.execSQL("DROP TABLE IF EXISTS certs"); db.execSQL("DROP TABLE IF EXISTS user_ids"); db.execSQL(CREATE_USER_PACKETS); db.execSQL(CREATE_CERTS); - case 10: + case 9: // do nothing here, just consolidate - case 11: + case 10: // fix problems in database, see #1402 for details // https://github.com/open-keychain/open-keychain/issues/1402 db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3"); - case 12: + case 11: db.execSQL(CREATE_UPDATE_KEYS); - case 13: + case 12: // do nothing here, just consolidate - case 14: + case 13: db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX verified_certs ON certs (" + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");"); - case 15: + case 14: db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT"); db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT"); db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT"); -- cgit v1.2.3 From 6029564d68afeba980bbdd5509622653efef1673 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 15 Mar 2016 23:51:21 +0100 Subject: small update to duplicate detection --- .../org/sufficientlysecure/keychain/provider/KeychainProvider.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index e6789bc7b..75a5e89b8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -316,8 +316,11 @@ public class KeychainProvider extends ContentProvider { + " WHERE dups." + UserPackets.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND dups." + UserPackets.RANK + " = 0" - + " AND dups." + UserPackets.NAME + " = "+ Tables.USER_PACKETS + "." + UserPackets.NAME - + " AND dups." + UserPackets.EMAIL + " = "+ Tables.USER_PACKETS + "." + UserPackets.EMAIL + + " AND (dups." + UserPackets.USER_ID + " = " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " COLLATE NOCASE" + + " OR (dups." + UserPackets.NAME + " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE" + + " AND dups." + UserPackets.EMAIL + " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE" + + ")" + + ")" + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, -- cgit v1.2.3 From ce5e5f36c56fe14cb4a90a912eece07e4cb92285 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Mar 2016 00:33:09 +0100 Subject: update null-check for duplicates, and add matching indexes to name and email columns --- .../keychain/provider/KeychainDatabase.java | 9 ++++++++- .../keychain/provider/KeychainProvider.java | 11 +++++------ 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index c3697997f..04c14491b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -54,7 +54,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 15; + private static final int DATABASE_VERSION = 16; static Boolean apgHack = false; private Context mContext; @@ -311,6 +311,13 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT"); db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT"); db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT"); + case 15: + db.execSQL("CREATE INDEX uids_by_name ON user_packets (name COLLATE NOCASE)"); + db.execSQL("CREATE INDEX uids_by_email ON user_packets (email COLLATE NOCASE)"); + if (oldVersion == 14) { + // no consolidate necessary + return; + } } // always do consolidate after upgrade diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 75a5e89b8..9c5d0c054 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -312,15 +312,14 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL); projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT); projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, - "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups" + "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups" + " WHERE dups." + UserPackets.MASTER_KEY_ID + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND dups." + UserPackets.RANK + " = 0" - + " AND (dups." + UserPackets.USER_ID + " = " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " COLLATE NOCASE" - + " OR (dups." + UserPackets.NAME + " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE" - + " AND dups." + UserPackets.EMAIL + " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE" - + ")" - + ")" + + " AND dups." + UserPackets.NAME + + " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE" + + " AND dups." + UserPackets.EMAIL + + " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE" + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.PUBKEY_DATA, -- cgit v1.2.3 From 7efeebc6381784a9362e5514fb50084c662dd335 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Sun, 20 Mar 2016 11:52:36 +0100 Subject: #1661 - Don't expose CertifyActivity *Add new activity - RedirectImportKeysActivity, which prompts user to use Openkeychain directly *Add intent filter for the activity *Change intent filter for CertifyActivity --- .../keychain/ui/RedirectImportKeysActivity.java | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java new file mode 100644 index 000000000..a59d3ba80 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -0,0 +1,60 @@ +/* + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; + +public class RedirectImportKeysActivity extends BaseActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setFullScreenDialogClose(Activity.RESULT_CANCELED, true); + final Intent intent = new Intent(this, org.sufficientlysecure.keychain.ui.ImportKeysActivity.class); + + new AlertDialog.Builder(this) + .setTitle("Import key attempt") + .setMessage("You scanned a fingerprint with another app, please scan with Openkeychain directly to be safe" ) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // intent directly to ImportKeyChain activity + startActivity(intent); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // close window + finish(); + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + + @Override + protected void initLayout() { + setContentView(R.layout.redirect_import_keys_activity); + } +} \ No newline at end of file -- cgit v1.2.3 From a8603d69749f0609e07f8894c695ba500b8404cb Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Mon, 7 Mar 2016 21:51:03 +0530 Subject: Fix CertifyKeyActivity back button on action bar not working #1758 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../org/sufficientlysecure/keychain/ui/base/BaseActivity.java | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index beaf68372..063181dfe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -65,6 +66,16 @@ public abstract class BaseActivity extends AppCompatActivity { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home : + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + public static void onResumeChecks(Context context) { KeyserverSyncAdapterService.cancelUpdates(context); // in case user has disabled sync from Android account settings -- cgit v1.2.3 From cab3fa7874f90f9aa8f5d3b9244e78ac4dd96638 Mon Sep 17 00:00:00 2001 From: Advaita Date: Tue, 15 Mar 2016 23:07:27 +0530 Subject: Final commit All changes included Improvements -Reformatted code wherever required -Only active wifi connections trigger events -Improve sync reliability -Removed extra permission Minor Changes -Refactored 2 variables for easier readbility -Wifi-Only-Sync is now enabled by default --- .../org/sufficientlysecure/keychain/Constants.java | 1 + .../keychain/receiver/NetworkReceiver.java | 52 ++++++++++++++++++++++ .../service/ContactSyncAdapterService.java | 9 ++-- .../service/KeyserverSyncAdapterService.java | 24 +++++++++- .../keychain/ui/SettingsActivity.java | 6 +-- .../keychain/util/Preferences.java | 32 +++++++------ 6 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 53fb5afc6..fd6e903fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -118,6 +118,7 @@ public final class Constants { // keyserver sync settings public static final String SYNC_CONTACTS = "syncContacts"; public static final String SYNC_KEYSERVER = "syncKeyserver"; + public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly"; // other settings public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java new file mode 100644 index 000000000..7c103a9a3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/receiver/NetworkReceiver.java @@ -0,0 +1,52 @@ +package org.sufficientlysecure.keychain.receiver; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; +import org.sufficientlysecure.keychain.util.Log; + +public class NetworkReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = conn.getActiveNetworkInfo(); + boolean isTypeWifi = (networkInfo.getType() == ConnectivityManager.TYPE_WIFI); + boolean isConnected = networkInfo.isConnected(); + + if (isTypeWifi && isConnected) { + + // broadcaster receiver disabled + setWifiReceiverComponent(false, context); + Intent serviceIntent = new Intent(context, KeyserverSyncAdapterService.class); + serviceIntent.setAction(KeyserverSyncAdapterService.ACTION_SYNC_NOW); + context.startService(serviceIntent); + } + } + + public void setWifiReceiverComponent(Boolean isEnabled, Context context) { + + PackageManager pm = context.getPackageManager(); + ComponentName compName = new ComponentName(context, + NetworkReceiver.class); + + if (isEnabled) { + pm.setComponentEnabledSetting(compName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + Log.d(Constants.TAG, "Wifi Receiver is enabled!"); + } else { + pm.setComponentEnabledSetting(compName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + Log.d(Constants.TAG, "Wifi Receiver is disabled!"); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 15f8a47db..965d15138 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -144,12 +144,13 @@ public class ContactSyncAdapterService extends Service { public static void requestContactsSync() { // if user has disabled automatic sync, do nothing - if (!ContentResolver.getSyncAutomatically( - new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), - ContactsContract.AUTHORITY)) { + boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account + (Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY); + + if (!isSyncEnabled) { return; } - + Bundle extras = new Bundle(); // no need to wait, do it immediately extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index 1c59782fc..f92c0d229 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -16,6 +16,8 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -35,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.receiver.NetworkReceiver; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -68,7 +71,7 @@ public class KeyserverSyncAdapterService extends Service { private static final String ACTION_IGNORE_TOR = "ignore_tor"; private static final String ACTION_UPDATE_ALL = "update_all"; - private static final String ACTION_SYNC_NOW = "sync_now"; + public static final String ACTION_SYNC_NOW = "sync_now"; private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync"; private static final String ACTION_START_ORBOT = "start_orbot"; private static final String ACTION_CANCEL = "cancel"; @@ -176,8 +179,25 @@ public class KeyserverSyncAdapterService extends Service { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - Log.d(Constants.TAG, "Performing a keyserver sync!"); + Preferences prefs = Preferences.getPreferences(getContext()); + + // for a wifi-ONLY sync + if (prefs.getWifiOnlySync()) { + + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + boolean isNotOnWifi = !(networkInfo.getType() == ConnectivityManager.TYPE_WIFI); + boolean isNotConnected = !(networkInfo.isConnected()); + + // if Wi-Fi connection doesn't exist then receiver is enabled + if (isNotOnWifi && isNotConnected) { + new NetworkReceiver().setWifiReceiverComponent(true, getContext()); + return; + } + } + Log.d(Constants.TAG, "Performing a keyserver sync!"); PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this .getSystemService(Context.POWER_SERVICE); @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index ea70cde2a..a01637d84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.List; - import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -59,6 +57,8 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; +import java.util.List; + public class SettingsActivity extends AppCompatPreferenceActivity { public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; @@ -405,7 +405,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } /** - * This fragment shows the keyserver/contacts sync preferences + * This fragment shows the keyserver/wifi-only-sync/contacts sync preferences */ public static class SyncPrefsFragment extends PresetPreferenceFragment { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index b3d679a0e..e0e50abf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -19,19 +19,6 @@ package org.sufficientlysecure.keychain.util; -import java.io.Serializable; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; @@ -45,6 +32,19 @@ import org.sufficientlysecure.keychain.Constants.Pref; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; +import java.io.Serializable; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + /** * Singleton Implementation of a Preference Helper */ @@ -424,6 +424,12 @@ public class Preferences { }; } + // sync preferences + + public boolean getWifiOnlySync() { + return mSharedPreferences.getBoolean(Pref.ENABLE_WIFI_SYNC_ONLY, true); + } + // experimental prefs public boolean getExperimentalEnableWordConfirm() { -- cgit v1.2.3 From c917262957e3fd182f77fa32b9de5bd24abbd6e1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 23 Mar 2016 17:49:26 +0100 Subject: get rid of code path for pin and notation data handling --- .../operations/results/OperationResult.java | 2 - .../keychain/pgp/PgpKeyOperation.java | 113 ++++----------------- .../keychain/pgp/UncachedKeyRing.java | 42 ++++++++ .../keychain/service/SaveKeyringParcel.java | 16 +-- .../keychain/ui/CreateKeyFinalFragment.java | 4 +- .../keychain/ui/EditKeyFragment.java | 3 +- .../keychain/ui/ViewKeyActivity.java | 3 +- 7 files changed, 70 insertions(+), 113 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 ec2fddbd0..a3979904c 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 @@ -566,8 +566,6 @@ public abstract class OperationResult implements Parcelable { 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), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin), MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin), 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 e43548165..102e11674 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,6 +18,23 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.spec.ECGenParameterSpec; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; + import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; @@ -57,13 +74,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -71,22 +87,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.SignatureException; -import java.security.spec.ECGenParameterSpec; -import java.util.Arrays; -import java.util.Date; -import java.util.Iterator; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicBoolean; - /** * This class is the single place where ALL operations that actually modify a PGP public or secret * key take place. @@ -1058,8 +1058,8 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; - sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + saveParcel.mNewUnlock.mNewPassphrase, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -1191,76 +1191,6 @@ public class PgpKeyOperation { } - - private static PGPSecretKeyRing applyNewUnlock( - PGPSecretKeyRing sKR, - PGPPublicKey masterPublicKey, - PGPPrivateKey masterPrivateKey, - Passphrase passphrase, - ChangeUnlockParcel newUnlock, - OperationLog log, int indent) throws PGPException { - - if (newUnlock.mNewPassphrase != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); - - // if there is any old packet with notation data - if (hasNotationData(sKR)) { - - log.add(LogType.MSG_MF_NOTATION_EMPTY, indent); - - // add packet with EMPTY notation data (updates old one, but will be stripped later) - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); - } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - } - - return sKR; - } - - if (newUnlock.mNewPin != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); - - log.add(LogType.MSG_MF_NOTATION_PIN, indent); - - // add packet with "pin" notation data - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1"); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); - } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - - return sKR; - } - - throw new UnsupportedOperationException("PIN passphrases not yet implemented!"); - - } - /** This method returns true iff the provided keyring has a local direct key signature * with notation data. */ @@ -1294,8 +1224,7 @@ public class PgpKeyOperation { PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); - // noinspection unchecked - for (PGPSecretKey sKey : new IterableIterator(sKR.getSecretKeys())) { + for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) { log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index d696b9d70..b0db36b06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -35,6 +35,8 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import android.support.annotation.VisibleForTesting; + import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -1310,4 +1319,37 @@ public class UncachedKeyRing { || algorithm == PGPPublicKey.ECDH; } + // ONLY TO BE USED FOR TESTING!! + @VisibleForTesting + public static UncachedKeyRing forTestingOnlyAddDummyLocalSignature( + UncachedKeyRing uncachedKeyRing, String passphrase) throws Exception { + PGPSecretKeyRing sKR = (PGPSecretKeyRing) uncachedKeyRing.mRing; + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey masterPrivateKey = sKR.getSecretKey().extractPrivateKey(keyDecryptor); + PGPPublicKey masterPublicKey = uncachedKeyRing.mRing.getPublicKey(); + + // add packet with "pin" notation data + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + hashedPacketsGen.setNotationData(false, true, "dummynotationdata", "some data"); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + + return new UncachedKeyRing(sKR); + } + } 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 472eb3b18..dc892ecc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -356,29 +356,21 @@ public class SaveKeyringParcel implements Parcelable { // The new passphrase to use public final Passphrase mNewPassphrase; - // A new pin to use. Must only contain [0-9]+ - public final Passphrase mNewPin; public ChangeUnlockParcel(Passphrase newPassphrase) { - this(newPassphrase, null); - } - public ChangeUnlockParcel(Passphrase newPassphrase, Passphrase newPin) { - if (newPassphrase == null && newPin == null) { - throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!"); + if (newPassphrase == null) { + throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); } mNewPassphrase = newPassphrase; - mNewPin = newPin; } public ChangeUnlockParcel(Parcel source) { mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader()); - mNewPin = source.readParcelable(Passphrase.class.getClassLoader()); } @Override public void writeToParcel(Parcel destination, int flags) { destination.writeParcelable(mNewPassphrase, flags); - destination.writeParcelable(mNewPin, flags); } @Override @@ -397,9 +389,7 @@ public class SaveKeyringParcel implements Parcelable { }; public String toString() { - return mNewPassphrase != null - ? ("passphrase (" + mNewPassphrase + ")") - : ("pin (" + mNewPin + ")"); + return "passphrase (" + mNewPassphrase + ")"; } } 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 b53bfc1d0..58a28da51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -278,7 +278,7 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase - saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); + saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -288,7 +288,7 @@ public class CreateKeyFinalFragment extends Fragment { 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) + ? new ChangeUnlockParcel(createKeyActivity.mPassphrase) : null; } String userId = KeyRing.createUserId( 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 2d94d0d93..2184f91f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -339,8 +339,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment Date: Wed, 23 Mar 2016 22:46:25 +0100 Subject: use new TokenAutoComplete 2.0.7, fixes #1636 --- .../keychain/ui/EncryptModeAsymmetricFragment.java | 3 --- .../org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java | 8 -------- 2 files changed, 11 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index ca5d20fb9..b2b85ec14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -79,9 +79,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - mEncryptKeyView.setDeletionStyle(TokenCompleteTextView.TokenDeleteStyle.ToString); final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon); mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index fb72a263e..4a68c55fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -333,14 +333,6 @@ public class KeyAdapter extends CursorAdapter { return mUserId.email; } } - - // TODO: workaround for bug in TokenAutoComplete, - // see https://github.com/open-keychain/open-keychain/issues/1636 - @Override - public String toString() { - return " "; - } - } public static String[] getProjectionWith(String[] projection) { -- cgit v1.2.3 From 298f89d36e5239f272709b6ec86e4b4b5d29b2a6 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Thu, 24 Mar 2016 00:54:28 +0100 Subject: upgrade of Redirect import activity -> RedirectImportActivity is transparent now -> some poinlessly data removed -> String moved into sources -> xml for activity simplified --- .../keychain/ui/RedirectImportKeysActivity.java | 51 ++++++++++++---------- 1 file changed, 28 insertions(+), 23 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index a59d3ba80..d32981c60 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -1,27 +1,26 @@ /* + * 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. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; +import android.view.Window; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -29,21 +28,32 @@ public class RedirectImportKeysActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); + setContentView(R.layout.redirect_import_keys_activity); + + startScanActivity(); + } + + @Override + protected void initLayout() { + + } - setFullScreenDialogClose(Activity.RESULT_CANCELED, true); + private void startScanActivity() { final Intent intent = new Intent(this, org.sufficientlysecure.keychain.ui.ImportKeysActivity.class); new AlertDialog.Builder(this) - .setTitle("Import key attempt") - .setMessage("You scanned a fingerprint with another app, please scan with Openkeychain directly to be safe" ) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + .setTitle(R.string.redirect_import_key_title) + .setMessage(R.string.redirect_import_key_message) + .setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // intent directly to ImportKeyChain activity startActivity(intent); + finish(); } }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + .setNegativeButton(R.string.redirect_import_key_no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // close window finish(); @@ -52,9 +62,4 @@ public class RedirectImportKeysActivity extends BaseActivity { .setIcon(android.R.drawable.ic_dialog_alert) .show(); } - - @Override - protected void initLayout() { - setContentView(R.layout.redirect_import_keys_activity); - } } \ No newline at end of file -- cgit v1.2.3 From 4b6df7d17c6eae43b1db5f858d925103258f207e Mon Sep 17 00:00:00 2001 From: Adithya Abraham Philip Date: Thu, 24 Mar 2016 19:40:18 +0530 Subject: change sync interval on change in code --- .../keychain/KeychainApplication.java | 5 ++++ .../service/KeyserverSyncAdapterService.java | 31 +++++++++++++++++----- .../keychain/ui/SettingsActivity.java | 16 ++++++++--- .../keychain/util/Preferences.java | 23 ++++++++++++++-- 4 files changed, 63 insertions(+), 12 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index e1f61a5ef..2f0ebe904 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -100,6 +100,11 @@ public class KeychainApplication extends Application { // Add OpenKeychain account to Android to link contacts with keys and keyserver sync createAccountIfNecessary(this); + if (Preferences.getKeyserverSyncEnabled(this)) { + // will update a keyserver sync if the interval has changed + KeyserverSyncAdapterService.enableKeyserverSync(this); + } + // if first time, enable keyserver and contact sync if (Preferences.getPreferences(this).isFirstTime()) { KeyserverSyncAdapterService.enableKeyserverSync(this); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java index f92c0d229..b71fbada8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -11,6 +11,7 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.PeriodicSync; import android.content.SyncResult; import android.database.Cursor; import android.graphics.Bitmap; @@ -529,6 +530,10 @@ public class KeyserverSyncAdapterService extends Service { return builder.build(); } + /** + * creates a new sync if one does not exist, or updates an existing sync if the sync interval + * has changed. + */ public static void enableKeyserverSync(Context context) { Account account = KeychainApplication.createAccountIfNecessary(context); @@ -539,12 +544,26 @@ public class KeyserverSyncAdapterService extends Service { ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); - ContentResolver.addPeriodicSync( - account, - Constants.PROVIDER_AUTHORITY, - new Bundle(), - SYNC_INTERVAL - ); + + boolean intervalChanged = false; + boolean syncExists = Preferences.getKeyserverSyncEnabled(context); + + if (syncExists) { + long oldInterval = ContentResolver.getPeriodicSyncs( + account, Constants.PROVIDER_AUTHORITY).get(0).period; + if (oldInterval != SYNC_INTERVAL) { + intervalChanged = true; + } + } + + if (!syncExists || intervalChanged) { + ContentResolver.addPeriodicSync( + account, + Constants.PROVIDER_AUTHORITY, + new Bundle(), + SYNC_INTERVAL + ); + } } private boolean isSyncEnabled() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index a01637d84..4fd327c8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -47,6 +47,7 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; @@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { super.onResume(); // this needs to be done in onResume since the user can change sync values from Android // settings and we need to reflect that change when the user navigates back - AccountManager manager = AccountManager.get(getActivity()); - final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; + final Account account = KeychainApplication.createAccountIfNecessary(getActivity()); // for keyserver sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), @@ -441,8 +441,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, final Account account, final String authority) { - boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority) - && checkContactsPermission(authority); + // account is null if it could not be created for some reason + boolean syncEnabled = + account != null + && ContentResolver.getSyncAutomatically(account, authority) + && checkContactsPermission(authority); syncCheckBox.setChecked(syncEnabled); setSummary(syncCheckBox, authority, syncEnabled); @@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { return false; } } else { + if (account == null) { + // if account could not be created for some reason, + // we can't have our sync + return false; + } // disable syncs ContentResolver.setSyncAutomatically(account, authority, false); // immediately delete any linked contacts diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index e0e50abf7..5f53845d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -19,7 +19,9 @@ package org.sufficientlysecure.keychain.util; +import android.accounts.Account; import android.annotation.SuppressLint; +import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.os.Parcel; @@ -29,6 +31,7 @@ import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; +import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; @@ -76,9 +79,8 @@ public class Preferences { /** * Makes android's preference framework write to our file instead of default. - * This allows us to use the "persistent" attribute to simplify code, which automatically + * This allows us to use the xml "persistent" attribute to simplify code, which automatically * writes and reads preference values. - * @param manager */ public static void setPreferenceManagerFileAndMode(PreferenceManager manager) { manager.setSharedPreferencesName(PREF_FILE_NAME); @@ -302,6 +304,23 @@ public class Preferences { } + /** + * @return true if a periodic sync exists and is set to run automatically, false otherwise + */ + public static boolean getKeyserverSyncEnabled(Context context) { + Account account = KeychainApplication.createAccountIfNecessary(context); + + if (account == null) { + // if the account could not be created for some reason, we can't have a sync + return false; + } + + String authority = Constants.PROVIDER_AUTHORITY; + + return ContentResolver.getSyncAutomatically(account, authority) && + !ContentResolver.getPeriodicSyncs(account, authority).isEmpty(); + } + public CacheTTLPrefs getPassphraseCacheTtl() { Set pref = mSharedPreferences.getStringSet(Constants.Pref.PASSPHRASE_CACHE_TTLS, null); if (pref == null) { -- cgit v1.2.3 From 05da8afad94ffa5f7c773e5bc0c6304b517e5419 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Thu, 24 Mar 2016 19:45:59 +0100 Subject: Theme change Theme.Keychain.Transparet is used --- .../org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index d32981c60..1f6a62f47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -28,7 +28,6 @@ public class RedirectImportKeysActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { - requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.redirect_import_keys_activity); -- cgit v1.2.3 From 360b5a658a412d9402a5fc9ea204329c91d306c3 Mon Sep 17 00:00:00 2001 From: Andrea Torlaschi Date: Fri, 11 Mar 2016 00:50:11 +0100 Subject: Handle encrypted files while importing keys --- .../keychain/ui/ImportKeysActivity.java | 15 ++- .../keychain/ui/ImportKeysFileFragment.java | 103 +++++++++++++++++++-- .../keychain/util/FileHelper.java | 27 ++++++ 3 files changed, 135 insertions(+), 10 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 72e42eec3..7d2d30c35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -416,11 +416,18 @@ public class ImportKeysActivity extends BaseActivity intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); setResult(RESULT_OK, intent); finish(); - return; + } else if (result.isOkNew() || result.isOkUpdated()) { + // User has successfully imported a key, hide first time dialog + Preferences.getPreferences(this).setFirstTime(false); + + // Close activities opened for importing keys and go to the list of keys + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } else { + result.createNotify(ImportKeysActivity.this) + .show((ViewGroup) findViewById(R.id.import_snackbar)); } - - result.createNotify(ImportKeysActivity.this) - .show((ViewGroup) findViewById(R.id.import_snackbar)); } // methods from CryptoOperationHelper.Callback diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 8de60dfd3..133cf299f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -17,29 +17,46 @@ package org.sufficientlysecure.keychain.ui; +import android.Manifest; import android.app.Activity; +import android.content.ContentResolver; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpHelper; +import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; public class ImportKeysFileFragment extends Fragment { private ImportKeysActivity mImportActivity; private View mBrowse; private View mClipboardButton; - public static final int REQUEST_CODE_FILE = 0x00007003; + private Uri mCurrentUri; + + private static final int REQUEST_CODE_FILE = 0x00007003; + private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; /** * Creates new instance of this fragment @@ -83,10 +100,10 @@ public class ImportKeysFileFragment extends Fragment { sendText = clipboardText.toString(); sendText = PgpHelper.getPgpKeyContent(sendText); if (sendText == null) { - Notify.create(mImportActivity, "Bad data!", Style.ERROR).show(); + Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); return; } - mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null)); + mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null)); } } }); @@ -106,11 +123,12 @@ public class ImportKeysFileFragment extends Fragment { switch (requestCode) { case REQUEST_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { + mCurrentUri = data.getData(); - // load data - mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(null, data.getData())); + if (checkAndRequestReadPermission(mCurrentUri)) { + startImportingKeys(); + } } - break; } @@ -121,4 +139,77 @@ public class ImportKeysFileFragment extends Fragment { } } + private void startImportingKeys() { + boolean isEncrypted; + try { + isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri); + } catch (IOException e) { + Log.e(Constants.TAG, "Error opening file", e); + + Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); + return; + } + + if (isEncrypted) { + Intent intent = new Intent(mImportActivity, DecryptActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(mCurrentUri); + startActivity(intent); + } else { + mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri)); + } + } + + /** + * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. + *

+ * This method returns true on Android < 6, or if permission is already granted. It + * requests the permission and returns false otherwise. + *

+ * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html + */ + private boolean checkAndRequestReadPermission(final Uri uri) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + return true; + } + + // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + requestPermissions( + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); + + return false; + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + + if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + return; + } + + boolean permissionWasGranted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (permissionWasGranted) { + startImportingKeys(); + } else { + Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java index 106775201..62dd87baa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java @@ -20,11 +20,13 @@ package org.sufficientlysecure.keychain.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.SecureRandom; @@ -223,6 +225,31 @@ public class FileHelper { } } + public static boolean isEncryptedFile(Context context, Uri uri) throws IOException { + boolean isEncrypted = false; + + BufferedReader br = null; + try { + InputStream is = context.getContentResolver().openInputStream(uri); + br = new BufferedReader(new InputStreamReader(is)); + + String header = "-----BEGIN PGP MESSAGE-----"; + int length = header.length(); + char[] buffer = new char[length]; + if (br.read(buffer, 0, length) == length) { + isEncrypted = new String(buffer).equals(header); + } + } finally { + try { + if (br != null) + br.close(); + } catch (IOException e) { + Log.e(Constants.TAG, "Error closing file", e); + } + } + return isEncrypted; + } + public static String readableFileSize(long size) { if (size <= 0) return "0"; final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; -- cgit v1.2.3 From 0f716f7b9f3a3a820723415a2351b883f320874a Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Mon, 7 Mar 2016 16:03:52 +0530 Subject: Display Key moved to Fragment Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../keychain/ui/CertifyKeyActivity.java | 2 +- .../keychain/ui/CertifyKeyFragment.java | 176 +---------------- .../keychain/ui/MultiUserIdsFragment.java | 217 +++++++++++++++++++++ 3 files changed, 224 insertions(+), 171 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 3845e07cb..32da02efe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; - public static final String EXTRA_KEY_IDS = "extra_key_ids"; + public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 357b445f0..22cfabcc9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -62,52 +62,19 @@ import java.util.ArrayList; import java.util.Date; public class CertifyKeyFragment - extends CachingCryptoOperationFragment - implements LoaderManager.LoaderCallbacks { - - public static final String ARG_CHECK_STATES = "check_states"; + extends CachingCryptoOperationFragment { private CheckBox mUploadKeyCheckbox; - ListView mUserIds; private CertifyKeySpinner mCertifyKeySpinner; - private long[] mPubMasterKeyIds; - - public static final String[] USER_IDS_PROJECTION = new String[]{ - UserPackets._ID, - UserPackets.MASTER_KEY_ID, - UserPackets.USER_ID, - UserPackets.IS_PRIMARY, - UserPackets.IS_REVOKED - }; - private static final int INDEX_MASTER_KEY_ID = 1; - private static final int INDEX_USER_ID = 2; - @SuppressWarnings("unused") - private static final int INDEX_IS_PRIMARY = 3; - @SuppressWarnings("unused") - private static final int INDEX_IS_REVOKED = 4; - - private MultiUserIdsAdapter mUserIdsAdapter; + private MultiUserIdsFragment mMultiUserIdsFragment; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); - if (mPubMasterKeyIds == null) { - Log.e(Constants.TAG, "List of key ids to certify missing!"); - getActivity().finish(); - return; - } - - ArrayList checkedStates; - if (savedInstanceState != null) { - checkedStates = (ArrayList) savedInstanceState.getSerializable(ARG_CHECK_STATES); - // key spinner and the checkbox keep their own state - } else { - checkedStates = null; - + if (savedInstanceState == null){ // preselect certify key id if given long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); @@ -124,12 +91,6 @@ public class CertifyKeyFragment } - mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates); - mUserIds.setAdapter(mUserIdsAdapter); - mUserIds.setDividerHeight(0); - - getLoaderManager().initLoader(0, null, this); - OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); if (result != null) { // display result from import @@ -137,22 +98,14 @@ public class CertifyKeyFragment } } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - ArrayList states = mUserIdsAdapter.getCheckStates(); - // no proper parceling method available :( - outState.putSerializable(ARG_CHECK_STATES, states); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); - mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mMultiUserIdsFragment = (MultiUserIdsFragment) + getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); // make certify image gray, like action icons ImageView vActionCertifyImage = @@ -183,128 +136,11 @@ public class CertifyKeyFragment return view; } - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri uri = UserPackets.buildUserIdsUri(); - - String selection, ids[]; - { - // generate placeholders and string selection args - ids = new String[mPubMasterKeyIds.length]; - StringBuilder placeholders = new StringBuilder("?"); - for (int i = 0; i < mPubMasterKeyIds.length; i++) { - ids[i] = Long.toString(mPubMasterKeyIds[i]); - if (i != 0) { - placeholders.append(",?"); - } - } - // put together selection string - selection = UserPackets.IS_REVOKED + " = 0" + " AND " - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + " IN (" + placeholders + ")"; - } - - return new CursorLoader(getActivity(), uri, - USER_IDS_PROJECTION, selection, ids, - Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" - + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC" - ); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - - MatrixCursor matrix = new MatrixCursor(new String[]{ - "_id", "user_data", "grouped" - }) { - @Override - public byte[] getBlob(int column) { - return super.getBlob(column); - } - }; - data.moveToFirst(); - - long lastMasterKeyId = 0; - String lastName = ""; - ArrayList uids = new ArrayList<>(); - - boolean header = true; - - // Iterate over all rows - while (!data.isAfterLast()) { - long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - String userId = data.getString(INDEX_USER_ID); - KeyRing.UserId pieces = KeyRing.splitUserId(userId); - - // Two cases: - - boolean grouped = masterKeyId == lastMasterKeyId; - boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); - // Remember for next loop - lastName = pieces.name; - - Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); - - if (!subGrouped) { - // 1. This name should NOT be grouped with the previous, so we flush the buffer - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - // indicate that we have a header for this masterKeyId - header = false; - - // Now clear the buffer, and add the new user id, for the next round - uids.clear(); - - } - - // 2. This name should be grouped with the previous, just add to buffer - uids.add(userId); - lastMasterKeyId = masterKeyId; - - // If this one wasn't grouped, the next one's gotta be a header - if (!grouped) { - header = true; - } - - // Regardless of the outcome, move to next entry - data.moveToNext(); - - } - - // If there is anything left in the buffer, flush it one last time - if (!uids.isEmpty()) { - - Parcel p = Parcel.obtain(); - p.writeStringList(uids); - byte[] d = p.marshall(); - p.recycle(); - - matrix.addRow(new Object[]{ - lastMasterKeyId, d, header ? 1 : 0 - }); - - } - - mUserIdsAdapter.swapCursor(matrix); - } - - @Override - public void onLoaderReset(Loader loader) { - mUserIdsAdapter.swapCursor(null); - } - @Override public CertifyActionsParcel createOperationInput() { // Bail out if there is not at least one user id selected - ArrayList certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); + ArrayList certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { Notify.create(getActivity(), "No identities selected!", Notify.Style.ERROR).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java new file mode 100644 index 000000000..b61ec79ee --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -0,0 +1,217 @@ +package org.sufficientlysecure.keychain.ui; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +/** + * Created by durgeshchoudhary on 07/03/16. + */ +public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks{ + public static final String ARG_CHECK_STATES = "check_states"; + public static final String EXTRA_KEY_IDS = "extra_key_ids"; + + ListView mUserIds; + private MultiUserIdsAdapter mUserIdsAdapter; + + private long[] mPubMasterKeyIds; + + public static final String[] USER_IDS_PROJECTION = new String[]{ + KeychainContract.UserPackets._ID, + KeychainContract.UserPackets.MASTER_KEY_ID, + KeychainContract.UserPackets.USER_ID, + KeychainContract.UserPackets.IS_PRIMARY, + KeychainContract.UserPackets.IS_REVOKED + }; + private static final int INDEX_MASTER_KEY_ID = 1; + private static final int INDEX_USER_ID = 2; + @SuppressWarnings("unused") + private static final int INDEX_IS_PRIMARY = 3; + @SuppressWarnings("unused") + private static final int INDEX_IS_REVOKED = 4; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.multi_user_ids_fragment, null); + + mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS); + if (mPubMasterKeyIds == null) { + Log.e(Constants.TAG, "List of key ids to certify missing!"); + getActivity().finish(); + return; + } + + ArrayList checkedStates = null; + if (savedInstanceState != null) { + checkedStates = (ArrayList) savedInstanceState.getSerializable(ARG_CHECK_STATES); + } + + mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates); + mUserIds.setAdapter(mUserIdsAdapter); + mUserIds.setDividerHeight(0); + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + ArrayList states = mUserIdsAdapter.getCheckStates(); + // no proper parceling method available :( + outState.putSerializable(ARG_CHECK_STATES, states); + } + + public ArrayList getSelectedCertifyActions() { + return mUserIdsAdapter.getSelectedCertifyActions(); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri uri = KeychainContract.UserPackets.buildUserIdsUri(); + + String selection, ids[]; + { + // generate placeholders and string selection args + ids = new String[mPubMasterKeyIds.length]; + StringBuilder placeholders = new StringBuilder("?"); + for (int i = 0; i < mPubMasterKeyIds.length; i++) { + ids[i] = Long.toString(mPubMasterKeyIds[i]); + if (i != 0) { + placeholders.append(",?"); + } + } + // put together selection string + selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND " + + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + + " IN (" + placeholders + ")"; + } + + return new CursorLoader(getActivity(), uri, + USER_IDS_PROJECTION, selection, ids, + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC" + + ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC" + ); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + + MatrixCursor matrix = new MatrixCursor(new String[]{ + "_id", "user_data", "grouped" + }) { + @Override + public byte[] getBlob(int column) { + return super.getBlob(column); + } + }; + data.moveToFirst(); + + long lastMasterKeyId = 0; + String lastName = ""; + ArrayList uids = new ArrayList<>(); + + boolean header = true; + + // Iterate over all rows + while (!data.isAfterLast()) { + long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + String userId = data.getString(INDEX_USER_ID); + KeyRing.UserId pieces = KeyRing.splitUserId(userId); + + // Two cases: + + boolean grouped = masterKeyId == lastMasterKeyId; + boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); + // Remember for next loop + lastName = pieces.name; + + Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); + + if (!subGrouped) { + // 1. This name should NOT be grouped with the previous, so we flush the buffer + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + // indicate that we have a header for this masterKeyId + header = false; + + // Now clear the buffer, and add the new user id, for the next round + uids.clear(); + + } + + // 2. This name should be grouped with the previous, just add to buffer + uids.add(userId); + lastMasterKeyId = masterKeyId; + + // If this one wasn't grouped, the next one's gotta be a header + if (!grouped) { + header = true; + } + + // Regardless of the outcome, move to next entry + data.moveToNext(); + + } + + // If there is anything left in the buffer, flush it one last time + if (!uids.isEmpty()) { + + Parcel p = Parcel.obtain(); + p.writeStringList(uids); + byte[] d = p.marshall(); + p.recycle(); + + matrix.addRow(new Object[]{ + lastMasterKeyId, d, header ? 1 : 0 + }); + + } + + mUserIdsAdapter.swapCursor(matrix); + } + + @Override + public void onLoaderReset(Loader loader) { + mUserIdsAdapter.swapCursor(null); + } +} -- cgit v1.2.3 From 8f4238df28bea3473fb1414f64ad959368575e28 Mon Sep 17 00:00:00 2001 From: Victor Hazali Date: Wed, 30 Mar 2016 04:21:33 +0800 Subject: Fixes #1298 --- .../main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 35c1615c7..6c96a40ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,6 +32,7 @@ import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Color; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.AsyncTask; @@ -923,6 +924,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } mPhoto.setImageBitmap(photo); + mPhoto.setColorFilter(Color.argb(30, 120, 166, 69)); mPhotoLayout.setVisibility(View.VISIBLE); } }; -- cgit v1.2.3 From ce70568ff3f87e194ef1dd9ff04501610b7661db Mon Sep 17 00:00:00 2001 From: Victor Hazali Date: Wed, 30 Mar 2016 05:37:31 +0800 Subject: Extracted method --- .../ui/dialog/EditSubkeyDialogFragment.java | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index c34312e79..ba8920d4f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -75,22 +75,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { sendMessageToHandler(MESSAGE_REVOKE, null); break; case 2: - CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); - stripAlertDialog.setTitle(getResources().getString(R.string.title_alert_strip)). - setMessage(R.string.alert_strip).setCancelable(true); - stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - sendMessageToHandler(MESSAGE_STRIP, null); - } - }); - stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dismiss(); - } - }); - stripAlertDialog.show(); + showAlertDialog(); break; case 3: sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); @@ -110,6 +95,25 @@ public class EditSubkeyDialogFragment extends DialogFragment { return builder.show(); } + private void showAlertDialog() { + CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); + stripAlertDialog.setTitle(getResources().getString(R.string.title_alert_strip)). + setMessage(R.string.alert_strip).setCancelable(true); + stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + sendMessageToHandler(MESSAGE_STRIP, null); + } + }); + stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dismiss(); + } + }); + stripAlertDialog.show(); + } + /** * Send message back to handler which is initialized in a activity * -- cgit v1.2.3 From 5411963aff0ed017f949de5cdbcf9a83d0e2087a Mon Sep 17 00:00:00 2001 From: Victor Hazali Date: Wed, 30 Mar 2016 06:07:59 +0800 Subject: Refactored switch statements to use constants --- .../keychain/ui/dialog/EditSubkeyDialogFragment.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index ba8920d4f..01decf2ef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -36,6 +36,10 @@ public class EditSubkeyDialogFragment extends DialogFragment { public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_STRIP = 3; public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; + public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0; + public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1; + public static final int SUBKEY_MENU_STRIP_SUBKEY = 2; + public static final int SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN = 3; private Messenger mMessenger; @@ -68,16 +72,16 @@ public class EditSubkeyDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { - case 0: + case SUBKEY_MENU_CHANGE_EXPIRY: sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null); break; - case 1: + case SUBKEY_MENU_REVOKE_SUBKEY: sendMessageToHandler(MESSAGE_REVOKE, null); break; - case 2: + case SUBKEY_MENU_STRIP_SUBKEY: showAlertDialog(); break; - case 3: + case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN: sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); break; default: @@ -98,7 +102,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { private void showAlertDialog() { CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); stripAlertDialog.setTitle(getResources().getString(R.string.title_alert_strip)). - setMessage(R.string.alert_strip).setCancelable(true); + setMessage(R.string.alert_strip).setCancelable(true); stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { -- cgit v1.2.3 From 990948e3fbb52b879b517c18c06328e7d005d236 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Mon, 7 Mar 2016 17:29:06 +0530 Subject: Fix Display Key in UploadKeyActivity, Issue #506 --- .../keychain/ui/CertifyKeyActivity.java | 1 + .../keychain/ui/CertifyKeyFragment.java | 6 +++--- .../keychain/ui/MultiUserIdsFragment.java | 14 ++++++++++---- .../keychain/ui/UploadKeyActivity.java | 21 +++++++-------------- .../keychain/ui/ViewKeyAdvShareFragment.java | 11 +++++++++++ .../keychain/ui/adapter/MultiUserIdsAdapter.java | 7 +++++++ 6 files changed, 39 insertions(+), 21 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 32da02efe..09149716c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -28,6 +28,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; + // For sending masterKeyIds to MultiUserIdsFragment to display list of keys public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 22cfabcc9..ad39ff43d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -74,13 +74,14 @@ public class CertifyKeyFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - if (savedInstanceState == null){ + if (savedInstanceState == null) { // preselect certify key id if given long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { try { - CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId); + CachedPublicKeyRing key = (new ProviderHelper(getActivity())) + .getCachedPublicKeyRing(certifyKeyId); if (key.canCertify()) { mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } @@ -88,7 +89,6 @@ public class CertifyKeyFragment Log.e(Constants.TAG, "certify certify check failed", e); } } - } OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java index b61ec79ee..8ba695cf7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -26,12 +26,10 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; -/** - * Created by durgeshchoudhary on 07/03/16. - */ public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks{ public static final String ARG_CHECK_STATES = "check_states"; public static final String EXTRA_KEY_IDS = "extra_key_ids"; + private boolean checkboxVisibility = true; ListView mUserIds; private MultiUserIdsAdapter mUserIdsAdapter; @@ -78,7 +76,7 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load checkedStates = (ArrayList) savedInstanceState.getSerializable(ARG_CHECK_STATES); } - mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates); + mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility); mUserIds.setAdapter(mUserIdsAdapter); mUserIds.setDividerHeight(0); @@ -95,6 +93,10 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load } public ArrayList getSelectedCertifyActions() { + if (!checkboxVisibility) { + throw new AssertionError("Item selection not allowed"); + } + return mUserIdsAdapter.getSelectedCertifyActions(); } @@ -214,4 +216,8 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load public void onLoaderReset(Loader loader) { mUserIdsAdapter.swapCursor(null); } + + public void setCheckboxVisibility(boolean checkboxVisibility) { + this.checkboxVisibility = checkboxVisibility; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index f38e4928d..306b022c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -31,10 +31,7 @@ import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.UploadResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity // CryptoOperationHelper.Callback vars private String mKeyserver; - private long mMasterKeyId; private CryptoOperationHelper mUploadOpHelper; @Override @@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity mUploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); + MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment) + getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); + mMultiUserIdsFragment.setCheckboxVisibility(false); + ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) .getKeyServers() @@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity return; } - try { - mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "Intent data pointed to bad key!"); - finish(); - return; - } - } @Override @@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity @Override public UploadKeyringParcel createOperationInput() { - return new UploadKeyringParcel(mKeyserver, mMasterKeyId); + long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); + + return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 0aa8330ea..02eae1b2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } private void uploadToKeyserver() { + long keyId; + try { + keyId = new ProviderHelper(getActivity()) + .getCachedPublicKeyRing(mDataUri) + .extractOrGetMasterKeyId(); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + Notify.create(getActivity(), "key not found", Style.ERROR).show(); + return; + } Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class); uploadIntent.setData(mDataUri); + uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId}); startActivityForResult(uploadIntent, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java index b91abf076..d247faddc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/MultiUserIdsAdapter.java @@ -39,6 +39,7 @@ import java.util.ArrayList; public class MultiUserIdsAdapter extends CursorAdapter { private LayoutInflater mInflater; private final ArrayList mCheckStates; + private boolean checkboxVisibility = true; public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList preselectStates) { super(context, c, flags); @@ -46,6 +47,11 @@ public class MultiUserIdsAdapter extends CursorAdapter { mCheckStates = preselectStates == null ? new ArrayList() : preselectStates; } + public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList preselectStates, boolean checkboxVisibility) { + this(context,c,flags,preselectStates); + this.checkboxVisibility = checkboxVisibility; + } + @Override public Cursor swapCursor(Cursor newCursor) { if (newCursor != null) { @@ -138,6 +144,7 @@ public class MultiUserIdsAdapter extends CursorAdapter { } }); vCheckBox.setClickable(false); + vCheckBox.setVisibility(checkboxVisibility?View.VISIBLE:View.GONE); View vUidBody = view.findViewById(R.id.user_id_body); vUidBody.setClickable(true); -- cgit v1.2.3 From e627006fcfecdc84b28298bdf4815aaa7d67cdfa Mon Sep 17 00:00:00 2001 From: Victor Hazali Date: Fri, 1 Apr 2016 10:34:52 +0800 Subject: setTitle now uses resource id directly --- .../sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index 01decf2ef..9f5ffc358 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -101,7 +101,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { private void showAlertDialog() { CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity()); - stripAlertDialog.setTitle(getResources().getString(R.string.title_alert_strip)). + stripAlertDialog.setTitle(R.string.title_alert_strip). setMessage(R.string.alert_strip).setCancelable(true); stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() { @Override -- cgit v1.2.3 From cb9bdb3cf72ec387e6f34d29a31e701f5f5e7d92 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Mon, 21 Mar 2016 21:37:19 +0800 Subject: Redesigned subkey creation dialog and changed default key type created to RSA, 3072 bit. Added code to prevent removal of master subkey when modifying a new key. --- .../keychain/ui/CreateKeyFinalFragment.java | 6 +- .../keychain/ui/EditKeyFragment.java | 10 +- .../keychain/ui/adapter/SubkeysAddedAdapter.java | 48 ++- .../ui/dialog/AddSubkeyDialogFragment.java | 473 +++++++-------------- .../ui/util/spinner/FocusFirstItemSpinner.java | 77 ++++ .../sufficientlysecure/keychain/util/Choice.java | 9 +- 6 files changed, 271 insertions(+), 352 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 b53bfc1d0..e1d20f2e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -281,11 +281,11 @@ public class CreateKeyFinalFragment extends Fragment { saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); + 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.SIGN_DATA, 0L)); + 3072, null, KeyFlags.SIGN_DATA, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, - 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) 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 2d94d0d93..1571a7104 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -562,15 +562,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment> choices = new ArrayList<>(); - choices.add(new Choice<>(Algorithm.DSA, getResources().getString( - R.string.dsa))); - if (!mWillBeMasterKey) { - choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString( - R.string.elgamal))); - } - choices.add(new Choice<>(Algorithm.RSA, getResources().getString( - R.string.rsa))); - choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString( - R.string.ecdsa))); - choices.add(new Choice<>(Algorithm.ECDH, getResources().getString( - R.string.ecdh))); - ArrayAdapter> adapter = new ArrayAdapter<>(context, + ArrayList> choices = new ArrayList<>(); + choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString( + R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString( + R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html))); + choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString( + R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString( + R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html))); + choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString( + R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html))); + TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context, android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAlgorithmSpinner.setAdapter(adapter); - // make RSA the default + mKeyTypeSpinner.setAdapter(adapter); + // make RSA 3072 the default for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Algorithm.RSA) { - mAlgorithmSpinner.setSelection(i); - break; - } - } - } - - // dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change - ArrayAdapter keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, - new ArrayList(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values)))); - keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mKeySizeSpinner.setAdapter(keySizeAdapter); - mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length - - { - ArrayList> choices = new ArrayList<>(); - - choices.add(new Choice<>(Curve.NIST_P256, getResources().getString( - R.string.key_curve_nist_p256))); - choices.add(new Choice<>(Curve.NIST_P384, getResources().getString( - R.string.key_curve_nist_p384))); - choices.add(new Choice<>(Curve.NIST_P521, getResources().getString( - R.string.key_curve_nist_p521))); - - /* @see SaveKeyringParcel - choices.add(new Choice(Curve.BRAINPOOL_P256, getResources().getString( - R.string.key_curve_bp_p256))); - choices.add(new Choice(Curve.BRAINPOOL_P384, getResources().getString( - R.string.key_curve_bp_p384))); - choices.add(new Choice(Curve.BRAINPOOL_P512, getResources().getString( - R.string.key_curve_bp_p512))); - */ - - ArrayAdapter> adapter = new ArrayAdapter<>(context, - android.R.layout.simple_spinner_item, choices); - mCurveSpinner.setAdapter(adapter); - // make NIST P-256 the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Curve.NIST_P256) { - mCurveSpinner.setSelection(i); + if (choices.get(i).getId() == SupportedKeyType.RSA_3072) { + mKeyTypeSpinner.setSelection(i); break; } } @@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment { final AlertDialog alertDialog = dialog.show(); - mCustomKeyEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void afterTextChanged(Editable s) { - setOkButtonAvailability(alertDialog); - } - }); - - mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); - } + // noinspection unchecked + SupportedKeyType keyType = ((Choice) parent.getSelectedItem()).getId(); - @Override - public void onNothingSelected(AdapterView parent) { - } - }); + // RadioGroup.getCheckedRadioButtonId() gives the wrong RadioButton checked + // when programmatically unchecking children radio buttons. Clearing all is the only option. + mUsageRadioGroup.clearCheck(); - mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - updateUiForAlgorithm(((Choice) parent.getSelectedItem()).getId()); + if(mWillBeMasterKey) { + mUsageNone.setChecked(true); + } - setCustomKeyVisibility(); - setOkButtonAvailability(alertDialog); + if (keyType == SupportedKeyType.ECC_P521 || keyType == SupportedKeyType.ECC_P256) { + mUsageSignAndEncrypt.setEnabled(false); + if (mWillBeMasterKey) { + mUsageEncrypt.setEnabled(false); + } + } else { + // need to enable if previously disabled for ECC masterkey + mUsageEncrypt.setEnabled(true); + mUsageSignAndEncrypt.setEnabled(true); + } } @Override - public void onNothingSelected(AdapterView parent) { - } + public void onNothingSelected(AdapterView parent) {} }); return alertDialog; @@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment { positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!mFlagCertify.isChecked() && !mFlagSign.isChecked() - && !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) { - Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show(); + if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) { + Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show(); return; } - Algorithm algorithm = ((Choice) mAlgorithmSpinner.getSelectedItem()).getId(); + // noinspection unchecked + SupportedKeyType keyType = ((Choice) mKeyTypeSpinner.getSelectedItem()).getId(); Curve curve = null; Integer keySize = null; - // For EC keys, add a curve - if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) { - curve = ((Choice) mCurveSpinner.getSelectedItem()).getId(); - // Otherwise, get a keysize - } else { - keySize = getProperKeyLength(algorithm, getSelectedKeyLength()); + Algorithm algorithm = null; + + // set keysize & curve, for RSA & ECC respectively + switch (keyType) { + case RSA_2048: { + keySize = 2048; + break; + } + case RSA_3072: { + keySize = 3072; + break; + } + case RSA_4096: { + keySize = 4096; + break; + } + case ECC_P256: { + curve = Curve.NIST_P256; + break; + } + case ECC_P521: { + curve = Curve.NIST_P521; + break; + } } + // set algorithm + switch (keyType) { + case RSA_2048: + case RSA_3072: + case RSA_4096: { + algorithm = Algorithm.RSA; + break; + } + + case ECC_P256: + case ECC_P521: { + if(mUsageEncrypt.isChecked()) { + algorithm = Algorithm.ECDH; + } else { + algorithm = Algorithm.ECDSA; + } + break; + } + } + + // set flags int flags = 0; - if (mFlagCertify.isChecked()) { + if (mWillBeMasterKey) { flags |= KeyFlags.CERTIFY_OTHER; } - if (mFlagSign.isChecked()) { + if (mUsageSign.isChecked()) { flags |= KeyFlags.SIGN_DATA; - } - if (mFlagEncrypt.isChecked()) { + } else if (mUsageEncrypt.isChecked()) { flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } else if (mUsageSignAndEncrypt.isChecked()) { + flags |= KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; } - if (mFlagAuthenticate.isChecked()) { - flags |= KeyFlags.AUTHENTICATION; - } + long expiry; if (mNoExpiryCheckBox.isChecked()) { @@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment { } } - private int getSelectedKeyLength() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - String keyLengthString = customSelected ? mCustomKeyEditText.getText().toString() : selectedItemString; - int keySize; - try { - keySize = Integer.parseInt(keyLengthString); - } catch (NumberFormatException e) { - keySize = 0; - } - return keySize; - } - - /** - *

RSA

- *

for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger - * than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check - * RSA key length plot and - * Cryptographic Key Length Recommendation). Also, key length must be a - * multiplicity of 8.

- *

ElGamal

- *

For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.

- *

DSA

- *

For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.

- * - * @return correct key length, according to BouncyCastle specification. Returns -1, if key length is - * inappropriate. - */ - private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) { - final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192}; - int properKeyLength = -1; - switch (algorithm) { - case RSA: { - if (currentKeyLength >= 2048 && currentKeyLength <= 16384) { - properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8); - } - break; - } - case ELGAMAL: { - int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length]; - for (int i = 0; i < elGamalSupportedLengths.length; i++) { - elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength); - } - int minimalValue = Integer.MAX_VALUE; - int minimalIndex = -1; - for (int i = 0; i < elGammalKeyDiff.length; i++) { - if (elGammalKeyDiff[i] <= minimalValue) { - minimalValue = elGammalKeyDiff[i]; - minimalIndex = i; - } - } - properKeyLength = elGamalSupportedLengths[minimalIndex]; - break; - } - case DSA: { - // Bouncy Castle supports 4096 maximum - if (currentKeyLength >= 2048 && currentKeyLength <= 4096) { - properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64); - } - break; - } + private class TwoLineArrayAdapter extends ArrayAdapter> { + public TwoLineArrayAdapter(Context context, int resource, List> objects) { + super(context, resource, objects); } - return properKeyLength; - } - private void setOkButtonAvailability(AlertDialog alertDialog) { - Algorithm algorithm = ((Choice) mAlgorithmSpinner.getSelectedItem()).getId(); - boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH - || getProperKeyLength(algorithm, getSelectedKeyLength()) > 0; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled); - } - private void setCustomKeyVisibility() { - final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem(); - final String customLengthString = getResources().getString(R.string.key_size_custom); - final boolean customSelected = customLengthString.equals(selectedItemString); - final int visibility = customSelected ? View.VISIBLE : View.GONE; - - mCustomKeyEditText.setVisibility(visibility); - mCustomKeyTextView.setVisibility(visibility); - mCustomKeyInfoTextView.setVisibility(visibility); - - // hide keyboard after setting visibility to gone - if (visibility == View.GONE) { - InputMethodManager imm = (InputMethodManager) - getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0); - } - } + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // inflate view if not given one + if (convertView == null) { + convertView = getActivity().getLayoutInflater() + .inflate(R.layout.two_line_spinner_dropdown_item, parent, false); + } - private void updateUiForAlgorithm(Algorithm algorithm) { - final ArrayAdapter keySizeAdapter = (ArrayAdapter) mKeySizeSpinner.getAdapter(); - keySizeAdapter.clear(); - switch (algorithm) { - case RSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(1); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa)); - // allowed flags: - mFlagSign.setEnabled(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setEnabled(true); - - if (mWillBeMasterKey) { - mFlagCertify.setEnabled(true); - - mFlagCertify.setChecked(true); - mFlagSign.setChecked(false); - mFlagEncrypt.setChecked(false); - } else { - mFlagCertify.setEnabled(false); + Choice c = this.getItem(position); - mFlagCertify.setChecked(false); - mFlagSign.setChecked(true); - mFlagEncrypt.setChecked(true); - } - mFlagAuthenticate.setChecked(false); - break; - } - case ELGAMAL: { - replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values); - mKeySizeSpinner.setSelection(3); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case DSA: { - replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values); - mKeySizeSpinner.setSelection(2); - mKeySizeRow.setVisibility(View.VISIBLE); - mCurveRow.setVisibility(View.GONE); - mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa)); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(true); - mFlagSign.setEnabled(true); - mFlagEncrypt.setChecked(false); - mFlagEncrypt.setEnabled(false); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - case ECDSA: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setEnabled(mWillBeMasterKey); - mFlagCertify.setChecked(mWillBeMasterKey); - mFlagSign.setEnabled(true); - mFlagSign.setChecked(!mWillBeMasterKey); - mFlagEncrypt.setEnabled(false); - mFlagEncrypt.setChecked(false); - mFlagAuthenticate.setEnabled(true); - mFlagAuthenticate.setChecked(false); - break; - } - case ECDH: { - mKeySizeRow.setVisibility(View.GONE); - mCurveRow.setVisibility(View.VISIBLE); - mCustomKeyInfoTextView.setText(""); - // allowed flags: - mFlagCertify.setChecked(false); - mFlagCertify.setEnabled(false); - mFlagSign.setChecked(false); - mFlagSign.setEnabled(false); - mFlagEncrypt.setChecked(true); - mFlagEncrypt.setEnabled(true); - mFlagAuthenticate.setChecked(false); - mFlagAuthenticate.setEnabled(false); - break; - } - } - keySizeAdapter.notifyDataSetChanged(); + TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); - } + text1.setText(c.getName()); + text2.setText(Html.fromHtml(c.getDescription())); - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void replaceArrayAdapterContent(ArrayAdapter arrayAdapter, int stringArrayResourceId) { - final String[] spinnerValuesStringArray = getResources().getStringArray(stringArrayResourceId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - arrayAdapter.addAll(spinnerValuesStringArray); - } else { - for (final String value : spinnerValuesStringArray) { - arrayAdapter.add(value); - } + return convertView; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java new file mode 100644 index 000000000..7919a0918 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/spinner/FocusFirstItemSpinner.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Alex Fong Jie Wen + * + * 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.util.spinner; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Spinner; + +/** + * Custom spinner which uses a hack to + * always set focus on first item in list + * + */ +public class FocusFirstItemSpinner extends Spinner { + /** + * Spinner is originally designed to set focus on the currently selected item. + * When Spinner is selected to show dropdown, 'performClick()' is called internally. + * 'getSelectedItemPosition()' is then called to obtain the item to focus on. + * We use a toggle to have 'getSelectedItemPosition()' return the 0th index + * for this particular case. + */ + + private boolean mToggleFlag = true; + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle, int mode) { + super(context, attrs, defStyle, mode); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + } + + public FocusFirstItemSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FocusFirstItemSpinner(Context context, int mode) { + super(context, mode); + } + + public FocusFirstItemSpinner(Context context) { + super(context); + } + + @Override + public int getSelectedItemPosition() { + if (!mToggleFlag) { + return 0; + } + return super.getSelectedItemPosition(); + } + + @Override + public boolean performClick() { + mToggleFlag = false; + boolean result = super.performClick(); + mToggleFlag = true; + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java index 48f10d4b9..5ffce9f24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Choice.java @@ -18,12 +18,15 @@ package org.sufficientlysecure.keychain.util; public class Choice { + private String mName; private E mId; + private String mDescription; - public Choice(E id, String name) { + public Choice(E id, String name, String description) { mId = id; mName = name; + mDescription = description; } public E getId() { @@ -34,6 +37,10 @@ public class Choice { return mName; } + public String getDescription() { + return mDescription; + } + @Override public String toString() { return mName; -- cgit v1.2.3 From db3e90821cfff9ac8fe369fdcbbf8ce7e708ba15 Mon Sep 17 00:00:00 2001 From: Victor Hazali Date: Mon, 4 Apr 2016 18:52:22 +0800 Subject: Used a constant instead of magic number for colour Explicitly stated the blending mode --- .../main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 6c96a40ba..d81797454 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.AsyncTask; @@ -924,7 +925,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } mPhoto.setImageBitmap(photo); - mPhoto.setColorFilter(Color.argb(30, 120, 166, 69)); + mPhoto.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP); mPhotoLayout.setVisibility(View.VISIBLE); } }; -- cgit v1.2.3 From 1e97149ce12939ec9c073141e6f1816745a346a2 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Mon, 4 Apr 2016 19:56:00 +0200 Subject: Changed activity during RedirectKeysActivity QrCodeCaptureActivity is open instead ImportKeysActivity --- .../sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index 1f6a62f47..fd3148a6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -34,13 +34,8 @@ public class RedirectImportKeysActivity extends BaseActivity { startScanActivity(); } - @Override - protected void initLayout() { - - } - private void startScanActivity() { - final Intent intent = new Intent(this, org.sufficientlysecure.keychain.ui.ImportKeysActivity.class); + final Intent intent = new Intent(this, QrCodeCaptureActivity.class); new AlertDialog.Builder(this) .setTitle(R.string.redirect_import_key_title) -- cgit v1.2.3 From 30afa3a1934b41d03adf89b0030eaba7ac6bf932 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Mon, 4 Apr 2016 20:03:05 +0200 Subject: Activity renamed Activity renamed from startScanActivity to startQrCodeCaptureActivity --- .../sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index fd3148a6c..635cdcaef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -31,10 +31,10 @@ public class RedirectImportKeysActivity extends BaseActivity { super.onCreate(savedInstanceState); setContentView(R.layout.redirect_import_keys_activity); - startScanActivity(); + startQrCodeCaptureActivity(); } - private void startScanActivity() { + private void startQrCodeCaptureActivity() { final Intent intent = new Intent(this, QrCodeCaptureActivity.class); new AlertDialog.Builder(this) -- cgit v1.2.3 From aec4744b31b74e697a147b30a3eda2f5b381c9e2 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Tue, 5 Apr 2016 02:07:29 +0530 Subject: Fix Unusable signing key not properly disabled, Issue #1468 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java | 5 ----- .../org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java | 5 ----- 2 files changed, 10 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 6a51085f3..63afb1e8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -113,11 +113,6 @@ public class CertifyKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { - // "none" entry is always enabled! - if (cursor.getPosition() == 0) { - return true; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index 8fb9e38aa..0c2d93ad9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -72,11 +72,6 @@ public class SignKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { - // "none" entry is always enabled! - if (cursor.getPosition() == 0) { - return true; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } -- cgit v1.2.3 From dac5f1db08b4dea5893d9fe0a1ab0daad5b44f09 Mon Sep 17 00:00:00 2001 From: Michal Kepkowski Date: Wed, 6 Apr 2016 19:10:00 +0200 Subject: OKhttp url factory --- .../keychain/util/OkHttpClientFactory.java | 7 +++++- .../keychain/util/OkHttpKeybaseClient.java | 27 ++++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java index 2bf3b7e14..cbbbf6e71 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -17,13 +17,18 @@ public class OkHttpClientFactory { public static OkHttpClient getSimpleClient(){ if(client == null){ - client = new OkHttpClient.Builder().build(); + client = new OkHttpClient.Builder() + .connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS) + .build(); } return client; } public static OkHttpClient getPinnedSimpleClient(CertificatePinner pinner){ return new OkHttpClient.Builder() + .connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS) .certificatePinner(pinner) .build(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index 04527c730..e9fe7f724 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -21,32 +21,45 @@ package org.sufficientlysecure.keychain.util; import com.textuality.keybase.lib.KeybaseUrlConnectionClient; import okhttp3.OkHttpClient; +import okhttp3.OkUrlFactory; import org.sufficientlysecure.keychain.Constants; import java.io.IOException; import java.net.Proxy; import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.TimeUnit; /** * Wrapper for Keybase Lib */ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { + private OkUrlFactory generateUrlFactory() { + OkHttpClient client = new OkHttpClient(); + return new OkUrlFactory(client); + } @Override - public OkHttpClient getClient(URL url, Proxy proxy, boolean pin) throws IOException { + public URLConnection openConnection(URL url, Proxy proxy, boolean isKeybase) throws IOException { + OkHttpClient client = OkHttpClientFactory.getSimpleClient(); + + OkUrlFactory factory = generateUrlFactory(); try { - if(pin) { - return OkHttpClientFactory.getPinnedClient(url, proxy); - }else{ - return OkHttpClientFactory.getClient( proxy); + if (isKeybase && proxy != null) { + client = OkHttpClientFactory.getPinnedClient(url, proxy); + } else if (proxy != null) { + client = OkHttpClientFactory.getClient(proxy); + } else { + client = OkHttpClientFactory.getSimpleClient(); } - } catch (IOException e) { - throw new IOException("no pinned certificate found for URL!"); } catch (TlsHelper.TlsHelperException e) { Log.e(Constants.TAG, "TlsHelper failed", e); throw new IOException("TlsHelper failed"); } + factory.setClient(client); + + return factory.open(url); } @Override -- cgit v1.2.3 From 26bfe06d80f4114da298a7e7e05a59db823a8d40 Mon Sep 17 00:00:00 2001 From: Michal Kepkowski Date: Wed, 6 Apr 2016 19:25:10 +0200 Subject: cleaning --- .../keychain/util/OkHttpKeybaseClient.java | 1 - .../org/sufficientlysecure/keychain/util/TlsHelper.java | 13 ++++++------- 2 files changed, 6 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index e9fe7f724..4700da5f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; -import java.util.concurrent.TimeUnit; /** * Wrapper for Keybase Lib diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index d1b8f768b..c23985ac0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -44,12 +44,6 @@ import javax.net.ssl.TrustManagerFactory; public class TlsHelper { - public static class TlsHelperException extends Exception { - public TlsHelperException(Exception e) { - super(e); - } - } - private static Map sPinnedCertificates = new HashMap<>(); /** @@ -87,7 +81,6 @@ public class TlsHelper { for (String host : sPinnedCertificates.keySet()) { if (url.getHost().endsWith(host)) { return pinCertificate(sPinnedCertificates.get(host)); - //return true; } } } @@ -138,4 +131,10 @@ public class TlsHelper { } } + public static class TlsHelperException extends Exception { + public TlsHelperException(Exception e) { + super(e); + } + } + } -- cgit v1.2.3 From 7b97c2bbede48713255520b74d608575df857dc8 Mon Sep 17 00:00:00 2001 From: Michal Kepkowski Date: Fri, 8 Apr 2016 19:10:59 +0200 Subject: KeyBaseLib response Client --- .../ui/linked/LinkedIdCreateTwitterStep1Fragment.java | 2 +- .../keychain/util/OkHttpKeybaseClient.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java index c25f775b0..334a7361b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -119,7 +119,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { private static Boolean checkHandle(String handle) { try { HttpURLConnection nection = - (HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection(); + (HttpURLConnection) new URL("https://twitter.com/" + handle).getUrlResponse(); nection.setRequestMethod("HEAD"); nection.setRequestProperty("User-Agent", "OpenKeychain"); return nection.getResponseCode() == 200; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index 4700da5f3..afe688bbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -22,6 +22,8 @@ import com.textuality.keybase.lib.KeybaseUrlConnectionClient; import okhttp3.OkHttpClient; import okhttp3.OkUrlFactory; +import okhttp3.Request; +import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import java.io.IOException; @@ -34,16 +36,12 @@ import java.net.URLConnection; */ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { - private OkUrlFactory generateUrlFactory() { - OkHttpClient client = new OkHttpClient(); - return new OkUrlFactory(client); - } + @Override - public URLConnection openConnection(URL url, Proxy proxy, boolean isKeybase) throws IOException { - OkHttpClient client = OkHttpClientFactory.getSimpleClient(); + public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException { + OkHttpClient client = null; - OkUrlFactory factory = generateUrlFactory(); try { if (isKeybase && proxy != null) { client = OkHttpClientFactory.getPinnedClient(url, proxy); @@ -56,9 +54,11 @@ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { Log.e(Constants.TAG, "TlsHelper failed", e); throw new IOException("TlsHelper failed"); } - factory.setClient(client); - return factory.open(url); + Request request = new Request.Builder() + .url(url).build(); + okhttp3.Response okResponse = client.newCall(request).execute(); + return new Response(okResponse.body().byteStream(),okResponse.code(),okResponse.message(), okResponse.headers().toMultimap()); } @Override -- cgit v1.2.3 From 2d762e55da92ef45576967c0d1befef55e7935ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 9 Apr 2016 11:53:37 +0200 Subject: Okhttp3 cleanups, docs, and fix timeouts for default client --- .../keychain/linked/LinkedTokenResource.java | 8 ++-- .../ui/dialog/AddEditKeyserverDialogFragment.java | 2 +- .../keychain/util/OkHttpClientFactory.java | 51 ++++++++++++++-------- .../keychain/util/OkHttpKeybaseClient.java | 5 +-- .../keychain/util/TlsHelper.java | 4 -- 5 files changed, 41 insertions(+), 29 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index 24fa3bd67..4a8882d50 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -241,11 +241,11 @@ public abstract class LinkedTokenResource extends LinkedResource { public static String getResponseBody(Request request, String... pins) throws IOException, HttpStatusException { - Log.d("Connection to: "+request.url().url().getHost(),""); + Log.d("Connection to: " + request.url().url().getHost(), ""); OkHttpClient client; - if(pins !=null){ - client = OkHttpClientFactory.getPinnedSimpleClient(getCertificatePinner(request.url().url().getHost(),pins)); - }else{ + if (pins != null) { + client = OkHttpClientFactory.getPinnedSimpleClient(getCertificatePinner(request.url().url().getHost(), pins)); + } else { client = OkHttpClientFactory.getSimpleClient(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 7abef97d2..a10b94b8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -356,7 +356,7 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On OkHttpClient client = OkHttpClientFactory.getPinnedClient(newKeyserver.toURL(), proxy); if (onlyTrustedKeyserver - && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL())==null) { + && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL()) == null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); reason = FailureReason.NO_PINNED_CERTIFICATE; return reason; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java index cbbbf6e71..f3606aa2f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -1,57 +1,74 @@ -package org.sufficientlysecure.keychain.util; +/* + * Copyright (C) 2016 Michał Kępkowski + * + * 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 . + */ -import okhttp3.CertificatePinner; -import okhttp3.OkHttpClient; -import org.sufficientlysecure.keychain.Constants; +package org.sufficientlysecure.keychain.util; import java.io.IOException; import java.net.Proxy; import java.net.URL; import java.util.concurrent.TimeUnit; -/** - * Created by Michał Kępkowski on 11/03/16. - */ +import okhttp3.CertificatePinner; +import okhttp3.OkHttpClient; + public class OkHttpClientFactory { private static OkHttpClient client; - public static OkHttpClient getSimpleClient(){ - if(client == null){ - client = new OkHttpClient.Builder() - .connectTimeout(30000, TimeUnit.MILLISECONDS) - .readTimeout(45000, TimeUnit.MILLISECONDS) + public static OkHttpClient getSimpleClient() { + if (client == null) { + client = new OkHttpClient.Builder() + .connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS) .build(); } return client; } - public static OkHttpClient getPinnedSimpleClient(CertificatePinner pinner){ + public static OkHttpClient getPinnedSimpleClient(CertificatePinner pinner) { return new OkHttpClient.Builder() - .connectTimeout(30000, TimeUnit.MILLISECONDS) - .readTimeout(45000, TimeUnit.MILLISECONDS) + .connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS) .certificatePinner(pinner) .build(); } - public static OkHttpClient getPinnedClient(URL url, Proxy proxy) throws IOException, TlsHelper.TlsHelperException { return new OkHttpClient.Builder() + // don't follow any redirects for keyservers, as discussed in the security audit .followRedirects(false) .followSslRedirects(false) .proxy(proxy) + // higher timeouts for Tor .connectTimeout(30000, TimeUnit.MILLISECONDS) .readTimeout(45000, TimeUnit.MILLISECONDS) + // use pinned cert with SocketFactory .sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)) .build(); } - public static OkHttpClient getClient( Proxy proxy) throws IOException, TlsHelper.TlsHelperException { + public static OkHttpClient getClient(Proxy proxy) throws IOException, TlsHelper.TlsHelperException { return new OkHttpClient.Builder() + // don't follow any redirects for keyservers, as discussed in the security audit .followRedirects(false) .followSslRedirects(false) .proxy(proxy) + // higher timeouts for Tor .connectTimeout(30000, TimeUnit.MILLISECONDS) .readTimeout(45000, TimeUnit.MILLISECONDS) .build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index afe688bbe..9b7b31d68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -24,6 +24,7 @@ import okhttp3.OkHttpClient; import okhttp3.OkUrlFactory; import okhttp3.Request; import okhttp3.Response; + import org.sufficientlysecure.keychain.Constants; import java.io.IOException; @@ -36,8 +37,6 @@ import java.net.URLConnection; */ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { - - @Override public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException { OkHttpClient client = null; @@ -58,7 +57,7 @@ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { Request request = new Request.Builder() .url(url).build(); okhttp3.Response okResponse = client.newCall(request).execute(); - return new Response(okResponse.body().byteStream(),okResponse.code(),okResponse.message(), okResponse.headers().toMultimap()); + return new Response(okResponse.body().byteStream(), okResponse.code(), okResponse.message(), okResponse.headers().toMultimap()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index c23985ac0..77ed6fe0b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -19,8 +19,6 @@ package org.sufficientlysecure.keychain.util; import android.content.res.AssetManager; - -import okhttp3.OkHttpClient; import org.sufficientlysecure.keychain.Constants; import java.io.ByteArrayInputStream; @@ -93,7 +91,6 @@ public class TlsHelper { * Therefore a builder that is pinned this way should be used to only make requests to URLs with passed certificate. * * @param certificate certificate to pin - * @param builder OkHttpBuilder to enforce pinning on * @throws TlsHelperException * @throws IOException */ @@ -125,7 +122,6 @@ public class TlsHelper { context.init(null, tmf.getTrustManagers(), null); return context.getSocketFactory(); - //builder.sslSocketFactory(context.getSocketFactory()); } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException e) { throw new TlsHelperException(e); } -- cgit v1.2.3 From c8e5395d4e3c3dcc349ebe6bb300016f44d430d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 9 Apr 2016 18:34:00 +0200 Subject: Use cert pinning only if available --- .../keychain/keyimport/FacebookKeyserver.java | 4 +- .../keychain/keyimport/HkpKeyserver.java | 4 +- .../keychain/linked/LinkedTokenResource.java | 2 +- .../ui/dialog/AddEditKeyserverDialogFragment.java | 4 +- .../keychain/util/OkHttpClientFactory.java | 47 +++++++++++----------- .../keychain/util/OkHttpKeybaseClient.java | 9 +---- .../keychain/util/TlsHelper.java | 7 ++-- 7 files changed, 36 insertions(+), 41 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java index 1c592003c..6217d1a01 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/FacebookKeyserver.java @@ -106,10 +106,10 @@ public class FacebookKeyserver extends Keyserver { String request = String.format(FB_KEY_URL_FORMAT, fbUsername); Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy); - OkHttpClient client = OkHttpClientFactory.getClient(mProxy); - URL url = new URL(request); + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy); + Response response = client.newCall(new Request.Builder().url(url).build()).execute(); // contains body both in case of success or failure diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java index bdf43b6da..5e3d2ebc6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java @@ -205,7 +205,7 @@ public class HkpKeyserver extends Keyserver { try { URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request); Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy); - OkHttpClient client = OkHttpClientFactory.getPinnedClient(url, proxy); + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy); Response response = client.newCall(new Request.Builder().url(url).build()).execute(); String responseBody = response.body().string(); // contains body both in case of success or failure @@ -396,7 +396,7 @@ public class HkpKeyserver extends Keyserver { .post(body) .build(); - Response response = OkHttpClientFactory.getPinnedClient(url, mProxy).newCall(request).execute(); + Response response = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy).newCall(request).execute(); Log.d(Constants.TAG, "response code: " + response.code()); Log.d(Constants.TAG, "answer: " + response.body().string()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index 4a8882d50..a5f882dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -244,7 +244,7 @@ public abstract class LinkedTokenResource extends LinkedResource { Log.d("Connection to: " + request.url().url().getHost(), ""); OkHttpClient client; if (pins != null) { - client = OkHttpClientFactory.getPinnedSimpleClient(getCertificatePinner(request.url().url().getHost(), pins)); + client = OkHttpClientFactory.getSimpleClientPinned(getCertificatePinner(request.url().url().getHost(), pins)); } else { client = OkHttpClientFactory.getSimpleClient(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index a10b94b8e..3dcc2f58b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -353,8 +353,6 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On Log.d("Converted URL", newKeyserver.toString()); - OkHttpClient client = OkHttpClientFactory.getPinnedClient(newKeyserver.toURL(), proxy); - if (onlyTrustedKeyserver && TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL()) == null) { Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets."); @@ -362,6 +360,8 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On return reason; } + OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(newKeyserver.toURL(), proxy); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java index f3606aa2f..ea2ae8368 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpClientFactory.java @@ -38,7 +38,7 @@ public class OkHttpClientFactory { return client; } - public static OkHttpClient getPinnedSimpleClient(CertificatePinner pinner) { + public static OkHttpClient getSimpleClientPinned(CertificatePinner pinner) { return new OkHttpClient.Builder() .connectTimeout(5000, TimeUnit.MILLISECONDS) .readTimeout(25000, TimeUnit.MILLISECONDS) @@ -46,32 +46,31 @@ public class OkHttpClientFactory { .build(); } - public static OkHttpClient getPinnedClient(URL url, Proxy proxy) throws IOException, TlsHelper.TlsHelperException { + public static OkHttpClient getClientPinnedIfAvailable(URL url, Proxy proxy) throws IOException, + TlsHelper.TlsHelperException { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); - return new OkHttpClient.Builder() - // don't follow any redirects for keyservers, as discussed in the security audit - .followRedirects(false) - .followSslRedirects(false) - .proxy(proxy) - // higher timeouts for Tor - .connectTimeout(30000, TimeUnit.MILLISECONDS) - .readTimeout(45000, TimeUnit.MILLISECONDS) - // use pinned cert with SocketFactory - .sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)) - .build(); - } + // don't follow any redirects for keyservers, as discussed in the security audit + builder.followRedirects(false) + .followSslRedirects(false); + + if (proxy != null) { + // set proxy and higher timeouts for Tor + builder.proxy(proxy); + builder.connectTimeout(30000, TimeUnit.MILLISECONDS) + .readTimeout(45000, TimeUnit.MILLISECONDS); + } else { + builder.connectTimeout(5000, TimeUnit.MILLISECONDS) + .readTimeout(25000, TimeUnit.MILLISECONDS); + } - public static OkHttpClient getClient(Proxy proxy) throws IOException, TlsHelper.TlsHelperException { + // If a pinned cert is available, use it! + // NOTE: this fails gracefully back to "no pinning" if no cert is available. + if (url != null && TlsHelper.getPinnedSslSocketFactory(url) != null) { + builder.sslSocketFactory(TlsHelper.getPinnedSslSocketFactory(url)); + } - return new OkHttpClient.Builder() - // don't follow any redirects for keyservers, as discussed in the security audit - .followRedirects(false) - .followSslRedirects(false) - .proxy(proxy) - // higher timeouts for Tor - .connectTimeout(30000, TimeUnit.MILLISECONDS) - .readTimeout(45000, TimeUnit.MILLISECONDS) - .build(); + return builder.build(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java index 9b7b31d68..8d3eb6963 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java @@ -21,16 +21,13 @@ package org.sufficientlysecure.keychain.util; import com.textuality.keybase.lib.KeybaseUrlConnectionClient; import okhttp3.OkHttpClient; -import okhttp3.OkUrlFactory; import okhttp3.Request; -import okhttp3.Response; import org.sufficientlysecure.keychain.Constants; import java.io.IOException; import java.net.Proxy; import java.net.URL; -import java.net.URLConnection; /** * Wrapper for Keybase Lib @@ -42,10 +39,8 @@ public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient { OkHttpClient client = null; try { - if (isKeybase && proxy != null) { - client = OkHttpClientFactory.getPinnedClient(url, proxy); - } else if (proxy != null) { - client = OkHttpClientFactory.getClient(proxy); + if (proxy != null) { + client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy); } else { client = OkHttpClientFactory.getSimpleClient(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java index 77ed6fe0b..fe62eff55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java @@ -86,9 +86,10 @@ public class TlsHelper { } /** - * Modifies the builder to accept only requests with a given certificate. Applies to all URLs requested by the - * builder. - * Therefore a builder that is pinned this way should be used to only make requests to URLs with passed certificate. + * Modifies the builder to accept only requests with a given certificate. + * Applies to all URLs requested by the builder. + * Therefore a builder that is pinned this way should be used to only make requests + * to URLs with passed certificate. * * @param certificate certificate to pin * @throws TlsHelperException -- cgit v1.2.3 From 2d73b74dde85f67b4302606938fdcc708bafc4c0 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Mon, 4 Apr 2016 17:42:21 +0200 Subject: Use email field to match email in KeychainProvider fixes #1699 --- .../sufficientlysecure/keychain/provider/KeychainProvider.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 9c5d0c054..8a5d09d7b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -456,12 +456,12 @@ public class KeychainProvider extends ContentProvider { if (i != 0) { emailWhere += " OR "; } - emailWhere += "tmp." + UserPackets.USER_ID + " LIKE "; - // match '*', so it has to be at the *end* of the user id if (match == KEY_RINGS_FIND_BY_EMAIL) { - emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); + emailWhere += "tmp." + UserPackets.EMAIL + " LIKE " + + DatabaseUtils.sqlEscapeString(chunks[i]); } else { - emailWhere += DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%"); + emailWhere += "tmp." + UserPackets.USER_ID + " LIKE " + + DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%"); } gotCondition = true; } -- cgit v1.2.3 From 6881754062dcd0b83431cfe36398d20e30922096 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Mon, 4 Apr 2016 17:40:31 +0200 Subject: Better handle user_id sidecases while splitting --- .../java/org/sufficientlysecure/keychain/pgp/KeyRing.java | 11 ++++++++++- .../keychain/provider/KeychainDatabase.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 77977b691..94949d749 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -61,6 +61,8 @@ public abstract class KeyRing { private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^.*@.*\\..*$"); + /** * Splits userId string into naming part, email part, and comment part *

@@ -71,7 +73,14 @@ public abstract class KeyRing { if (!TextUtils.isEmpty(userId)) { final Matcher matcher = USER_ID_PATTERN.matcher(userId); if (matcher.matches()) { - return new UserId(matcher.group(1), matcher.group(3), matcher.group(2)); + String name = matcher.group(1).isEmpty() ? null : matcher.group(1); + String comment = matcher.group(2); + String email = matcher.group(3); + if (comment == null && email == null && name != null && EMAIL_PATTERN.matcher(name).matches()) { + email = name; + name = null; + } + return new UserId(name, email, comment); } } return new UserId(null, null, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 04c14491b..0eb7a0cdb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -54,7 +54,7 @@ import java.io.IOException; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 16; + private static final int DATABASE_VERSION = 17; static Boolean apgHack = false; private Context mContext; -- cgit v1.2.3 From a7a0d9dd4b545ea65444efad5ca96cb7bca919ef Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Tue, 12 Apr 2016 15:41:42 +0200 Subject: Unnecesary code removed and string repaired - Unnecesary import from RedirectImportKeyActivity.java removed - ..\value-w820dp\dimens.xml was unnecesary - \..value\dimens.xml unnecasary line - string sources repaired --- .../org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index 635cdcaef..3d929636c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -19,7 +19,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; -import android.view.Window; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -- cgit v1.2.3 From 8933263665c3f6fdb464b88828f47417820f39e8 Mon Sep 17 00:00:00 2001 From: Lubo Viluda Date: Tue, 12 Apr 2016 16:43:06 +0200 Subject: unnecesary code removed - setContentView removed - R.layout.redirect_import_keys_activity.xml removed --- .../org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java | 1 - 1 file changed, 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index 3d929636c..2abb98a03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -29,7 +29,6 @@ public class RedirectImportKeysActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.redirect_import_keys_activity); startQrCodeCaptureActivity(); } -- cgit v1.2.3 From f7654af00b510aa91badc52eeacb09bf3d0b24e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 12 Apr 2016 19:06:08 +0200 Subject: Revert usage of masked-edittext, back to plain edittexts for backup codes --- .../keychain/ui/BackupCodeFragment.java | 218 ++++++++++++--------- .../keychain/ui/PassphraseDialogActivity.java | 77 +++++--- 2 files changed, 177 insertions(+), 118 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index e7ff6ce46..65c51969e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -18,14 +18,6 @@ package org.sufficientlysecure.keychain.ui; -import java.io.File; -import java.io.IOException; -import java.security.SecureRandom; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; - import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -41,9 +33,8 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.text.Editable; -import android.text.InputType; +import android.text.TextUtils; import android.text.TextWatcher; -import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -52,12 +43,9 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; -import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.TextView; -import com.github.pinball83.maskededittext.MaskedEditText; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ExportResult; @@ -71,6 +59,14 @@ import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Passphrase; +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Random; + public class BackupCodeFragment extends CryptoOperationFragment implements OnBackStackChangedListener { @@ -96,7 +92,7 @@ public class BackupCodeFragment extends CryptoOperationFragment=16 + textView.setBackgroundDrawable(null); } - }); - - - TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display); - setupAutomaticLinebreak(codeDisplayText); - - // set background to null in TextViews - this will retain padding from EditText style! - // noinspection deprecation, setBackground(Drawable) is API level >=16 - codeDisplayText.setBackgroundDrawable(null); + } - codeDisplayText.setText(mBackupCode); + setupEditTextFocusNext(mCodeEditText); + setupEditTextSuccessListener(mCodeEditText); mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator); mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator); @@ -347,67 +346,76 @@ public class BackupCodeFragment extends CryptoOperationFragment - * NOTE: I was not able to get this behaviour using XML! - * Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307 - */ - private void setupAutomaticLinebreak(TextView textview) { - textview.setSingleLine(true); - textview.setMaxLines(6); - textview.setHorizontallyScrolling(false); - } + private void setupEditTextSuccessListener(final EditText[] backupCodes) { + for (EditText backupCode : backupCodes) { - private void setupEditTextSuccessListener(final MaskedEditText backupCode) { - backupCode.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + backupCode.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } - @Override - public void afterTextChanged(Editable s) { - String currentBackupCode = backupCode.getText().toString(); - boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT - || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; - boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1); - if (!inInputState || !partIsComplete) { - return; + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 4) { + throw new AssertionError("max length of each field is 4!"); + } + + boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT + || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; + boolean partIsComplete = s.length() == 4; + if (!inInputState || !partIsComplete) { + return; + } + + checkIfCodeIsCorrect(); } + }); - checkIfCodeIsCorrect(currentBackupCode); - } - }); + } } - private void checkIfCodeIsCorrect(String currentBackupCode) { + private void checkIfCodeIsCorrect() { if (Constants.DEBUG && mDebugModeAcceptAnyCode) { switchState(BackupCodeState.STATE_OK, true); return; } - if (currentBackupCode.equals(mBackupCode)) { + StringBuilder backupCodeInput = new StringBuilder(26); + for (EditText editText : mCodeEditText) { + if (editText.getText().length() < 4) { + return; + } + backupCodeInput.append(editText.getText()); + backupCodeInput.append('-'); + } + backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); + + // if they don't match, do nothing + if (backupCodeInput.toString().equals(mBackupCode)) { switchState(BackupCodeState.STATE_OK, true); return; } switchState(BackupCodeState.STATE_INPUT_ERROR, true); + } private static void animateFlashText( - final TextView textView, int color1, int color2, boolean staySecondColor) { + final TextView[] textViews, int color1, int color2, boolean staySecondColor) { ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { - textView.setTextColor((Integer) animator.getAnimatedValue()); + for (TextView textView : textViews) { + textView.setTextColor((Integer) animator.getAnimatedValue()); + } } }); anim.setRepeatMode(ValueAnimator.REVERSE); @@ -418,6 +426,34 @@ public class BackupCodeFragment extends CryptoOperationFragment - * NOTE: I was not able to get this behaviour using XML! - * Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307 - */ - private void setupAutomaticLinebreak(TextView textview) { - textview.setSingleLine(true); - textview.setMaxLines(6); - textview.setHorizontallyScrolling(false); + private static void setupEditTextFocusNext(final EditText[] backupCodes) { + for (int i = 0; i < backupCodes.length - 1; i++) { + + final int next = i + 1; + + backupCodes[i].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + boolean inserting = before < count; + boolean cursorAtEnd = (start + count) == 4; + + if (inserting && cursorAtEnd) { + backupCodes[next].requestFocus(); + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + } } + @Override public void onStart() { super.onStart(); @@ -356,8 +370,17 @@ public class PassphraseDialogActivity extends FragmentActivity { public void onClick(View v) { if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { - Passphrase passphrase = - new Passphrase(mBackupCodeEditText.getText().toString()); + StringBuilder backupCodeInput = new StringBuilder(26); + for (EditText editText : mBackupCodeEditText) { + if (editText.getText().length() < 4) { + return; + } + backupCodeInput.append(editText.getText()); + backupCodeInput.append('-'); + } + backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); + + Passphrase passphrase = new Passphrase(backupCodeInput.toString()); finishCaching(passphrase); return; -- cgit v1.2.3 From 72d2a627ff7641a660628ed5d634209a93efebd2 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Tue, 5 Apr 2016 23:22:13 +0530 Subject: Add + button besides TokenAutoComplete, Issue #925 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../keychain/ui/EncryptModeAsymmetricFragment.java | 9 +++++++++ .../keychain/ui/widget/EncryptKeyCompletionView.java | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index b2b85ec14..51022094b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -21,6 +21,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.ViewAnimator; import com.tokenautocomplete.TokenCompleteTextView; @@ -109,6 +110,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { } }); + ImageView addRecipientImgView = (ImageView) view.findViewById(R.id.add_recipient); + addRecipientImgView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mEncryptKeyView.showAllKeys(); + } + }); + return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index f98fda56f..fb9e502e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -171,4 +171,22 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView mLoaderManager.restartLoader(0, args, this); } + @Override + public boolean enoughToFilter() { + return true; + } + + public void showAllKeys(){ + Bundle args = new Bundle(); + args.putString(ARG_QUERY, ""); + mLoaderManager.restartLoader(0, args, this); + super.showDropDown(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + this.setDropDownWidth(this.getLeft()+this.getRight()); + this.setDropDownHorizontalOffset(-this.getLeft()); + } } -- cgit v1.2.3 From 8f88efe13fc9b895654d02459b4fab6afdff0406 Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Tue, 5 Apr 2016 04:01:24 +0530 Subject: Fix Crash when no encryption subkey is available, Issue #1817 Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../operations/results/OperationResult.java | 1 + .../keychain/pgp/PgpSignEncryptInputParcel.java | 12 ---------- .../keychain/pgp/PgpSignEncryptOperation.java | 28 ++++++++++++++-------- .../keychain/remote/OpenPgpService.java | 3 +-- .../keychain/ui/adapter/KeyAdapter.java | 5 ++++ .../ui/widget/EncryptKeyCompletionView.java | 6 +++++ 6 files changed, 31 insertions(+), 24 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 a3979904c..02256aebd 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 @@ -728,6 +728,7 @@ public abstract class OperationResult implements Parcelable { MSG_PSE_ERROR_PGP (LogLevel.ERROR, R.string.msg_pse_error_pgp), MSG_PSE_ERROR_SIG (LogLevel.ERROR, R.string.msg_pse_error_sig), MSG_PSE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_pse_error_unlock), + MSG_PSE_ERROR_REVOKED_OR_EXPIRED (LogLevel.ERROR, R.string.msg_pse_error_revoked_or_expired), MSG_PSE_KEY_OK (LogLevel.OK, R.string.msg_pse_key_ok), MSG_PSE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_pse_key_unknown), MSG_PSE_KEY_WARN (LogLevel.WARN, R.string.msg_pse_key_warn), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java index 580103942..8eae92e63 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -38,7 +38,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { protected Long mSignatureSubKeyId = null; protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT; protected long mAdditionalEncryptId = Constants.key.none; - protected boolean mFailOnMissingEncryptionKeyIds = false; protected String mCharset; protected boolean mCleartextSignature; protected boolean mDetachedSignature = false; @@ -65,7 +64,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null; mSignatureHashAlgorithm = source.readInt(); mAdditionalEncryptId = source.readLong(); - mFailOnMissingEncryptionKeyIds = source.readInt() == 1; mCharset = source.readString(); mCleartextSignature = source.readInt() == 1; mDetachedSignature = source.readInt() == 1; @@ -96,7 +94,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { } dest.writeInt(mSignatureHashAlgorithm); dest.writeLong(mAdditionalEncryptId); - dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); dest.writeString(mCharset); dest.writeInt(mCleartextSignature ? 1 : 0); dest.writeInt(mDetachedSignature ? 1 : 0); @@ -113,10 +110,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { this.mCharset = mCharset; } - public boolean isFailOnMissingEncryptionKeyIds() { - return mFailOnMissingEncryptionKeyIds; - } - public long getAdditionalEncryptId() { return mAdditionalEncryptId; } @@ -207,11 +200,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { return this; } - public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { - mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; - return this; - } - public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) { this.mCleartextSignature = cleartextSignature; return this; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 328daa7ff..48ba68fa6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -168,10 +168,17 @@ public class PgpSignEncryptOperation extends BaseOperation { try { long signingMasterKeyId = input.getSignatureMasterKeyId(); long signingSubKeyId = input.getSignatureSubKeyId(); - { - CanonicalizedSecretKeyRing signingKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId); - signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId()); + + CanonicalizedSecretKeyRing signingKeyRing = + mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId); + signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId()); + + + // Make sure key is not expired or revoked + if (signingKeyRing.isExpired() || signingKeyRing.isRevoked() + || signingKey.isExpired() || signingKey.isRevoked()) { + log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // Make sure we are allowed to sign here! @@ -281,16 +288,17 @@ public class PgpSignEncryptOperation extends BaseOperation { if (encryptSubKeyIds.isEmpty()) { log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (input.isFailOnMissingEncryptionKeyIds()) { - return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); - } + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + // Make sure key is not expired or revoked + if (keyRing.isExpired() || keyRing.isRevoked()) { + log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (ProviderHelper.NotFoundException e) { log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (input.isFailOnMissingEncryptionKeyIds()) { - return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); - } + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } } 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 02d9ba62d..5a2aa6e71 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -336,8 +336,7 @@ public class OpenPgpService extends Service { .setVersionHeader(null) .setCompressionAlgorithm(compressionId) .setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) - .setEncryptionMasterKeyIds(keyIds) - .setFailOnMissingEncryptionKeyIds(true); + .setEncryptionMasterKeyIds(keyIds); if (sign) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index 4a68c55fe..78aaecab3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -66,6 +66,7 @@ public class KeyAdapter extends CursorAdapter { KeyRings.HAS_DUPLICATE_USER_ID, KeyRings.FINGERPRINT, KeyRings.CREATION, + KeyRings.HAS_ENCRYPT }; public static final int INDEX_MASTER_KEY_ID = 1; @@ -77,6 +78,7 @@ public class KeyAdapter extends CursorAdapter { public static final int INDEX_HAS_DUPLICATE_USER_ID = 7; public static final int INDEX_FINGERPRINT = 8; public static final int INDEX_CREATION = 9; + public static final int INDEX_HAS_ENCRYPT = 10; public KeyAdapter(Context context, Cursor c, int flags) { super(context, c, flags); @@ -289,6 +291,7 @@ public class KeyAdapter extends CursorAdapter { public final KeyRing.UserId mUserId; public final long mKeyId; public final boolean mHasDuplicate; + public final boolean mHasEncrypt; public final Date mCreation; public final String mFingerprint; public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified; @@ -299,6 +302,7 @@ public class KeyAdapter extends CursorAdapter { mUserIdFull = userId; mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0; + mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( cursor.getBlob(INDEX_FINGERPRINT)); @@ -315,6 +319,7 @@ public class KeyAdapter extends CursorAdapter { mUserIdFull = userId; mKeyId = ring.getMasterKeyId(); mHasDuplicate = false; + mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0; mCreation = key.getCreationTime(); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( ring.getFingerprint()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index f98fda56f..78f9f431b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui.widget; import android.content.Context; import android.database.Cursor; +import android.graphics.Color; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; @@ -83,6 +84,11 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView LayoutInflater l = LayoutInflater.from(getContext()); View view = l.inflate(R.layout.recipient_box_entry, null); ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName()); + + if (keyItem.mIsRevoked || !keyItem.mHasEncrypt || keyItem.mIsExpired) { + ((TextView) view.findViewById(android.R.id.text1)).setTextColor(Color.RED); + } + return view; } -- cgit v1.2.3 From 65f17d74495b2bfdb323dc5bc87ef95f8466347a Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 3 Apr 2016 19:54:24 +0600 Subject: OTG: port old primitives from otg_alt branch --- .../keychain/javacard/BaseJavacardDevice.java | 590 +++++++++++++++++++++ .../javacard/CachingBaseJavacardDevice.java | 46 ++ .../keychain/javacard/CardException.java | 17 + .../keychain/javacard/JavacardDevice.java | 65 +++ .../keychain/javacard/KeyType.java | 48 ++ .../keychain/javacard/NfcTransport.java | 31 ++ .../keychain/javacard/PinException.java | 7 + .../keychain/javacard/PinType.java | 16 + .../keychain/javacard/Transport.java | 11 + .../keychain/javacard/TransportIoException.java | 20 + .../keychain/javacard/UsbTransport.java | 144 +++++ 11 files changed, 995 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java new file mode 100644 index 000000000..323cb9628 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -0,0 +1,590 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey; + +public class BaseJavacardDevice implements JavacardDevice { + 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 final Transport mTransport; + + private Passphrase mPin; + private Passphrase mAdminPin; + private boolean mPw1ValidForMultipleSignatures; + private boolean mPw1ValidatedForSignature; + private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + private boolean mPw3Validated; + private boolean mTagHandlingEnabled; + + public BaseJavacardDevice(final Transport mTransport) { + this.mTransport = mTransport; + } + + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + public Passphrase getPin() { + return mPin; + } + + public void setPin(final Passphrase pin) { + this.mPin = pin; + } + + public Passphrase getAdminPin() { + return mAdminPin; + } + + public void setAdminPin(final Passphrase adminPin) { + this.mAdminPin = adminPin; + } + + public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { + long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; + byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); + KeyType keyType = KeyType.from(secretKey); + + if (keyType == null) { + throw new IOException("Inappropriate key flags for smart card key."); + } + + // Slot is empty, or contains this key already. PUT KEY operation is safe + boolean canPutKey = !containsKey(keyType) + || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); + if (!canPutKey) { + throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", + keyType.toString())); + } + + nfcPutKey(keyType.getmSlot(), secretKey, passphrase); + nfcPutData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + nfcPutData(keyType.getTimestampObjectId(), timestampBytes); + } + + public boolean containsKey(KeyType keyType) throws IOException { + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + } + + public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + return java.util.Arrays.equals(nfcGetFingerprint(keyType.getIdx()), fingerprint); + } + + public void connectToDevice() throws IOException { + // 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; + } + + /** + * 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; + } + } + + /** + * 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 pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. + */ + public void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException { + final int MAX_PW1_LENGTH_INDEX = 1; + final int MAX_PW3_LENGTH_INDEX = 3; + + byte[] pwStatusBytes = nfcGetPwStatusBytes(); + byte[] oldPin; + + if (pinType == PinType.BASIC) { + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + oldPin = mPin.toStringUnsafe().getBytes(); + } else { + if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + oldPin = mAdminPin.toStringUnsafe().getBytes(); + } + + // Command APDU for CHANGE REFERENCE DATA command (page 32) + String changeReferenceDataApdu = "00" // CLA + + "24" // INS + + "00" // P1 + + String.format("%02x", pinType.getmMode()) // P2 + + String.format("%02x", oldPin.length + newPin.length) // Lc + + getHex(oldPin) + + getHex(newPin); + String response = nfcCommunicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { + throw new PinException("Failed to change PIN", parseCardStatus(response)); + } + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + public byte[] decryptSessionKey(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); + + Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + + 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"; + + // 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); + String response = nfcCommunicate(login); // login + if (!response.equals(accepted)) { + throw new PinException("Bad PIN!", parseCardStatus(response)); + } + + if (mode == 0x81) { + mPw1ValidatedForSignature = true; + } else if (mode == 0x82) { + mPw1ValidatedForDecrypt = true; + } else if (mode == 0x83) { + mPw3Validated = true; + } + } + } + + /** + * 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); + } + + /** + * 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 = nfcGetFingerprint(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[] getFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mTransport.sendAndReceive(Hex.decode(data)); + + Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); + Log.d(Constants.TAG, "nfc 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 mTransport.sendAndReceive(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[] nfcGetFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + + // 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[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.sendAndReceive(Hex.decode(info)); + } + + public String getUserId() 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); + } + + public String nfcGetHolderName(String name) { + 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); + } + + private String nfcGetDataField(String output) { + return output.substring(0, output.length() - 4); + } + + public String nfcCommunicate(String apdu) throws IOException, TransportIoException { + return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + } + + public boolean isConnected() { + return mTransport.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java new file mode 100644 index 000000000..5ad157de1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java @@ -0,0 +1,46 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; + +public class CachingBaseJavacardDevice extends BaseJavacardDevice { + private byte[] mFingerprintsCache; + private String mUserIdCache; + private byte[] mAidCache; + + public CachingBaseJavacardDevice(final Transport mTransport) { + super(mTransport); + } + + @Override + public byte[] getFingerprints() throws IOException { + if (mFingerprintsCache == null) { + mFingerprintsCache = super.getFingerprints(); + } + return mFingerprintsCache; + } + + @Override + public String getUserId() throws IOException { + if (mUserIdCache == null) { + mUserIdCache = super.getUserId(); + } + return mUserIdCache; + } + + @Override + public byte[] getAid() throws IOException { + if (mAidCache == null) { + mAidCache = super.getAid(); + } + return mAidCache; + } + + @Override + public void changeKey(final CanonicalizedSecretKey secretKey, final Passphrase passphrase) throws IOException { + super.changeKey(secretKey, passphrase); + mFingerprintsCache = null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java new file mode 100644 index 000000000..3e9e9f2ca --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java new file mode 100644 index 000000000..240fffaf8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -0,0 +1,65 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; + +public interface JavacardDevice { + + Passphrase getPin(); + + void setPin(final Passphrase pin); + + Passphrase getAdminPin(); + + void setAdminPin(final Passphrase adminPin); + + void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException; + + boolean containsKey(KeyType keyType) throws IOException; + + boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException; + + void connectToDevice() throws IOException; + + /** + * 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 pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. + */ + void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException; + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException; + + /** + * 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. + */ + byte[] getFingerprints() throws IOException; + + + byte[] getAid() throws IOException; + + String getUserId() throws IOException; + + boolean isConnected(); + + /** + * 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 + */ + byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java new file mode 100644 index 000000000..e0190f8bd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java new file mode 100644 index 000000000..b5af8b374 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.nfc.tech.IsoDep; + +import java.io.IOException; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final IsoDep mIsoDep; + + public NfcTransport(final IsoDep isoDep) throws IOException { + this.mIsoDep = isoDep; + mIsoDep.setTimeout(TIMEOUT); + mIsoDep.connect(); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + return mIsoDep.transceive(data); + } + + @Override + public void release() { + } + + @Override + public boolean isConnected() { + return mIsoDep.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java new file mode 100644 index 000000000..84a34f116 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.javacard; + +public class PinException extends CardException { + public PinException(final String detailMessage, final short responseCode) { + super(detailMessage, responseCode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java new file mode 100644 index 000000000..b6787a9e1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java @@ -0,0 +1,16 @@ +package org.sufficientlysecure.keychain.javacard; + +public enum PinType { + BASIC(0x81), + ADMIN(0x83),; + + private final int mMode; + + PinType(final int mode) { + this.mMode = mode; + } + + public int getmMode() { + return mMode; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java new file mode 100644 index 000000000..2d7dd3309 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public interface Transport { + byte[] sendAndReceive(byte[] data) throws IOException; + + void release(); + + boolean isConnected(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java new file mode 100644 index 000000000..2dfb7df94 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public class TransportIoException extends IOException { + public TransportIoException() { + } + + public TransportIoException(final String detailMessage) { + super(detailMessage); + } + + public TransportIoException(final String message, final Throwable cause) { + super(message, cause); + } + + public TransportIoException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java new file mode 100644 index 000000000..f2af34c39 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java @@ -0,0 +1,144 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; + +public class UsbTransport implements Transport { + private static final int CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 1000; // 1 s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private final UsbInterface mUsbInterface; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final UsbDeviceConnection mConnection; + private byte counter = 0; + + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + final byte[] iccPowerOn = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + counter++, + 0x03, + 0x00, 0x00 + }; + sendRaw(iccPowerOn); + receiveRaw(); + // Check result + } + + /** + * Get first class 11 (Chip/Smartcard) interface for the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(final UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + @Override + public void release() { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + } + + @Override + public boolean isConnected() { + // TODO: redo + return mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException { + send(data); + return receive(); + } + + public void send(final byte[] d) throws TransportIoException { + int l = d.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + counter++, + 0x01, + 0x00, 0x00}, + d); + sendRaw(data); + } + + public byte[] receive() throws TransportIoException { + final byte[] bytes = receiveRaw(); + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + private void sendRaw(final byte[] data) throws TransportIoException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new TransportIoException("USB error, failed to send data " + tr1); + } + } + + private byte[] receiveRaw() throws TransportIoException { + byte[] buffer = new byte[1024]; + + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + + return Arrays.copyOfRange(buffer, 0, res); + } +} -- cgit v1.2.3 From 834440199a97f3b253d85915ce83613f67e33a25 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 3 Apr 2016 22:20:33 +0600 Subject: OTG: update methods --- .../keychain/javacard/BaseJavacardDevice.java | 279 +++++++--- .../keychain/javacard/JavacardDevice.java | 42 +- .../keychain/javacard/NfcTransport.java | 20 +- .../keychain/ui/CreateKeyActivity.java | 6 +- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 36 +- .../keychain/ui/ViewKeyActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 620 +-------------------- 8 files changed, 308 insertions(+), 707 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index 323cb9628..46f4c0443 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -15,7 +15,12 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.interfaces.RSAPrivateCrtKey; +import nordpol.Apdu; + public class BaseJavacardDevice implements JavacardDevice { + // Fidesmo constants + private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; + 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 final Transport mTransport; @@ -68,9 +73,9 @@ public class BaseJavacardDevice implements JavacardDevice { keyType.toString())); } - nfcPutKey(keyType.getmSlot(), secretKey, passphrase); - nfcPutData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); - nfcPutData(keyType.getTimestampObjectId(), timestampBytes); + putKey(keyType.getmSlot(), secretKey, passphrase); + putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + putData(keyType.getTimestampObjectId(), timestampBytes); } public boolean containsKey(KeyType keyType) throws IOException { @@ -78,9 +83,10 @@ public class BaseJavacardDevice implements JavacardDevice { } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(nfcGetFingerprint(keyType.getIdx()), fingerprint); + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); } + // METHOD UPDATED OK public void connectToDevice() throws IOException { // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 @@ -127,53 +133,61 @@ public class BaseJavacardDevice implements JavacardDevice { /** * 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. + * conformance to the token's requirements for key length. * - * @param pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPin The new PW1 or PW3. + * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException { + // METHOD UPDATED[OK] + public void modifyPin(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; byte[] pwStatusBytes = nfcGetPwStatusBytes(); - byte[] oldPin; - if (pinType == PinType.BASIC) { + if (pw == 0x81) { if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { throw new IOException("Invalid PIN length"); } - oldPin = mPin.toStringUnsafe().getBytes(); - } else { + } else if (pw == 0x83) { if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { throw new IOException("Invalid PIN length"); } - oldPin = mAdminPin.toStringUnsafe().getBytes(); + } 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", pinType.getmMode()) // P2 - + String.format("%02x", oldPin.length + newPin.length) // Lc - + getHex(oldPin) + + 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 PinException("Failed to change PIN", parseCardStatus(response)); + throw new CardException("Failed to change PIN", parseCardStatus(response)); } } /** - * Calls to calculate the signature and returns the MPI value + * Call DECIPHER command * * @param encryptedSessionKey the encoded session key * @return the decoded session key */ + // METHOD UPDATED [OK] public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } String firstApdu = "102a8086fe"; @@ -189,12 +203,10 @@ public class BaseJavacardDevice implements JavacardDevice { two[i] = encryptedSessionKey[i + one.length + 1]; } - String first = nfcCommunicate(firstApdu + getHex(one)); + nfcCommunicate(firstApdu + getHex(one)); String second = nfcCommunicate(secondApdu + getHex(two) + le); - String decryptedSessionKey = nfcGetDataField(second); - - Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + String decryptedSessionKey = getDataField(second); return Hex.decode(decryptedSessionKey); } @@ -205,7 +217,8 @@ public class BaseJavacardDevice implements JavacardDevice { * @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 { + // METHOD UPDATED [OK] + public void nfcVerifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { byte[] pin; @@ -218,18 +231,9 @@ public class BaseJavacardDevice implements JavacardDevice { // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; - - // 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); - String response = nfcCommunicate(login); // login + String response = nfcTryPin(mode, pin); // login if (!response.equals(accepted)) { - throw new PinException("Bad PIN!", parseCardStatus(response)); + throw new CardException("Bad PIN!", parseCardStatus(response)); } if (mode == 0x81) { @@ -243,23 +247,24 @@ public class BaseJavacardDevice implements JavacardDevice { } /** - * Stores a data object on the card. Automatically validates the proper PIN for the operation. + * 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 { + // METHOD UPDATED [OK] + public void putData(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) + nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) } } else if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3) + nfcVerifyPin(0x83); // (Verify PW3) } String putDataApdu = "00" // CLA @@ -275,15 +280,17 @@ public class BaseJavacardDevice implements JavacardDevice { } } + /** - * Puts a key on the card in the given slot. + * Puts a key on the token in the given slot. * - * @param slot The slot on the card where the key should be stored: + * @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) + // METHOD UPDATED [OK] + public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); @@ -299,16 +306,16 @@ public class BaseJavacardDevice implements JavacardDevice { // 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."); + 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 card key."); + throw new IOException("Invalid public exponent for smart Security Token."); } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } byte[] header = Hex.decode( @@ -360,7 +367,7 @@ public class BaseJavacardDevice implements JavacardDevice { String putKeyCommand = "10DB3FFF"; String lastPutKeyCommand = "00DB3FFF"; - // Now we're ready to communicate with the card. + // Now we're ready to communicate with the token. offset = 0; String response; while (offset < dataToSend.length) { @@ -379,7 +386,7 @@ public class BaseJavacardDevice implements JavacardDevice { } if (!response.endsWith("9000")) { - throw new CardException("Key export to card failed", parseCardStatus(response)); + throw new CardException("Key export to Security Token failed", parseCardStatus(response)); } } @@ -387,6 +394,7 @@ public class BaseJavacardDevice implements JavacardDevice { Arrays.fill(dataToSend, (byte) 0); } + /** * Return the key id from application specific data stored on tag, or null * if it doesn't exist. @@ -395,7 +403,7 @@ public class BaseJavacardDevice implements JavacardDevice { * @return The long key id of the requested key, or null if not found. */ public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = nfcGetFingerprint(idx); + byte[] fp = getMasterKeyFingerprint(idx); if (fp == null) { return null; } @@ -412,12 +420,13 @@ public class BaseJavacardDevice implements JavacardDevice { * * @return The fingerprints of all subkeys in a contiguous byte array. */ + // METHOD UPDATED [OK] public byte[] getFingerprints() throws IOException { String data = "00CA006E00"; byte[] buf = mTransport.sendAndReceive(Hex.decode(data)); Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); - Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); + Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); if (fptlv == null) { @@ -427,53 +436,38 @@ public class BaseJavacardDevice implements JavacardDevice { } /** - * Return the PW Status Bytes from the card. This is a simple DO; no TLV decoding needed. + * 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. */ + // METHOD UPDATED [OK] public byte[] nfcGetPwStatusBytes() throws IOException { String data = "00CA00C400"; return mTransport.sendAndReceive(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[] nfcGetFingerprint(int idx) throws IOException { - byte[] data = getFingerprints(); - - // 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; - } - + // METHOD UPDATED [OK] public byte[] getAid() throws IOException { String info = "00CA004F00"; return mTransport.sendAndReceive(Hex.decode(info)); } + // METHOD UPDATED [OK] public String getUserId() throws IOException { String info = "00CA006500"; return nfcGetHolderName(nfcCommunicate(info)); } /** - * Calls to calculate the signature and returns the MPI value + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value * * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ - public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { + // METHOD UPDATED [OK] + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { if (!mPw1ValidatedForSignature) { - nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) } // dsi, including Lc @@ -580,6 +574,10 @@ public class BaseJavacardDevice implements JavacardDevice { return output.substring(0, output.length() - 4); } + /** + * Transceive data via NFC encoded as Hex + */ + // METHOD UPDATED [OK] public String nfcCommunicate(String apdu) throws IOException, TransportIoException { return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); } @@ -587,4 +585,141 @@ public class BaseJavacardDevice implements JavacardDevice { public boolean isConnected() { return mTransport.isConnected(); } + + // NEW METHOD [OK] + public boolean isFidesmoToken() { + if (isConnected()) { // Check if we can still talk to the card + try { + // By trying to select any apps that have the Fidesmo AID prefix we can + // see if it is a Fidesmo device or not + byte[] mSelectResponse = mTransport.sendAndReceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); + // Compare the status returned by our select with the OK status code + return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); + } catch (IOException e) { + Log.e(Constants.TAG, "Card communication failed!", e); + } + } + return false; + } + + /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + // NEW METHOD [OK] + public byte[] nfcGenerateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = nfcCommunicate(generateKeyApdu); + String second = nfcCommunicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + // NEW METHOD [OK][OK] + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + // NEW METHOD [OK] + private String nfcTryPin(int mode, byte[] pin) throws IOException { + // Command APDU for VERIFY command (page 32) + String login = + "00" // CLA + + "20" // INS + + "00" // P1 + + String.format("%02x", mode) // P2 + + String.format("%02x", pin.length) // Lc + + Hex.toHexString(pin); + + return nfcCommunicate(login); + } + + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + // NEW METHOD [OK] + public void resetAndWipeToken() throws IOException { + String accepted = "9000"; + + // try wrong PIN 4 times until counter goes to C0 + byte[] pin = "XXXXXX".getBytes(); + for (int i = 0; i <= 4; i++) { + String response = nfcTryPin(0x81, pin); + if (response.equals(accepted)) { // Should NOT accept! + throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); + } + } + + // try wrong Admin PIN 4 times until counter goes to C0 + byte[] adminPin = "XXXXXXXX".getBytes(); + for (int i = 0; i <= 4; i++) { + String response = nfcTryPin(0x83, adminPin); + if (response.equals(accepted)) { // Should NOT accept! + throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); + } + } + + // reactivate token! + String reactivate1 = "00" + "e6" + "00" + "00"; + String reactivate2 = "00" + "44" + "00" + "00"; + String response1 = nfcCommunicate(reactivate1); + String response2 = nfcCommunicate(reactivate2); + if (!response1.equals(accepted) || !response2.equals(accepted)) { + throw new CardException("Reactivating failed!", parseCardStatus(response1)); + } + + } + + /** + * 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[] getMasterKeyFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + 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; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java index 240fffaf8..04c2c0006 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -30,7 +30,7 @@ public interface JavacardDevice { * @param pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. * @param newPin The new PW1 or PW3. */ - void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException; + void modifyPin(int pinType, byte[] newPin) throws IOException; /** * Calls to calculate the signature and returns the MPI value @@ -61,5 +61,43 @@ public interface JavacardDevice { * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ - byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException; + byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException; + + boolean isFidesmoToken(); + + /** + * 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. + */ + byte[] getMasterKeyFingerprint(int idx) throws IOException; + + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + void resetAndWipeToken() throws IOException; + + /** + * 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 + */ + void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException; + + /** + * 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 + */ + void putData(int dataObject, byte[] data) throws IOException; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java index b5af8b374..421b28aa8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java @@ -1,23 +1,23 @@ package org.sufficientlysecure.keychain.javacard; -import android.nfc.tech.IsoDep; - import java.io.IOException; -public class NfcTransport implements Transport { +import nordpol.IsoCard; + +public class NfcTransport implements Transport { // timeout is set to 100 seconds to avoid cancellation during calculation private static final int TIMEOUT = 100 * 1000; - private final IsoDep mIsoDep; + private final IsoCard mIsoCard; - public NfcTransport(final IsoDep isoDep) throws IOException { - this.mIsoDep = isoDep; - mIsoDep.setTimeout(TIMEOUT); - mIsoDep.connect(); + public NfcTransport(final IsoCard isoDep) throws IOException { + this.mIsoCard = isoDep; + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); } @Override public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { - return mIsoDep.transceive(data); + return mIsoCard.transceive(data); } @Override @@ -26,6 +26,6 @@ public class NfcTransport implements Transport { @Override public boolean isConnected() { - return mIsoDep.isConnected(); + return mIsoCard.isConnected(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b1fec3aae..b3f60ba41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -149,9 +149,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { return; } - mScannedFingerprints = nfcGetFingerprints(); - mNfcAid = nfcGetAid(); - mNfcUserId = nfcGetUserId(); + mScannedFingerprints = mJavacardDevice.getFingerprints(); + mNfcAid = mJavacardDevice.getAid(); + mNfcUserId = mJavacardDevice.getUserId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index ea57fe558..401db0b98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -250,9 +250,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints(); - mTokenAid = mCreateKeyActivity.nfcGetAid(); - mTokenUserId = mCreateKeyActivity.nfcGetUserId(); + mTokenFingerprints = mCreateKeyActivity.mJavacardDevice.getFingerprints(); + mTokenAid = mCreateKeyActivity.mJavacardDevice.getAid(); + mTokenUserId = mCreateKeyActivity.mJavacardDevice.getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index d2ecce242..7e1474eb7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -162,7 +162,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity case NFC_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mJavacardDevice.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -173,15 +173,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = nfcCalculateSignature(hash, algo); + byte[] signedHash = mJavacardDevice.calculateSignature(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"); + mJavacardDevice.setPin(new Passphrase("123456")); + mJavacardDevice.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -206,7 +206,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long keyGenerationTimestampMillis = key.getCreationTime().getTime(); long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(nfcGetAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mJavacardDevice.getAid(), 16); Passphrase passphrase; try { @@ -218,25 +218,25 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (key.canSign() || key.canCertify()) { if (shouldPutKey(key.getFingerprint(), 0)) { - nfcPutKey(0xB6, key, passphrase); - nfcPutData(0xCE, timestampBytes); - nfcPutData(0xC7, key.getFingerprint()); + mJavacardDevice.putKey(0xB6, key, passphrase); + mJavacardDevice.putData(0xCE, timestampBytes); + mJavacardDevice.putData(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()); + mJavacardDevice.putKey(0xB8, key, passphrase); + mJavacardDevice.putData(0xCF, timestampBytes); + mJavacardDevice.putData(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()); + mJavacardDevice.putKey(0xA4, key, passphrase); + mJavacardDevice.putData(0xD0, timestampBytes); + mJavacardDevice.putData(0xC9, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new authentication key."); } @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - nfcModifyPin(0x81, newPin); - nfcModifyPin(0x83, newAdminPin); + mJavacardDevice.modifyPin(0x81, newPin); + mJavacardDevice.modifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - nfcReset(); + mJavacardDevice.resetAndWipeToken(); break; } @@ -330,7 +330,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = nfcGetMasterKeyFingerprint(idx); + byte[] tokenFingerprint = mJavacardDevice.getMasterKeyFingerprint(idx); // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index d81797454..7d6ba5c8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -649,9 +649,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements @Override protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + mNfcFingerprints = mJavacardDevice.getFingerprints(); + mNfcUserId = mJavacardDevice.getUserId(); + mNfcAid = mJavacardDevice.getAid(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 77044cd2b..94d640768 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -35,16 +35,19 @@ import android.os.AsyncTask; import android.os.Bundle; import nordpol.Apdu; +import nordpol.IsoCard; import nordpol.android.TagDispatcher; import nordpol.android.AndroidCard; import nordpol.android.OnDiscoveredTagListener; -import nordpol.IsoCard; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; +import org.sufficientlysecure.keychain.javacard.JavacardDevice; +import org.sufficientlysecure.keychain.javacard.NfcTransport; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -72,22 +75,20 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - // Fidesmo constants - private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - protected Passphrase mPin; - protected Passphrase mAdminPin; - protected boolean mPw1ValidForMultipleSignatures; - protected boolean mPw1ValidatedForSignature; - protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - protected boolean mPw3Validated; + //protected Passphrase mPin; + //protected Passphrase mAdminPin; + //protected boolean mPw1ValidForMultipleSignatures; + //protected boolean mPw1ValidatedForSignature; + //protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + //protected boolean mPw3Validated; + + public JavacardDevice mJavacardDevice; protected TagDispatcher mTagDispatcher; - private IsoCard mIsoCard; +// private IsoCard mIsoCard; private boolean mTagHandlingEnabled; - private static final int TIMEOUT = 100000; - private byte[] mNfcFingerprints; private String mNfcUserId; private byte[] mNfcAid; @@ -102,9 +103,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * Override to implement NFC operations (background thread) */ protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + mNfcFingerprints = mJavacardDevice.getFingerprints(); + mNfcUserId = mJavacardDevice.getUserId(); + mNfcAid = mJavacardDevice.getAid(); } /** @@ -316,7 +317,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoToken()) { + if (mJavacardDevice.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -363,7 +364,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mPin = passphrase; + mJavacardDevice.setPin(passphrase); return; } @@ -388,7 +389,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mPin = input.getPassphrase(); + mJavacardDevice.setPin(input.getPassphrase()); break; } default: @@ -413,573 +414,19 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen protected void handleTagDiscovered(Tag tag) throws IOException { // Connect to the detected tag, setting a couple of settings - mIsoCard = AndroidCard.get(tag); - if (mIsoCard == null) { + IsoCard isoCard = AndroidCard.get(tag); + if (isoCard == null) { throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mIsoCard.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation - mIsoCard.connect(); - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - - // Command APDU (page 51) for SELECT FILE command (page 29) - String opening = - "00" // CLA - + "A4" // INS - + "04" // P1 - + "00" // P2 - + "06" // Lc (number of bytes) - + "D27600012401" // Data (6 bytes) - + "00"; // Le - String response = nfcCommunicate(opening); // activate connection - if ( ! response.endsWith(accepted) ) { - throw new CardException("Initialization failed!", parseCardStatus(response)); - } - byte[] pwStatusBytes = nfcGetPwStatusBytes(); - mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); - mPw1ValidatedForSignature = false; - mPw1ValidatedForDecrypt = false; - mPw3Validated = false; + mJavacardDevice = new BaseJavacardDevice(new NfcTransport(isoCard)); + mJavacardDevice.connectToDevice(); doNfcInBackground(); - } public boolean isNfcConnected() { - return mIsoCard.isConnected(); - } - - /** Return the key id from application specific data stored on tag, or null - * if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The long key id of the requested key, or null if not found. - */ - public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = nfcGetMasterKeyFingerprint(idx); - if (fp == null) { - return null; - } - ByteBuffer buf = ByteBuffer.wrap(fp); - // skip first 12 bytes of the fingerprint - buf.position(12); - // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) - return buf.getLong(); - } - - /** Return fingerprints of all keys from application specific data stored - * on tag, or null if data not available. - * - * @return The fingerprints of all subkeys in a contiguous byte array. - */ - public byte[] nfcGetFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mIsoCard.transceive(Hex.decode(data)); - - Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); - Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); - - Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); - if (fptlv == null) { - return null; - } - - return fptlv.mV; - } - - /** Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed. - * - * @return Seven bytes in fixed format, plus 0x9000 status word at the end. - */ - public byte[] nfcGetPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mIsoCard.transceive(Hex.decode(data)); - } - - /** Return the fingerprint from application specific data stored on tag, or - * null if it doesn't exist. - * - * @param idx Index of the key to return the fingerprint from. - * @return The fingerprint of the requested key, or null if not found. - */ - public byte[] nfcGetMasterKeyFingerprint(int idx) throws IOException { - byte[] data = nfcGetFingerprints(); - if (data == null) { - return null; - } - - // return the master key fingerprint - ByteBuffer fpbuf = ByteBuffer.wrap(data); - byte[] fp = new byte[20]; - fpbuf.position(idx * 20); - fpbuf.get(fp, 0, 20); - - return fp; - } - - public byte[] nfcGetAid() throws IOException { - String info = "00CA004F00"; - return mIsoCard.transceive(Hex.decode(info)); - } - - public String nfcGetUserId() throws IOException { - String info = "00CA006500"; - return getHolderName(nfcCommunicate(info)); - } - - /** - * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value - * - * @param hash the hash for signing - * @return a big integer representing the MPI for the given hash - */ - public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { - if (!mPw1ValidatedForSignature) { - nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) - } - - // dsi, including Lc - String dsi; - - Log.i(Constants.TAG, "Hash: " + hashAlgo); - switch (hashAlgo) { - case HashAlgorithmTags.SHA1: - if (hash.length != 20) { - throw new IOException("Bad hash length (" + hash.length + ", expected 10!"); - } - dsi = "23" // Lc - + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes - + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes - + "0605" + "2B0E03021A" // OID of SHA1 - + "0500" // TLV coding of ZERO - + "0414" + getHex(hash); // 0x14 are 20 hash bytes - break; - case HashAlgorithmTags.RIPEMD160: - if (hash.length != 20) { - throw new IOException("Bad hash length (" + hash.length + ", expected 20!"); - } - dsi = "233021300906052B2403020105000414" + getHex(hash); - break; - case HashAlgorithmTags.SHA224: - if (hash.length != 28) { - throw new IOException("Bad hash length (" + hash.length + ", expected 28!"); - } - dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); - break; - case HashAlgorithmTags.SHA256: - if (hash.length != 32) { - throw new IOException("Bad hash length (" + hash.length + ", expected 32!"); - } - dsi = "333031300D060960864801650304020105000420" + getHex(hash); - break; - case HashAlgorithmTags.SHA384: - if (hash.length != 48) { - throw new IOException("Bad hash length (" + hash.length + ", expected 48!"); - } - dsi = "433041300D060960864801650304020205000430" + getHex(hash); - break; - case HashAlgorithmTags.SHA512: - if (hash.length != 64) { - throw new IOException("Bad hash length (" + hash.length + ", expected 64!"); - } - dsi = "533051300D060960864801650304020305000440" + getHex(hash); - break; - default: - throw new IOException("Not supported hash algo!"); - } - - // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) - String apdu = - "002A9E9A" // CLA, INS, P1, P2 - + dsi // digital signature input - + "00"; // Le - - String response = nfcCommunicate(apdu); - - // split up response into signature and status - String status = response.substring(response.length()-4); - String signature = response.substring(0, response.length() - 4); - - // while we are getting 0x61 status codes, retrieve more data - while (status.substring(0, 2).equals("61")) { - Log.d(Constants.TAG, "requesting more data, status " + status); - // Send GET RESPONSE command - response = nfcCommunicate("00C00000" + status.substring(2)); - status = response.substring(response.length()-4); - signature += response.substring(0, response.length()-4); - } - - Log.d(Constants.TAG, "final response:" + status); - - if (!mPw1ValidForMultipleSignatures) { - mPw1ValidatedForSignature = false; - } - - if ( ! "9000".equals(status)) { - throw new CardException("Bad NFC response code: " + status, parseCardStatus(response)); - } - - // Make sure the signature we received is actually the expected number of bytes long! - if (signature.length() != 256 && signature.length() != 512) { - throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); - } - - return Hex.decode(signature); - } - - /** - * Call DECIPHER command - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - */ - public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { - if (!mPw1ValidatedForDecrypt) { - nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) - } - - String firstApdu = "102a8086fe"; - String secondApdu = "002a808603"; - String le = "00"; - - byte[] one = new byte[254]; - // leave out first byte: - System.arraycopy(encryptedSessionKey, 1, one, 0, one.length); - - byte[] two = new byte[encryptedSessionKey.length - 1 - one.length]; - for (int i = 0; i < two.length; i++) { - two[i] = encryptedSessionKey[i + one.length + 1]; - } - - nfcCommunicate(firstApdu + getHex(one)); - String second = nfcCommunicate(secondApdu + getHex(two) + le); - - String decryptedSessionKey = getDataField(second); - - return Hex.decode(decryptedSessionKey); - } - - /** Verifies the user's PW1 or PW3 with the appropriate mode. - * - * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. - * For PW3 (Admin PIN), mode is 0x83. - */ - public void nfcVerifyPin(int mode) throws IOException { - if (mPin != null || mode == 0x83) { - - byte[] pin; - if (mode == 0x83) { - pin = mAdminPin.toStringUnsafe().getBytes(); - } else { - pin = mPin.toStringUnsafe().getBytes(); - } - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - String response = nfcTryPin(mode, pin); // login - if (!response.equals(accepted)) { - throw new CardException("Bad PIN!", parseCardStatus(response)); - } - - if (mode == 0x81) { - mPw1ValidatedForSignature = true; - } else if (mode == 0x82) { - mPw1ValidatedForDecrypt = true; - } else if (mode == 0x83) { - mPw3Validated = true; - } - } - } - - /** - * Resets security token, which deletes all keys and data objects. - * This works by entering a wrong PIN and then Admin PIN 4 times respectively. - * Afterwards, the token is reactivated. - */ - public void nfcReset() throws IOException { - String accepted = "9000"; - - // try wrong PIN 4 times until counter goes to C0 - byte[] pin = "XXXXXX".getBytes(); - for (int i = 0; i <= 4; i++) { - String response = nfcTryPin(0x81, pin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); - } - } - - // try wrong Admin PIN 4 times until counter goes to C0 - byte[] adminPin = "XXXXXXXX".getBytes(); - for (int i = 0; i <= 4; i++) { - String response = nfcTryPin(0x83, adminPin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); - } - } - - // reactivate token! - String reactivate1 = "00" + "e6" + "00" + "00"; - String reactivate2 = "00" + "44" + "00" + "00"; - String response1 = nfcCommunicate(reactivate1); - String response2 = nfcCommunicate(reactivate2); - if (!response1.equals(accepted) || !response2.equals(accepted)) { - throw new CardException("Reactivating failed!", parseCardStatus(response1)); - } - - } - - private String nfcTryPin(int mode, byte[] pin) throws IOException { - // Command APDU for VERIFY command (page 32) - String login = - "00" // CLA - + "20" // INS - + "00" // P1 - + String.format("%02x", mode) // P2 - + String.format("%02x", pin.length) // Lc - + Hex.toHexString(pin); - - return nfcCommunicate(login); - } - - /** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for - * conformance to the token's requirements for key length. - * - * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPin The new PW1 or PW3. - */ - public void nfcModifyPin(int pw, byte[] newPin) throws IOException { - final int MAX_PW1_LENGTH_INDEX = 1; - final int MAX_PW3_LENGTH_INDEX = 3; - - byte[] pwStatusBytes = nfcGetPwStatusBytes(); - - if (pw == 0x81) { - if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else if (pw == 0x83) { - if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else { - throw new IOException("Invalid PW index for modify PIN operation"); - } - - byte[] pin; - if (pw == 0x83) { - pin = mAdminPin.toStringUnsafe().getBytes(); - } else { - pin = mPin.toStringUnsafe().getBytes(); - } - - // Command APDU for CHANGE REFERENCE DATA command (page 32) - String changeReferenceDataApdu = "00" // CLA - + "24" // INS - + "00" // P1 - + String.format("%02x", pw) // P2 - + String.format("%02x", pin.length + newPin.length) // Lc - + getHex(pin) - + getHex(newPin); - String response = nfcCommunicate(changeReferenceDataApdu); // change PIN - if (!response.equals("9000")) { - throw new CardException("Failed to change PIN", parseCardStatus(response)); - } - } - - /** - * Stores a data object on the token. Automatically validates the proper PIN for the operation. - * Supported for all data objects < 255 bytes in length. Only the cardholder certificate - * (0x7F21) can exceed this length. - * - * @param dataObject The data object to be stored. - * @param data The data to store in the object - */ - public void nfcPutData(int dataObject, byte[] data) throws IOException { - if (data.length > 254) { - throw new IOException("Cannot PUT DATA with length > 254"); - } - if (dataObject == 0x0101 || dataObject == 0x0103) { - if (!mPw1ValidatedForDecrypt) { - nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) - } - } else if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3) - } - - String putDataApdu = "00" // CLA - + "DA" // INS - + String.format("%02x", (dataObject & 0xFF00) >> 8) // P1 - + String.format("%02x", dataObject & 0xFF) // P2 - + String.format("%02x", data.length) // Lc - + getHex(data); - - String response = nfcCommunicate(putDataApdu); // put data - if (!response.equals("9000")) { - throw new CardException("Failed to put data.", parseCardStatus(response)); - } - } - - /** - * Puts a key on the token in the given slot. - * - * @param slot The slot on the token where the key should be stored: - * 0xB6: Signature Key - * 0xB8: Decipherment Key - * 0xA4: Authentication Key - */ - public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) - throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - RSAPrivateCrtKey crtSecretKey; - try { - secretKey.unlock(passphrase); - crtSecretKey = secretKey.getCrtSecretKey(); - } catch (PgpGeneralException e) { - throw new IOException(e.getMessage()); - } - - // Shouldn't happen; the UI should block the user from getting an incompatible key this far. - if (crtSecretKey.getModulus().bitLength() > 2048) { - throw new IOException("Key too large to export to Security Token."); - } - - // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. - if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { - throw new IOException("Invalid public exponent for smart Security Token."); - } - - if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3 with mode 83) - } - - byte[] header= Hex.decode( - "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23) - + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length - + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex) - + "9103" // Public modulus, length 3 - + "928180" // Prime P, length 128 - + "938180" // Prime Q, length 128 - + "948180" // Coefficient (1/q mod p), length 128 - + "958180" // Prime exponent P (d mod (p - 1)), length 128 - + "968180" // Prime exponent Q (d mod (1 - 1)), length 128 - + "97820100" // Modulus, length 256, last item in private key template - + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow - byte[] dataToSend = new byte[934]; - byte[] currentKeyObject; - int offset = 0; - - System.arraycopy(header, 0, dataToSend, offset, header.length); - offset += header.length; - currentKeyObject = crtSecretKey.getPublicExponent().toByteArray(); - System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3); - offset += 3; - // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0 - // in the array to represent sign, so we take care to set the offset to 1 if necessary. - currentKeyObject = crtSecretKey.getPrimeP().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeQ().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128); - Arrays.fill(currentKeyObject, (byte)0); - offset += 128; - currentKeyObject = crtSecretKey.getModulus().toByteArray(); - System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256); - - String putKeyCommand = "10DB3FFF"; - String lastPutKeyCommand = "00DB3FFF"; - - // Now we're ready to communicate with the token. - offset = 0; - String response; - while(offset < dataToSend.length) { - int dataRemaining = dataToSend.length - offset; - if (dataRemaining > 254) { - response = nfcCommunicate( - putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) - ); - offset += 254; - } else { - int length = dataToSend.length - offset; - response = nfcCommunicate( - lastPutKeyCommand + String.format("%02x", length) - + Hex.toHexString(dataToSend, offset, length)); - offset += length; - } - - if (!response.endsWith("9000")) { - throw new CardException("Key export to Security Token failed", parseCardStatus(response)); - } - } - - // Clear array with secret data before we return. - Arrays.fill(dataToSend, (byte) 0); - } - - /** - * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), - * this command also has the effect of resetting the digital signature counter. - * NOTE: This does not set the key fingerprint data object! After calling this command, you - * must construct a public key packet using the returned public key data objects, compute the - * key fingerprint, and store it on the card using: nfcPutData(0xC8, key.getFingerprint()) - * - * @param slot The slot on the card where the key should be generated: - * 0xB6: Signature Key - * 0xB8: Decipherment Key - * 0xA4: Authentication Key - * @return the public key data objects, in TLV format. For RSA this will be the public modulus - * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. - */ - public byte[] nfcGenerateKey(int slot) throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3 with mode 83) - } - - String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; - String getResponseApdu = "00C00000"; - - String first = nfcCommunicate(generateKeyApdu); - String second = nfcCommunicate(getResponseApdu); - - if (!second.endsWith("9000")) { - throw new IOException("On-card key generation failed"); - } - - String publicKeyData = getDataField(first) + getDataField(second); - - Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); - - return Hex.decode(publicKeyData); - } - - /** - * Transceive data via NFC encoded as Hex - */ - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoCard.transceive(Hex.decode(apdu))); + return mJavacardDevice.isConnected(); } /** @@ -1020,10 +467,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - public static String getHex(byte[] raw) { return new String(Hex.encode(raw)); } @@ -1050,21 +493,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private boolean isFidesmoToken() { - if (isNfcConnected()) { // Check if we can still talk to the card - try { - // By trying to select any apps that have the Fidesmo AID prefix we can - // see if it is a Fidesmo device or not - byte[] mSelectResponse = mIsoCard.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); - // Compare the status returned by our select with the OK status code - return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); - } catch (IOException e) { - Log.e(Constants.TAG, "Card communication failed!", e); - } - } - return false; - } - /** * Ask user if she wants to install PGP onto her Fidesmo token */ -- cgit v1.2.3 From bd2906a887f22c415f9b194481607660db2216f6 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Mon, 4 Apr 2016 00:33:59 +0600 Subject: OTG: Add usb device manager prototype --- .../javacard/OnDiscoveredUsbDeviceListener.java | 7 ++ .../keychain/javacard/UsbConnectionManager.java | 135 +++++++++++++++++++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 96 ++++++++++----- 3 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java new file mode 100644 index 000000000..6104985be --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.hardware.usb.UsbDevice; + +public interface OnDiscoveredUsbDeviceListener { + void usbDeviceDiscovered(UsbDevice usbDevice); +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java new file mode 100644 index 000000000..d140c771c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java @@ -0,0 +1,135 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +public class UsbConnectionManager { + private static final String LOG_TAG = UsbConnectionManager.class.getName(); + private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION"; + + private Activity mActivity; + private OnDiscoveredUsbDeviceListener mListener; + private final Semaphore mRunning = new Semaphore(1); + private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); + + /** + * Receives broadcast when a supported USB device is attached, detached or + * when a permission to communicate to the device has been granted. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + String deviceName = usbDevice.getDeviceName(); + + if (ACTION_USB_PERMISSION.equals(action)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + interceptIntent(intent); + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + + private final Thread mWatchThread = new Thread() { + @Override + public void run() { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + + while (true) { + mRunning.acquireUninterruptibly(); + mRunning.release(); + + // + + final UsbDevice device = getDevice(usbManager); + if (device != null && !mProcessedDevices.contains(device)) { + mProcessedDevices.add(device); + + final Intent intent = new Intent(ACTION_USB_PERMISSION); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USB_PERMISSION); + mActivity.registerReceiver(mUsbReceiver, filter); + + usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); + } + + try { + sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }; + + public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + mRunning.acquireUninterruptibly(); + mWatchThread.start(); + } + + public void startListeningForDevices() { + mRunning.release(); + } + + public void stopListeningForDevices() { + mRunning.acquireUninterruptibly(); + } + + public void interceptIntent(final Intent intent) { + if (intent == null || intent.getAction() == null) return; + switch (intent.getAction()) { + case UsbManager.ACTION_USB_DEVICE_ATTACHED: { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + usbI.setAction(ACTION_USB_PERMISSION); + usbI.putExtra(UsbManager.EXTRA_DEVICE, device); + PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT); + usbManager.requestPermission(device, pi); + break; + } + case ACTION_USB_PERMISSION: { + UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) + mListener.usbDeviceDiscovered(device); + break; + } + default: + break; + } + } + + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + Log.d(LOG_TAG, device.getDeviceName() + " " + device.getDeviceId()); + if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + Log.d(LOG_TAG, device.getDeviceName() + " OK"); + return device; + } + } + return null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 94d640768..573123daf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -20,42 +20,32 @@ 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.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.AsyncTask; import android.os.Bundle; -import nordpol.Apdu; -import nordpol.IsoCard; -import nordpol.android.TagDispatcher; -import nordpol.android.AndroidCard; -import nordpol.android.OnDiscoveredTagListener; - -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; import org.sufficientlysecure.keychain.javacard.JavacardDevice; import org.sufficientlysecure.keychain.javacard.NfcTransport; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.javacard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.javacard.UsbConnectionManager; +import org.sufficientlysecure.keychain.javacard.UsbTransport; 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; @@ -66,27 +56,27 @@ import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implements OnDiscoveredTagListener { +import java.io.IOException; + +import nordpol.IsoCard; +import nordpol.android.AndroidCard; +import nordpol.android.OnDiscoveredTagListener; +import nordpol.android.TagDispatcher; + +public abstract class BaseSecurityTokenNfcActivity extends BaseActivity + implements OnDiscoveredTagListener, OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - //protected Passphrase mPin; - //protected Passphrase mAdminPin; - //protected boolean mPw1ValidForMultipleSignatures; - //protected boolean mPw1ValidatedForSignature; - //protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - //protected boolean mPw3Validated; - public JavacardDevice mJavacardDevice; protected TagDispatcher mTagDispatcher; -// private IsoCard mIsoCard; + protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; private byte[] mNfcFingerprints; @@ -185,6 +175,43 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen }.execute(); } + + public void usbDeviceDiscovered(final UsbDevice device) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + if(!mTagHandlingEnabled) + return; + new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onNfcPreExecute(); + } + + @Override + protected IOException doInBackground(Void... params) { + try { + handleUsbDevice(device); + } 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; } @@ -198,6 +225,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onCreate(savedInstanceState); mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); + mUsbDispatcher = new UsbConnectionManager(this, this); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -228,7 +256,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen */ @Override public void onNewIntent(final Intent intent) { - mTagDispatcher.interceptIntent(intent); + if (!mTagDispatcher.interceptIntent(intent)) { + mUsbDispatcher.interceptIntent(intent); + } } private void handleNfcError(IOException e) { @@ -346,6 +376,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Log.d(Constants.TAG, "BaseNfcActivity.onPause"); mTagDispatcher.disableExclusiveNfc(); + mUsbDispatcher.stopListeningForDevices(); } /** @@ -356,6 +387,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onResume(); Log.d(Constants.TAG, "BaseNfcActivity.onResume"); mTagDispatcher.enableExclusiveNfc(); + mUsbDispatcher.startListeningForDevices(); } protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { @@ -372,7 +404,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, RequiredInputParcel.createRequiredPassphrase(requiredInput)); startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (KeyNotFoundException e) { + } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( "tried to find passphrase for non-existing key. this is a programming error!"); } @@ -425,6 +457,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen doNfcInBackground(); } + protected void handleUsbDevice(UsbDevice device) throws IOException { + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + mJavacardDevice = new BaseJavacardDevice(new UsbTransport(device, usbManager)); + mJavacardDevice.connectToDevice(); + + doNfcInBackground(); + } + public boolean isNfcConnected() { return mJavacardDevice.isConnected(); } -- cgit v1.2.3 From 3d538d885e0ed52640ea72d538fe1752a5ffe1f2 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Mon, 4 Apr 2016 01:31:14 +0600 Subject: OTG: Usb device manager fixes --- .../keychain/javacard/BaseJavacardDevice.java | 13 ++- .../javacard/CachingBaseJavacardDevice.java | 46 ----------- .../keychain/javacard/JavacardDevice.java | 2 + .../keychain/javacard/UsbConnectionManager.java | 94 ++++++++++++---------- .../ui/base/BaseSecurityTokenNfcActivity.java | 12 ++- 5 files changed, 74 insertions(+), 93 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index 46f4c0443..f81d234b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -22,7 +22,7 @@ public class BaseJavacardDevice implements JavacardDevice { private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; 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 final Transport mTransport; + private Transport mTransport; private Passphrase mPin; private Passphrase mAdminPin; @@ -32,8 +32,7 @@ public class BaseJavacardDevice implements JavacardDevice { private boolean mPw3Validated; private boolean mTagHandlingEnabled; - public BaseJavacardDevice(final Transport mTransport) { - this.mTransport = mTransport; + public BaseJavacardDevice() { } private static String getHex(byte[] raw) { @@ -528,6 +527,9 @@ public class BaseJavacardDevice implements JavacardDevice { String response = nfcCommunicate(apdu); + if (response.length() < 4) { + throw new CardException("Bad response", (short) 0); + } // split up response into signature and status String status = response.substring(response.length() - 4); String signature = response.substring(0, response.length() - 4); @@ -722,4 +724,9 @@ public class BaseJavacardDevice implements JavacardDevice { return fp; } + @Override + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java deleted file mode 100644 index 5ad157de1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; - -public class CachingBaseJavacardDevice extends BaseJavacardDevice { - private byte[] mFingerprintsCache; - private String mUserIdCache; - private byte[] mAidCache; - - public CachingBaseJavacardDevice(final Transport mTransport) { - super(mTransport); - } - - @Override - public byte[] getFingerprints() throws IOException { - if (mFingerprintsCache == null) { - mFingerprintsCache = super.getFingerprints(); - } - return mFingerprintsCache; - } - - @Override - public String getUserId() throws IOException { - if (mUserIdCache == null) { - mUserIdCache = super.getUserId(); - } - return mUserIdCache; - } - - @Override - public byte[] getAid() throws IOException { - if (mAidCache == null) { - mAidCache = super.getAid(); - } - return mAidCache; - } - - @Override - public void changeKey(final CanonicalizedSecretKey secretKey, final Passphrase passphrase) throws IOException { - super.changeKey(secretKey, passphrase); - mFingerprintsCache = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java index 04c2c0006..63d4d2ad7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -100,4 +100,6 @@ public interface JavacardDevice { * @param data The data to store in the object */ void putData(int dataObject, byte[] data) throws IOException; + + void setTransport(Transport mTransport); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java index d140c771c..9dd3bc028 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java @@ -8,6 +8,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.os.Handler; +import android.os.Looper; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; @@ -17,50 +19,29 @@ import java.util.HashMap; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; public class UsbConnectionManager { private static final String LOG_TAG = UsbConnectionManager.class.getName(); private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION"; - - private Activity mActivity; - private OnDiscoveredUsbDeviceListener mListener; private final Semaphore mRunning = new Semaphore(1); private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - - /** - * Receives broadcast when a supported USB device is attached, detached or - * when a permission to communicate to the device has been granted. - */ - private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - String deviceName = usbDevice.getDeviceName(); - - if (ACTION_USB_PERMISSION.equals(action)) { - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - - interceptIntent(intent); - - context.unregisterReceiver(mUsbReceiver); - } - } - }; - + private final AtomicBoolean mStopped = new AtomicBoolean(false); + private Activity mActivity; private final Thread mWatchThread = new Thread() { @Override public void run() { final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - while (true) { - mRunning.acquireUninterruptibly(); + while (!mStopped.get()) { + try { + mRunning.acquire(); + } catch (InterruptedException e) { + } mRunning.release(); + if (mStopped.get()) return; // - final UsbDevice device = getDevice(usbManager); if (device != null && !mProcessedDevices.contains(device)) { mProcessedDevices.add(device); @@ -71,6 +52,7 @@ public class UsbConnectionManager { filter.addAction(ACTION_USB_PERMISSION); mActivity.registerReceiver(mUsbReceiver, filter); + Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); } @@ -81,6 +63,30 @@ public class UsbConnectionManager { } } }; + private OnDiscoveredUsbDeviceListener mListener; + /** + * Receives broadcast when a supported USB device is attached, detached or + * when a permission to communicate to the device has been granted. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + String deviceName = usbDevice.getDeviceName(); + + if (ACTION_USB_PERMISSION.equals(action)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + interceptIntent(intent); + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + private Handler handler = new Handler(Looper.getMainLooper()); public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; @@ -89,6 +95,16 @@ public class UsbConnectionManager { mWatchThread.start(); } + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + return device; + } + } + return null; + } + public void startListeningForDevices() { mRunning.release(); } @@ -100,7 +116,7 @@ public class UsbConnectionManager { public void interceptIntent(final Intent intent) { if (intent == null || intent.getAction() == null) return; switch (intent.getAction()) { - case UsbManager.ACTION_USB_DEVICE_ATTACHED: { + /*case UsbManager.ACTION_USB_DEVICE_ATTACHED: { final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -109,7 +125,7 @@ public class UsbConnectionManager { PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT); usbManager.requestPermission(device, pi); break; - } + }*/ case ACTION_USB_PERMISSION: { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) @@ -121,15 +137,11 @@ public class UsbConnectionManager { } } - private static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - Log.d(LOG_TAG, device.getDeviceName() + " " + device.getDeviceId()); - if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { - Log.d(LOG_TAG, device.getDeviceName() + " OK"); - return device; - } + public void onDestroy() { + mStopped.set(true); + try { + mActivity.unregisterReceiver(mUsbReceiver); + } catch (IllegalArgumentException ignore) { } - return null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 573123daf..e3c331b0b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -74,7 +74,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public JavacardDevice mJavacardDevice; + public JavacardDevice mJavacardDevice = new BaseJavacardDevice(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -451,7 +451,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mJavacardDevice = new BaseJavacardDevice(new NfcTransport(isoCard)); + mJavacardDevice.setTransport(new NfcTransport(isoCard)); mJavacardDevice.connectToDevice(); doNfcInBackground(); @@ -459,7 +459,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleUsbDevice(UsbDevice device) throws IOException { UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mJavacardDevice = new BaseJavacardDevice(new UsbTransport(device, usbManager)); + mJavacardDevice.setTransport(new UsbTransport(device, usbManager)); mJavacardDevice.connectToDevice(); doNfcInBackground(); @@ -567,4 +567,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } return mAppInstalled; } + + @Override + protected void onDestroy() { + super.onDestroy(); + mUsbDispatcher.onDestroy(); + } } -- cgit v1.2.3 From 79a0918072e2b4a01f328cb8d8d2a0a8761394f6 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Wed, 6 Apr 2016 01:33:01 +0600 Subject: OTG: Fix usb transport --- .../keychain/javacard/BaseJavacardDevice.java | 20 ---- .../keychain/javacard/UsbConnectionManager.java | 9 +- .../keychain/javacard/UsbTransport.java | 122 +++++++++++++++++---- 3 files changed, 108 insertions(+), 43 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index f81d234b3..796b4e1f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -393,26 +393,6 @@ public class BaseJavacardDevice implements JavacardDevice { Arrays.fill(dataToSend, (byte) 0); } - - /** - * 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 = getMasterKeyFingerprint(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. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java index 9dd3bc028..6b049159a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java @@ -80,13 +80,14 @@ public class UsbConnectionManager { false); Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - interceptIntent(intent); + if (permission) { + interceptIntent(intent); + } context.unregisterReceiver(mUsbReceiver); } } }; - private Handler handler = new Handler(Looper.getMainLooper()); public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; @@ -98,7 +99,7 @@ public class UsbConnectionManager { private static UsbDevice getDevice(UsbManager manager) { HashMap deviceList = manager.getDeviceList(); for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { return device; } } @@ -139,9 +140,11 @@ public class UsbConnectionManager { public void onDestroy() { mStopped.set(true); + mRunning.release(); try { mActivity.unregisterReceiver(mUsbReceiver); } catch (IllegalArgumentException ignore) { } + mActivity = null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java index f2af34c39..07697f11e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java @@ -11,10 +11,14 @@ import android.support.annotation.Nullable; import android.util.Pair; import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; public class UsbTransport implements Transport { private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 1000; // 1 s + private static final int TIMEOUT = 20 * 1000; // 2 s private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; @@ -22,7 +26,7 @@ public class UsbTransport implements Transport { private final UsbEndpoint mBulkIn; private final UsbEndpoint mBulkOut; private final UsbDeviceConnection mConnection; - private byte counter = 0; + private byte mCounter = 0; public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { mUsbDevice = usbDevice; @@ -40,17 +44,60 @@ public class UsbTransport implements Transport { mConnection.claimInterface(mUsbInterface, true); // check result + powerOn(); + + setTimings(); + } + + private void setTimings() throws TransportIoException { + byte[] data = { + 0x6C, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, 0x00, 0x00 + }; + sendRaw(data); + data = receive(); + + data[0] = 0x61; + data[1] = 0x04; + data[2] = data[3] = data[4] = 0x00; + data[5] = 0x00; + data[6] = mCounter++; + data[7] = 0x00; + data[8] = data[9] = 0x00; + + data[13] = 1; + + sendRaw(data); + receive(); + } + + private void powerOff() throws TransportIoException { + final byte[] iccPowerOff = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOff); + receive(); + } + + void powerOn() throws TransportIoException { final byte[] iccPowerOn = { 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, - counter++, - 0x03, + mCounter++, + 0x00, 0x00, 0x00 }; sendRaw(iccPowerOn); - receiveRaw(); - // Check result + receive(); } /** @@ -101,27 +148,58 @@ public class UsbTransport implements Transport { } @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException { + public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); - return receive(); + byte[] bytes; + do { + bytes = receive(); + } while (isXfrBlockNotReady(bytes)); + + checkXfrBlockResult(bytes); + return Arrays.copyOfRange(bytes, 10, bytes.length); } - public void send(final byte[] d) throws TransportIoException { + public void send(byte[] d) throws TransportIoException { int l = d.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), 0x00, - counter++, - 0x01, + mCounter++, + 0x00, 0x00, 0x00}, d); - sendRaw(data); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } } public byte[] receive() throws TransportIoException { - final byte[] bytes = receiveRaw(); - return Arrays.copyOfRange(bytes, 10, bytes.length); + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new TransportIoException("USB error, failed to receive ccid header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; } private void sendRaw(final byte[] data) throws TransportIoException { @@ -131,14 +209,18 @@ public class UsbTransport implements Transport { } } - private byte[] receiveRaw() throws TransportIoException { - byte[] buffer = new byte[1024]; + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); + private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } + } - return Arrays.copyOfRange(buffer, 0, res); + private boolean isXfrBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; } } -- cgit v1.2.3 From 5e18b15775f4c6d9c563d61a71143320620e968e Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Wed, 6 Apr 2016 22:49:52 +0600 Subject: OTG: Rename 'javacard' package, methods, remove JavacardInterface --- .../keychain/javacard/CardException.java | 17 - .../keychain/javacard/KeyType.java | 48 -- .../keychain/javacard/NfcTransport.java | 31 - .../javacard/OnDiscoveredUsbDeviceListener.java | 7 - .../keychain/javacard/PinException.java | 7 - .../keychain/javacard/PinType.java | 16 - .../keychain/javacard/Transport.java | 11 - .../keychain/javacard/TransportIoException.java | 20 - .../keychain/javacard/UsbConnectionManager.java | 150 ----- .../keychain/javacard/UsbTransport.java | 226 ------- .../keychain/smartcard/CardException.java | 17 + .../keychain/smartcard/KeyType.java | 48 ++ .../keychain/smartcard/NfcTransport.java | 31 + .../smartcard/OnDiscoveredUsbDeviceListener.java | 7 + .../keychain/smartcard/PinException.java | 7 + .../keychain/smartcard/PinType.java | 16 + .../keychain/smartcard/SmartcardDevice.java | 707 +++++++++++++++++++++ .../keychain/smartcard/Transport.java | 11 + .../keychain/smartcard/TransportIoException.java | 20 + .../keychain/smartcard/UsbConnectionManager.java | 148 +++++ .../keychain/smartcard/UsbTransport.java | 226 +++++++ .../keychain/ui/CreateKeyActivity.java | 6 +- .../ui/CreateSecurityTokenImportResetFragment.java | 7 +- .../ui/SecurityTokenOperationActivity.java | 36 +- .../keychain/ui/ViewKeyActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 53 +- 26 files changed, 1291 insertions(+), 588 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java deleted file mode 100644 index 3e9e9f2ca..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java deleted file mode 100644 index e0190f8bd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; - -public enum KeyType { - SIGN(0, 0xB6, 0xCE, 0xC7), - ENCRYPT(1, 0xB8, 0xCF, 0xC8), - AUTH(2, 0xA4, 0xD0, 0xC9),; - - private final int mIdx; - private final int mSlot; - private final int mTimestampObjectId; - private final int mFingerprintObjectId; - - KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { - this.mIdx = idx; - this.mSlot = slot; - this.mTimestampObjectId = timestampObjectId; - this.mFingerprintObjectId = fingerprintObjectId; - } - - public static KeyType from(final CanonicalizedSecretKey key) { - if (key.canSign() || key.canCertify()) { - return SIGN; - } else if (key.canEncrypt()) { - return ENCRYPT; - } else if (key.canAuthenticate()) { - return AUTH; - } - return null; - } - - public int getIdx() { - return mIdx; - } - - public int getmSlot() { - return mSlot; - } - - public int getTimestampObjectId() { - return mTimestampObjectId; - } - - public int getmFingerprintObjectId() { - return mFingerprintObjectId; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java deleted file mode 100644 index 421b28aa8..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -import nordpol.IsoCard; - -public class NfcTransport implements Transport { - // timeout is set to 100 seconds to avoid cancellation during calculation - private static final int TIMEOUT = 100 * 1000; - private final IsoCard mIsoCard; - - public NfcTransport(final IsoCard isoDep) throws IOException { - this.mIsoCard = isoDep; - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); - } - - @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { - return mIsoCard.transceive(data); - } - - @Override - public void release() { - } - - @Override - public boolean isConnected() { - return mIsoCard.isConnected(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java deleted file mode 100644 index 6104985be..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.hardware.usb.UsbDevice; - -public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java deleted file mode 100644 index 84a34f116..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public class PinException extends CardException { - public PinException(final String detailMessage, final short responseCode) { - super(detailMessage, responseCode); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java deleted file mode 100644 index b6787a9e1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public enum PinType { - BASIC(0x81), - ADMIN(0x83),; - - private final int mMode; - - PinType(final int mode) { - this.mMode = mode; - } - - public int getmMode() { - return mMode; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java deleted file mode 100644 index 2d7dd3309..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public interface Transport { - byte[] sendAndReceive(byte[] data) throws IOException; - - void release(); - - boolean isConnected(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java deleted file mode 100644 index 2dfb7df94..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class TransportIoException extends IOException { - public TransportIoException() { - } - - public TransportIoException(final String detailMessage) { - super(detailMessage); - } - - public TransportIoException(final String message, final Throwable cause) { - super(message, cause); - } - - public TransportIoException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java deleted file mode 100644 index 6b049159a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.os.Handler; -import android.os.Looper; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -public class UsbConnectionManager { - private static final String LOG_TAG = UsbConnectionManager.class.getName(); - private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION"; - private final Semaphore mRunning = new Semaphore(1); - private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - private final AtomicBoolean mStopped = new AtomicBoolean(false); - private Activity mActivity; - private final Thread mWatchThread = new Thread() { - @Override - public void run() { - final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - - while (!mStopped.get()) { - try { - mRunning.acquire(); - } catch (InterruptedException e) { - } - mRunning.release(); - if (mStopped.get()) return; - - // - final UsbDevice device = getDevice(usbManager); - if (device != null && !mProcessedDevices.contains(device)) { - mProcessedDevices.add(device); - - final Intent intent = new Intent(ACTION_USB_PERMISSION); - - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_USB_PERMISSION); - mActivity.registerReceiver(mUsbReceiver, filter); - - Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); - usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); - } - - try { - sleep(1000); - } catch (InterruptedException ignored) { - } - } - } - }; - private OnDiscoveredUsbDeviceListener mListener; - /** - * Receives broadcast when a supported USB device is attached, detached or - * when a permission to communicate to the device has been granted. - */ - private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - String deviceName = usbDevice.getDeviceName(); - - if (ACTION_USB_PERMISSION.equals(action)) { - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - - if (permission) { - interceptIntent(intent); - } - - context.unregisterReceiver(mUsbReceiver); - } - } - }; - - public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - mRunning.acquireUninterruptibly(); - mWatchThread.start(); - } - - private static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { - return device; - } - } - return null; - } - - public void startListeningForDevices() { - mRunning.release(); - } - - public void stopListeningForDevices() { - mRunning.acquireUninterruptibly(); - } - - public void interceptIntent(final Intent intent) { - if (intent == null || intent.getAction() == null) return; - switch (intent.getAction()) { - /*case UsbManager.ACTION_USB_DEVICE_ATTACHED: { - final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - usbI.setAction(ACTION_USB_PERMISSION); - usbI.putExtra(UsbManager.EXTRA_DEVICE, device); - PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT); - usbManager.requestPermission(device, pi); - break; - }*/ - case ACTION_USB_PERMISSION: { - UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - if (device != null) - mListener.usbDeviceDiscovered(device); - break; - } - default: - break; - } - } - - public void onDestroy() { - mStopped.set(true); - mRunning.release(); - try { - mActivity.unregisterReceiver(mUsbReceiver); - } catch (IllegalArgumentException ignore) { - } - mActivity = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java deleted file mode 100644 index 07697f11e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Pair; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class UsbTransport implements Transport { - private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 2 s - - private final UsbManager mUsbManager; - private final UsbDevice mUsbDevice; - private final UsbInterface mUsbInterface; - private final UsbEndpoint mBulkIn; - private final UsbEndpoint mBulkOut; - private final UsbDeviceConnection mConnection; - private byte mCounter = 0; - - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { - mUsbDevice = usbDevice; - mUsbManager = usbManager; - - mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - // throw if any endpoint is null - - mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result - - powerOn(); - - setTimings(); - } - - private void setTimings() throws TransportIoException { - byte[] data = { - 0x6C, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, 0x00, 0x00 - }; - sendRaw(data); - data = receive(); - - data[0] = 0x61; - data[1] = 0x04; - data[2] = data[3] = data[4] = 0x00; - data[5] = 0x00; - data[6] = mCounter++; - data[7] = 0x00; - data[8] = data[9] = 0x00; - - data[13] = 1; - - sendRaw(data); - receive(); - } - - private void powerOff() throws TransportIoException { - final byte[] iccPowerOff = { - 0x63, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOff); - receive(); - } - - void powerOn() throws TransportIoException { - final byte[] iccPowerOn = { - 0x62, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOn); - receive(); - } - - /** - * Get first class 11 (Chip/Smartcard) interface for the device - * - * @param device {@link UsbDevice} which will be searched - * @return {@link UsbInterface} of smartcard or null if it doesn't exist - */ - @Nullable - private static UsbInterface getSmartCardInterface(final UsbDevice device) { - for (int i = 0; i < device.getInterfaceCount(); i++) { - final UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { - return anInterface; - } - } - return null; - } - - @NonNull - private static Pair getIoEndpoints(final UsbInterface usbInterface) { - UsbEndpoint bulkIn = null, bulkOut = null; - for (int i = 0; i < usbInterface.getEndpointCount(); i++) { - final UsbEndpoint endpoint = usbInterface.getEndpoint(i); - if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue; - } - - if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint; - } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint; - } - } - return new Pair<>(bulkIn, bulkOut); - } - - @Override - public void release() { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - } - - @Override - public boolean isConnected() { - // TODO: redo - return mUsbManager.getDeviceList().containsValue(mUsbDevice); - } - - @Override - public byte[] sendAndReceive(byte[] data) throws TransportIoException { - send(data); - byte[] bytes; - do { - bytes = receive(); - } while (isXfrBlockNotReady(bytes)); - - checkXfrBlockResult(bytes); - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - public void send(byte[] d) throws TransportIoException { - int l = d.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - d); - - int send = 0; - while (send < data.length) { - final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); - sendRaw(Arrays.copyOfRange(data, send, send + len)); - send += len; - } - } - - public byte[] receive() throws TransportIoException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new TransportIoException("USB error, failed to receive ccid header"); - } - totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; - result = new byte[totalBytes]; - } - System.arraycopy(buffer, 0, result, readBytes, res); - readBytes += res; - } while (readBytes < totalBytes); - - return result; - } - - private void sendRaw(final byte[] data) throws TransportIoException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new TransportIoException("USB error, failed to send data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isXfrBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java new file mode 100644 index 000000000..9ea67f711 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java new file mode 100644 index 000000000..625e1e669 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.smartcard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java new file mode 100644 index 000000000..557c6f37d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +import nordpol.IsoCard; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final IsoCard mIsoCard; + + public NfcTransport(final IsoCard isoDep) throws IOException { + this.mIsoCard = isoDep; + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + return mIsoCard.transceive(data); + } + + @Override + public void release() { + } + + @Override + public boolean isConnected() { + return mIsoCard.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java new file mode 100644 index 000000000..46b503b42 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.hardware.usb.UsbDevice; + +public interface OnDiscoveredUsbDeviceListener { + void usbDeviceDiscovered(UsbDevice usbDevice); +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java new file mode 100644 index 000000000..58a7a31c9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +public class PinException extends CardException { + public PinException(final String detailMessage, final short responseCode) { + super(detailMessage, responseCode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java new file mode 100644 index 000000000..7601edcf3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java @@ -0,0 +1,16 @@ +package org.sufficientlysecure.keychain.smartcard; + +public enum PinType { + BASIC(0x81), + ADMIN(0x83),; + + private final int mMode; + + PinType(final int mode) { + this.mMode = mode; + } + + public int getmMode() { + return mMode; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java new file mode 100644 index 000000000..cffc49555 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -0,0 +1,707 @@ +package org.sufficientlysecure.keychain.smartcard; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey; + +import nordpol.Apdu; + +public class SmartcardDevice { + // Fidesmo constants + private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; + + 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 Transport mTransport; + + private Passphrase mPin; + private Passphrase mAdminPin; + private boolean mPw1ValidForMultipleSignatures; + private boolean mPw1ValidatedForSignature; + private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + private boolean mPw3Validated; + private boolean mTagHandlingEnabled; + + public SmartcardDevice() { + } + + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + public Passphrase getPin() { + return mPin; + } + + public void setPin(final Passphrase pin) { + this.mPin = pin; + } + + public Passphrase getAdminPin() { + return mAdminPin; + } + + public void setAdminPin(final Passphrase adminPin) { + this.mAdminPin = adminPin; + } + + public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { + long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; + byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); + KeyType keyType = KeyType.from(secretKey); + + if (keyType == null) { + throw new IOException("Inappropriate key flags for smart card key."); + } + + // Slot is empty, or contains this key already. PUT KEY operation is safe + boolean canPutKey = !containsKey(keyType) + || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); + if (!canPutKey) { + throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", + keyType.toString())); + } + + putKey(keyType.getmSlot(), secretKey, passphrase); + putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + putData(keyType.getTimestampObjectId(), timestampBytes); + } + + public boolean containsKey(KeyType keyType) throws IOException { + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + } + + public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); + } + + // METHOD UPDATED OK + public void connectToDevice() throws IOException { + // 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 = communicate(opening); // activate connection + if (!response.endsWith(accepted)) { + throw new CardException("Initialization failed!", parseCardStatus(response)); + } + + byte[] pwStatusBytes = getPwStatusBytes(); + mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); + mPw1ValidatedForSignature = false; + mPw1ValidatedForDecrypt = false; + mPw3Validated = false; + } + + /** + * 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. + */ + private 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; + } + } + + /** + * 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. + */ + // METHOD UPDATED[OK] + public void modifyPin(int pw, byte[] newPin) throws IOException { + final int MAX_PW1_LENGTH_INDEX = 1; + final int MAX_PW3_LENGTH_INDEX = 3; + + byte[] pwStatusBytes = getPwStatusBytes(); + + 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 = communicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { + throw new CardException("Failed to change PIN", parseCardStatus(response)); + } + } + + /** + * Call DECIPHER command + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + // METHOD UPDATED [OK] + public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { + if (!mPw1ValidatedForDecrypt) { + verifyPin(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]; + } + + communicate(firstApdu + getHex(one)); + String second = communicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = getDataField(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. + */ + // METHOD UPDATED [OK] + private void verifyPin(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; + } + } + } + + /** + * 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 + */ + // METHOD UPDATED [OK] + public void putData(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) { + verifyPin(0x82); // (Verify PW1 for non-signing operations) + } + } else if (!mPw3Validated) { + verifyPin(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 = communicate(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 + */ + // METHOD UPDATED [OK] + public void putKey(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) { + verifyPin(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 = communicate( + putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) + ); + offset += 254; + } else { + int length = dataToSend.length - offset; + response = communicate( + 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); + } + + /** + * 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. + */ + // METHOD UPDATED [OK] + public byte[] getFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mTransport.sendAndReceive(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. + */ + // METHOD UPDATED [OK] + private byte[] getPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mTransport.sendAndReceive(Hex.decode(data)); + } + + // METHOD UPDATED [OK] + public byte[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.sendAndReceive(Hex.decode(info)); + } + + // METHOD UPDATED [OK] + public String getUserId() throws IOException { + String info = "00CA006500"; + return getHolderName(communicate(info)); + } + + /** + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + */ + // METHOD UPDATED [OK] + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { + if (!mPw1ValidatedForSignature) { + verifyPin(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 = communicate(apdu); + + if (response.length() < 4) { + throw new CardException("Bad response", (short) 0); + } + // 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 = communicate("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); + } + + private String getHolderName(String name) { + 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); + } + + /** + * Transceive data via NFC encoded as Hex + */ + // METHOD UPDATED [OK] + private String communicate(String apdu) throws IOException, TransportIoException { + return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + } + + public boolean isConnected() { + return mTransport.isConnected(); + } + + // NEW METHOD [OK] + public boolean isFidesmoToken() { + if (isConnected()) { // Check if we can still talk to the card + try { + // By trying to select any apps that have the Fidesmo AID prefix we can + // see if it is a Fidesmo device or not + byte[] mSelectResponse = mTransport.sendAndReceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); + // Compare the status returned by our select with the OK status code + return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); + } catch (IOException e) { + Log.e(Constants.TAG, "Card communication failed!", e); + } + } + return false; + } + + /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + // NEW METHOD [OK] + public byte[] generateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = communicate(generateKeyApdu); + String second = communicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + // NEW METHOD [OK][OK] + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + // NEW METHOD [OK] + 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 communicate(login); + } + + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + // NEW METHOD [OK] + public void resetAndWipeToken() 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 = communicate(reactivate1); + String response2 = communicate(reactivate2); + if (!response1.equals(accepted) || !response2.equals(accepted)) { + throw new CardException("Reactivating failed!", parseCardStatus(response1)); + } + + } + + /** + * 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[] getMasterKeyFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + 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 void setTransport(Transport mTransport) { + this.mTransport = mTransport; + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java new file mode 100644 index 000000000..e01d7da16 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public interface Transport { + byte[] sendAndReceive(byte[] data) throws IOException; + + void release(); + + boolean isConnected(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java new file mode 100644 index 000000000..544dd4045 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class TransportIoException extends IOException { + public TransportIoException() { + } + + public TransportIoException(final String detailMessage) { + super(detailMessage); + } + + public TransportIoException(final String message, final Throwable cause) { + super(message, cause); + } + + public TransportIoException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java new file mode 100644 index 000000000..c98d5d43f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -0,0 +1,148 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + +public class UsbConnectionManager { + private static final String LOG_TAG = UsbConnectionManager.class.getName(); + private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION"; + private final Semaphore mRunning = new Semaphore(1); + private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); + private final AtomicBoolean mStopped = new AtomicBoolean(false); + private Activity mActivity; + private final Thread mWatchThread = new Thread() { + @Override + public void run() { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + + while (!mStopped.get()) { + try { + mRunning.acquire(); + } catch (InterruptedException e) { + } + mRunning.release(); + if (mStopped.get()) return; + + // + final UsbDevice device = getDevice(usbManager); + if (device != null && !mProcessedDevices.contains(device)) { + mProcessedDevices.add(device); + + final Intent intent = new Intent(ACTION_USB_PERMISSION); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USB_PERMISSION); + mActivity.registerReceiver(mUsbReceiver, filter); + + Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); + usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); + } + + try { + sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }; + private OnDiscoveredUsbDeviceListener mListener; + /** + * Receives broadcast when a supported USB device is attached, detached or + * when a permission to communicate to the device has been granted. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + String deviceName = usbDevice.getDeviceName(); + + if (ACTION_USB_PERMISSION.equals(action)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + if (permission) { + interceptIntent(intent); + } + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + + public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + mRunning.acquireUninterruptibly(); + mWatchThread.start(); + } + + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { + return device; + } + } + return null; + } + + public void startListeningForDevices() { + mRunning.release(); + } + + public void stopListeningForDevices() { + mRunning.acquireUninterruptibly(); + } + + public void interceptIntent(final Intent intent) { + if (intent == null || intent.getAction() == null) return; + switch (intent.getAction()) { + /*case UsbManager.ACTION_USB_DEVICE_ATTACHED: { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + usbI.setAction(ACTION_USB_PERMISSION); + usbI.putExtra(UsbManager.EXTRA_DEVICE, device); + PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT); + usbManager.requestPermission(device, pi); + break; + }*/ + case ACTION_USB_PERMISSION: { + UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) + mListener.usbDeviceDiscovered(device); + break; + } + default: + break; + } + } + + public void onDestroy() { + mStopped.set(true); + mRunning.release(); + try { + mActivity.unregisterReceiver(mUsbReceiver); + } catch (IllegalArgumentException ignore) { + } + mActivity = null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java new file mode 100644 index 000000000..08f296c25 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -0,0 +1,226 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class UsbTransport implements Transport { + private static final int CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 2 s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private final UsbInterface mUsbInterface; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final UsbDeviceConnection mConnection; + private byte mCounter = 0; + + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + powerOn(); + + setTimings(); + } + + private void setTimings() throws TransportIoException { + byte[] data = { + 0x6C, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, 0x00, 0x00 + }; + sendRaw(data); + data = receive(); + + data[0] = 0x61; + data[1] = 0x04; + data[2] = data[3] = data[4] = 0x00; + data[5] = 0x00; + data[6] = mCounter++; + data[7] = 0x00; + data[8] = data[9] = 0x00; + + data[13] = 1; + + sendRaw(data); + receive(); + } + + private void powerOff() throws TransportIoException { + final byte[] iccPowerOff = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOff); + receive(); + } + + void powerOn() throws TransportIoException { + final byte[] iccPowerOn = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOn); + receive(); + } + + /** + * Get first class 11 (Chip/Smartcard) interface for the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(final UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + @Override + public void release() { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + } + + @Override + public boolean isConnected() { + // TODO: redo + return mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + @Override + public byte[] sendAndReceive(byte[] data) throws TransportIoException { + send(data); + byte[] bytes; + do { + bytes = receive(); + } while (isXfrBlockNotReady(bytes)); + + checkXfrBlockResult(bytes); + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + public void send(byte[] d) throws TransportIoException { + int l = d.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + d); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } + } + + public byte[] receive() throws TransportIoException { + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new TransportIoException("USB error, failed to receive ccid header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; + } + + private void sendRaw(final byte[] data) throws TransportIoException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new TransportIoException("USB error, failed to send data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isXfrBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } +} 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 b3f60ba41..268dbad02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -149,9 +149,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { return; } - mScannedFingerprints = mJavacardDevice.getFingerprints(); - mNfcAid = mJavacardDevice.getAid(); - mNfcUserId = mJavacardDevice.getUserId(); + mScannedFingerprints = mSmartcardDevice.getFingerprints(); + mNfcAid = mSmartcardDevice.getAid(); + mNfcUserId = mSmartcardDevice.getUserId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index 401db0b98..a0e93ed85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -250,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.mJavacardDevice.getFingerprints(); - mTokenAid = mCreateKeyActivity.mJavacardDevice.getAid(); - mTokenUserId = mCreateKeyActivity.mJavacardDevice.getUserId(); + mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints(); + mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid(); + mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 7e1474eb7..c68936577 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -162,7 +162,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity case NFC_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = mJavacardDevice.decryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -173,15 +173,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mJavacardDevice.calculateSignature(hash, algo); + byte[] signedHash = mSmartcardDevice.calculateSignature(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 - mJavacardDevice.setPin(new Passphrase("123456")); - mJavacardDevice.setAdminPin(new Passphrase("12345678")); + mSmartcardDevice.setPin(new Passphrase("123456")); + mSmartcardDevice.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -206,7 +206,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long keyGenerationTimestampMillis = key.getCreationTime().getTime(); long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(mJavacardDevice.getAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); Passphrase passphrase; try { @@ -218,25 +218,25 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (key.canSign() || key.canCertify()) { if (shouldPutKey(key.getFingerprint(), 0)) { - mJavacardDevice.putKey(0xB6, key, passphrase); - mJavacardDevice.putData(0xCE, timestampBytes); - mJavacardDevice.putData(0xC7, key.getFingerprint()); + mSmartcardDevice.putKey(0xB6, key, passphrase); + mSmartcardDevice.putData(0xCE, timestampBytes); + mSmartcardDevice.putData(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)) { - mJavacardDevice.putKey(0xB8, key, passphrase); - mJavacardDevice.putData(0xCF, timestampBytes); - mJavacardDevice.putData(0xC8, key.getFingerprint()); + mSmartcardDevice.putKey(0xB8, key, passphrase); + mSmartcardDevice.putData(0xCF, timestampBytes); + mSmartcardDevice.putData(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)) { - mJavacardDevice.putKey(0xA4, key, passphrase); - mJavacardDevice.putData(0xD0, timestampBytes); - mJavacardDevice.putData(0xC9, key.getFingerprint()); + mSmartcardDevice.putKey(0xA4, key, passphrase); + mSmartcardDevice.putData(0xD0, timestampBytes); + mSmartcardDevice.putData(0xC9, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new authentication key."); } @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - mJavacardDevice.modifyPin(0x81, newPin); - mJavacardDevice.modifyPin(0x83, newAdminPin); + mSmartcardDevice.modifyPin(0x81, newPin); + mSmartcardDevice.modifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - mJavacardDevice.resetAndWipeToken(); + mSmartcardDevice.resetAndWipeToken(); break; } @@ -330,7 +330,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = mJavacardDevice.getMasterKeyFingerprint(idx); + byte[] tokenFingerprint = mSmartcardDevice.getMasterKeyFingerprint(idx); // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true 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 7d6ba5c8f..8ed2db9b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -649,9 +649,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements @Override protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index e3c331b0b..8dde54a1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; -import android.app.PendingIntent; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; @@ -35,12 +34,6 @@ import android.os.Bundle; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; -import org.sufficientlysecure.keychain.javacard.JavacardDevice; -import org.sufficientlysecure.keychain.javacard.NfcTransport; -import org.sufficientlysecure.keychain.javacard.OnDiscoveredUsbDeviceListener; -import org.sufficientlysecure.keychain.javacard.UsbConnectionManager; -import org.sufficientlysecure.keychain.javacard.UsbTransport; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -48,6 +41,11 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; +import org.sufficientlysecure.keychain.smartcard.NfcTransport; +import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; +import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -74,7 +72,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public JavacardDevice mJavacardDevice = new BaseJavacardDevice(); + public SmartcardDevice mSmartcardDevice = new SmartcardDevice(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -93,9 +91,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * Override to implement NFC operations (background thread) */ protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } /** @@ -141,7 +139,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void tagDiscovered(final Tag tag) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -178,7 +176,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void usbDeviceDiscovered(final UsbDevice device) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -347,7 +345,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } // 6A82 app not installed on security token! case 0x6A82: { - if (mJavacardDevice.isFidesmoToken()) { + if (mSmartcardDevice.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -396,7 +394,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mJavacardDevice.setPin(passphrase); + mSmartcardDevice.setPin(passphrase); return; } @@ -421,7 +419,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mJavacardDevice.setPin(input.getPassphrase()); + mSmartcardDevice.setPin(input.getPassphrase()); break; } default: @@ -429,19 +427,19 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - /** Handle NFC communication and return a result. - * + /** + * Handle NFC communication and return a result. + *

* This method is called by onNewIntent above upon discovery of an NFC tag. * It handles initialization and login to the application, subsequently * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then * finishes the activity with an appropriate result. - * + *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - * + *

* References to pages are generally related to the OpenPGP Application * on ISO SmartCard Systems specification. - * */ protected void handleTagDiscovered(Tag tag) throws IOException { @@ -451,22 +449,22 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mJavacardDevice.setTransport(new NfcTransport(isoCard)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new NfcTransport(isoCard)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } protected void handleUsbDevice(UsbDevice device) throws IOException { UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mJavacardDevice.setTransport(new UsbTransport(device, usbManager)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } public boolean isNfcConnected() { - return mJavacardDevice.isConnected(); + return mSmartcardDevice.isConnected(); } /** @@ -535,7 +533,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Ask user if she wants to install PGP onto her Fidesmo token - */ + */ private void promptFidesmoPgpInstall() { FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); @@ -552,6 +550,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Use the package manager to detect if an application is installed on the phone + * * @param uri an URI identifying the application's package * @return 'true' if the app is installed */ -- cgit v1.2.3 From 3798249570e97861793f5d0ebc695d94e8d5ddcd Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Thu, 7 Apr 2016 01:22:24 +0600 Subject: OTG: Implement hidden activity usb detection technique --- .../keychain/javacard/BaseJavacardDevice.java | 712 --------------------- .../keychain/javacard/JavacardDevice.java | 105 --- .../keychain/smartcard/SmartcardDevice.java | 2 +- .../keychain/smartcard/UsbConnectionManager.java | 118 +--- .../keychain/ui/UsbEventReceiverActivity.java | 42 ++ .../ui/base/BaseSecurityTokenNfcActivity.java | 19 +- 6 files changed, 66 insertions(+), 932 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java deleted file mode 100644 index 796b4e1f3..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ /dev/null @@ -1,712 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.util.Iso7816TLV; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.interfaces.RSAPrivateCrtKey; - -import nordpol.Apdu; - -public class BaseJavacardDevice implements JavacardDevice { - // Fidesmo constants - private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; - - 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 Transport mTransport; - - private Passphrase mPin; - private Passphrase mAdminPin; - private boolean mPw1ValidForMultipleSignatures; - private boolean mPw1ValidatedForSignature; - private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - private boolean mPw3Validated; - private boolean mTagHandlingEnabled; - - public BaseJavacardDevice() { - } - - private static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - - public Passphrase getPin() { - return mPin; - } - - public void setPin(final Passphrase pin) { - this.mPin = pin; - } - - public Passphrase getAdminPin() { - return mAdminPin; - } - - public void setAdminPin(final Passphrase adminPin) { - this.mAdminPin = adminPin; - } - - public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { - long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; - byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - KeyType keyType = KeyType.from(secretKey); - - if (keyType == null) { - throw new IOException("Inappropriate key flags for smart card key."); - } - - // Slot is empty, or contains this key already. PUT KEY operation is safe - boolean canPutKey = !containsKey(keyType) - || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); - if (!canPutKey) { - throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", - keyType.toString())); - } - - putKey(keyType.getmSlot(), secretKey, passphrase); - putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); - putData(keyType.getTimestampObjectId(), timestampBytes); - } - - public boolean containsKey(KeyType keyType) throws IOException { - return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); - } - - public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); - } - - // METHOD UPDATED OK - public void connectToDevice() throws IOException { - // 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; - } - - /** - * 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; - } - } - - /** - * 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. - */ - // METHOD UPDATED[OK] - public void modifyPin(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)); - } - } - - /** - * Call DECIPHER command - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - */ - // METHOD UPDATED [OK] - public byte[] decryptSessionKey(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]; - } - - nfcCommunicate(firstApdu + getHex(one)); - String second = nfcCommunicate(secondApdu + getHex(two) + le); - - String decryptedSessionKey = getDataField(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. - */ - // METHOD UPDATED [OK] - public void nfcVerifyPin(int mode) throws IOException { - if (mPin != null || mode == 0x83) { - - byte[] pin; - if (mode == 0x83) { - pin = mAdminPin.toStringUnsafe().getBytes(); - } else { - pin = mPin.toStringUnsafe().getBytes(); - } - - // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. - // See specification, page 51 - String accepted = "9000"; - String response = nfcTryPin(mode, pin); // login - if (!response.equals(accepted)) { - throw new CardException("Bad PIN!", parseCardStatus(response)); - } - - if (mode == 0x81) { - mPw1ValidatedForSignature = true; - } else if (mode == 0x82) { - mPw1ValidatedForDecrypt = true; - } else if (mode == 0x83) { - mPw3Validated = true; - } - } - } - - /** - * 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 - */ - // METHOD UPDATED [OK] - public void putData(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 - */ - // METHOD UPDATED [OK] - public void putKey(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); - } - - /** - * 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. - */ - // METHOD UPDATED [OK] - public byte[] getFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mTransport.sendAndReceive(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. - */ - // METHOD UPDATED [OK] - public byte[] nfcGetPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mTransport.sendAndReceive(Hex.decode(data)); - } - - // METHOD UPDATED [OK] - public byte[] getAid() throws IOException { - String info = "00CA004F00"; - return mTransport.sendAndReceive(Hex.decode(info)); - } - - // METHOD UPDATED [OK] - public String getUserId() throws IOException { - String info = "00CA006500"; - return nfcGetHolderName(nfcCommunicate(info)); - } - - /** - * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value - * - * @param hash the hash for signing - * @return a big integer representing the MPI for the given hash - */ - // METHOD UPDATED [OK] - public byte[] calculateSignature(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); - - if (response.length() < 4) { - throw new CardException("Bad response", (short) 0); - } - // 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); - } - - public String nfcGetHolderName(String name) { - 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); - } - - private String nfcGetDataField(String output) { - return output.substring(0, output.length() - 4); - } - - /** - * Transceive data via NFC encoded as Hex - */ - // METHOD UPDATED [OK] - public String nfcCommunicate(String apdu) throws IOException, TransportIoException { - return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); - } - - public boolean isConnected() { - return mTransport.isConnected(); - } - - // NEW METHOD [OK] - public boolean isFidesmoToken() { - if (isConnected()) { // Check if we can still talk to the card - try { - // By trying to select any apps that have the Fidesmo AID prefix we can - // see if it is a Fidesmo device or not - byte[] mSelectResponse = mTransport.sendAndReceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); - // Compare the status returned by our select with the OK status code - return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); - } catch (IOException e) { - Log.e(Constants.TAG, "Card communication failed!", e); - } - } - return false; - } - - /** - * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), - * this command also has the effect of resetting the digital signature counter. - * NOTE: This does not set the key fingerprint data object! After calling this command, you - * must construct a public key packet using the returned public key data objects, compute the - * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) - * - * @param slot The slot on the card where the key should be generated: - * 0xB6: Signature Key - * 0xB8: Decipherment Key - * 0xA4: Authentication Key - * @return the public key data objects, in TLV format. For RSA this will be the public modulus - * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. - */ - // NEW METHOD [OK] - public byte[] nfcGenerateKey(int slot) throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - if (!mPw3Validated) { - nfcVerifyPin(0x83); // (Verify PW3 with mode 83) - } - - String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; - String getResponseApdu = "00C00000"; - - String first = nfcCommunicate(generateKeyApdu); - String second = nfcCommunicate(getResponseApdu); - - if (!second.endsWith("9000")) { - throw new IOException("On-card key generation failed"); - } - - String publicKeyData = getDataField(first) + getDataField(second); - - Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); - - return Hex.decode(publicKeyData); - } - - // NEW METHOD [OK][OK] - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - - // NEW METHOD [OK] - private String nfcTryPin(int mode, byte[] pin) throws IOException { - // Command APDU for VERIFY command (page 32) - String login = - "00" // CLA - + "20" // INS - + "00" // P1 - + String.format("%02x", mode) // P2 - + String.format("%02x", pin.length) // Lc - + Hex.toHexString(pin); - - return nfcCommunicate(login); - } - - /** - * Resets security token, which deletes all keys and data objects. - * This works by entering a wrong PIN and then Admin PIN 4 times respectively. - * Afterwards, the token is reactivated. - */ - // NEW METHOD [OK] - public void resetAndWipeToken() throws IOException { - String accepted = "9000"; - - // try wrong PIN 4 times until counter goes to C0 - byte[] pin = "XXXXXX".getBytes(); - for (int i = 0; i <= 4; i++) { - String response = nfcTryPin(0x81, pin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); - } - } - - // try wrong Admin PIN 4 times until counter goes to C0 - byte[] adminPin = "XXXXXXXX".getBytes(); - for (int i = 0; i <= 4; i++) { - String response = nfcTryPin(0x83, adminPin); - if (response.equals(accepted)) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); - } - } - - // reactivate token! - String reactivate1 = "00" + "e6" + "00" + "00"; - String reactivate2 = "00" + "44" + "00" + "00"; - String response1 = nfcCommunicate(reactivate1); - String response2 = nfcCommunicate(reactivate2); - if (!response1.equals(accepted) || !response2.equals(accepted)) { - throw new CardException("Reactivating failed!", parseCardStatus(response1)); - } - - } - - /** - * 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[] getMasterKeyFingerprint(int idx) throws IOException { - byte[] data = getFingerprints(); - 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; - } - - @Override - public void setTransport(Transport mTransport) { - this.mTransport = mTransport; - - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java deleted file mode 100644 index 63d4d2ad7..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; - -public interface JavacardDevice { - - Passphrase getPin(); - - void setPin(final Passphrase pin); - - Passphrase getAdminPin(); - - void setAdminPin(final Passphrase adminPin); - - void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException; - - boolean containsKey(KeyType keyType) throws IOException; - - boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException; - - void connectToDevice() throws IOException; - - /** - * 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 pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPin The new PW1 or PW3. - */ - void modifyPin(int pinType, byte[] newPin) throws IOException; - - /** - * Calls to calculate the signature and returns the MPI value - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - */ - byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException; - - /** - * 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. - */ - byte[] getFingerprints() throws IOException; - - - byte[] getAid() throws IOException; - - String getUserId() throws IOException; - - boolean isConnected(); - - /** - * 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 - */ - byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException; - - boolean isFidesmoToken(); - - /** - * 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. - */ - byte[] getMasterKeyFingerprint(int idx) throws IOException; - - /** - * Resets security token, which deletes all keys and data objects. - * This works by entering a wrong PIN and then Admin PIN 4 times respectively. - * Afterwards, the token is reactivated. - */ - void resetAndWipeToken() throws IOException; - - /** - * 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 - */ - void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException; - - /** - * 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 - */ - void putData(int dataObject, byte[] data) throws IOException; - - void setTransport(Transport mTransport); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index cffc49555..b86c3cf4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -78,7 +78,7 @@ public class SmartcardDevice { } public boolean containsKey(KeyType keyType) throws IOException { - return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java index c98d5d43f..8a6971fe6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -1,7 +1,6 @@ package org.sufficientlysecure.keychain.smartcard; import android.app.Activity; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -10,79 +9,29 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; import org.sufficientlysecure.keychain.util.Log; -import java.util.Collections; -import java.util.HashMap; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - public class UsbConnectionManager { - private static final String LOG_TAG = UsbConnectionManager.class.getName(); - private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION"; - private final Semaphore mRunning = new Semaphore(1); - private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - private final AtomicBoolean mStopped = new AtomicBoolean(false); private Activity mActivity; - private final Thread mWatchThread = new Thread() { - @Override - public void run() { - final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - - while (!mStopped.get()) { - try { - mRunning.acquire(); - } catch (InterruptedException e) { - } - mRunning.release(); - if (mStopped.get()) return; - - // - final UsbDevice device = getDevice(usbManager); - if (device != null && !mProcessedDevices.contains(device)) { - mProcessedDevices.add(device); - - final Intent intent = new Intent(ACTION_USB_PERMISSION); - - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_USB_PERMISSION); - mActivity.registerReceiver(mUsbReceiver, filter); - Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName()); - usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); - } - - try { - sleep(1000); - } catch (InterruptedException ignored) { - } - } - } - }; private OnDiscoveredUsbDeviceListener mListener; /** - * Receives broadcast when a supported USB device is attached, detached or - * when a permission to communicate to the device has been granted. + * Receives broadcast when a supported USB device get permission. */ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - String deviceName = usbDevice.getDeviceName(); - if (ACTION_USB_PERMISSION.equals(action)) { + if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); - Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - if (permission) { - interceptIntent(intent); + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + mListener.usbDeviceDiscovered(usbDevice); } - - context.unregisterReceiver(mUsbReceiver); } } }; @@ -90,59 +39,16 @@ public class UsbConnectionManager { public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; this.mListener = listener; - mRunning.acquireUninterruptibly(); - mWatchThread.start(); - } - - private static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { - return device; - } - } - return null; } - public void startListeningForDevices() { - mRunning.release(); - } - - public void stopListeningForDevices() { - mRunning.acquireUninterruptibly(); - } + public void onStart() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - public void interceptIntent(final Intent intent) { - if (intent == null || intent.getAction() == null) return; - switch (intent.getAction()) { - /*case UsbManager.ACTION_USB_DEVICE_ATTACHED: { - final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); - final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - usbI.setAction(ACTION_USB_PERMISSION); - usbI.putExtra(UsbManager.EXTRA_DEVICE, device); - PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT); - usbManager.requestPermission(device, pi); - break; - }*/ - case ACTION_USB_PERMISSION: { - UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - if (device != null) - mListener.usbDeviceDiscovered(device); - break; - } - default: - break; - } + mActivity.registerReceiver(mUsbReceiver, intentFilter); } - public void onDestroy() { - mStopped.set(true); - mRunning.release(); - try { - mActivity.unregisterReceiver(mUsbReceiver); - } catch (IllegalArgumentException ignore) { - } - mActivity = null; + public void onStop() { + mActivity.unregisterReceiver(mUsbReceiver); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java new file mode 100644 index 000000000..9df5800b5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java @@ -0,0 +1,42 @@ +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class UsbEventReceiverActivity extends Activity { + public static final String ACTION_USB_PERMISSION = + "org.sufficientlysecure.keychain.ui.USB_PERMISSION"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + + Intent intent = getIntent(); + if (intent != null) { + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + + Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName()); + usbManager.requestPermission(usbDevice, + PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0)); + } + } + + // Close the activity + finish(); + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 8dde54a1f..a6f8c0b0f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -254,9 +254,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity */ @Override public void onNewIntent(final Intent intent) { - if (!mTagDispatcher.interceptIntent(intent)) { - mUsbDispatcher.interceptIntent(intent); - } + mTagDispatcher.interceptIntent(intent); } private void handleNfcError(IOException e) { @@ -374,7 +372,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Log.d(Constants.TAG, "BaseNfcActivity.onPause"); mTagDispatcher.disableExclusiveNfc(); - mUsbDispatcher.stopListeningForDevices(); +// mUsbDispatcher.stopListeningForDevices(); } /** @@ -385,7 +383,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onResume(); Log.d(Constants.TAG, "BaseNfcActivity.onResume"); mTagDispatcher.enableExclusiveNfc(); - mUsbDispatcher.startListeningForDevices(); } protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { @@ -568,8 +565,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } @Override - protected void onDestroy() { - super.onDestroy(); - mUsbDispatcher.onDestroy(); + protected void onStop() { + super.onStop(); + mUsbDispatcher.onStop(); + } + + @Override + protected void onStart() { + super.onStart(); + mUsbDispatcher.onStart(); } } -- cgit v1.2.3 From b5eb6468fecfc16ea041eb0f4bf48c37ec2e81f2 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 8 Apr 2016 00:41:53 +0600 Subject: OTG: Add support for persistent usb connection No need to reinsert token on each operation --- .../keychain/smartcard/NfcTransport.java | 5 ++ .../keychain/smartcard/SmartcardDevice.java | 49 ++++++++++++---- .../keychain/smartcard/Transport.java | 2 + .../keychain/smartcard/UsbConnectionManager.java | 8 +++ .../keychain/smartcard/UsbTransport.java | 39 ++++--------- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 67 ++++++++++++++++------ .../ui/base/BaseSecurityTokenNfcActivity.java | 67 ++++++---------------- 8 files changed, 132 insertions(+), 111 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index 557c6f37d..d56f5b5bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -28,4 +28,9 @@ public class NfcTransport implements Transport { public boolean isConnected() { return mIsoCard.isConnected(); } + + @Override + public boolean allowPersistentConnection() { + return false; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index b86c3cf4c..4420c0c88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -32,7 +32,32 @@ public class SmartcardDevice { private boolean mPw3Validated; private boolean mTagHandlingEnabled; - public SmartcardDevice() { + protected SmartcardDevice() { + } + + public static SmartcardDevice getInstance() { + return LazyHolder.mSmartcardDevice; + } + + // METHOD UPDATED [OK] + private String getHolderName(String name) { + try { + String slength; + int ilength; + name = name.substring(6); + slength = name.substring(0, 2); + ilength = Integer.parseInt(slength, 16) * 2; + name = name.substring(2, ilength + 2); + name = (new String(Hex.decode(name))).replace('<', ' '); + return name; + } catch (IndexOutOfBoundsException e) { + // try-catch for https://github.com/FluffyKaon/OpenPGP-Card + // Note: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! + + Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); + return ""; + } } private static String getHex(byte[] raw) { @@ -541,15 +566,8 @@ public class SmartcardDevice { return Hex.decode(signature); } - private String getHolderName(String name) { - 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); + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); } /** @@ -560,8 +578,8 @@ public class SmartcardDevice { return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); } - public boolean isConnected() { - return mTransport.isConnected(); + public Transport getTransport() { + return mTransport; } // NEW METHOD [OK] @@ -702,6 +720,13 @@ public class SmartcardDevice { public void setTransport(Transport mTransport) { this.mTransport = mTransport; + } + + public boolean allowPersistentConnection() { + return mTransport != null && mTransport.allowPersistentConnection(); + } + private static class LazyHolder { + private static final SmartcardDevice mSmartcardDevice = new SmartcardDevice(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index e01d7da16..9b0ad2998 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -8,4 +8,6 @@ public interface Transport { void release(); boolean isConnected(); + + boolean allowPersistentConnection(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java index 8a6971fe6..e6fb5b04d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -51,4 +51,12 @@ public class UsbConnectionManager { public void onStop() { mActivity.unregisterReceiver(mUsbReceiver); } + + public void rescanDevices() { + final SmartcardDevice smartcardDevice = SmartcardDevice.getInstance(); + if (smartcardDevice.isConnected() + && (smartcardDevice.getTransport() instanceof UsbTransport)) { + mListener.usbDeviceDiscovered(((UsbTransport) smartcardDevice.getTransport()).getUsbDevice()); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 08f296c25..2d435ccbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -12,6 +12,8 @@ import android.util.Pair; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -45,33 +47,7 @@ public class UsbTransport implements Transport { // check result powerOn(); - - setTimings(); - } - - private void setTimings() throws TransportIoException { - byte[] data = { - 0x6C, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, 0x00, 0x00 - }; - sendRaw(data); - data = receive(); - - data[0] = 0x61; - data[1] = 0x04; - data[2] = data[3] = data[4] = 0x00; - data[5] = 0x00; - data[6] = mCounter++; - data[7] = 0x00; - data[8] = data[9] = 0x00; - - data[13] = 1; - - sendRaw(data); - receive(); + Log.d(Constants.TAG, "Usb transport connected"); } private void powerOff() throws TransportIoException { @@ -147,6 +123,11 @@ public class UsbTransport implements Transport { return mUsbManager.getDeviceList().containsValue(mUsbDevice); } + @Override + public boolean allowPersistentConnection() { + return true; + } + @Override public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); @@ -223,4 +204,8 @@ public class UsbTransport implements Transport { private boolean isXfrBlockNotReady(byte[] bytes) { return getStatus(bytes) == 2; } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index a0e93ed85..aba06ac47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -249,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints(); - mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid(); - mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId(); + mTokenFingerprints = mCreateKeyActivity.getSmartcardDevice().getFingerprints(); + mTokenAid = mCreateKeyActivity.getSmartcardDevice().getAid(); + mTokenUserId = mCreateKeyActivity.getSmartcardDevice().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index c68936577..5f0093678 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -140,6 +140,32 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); + checkPinAvailability(); + } else { + // No need for pin, rescan USB devices + mUsbDispatcher.rescanDevices(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (REQUEST_CODE_PIN == requestCode) { + checkPinAvailability(); + } + } + + private void checkPinAvailability() { + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); + if (passphrase != null) { + // Rescan USB devices + mUsbDispatcher.rescanDevices(); + } + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new AssertionError( + "tried to find passphrase for non-existing key. this is a programming error!"); } } @@ -275,28 +301,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - 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) { + if (mSmartcardDevice.allowPersistentConnection()) { + // Just close + finish(); + } else { + 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; } - } else { - return null; } } - } - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - finish(); - } - }.execute(); + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index a6f8c0b0f..ecec98aaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -31,7 +31,6 @@ import android.nfc.TagLostException; import android.os.AsyncTask; import android.os.Bundle; -import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -41,9 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -72,7 +71,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public SmartcardDevice mSmartcardDevice = new SmartcardDevice(); + protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -175,7 +174,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void usbDeviceDiscovered(final UsbDevice device) { - // Actual NFC operations are executed in doInBackground to not block the UI thread + // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @@ -372,7 +371,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Log.d(Constants.TAG, "BaseNfcActivity.onPause"); mTagDispatcher.disableExclusiveNfc(); -// mUsbDispatcher.stopListeningForDevices(); } /** @@ -453,10 +451,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } protected void handleUsbDevice(UsbDevice device) throws IOException { - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); - mSmartcardDevice.connectToDevice(); - + // Don't reconnect if device was already connected + if (!mSmartcardDevice.isConnected() + || !(mSmartcardDevice.getTransport() instanceof UsbTransport) + || !((UsbTransport) mSmartcardDevice.getTransport()).getUsbDevice().equals(device)) { + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + + mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + mSmartcardDevice.connectToDevice(); + } doNfcInBackground(); } @@ -464,48 +467,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice.isConnected(); } - /** - * Parses out the status word from a JavaCard response string. - * - * @param response A hex string with the response from the token - * @return A short indicating the SW1/SW2, or 0 if a status could not be determined. - */ - short parseCardStatus(String response) { - if (response.length() < 4) { - return 0; // invalid input - } - - try { - return Short.parseShort(response.substring(response.length() - 4), 16); - } catch (NumberFormatException e) { - return 0; - } - } - - public String getHolderName(String name) { - try { - String slength; - int ilength; - name = name.substring(6); - slength = name.substring(0, 2); - ilength = Integer.parseInt(slength, 16) * 2; - name = name.substring(2, ilength + 2); - name = (new String(Hex.decode(name))).replace('<', ' '); - return name; - } catch (IndexOutOfBoundsException e) { - // try-catch for https://github.com/FluffyKaon/OpenPGP-Card - // Note: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! - - Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); - return ""; - } - } - - public static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - public class IsoDepNotSupportedException extends IOException { public IsoDepNotSupportedException(String detailMessage) { @@ -575,4 +536,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onStart(); mUsbDispatcher.onStart(); } + + public SmartcardDevice getSmartcardDevice() { + return mSmartcardDevice; + } } -- cgit v1.2.3 From 4d9ce8e95b4604f753aa5f49fe2c243dc73a13a9 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 13:13:02 +0600 Subject: OTG: Refactor persistent connections, naming --- .../keychain/smartcard/NfcTransport.java | 57 ++++++++++-- .../keychain/smartcard/SmartcardDevice.java | 4 +- .../keychain/smartcard/Transport.java | 2 + .../keychain/smartcard/UsbConnectionManager.java | 8 -- .../keychain/smartcard/UsbTransport.java | 61 +++++++----- .../keychain/ui/CreateKeyActivity.java | 2 +- .../ui/SecurityTokenOperationActivity.java | 12 +-- .../keychain/ui/ViewKeyActivity.java | 2 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 102 ++++++--------------- 9 files changed, 132 insertions(+), 118 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index d56f5b5bf..e47ba5360 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -1,18 +1,22 @@ package org.sufficientlysecure.keychain.smartcard; +import android.nfc.Tag; + +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; + import java.io.IOException; import nordpol.IsoCard; +import nordpol.android.AndroidCard; public class NfcTransport implements Transport { // timeout is set to 100 seconds to avoid cancellation during calculation private static final int TIMEOUT = 100 * 1000; - private final IsoCard mIsoCard; + private final Tag mTag; + private IsoCard mIsoCard; - public NfcTransport(final IsoCard isoDep) throws IOException { - this.mIsoCard = isoDep; - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); + public NfcTransport(Tag tag) { + this.mTag = tag; } @Override @@ -26,11 +30,52 @@ public class NfcTransport implements Transport { @Override public boolean isConnected() { - return mIsoCard.isConnected(); + return mIsoCard != null && mIsoCard.isConnected(); } @Override public boolean allowPersistentConnection() { return false; } + + /** + * Handle NFC communication and return a 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. + */ + @Override + public void connect() throws IOException { + mIsoCard = AndroidCard.get(mTag); + if (mIsoCard == null) { + throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + } + + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final NfcTransport that = (NfcTransport) o; + + if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; + if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = mTag != null ? mTag.hashCode() : 0; + result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); + return result; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index 4420c0c88..286a38d1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -112,6 +112,8 @@ public class SmartcardDevice { // METHOD UPDATED OK public void connectToDevice() throws IOException { + mTransport.connect(); + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; @@ -722,7 +724,7 @@ public class SmartcardDevice { this.mTransport = mTransport; } - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return mTransport != null && mTransport.allowPersistentConnection(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index 9b0ad2998..5252a95d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -10,4 +10,6 @@ public interface Transport { boolean isConnected(); boolean allowPersistentConnection(); + + void connect() throws IOException; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java index e6fb5b04d..8a6971fe6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -51,12 +51,4 @@ public class UsbConnectionManager { public void onStop() { mActivity.unregisterReceiver(mUsbReceiver); } - - public void rescanDevices() { - final SmartcardDevice smartcardDevice = SmartcardDevice.getInstance(); - if (smartcardDevice.isConnected() - && (smartcardDevice.getTransport() instanceof UsbTransport)) { - mListener.usbDeviceDiscovered(((UsbTransport) smartcardDevice.getTransport()).getUsbDevice()); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 2d435ccbe..2a21c10dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -15,6 +15,7 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -24,30 +25,15 @@ public class UsbTransport implements Transport { private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; - private final UsbInterface mUsbInterface; - private final UsbEndpoint mBulkIn; - private final UsbEndpoint mBulkOut; - private final UsbDeviceConnection mConnection; + private UsbInterface mUsbInterface; + private UsbEndpoint mBulkIn; + private UsbEndpoint mBulkOut; + private UsbDeviceConnection mConnection; private byte mCounter = 0; - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) { mUsbDevice = usbDevice; mUsbManager = usbManager; - - mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - // throw if any endpoint is null - - mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result - - powerOn(); - Log.d(Constants.TAG, "Usb transport connected"); } private void powerOff() throws TransportIoException { @@ -120,7 +106,7 @@ public class UsbTransport implements Transport { @Override public boolean isConnected() { // TODO: redo - return mUsbManager.getDeviceList().containsValue(mUsbDevice); + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); } @Override @@ -128,6 +114,24 @@ public class UsbTransport implements Transport { return true; } + @Override + public void connect() throws IOException { + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + powerOn(); + Log.d(Constants.TAG, "Usb transport connected"); + } + @Override public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); @@ -208,4 +212,19 @@ public class UsbTransport implements Transport { public UsbDevice getUsbDevice() { return mUsbDevice; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final UsbTransport that = (UsbTransport) o; + + return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; + } + + @Override + public int hashCode() { + return mUsbDevice != null ? mUsbDevice.hashCode() : 0; + } } 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 268dbad02..07d5be821 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -155,7 +155,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 5f0093678..884f33365 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -142,8 +142,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { - // No need for pin, rescan USB devices - mUsbDispatcher.rescanDevices(); + checkDeviceConnection(); } } @@ -160,8 +159,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); if (passphrase != null) { - // Rescan USB devices - mUsbDispatcher.rescanDevices(); + checkDeviceConnection(); } } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( @@ -175,7 +173,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPreExecute() { + public void onSmartcardPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); @@ -293,7 +291,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected final void onNfcPostExecute() { + protected final void onSmartcardPostExecute() { handleResult(mInputParcel); // show finish @@ -301,7 +299,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - if (mSmartcardDevice.allowPersistentConnection()) { + if (mSmartcardDevice.isPersistentConnectionAllowed()) { // Just close finish(); } else { 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 8ed2db9b7..5bf81f1aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -655,7 +655,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index ecec98aaf..5e1592346 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; +import org.sufficientlysecure.keychain.smartcard.Transport; import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -83,7 +84,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to change UI before NFC handling (UI thread) */ - protected void onNfcPreExecute() { + protected void onSmartcardPreExecute() { } /** @@ -98,7 +99,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to handle result of NFC operations (UI thread) */ - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); @@ -140,54 +141,34 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // Actual NFC operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - new AsyncTask() { - @Override - protected void onPreExecute() { - super.onPreExecute(); - onNfcPreExecute(); - } - - @Override - protected IOException doInBackground(Void... params) { - try { - handleTagDiscovered(tag); - } catch (IOException e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(IOException exception) { - super.onPostExecute(exception); - - if (exception != null) { - handleNfcError(exception); - return; - } - onNfcPostExecute(); - } - }.execute(); + smartcardDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbDevice device) { // Actual USB operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + smartcardDiscovered(new UsbTransport(device, usbManager)); + } + + public void smartcardDiscovered(final Transport transport) { + // Actual Smartcard operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @Override protected void onPreExecute() { super.onPreExecute(); - onNfcPreExecute(); + onSmartcardPreExecute(); } @Override protected IOException doInBackground(Void... params) { try { - handleUsbDevice(device); + handleSmartcard(transport); } catch (IOException e) { return e; } @@ -200,11 +181,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onPostExecute(exception); if (exception != null) { - handleNfcError(exception); + handleSmartcardError(exception); return; } - onNfcPostExecute(); + onSmartcardPostExecute(); } }.execute(); } @@ -256,7 +237,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mTagDispatcher.interceptIntent(intent); } - private void handleNfcError(IOException e) { + private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { onNfcError(getString(R.string.security_token_error_tag_lost)); @@ -422,42 +403,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - /** - * Handle NFC communication and return a result. - *

- * This method is called by onNewIntent above upon discovery of an NFC tag. - * It handles initialization and login to the application, subsequently - * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then - * finishes the activity with an appropriate result. - *

- * On general communication, see also - * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - *

- * References to pages are generally related to the OpenPGP Application - * on ISO SmartCard Systems specification. - */ - protected void handleTagDiscovered(Tag tag) throws IOException { - - // Connect to the detected tag, setting a couple of settings - IsoCard isoCard = AndroidCard.get(tag); - if (isoCard == null) { - throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - - mSmartcardDevice.setTransport(new NfcTransport(isoCard)); - mSmartcardDevice.connectToDevice(); - - doNfcInBackground(); - } - - protected void handleUsbDevice(UsbDevice device) throws IOException { + protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!mSmartcardDevice.isConnected() - || !(mSmartcardDevice.getTransport() instanceof UsbTransport) - || !((UsbTransport) mSmartcardDevice.getTransport()).getUsbDevice().equals(device)) { - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - - mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + if (!(mSmartcardDevice.isConnected() + && mSmartcardDevice.getTransport().equals(transport))) { + mSmartcardDevice.setTransport(transport); mSmartcardDevice.connectToDevice(); } doNfcInBackground(); @@ -467,7 +417,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice.isConnected(); } - public class IsoDepNotSupportedException extends IOException { + public static class IsoDepNotSupportedException extends IOException { public IsoDepNotSupportedException(String detailMessage) { super(detailMessage); @@ -540,4 +490,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public SmartcardDevice getSmartcardDevice() { return mSmartcardDevice; } + + protected void checkDeviceConnection() { + if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { + this.smartcardDiscovered(mSmartcardDevice.getTransport()); + } + } } -- cgit v1.2.3 From 38a1c2d3ab5a9fe0fa74acbd8301d671eab35d59 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 13:23:29 +0600 Subject: OTG: refactor, change nfc prefix to smartcard --- .../keychain/remote/ApiPendingIntentFactory.java | 6 +- .../service/input/RequiredInputParcel.java | 18 ++--- .../keychain/ui/CreateKeyActivity.java | 2 +- .../ui/SecurityTokenOperationActivity.java | 22 +++--- .../keychain/ui/ViewKeyActivity.java | 2 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 78 +++++++++++----------- .../keychain/ui/base/CryptoOperationHelper.java | 6 +- 7 files changed, 68 insertions(+), 66 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 690a4d1a2..03789f118 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -50,9 +50,9 @@ public class ApiPendingIntentFactory { CryptoInputParcel cryptoInput) { switch (requiredInput.mType) { - case NFC_MOVE_KEY_TO_CARD: - case NFC_DECRYPT: - case NFC_SIGN: { + case SMARTCARD_MOVE_KEY_TO_CARD: + case SMARTCARD_DECRYPT: + case SMARTCARD_SIGN: { return createNfcOperationPendingIntent(data, requiredInput, cryptoInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 429d7a7e5..24aa6f118 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -14,8 +14,8 @@ import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT, - NFC_MOVE_KEY_TO_CARD, NFC_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, + PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SMARTCARD_SIGN, SMARTCARD_DECRYPT, + SMARTCARD_MOVE_KEY_TO_CARD, SMARTCARD_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, } public Date mSignatureTime; @@ -92,19 +92,19 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { - return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcDecryptOperation( long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { - return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_DECRYPT, new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcReset() { - return new RequiredInputParcel(RequiredInputType.NFC_RESET_CARD, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_RESET_CARD, null, null, null, null, null); } @@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable { signAlgos[i] = mSignAlgos.get(i); } - return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId); } @@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable { if (!mSignatureTime.equals(input.mSignatureTime)) { throw new AssertionError("input times must match, this is a programming error!"); } - if (input.mType != RequiredInputType.NFC_SIGN) { + if (input.mType != RequiredInputType.SMARTCARD_SIGN) { throw new AssertionError("operation types must match, this is a progrmming error!"); } @@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable { ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... - return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD, inputData, null, null, mMasterKeyId, buf.getLong()); } @@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); } - if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) { + if (input.mType != RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD) { throw new AssertionError("Operation types must match, this is a programming error!"); } 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 07d5be821..a9d259b00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -143,7 +143,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 884f33365..ed6e3faf3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -137,8 +137,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD - && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD + && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { @@ -180,10 +180,10 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { switch (mRequiredInput.mType) { - case NFC_DECRYPT: { + case SMARTCARD_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); @@ -191,7 +191,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case NFC_SIGN: { + case SMARTCARD_SIGN: { mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { @@ -202,7 +202,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case NFC_MOVE_KEY_TO_CARD: { + case SMARTCARD_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation mSmartcardDevice.setPin(new Passphrase("123456")); mSmartcardDevice.setAdminPin(new Passphrase("12345678")); @@ -278,7 +278,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity break; } - case NFC_RESET_CARD: { + case SMARTCARD_RESET_CARD: { mSmartcardDevice.resetAndWipeToken(); break; @@ -308,7 +308,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected Void doInBackground(Void... params) { // check all 200ms if Security Token has been taken away while (true) { - if (isNfcConnected()) { + if (isSmartcardConnected()) { try { Thread.sleep(200); } catch (InterruptedException ignored) { @@ -340,7 +340,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void onNfcError(String error) { + protected void onSmartcardError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); @@ -350,8 +350,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPinError(String error) { - onNfcError(error); + public void onSmartcardPinError(String error) { + onSmartcardError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( 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 5bf81f1aa..dd753a431 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -647,7 +647,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { mNfcFingerprints = mSmartcardDevice.getFingerprints(); mNfcUserId = mSmartcardDevice.getUserId(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 5e1592346..e138af895 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -59,8 +59,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import java.io.IOException; -import nordpol.IsoCard; -import nordpol.android.AndroidCard; import nordpol.android.OnDiscoveredTagListener; import nordpol.android.TagDispatcher; @@ -77,9 +75,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSmartcardFingerprints; + private String mSmartcardUserId; + private byte[] mSmartcardAid; /** * Override to change UI before NFC handling (UI thread) @@ -90,10 +88,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to implement NFC operations (background thread) */ - protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mSmartcardDevice.getFingerprints(); - mNfcUserId = mSmartcardDevice.getUserId(); - mNfcAid = mSmartcardDevice.getAid(); + protected void doSmartcardInBackground() throws IOException { + mSmartcardFingerprints = mSmartcardDevice.getFingerprints(); + mSmartcardUserId = mSmartcardDevice.getUserId(); + mSmartcardAid = mSmartcardDevice.getAid(); } /** @@ -101,7 +99,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity */ protected void onSmartcardPostExecute() { - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSmartcardFingerprints); try { CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( @@ -110,15 +108,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSmartcardAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSmartcardUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSmartcardFingerprints); startActivity(intent); } catch (PgpKeyNotFoundException e) { Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mSmartcardAid); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mSmartcardUserId); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mSmartcardFingerprints); startActivity(intent); } } @@ -126,15 +124,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to use something different than Notify (UI thread) */ - protected void onNfcError(String error) { + protected void onSmartcardError(String error) { Notify.create(this, error, Style.WARN).show(); } /** * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) */ - protected void onNfcPinError(String error) { - onNfcError(error); + protected void onSmartcardPinError(String error) { + onSmartcardError(error); } public void tagDiscovered(final Tag tag) { @@ -240,12 +238,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { - onNfcError(getString(R.string.security_token_error_tag_lost)); + onSmartcardError(getString(R.string.security_token_error_tag_lost)); return; } if (e instanceof IsoDepNotSupportedException) { - onNfcError(getString(R.string.security_token_error_iso_dep_not_supported)); + onSmartcardError(getString(R.string.security_token_error_iso_dep_not_supported)); return; } @@ -260,7 +258,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity 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)); + onSmartcardPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); return; } @@ -269,56 +267,56 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // 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)); + onSmartcardError(getString(R.string.security_token_error_bad_data)); break; } case 0x6883: { - onNfcError(getString(R.string.security_token_error_chaining_error)); + onSmartcardError(getString(R.string.security_token_error_chaining_error)); break; } case 0x6B00: { - onNfcError(getString(R.string.security_token_error_header, "P1/P2")); + onSmartcardError(getString(R.string.security_token_error_header, "P1/P2")); break; } case 0x6D00: { - onNfcError(getString(R.string.security_token_error_header, "INS")); + onSmartcardError(getString(R.string.security_token_error_header, "INS")); break; } case 0x6E00: { - onNfcError(getString(R.string.security_token_error_header, "CLA")); + onSmartcardError(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)); + onSmartcardError(getString(R.string.security_token_error_terminated)); break; } case 0x6700: { - onNfcPinError(getString(R.string.security_token_error_wrong_length)); + onSmartcardPinError(getString(R.string.security_token_error_wrong_length)); break; } case 0x6982: { - onNfcError(getString(R.string.security_token_error_security_not_satisfied)); + onSmartcardError(getString(R.string.security_token_error_security_not_satisfied)); break; } case 0x6983: { - onNfcError(getString(R.string.security_token_error_authentication_blocked)); + onSmartcardError(getString(R.string.security_token_error_authentication_blocked)); break; } case 0x6985: { - onNfcError(getString(R.string.security_token_error_conditions_not_satisfied)); + onSmartcardError(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)); + onSmartcardError(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)); + onSmartcardError(getString(R.string.security_token_error_unknown)); break; } // 6A82 app not installed on security token! @@ -331,12 +329,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity promptFidesmoAppInstall(); } } else { // Other (possibly) compatible hardware - onNfcError(getString(R.string.security_token_error_pgp_app_not_installed)); + onSmartcardError(getString(R.string.security_token_error_pgp_app_not_installed)); } break; } default: { - onNfcError(getString(R.string.security_token_error, e.getMessage())); + onSmartcardError(getString(R.string.security_token_error, e.getMessage())); break; } } @@ -410,10 +408,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mSmartcardDevice.setTransport(transport); mSmartcardDevice.connectToDevice(); } - doNfcInBackground(); + doSmartcardInBackground(); } - public boolean isNfcConnected() { + public boolean isSmartcardConnected() { return mSmartcardDevice.isConnected(); } @@ -491,6 +489,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice; } + /** + * Run smartcard routines if last used token is connected and supports + * persistent connections + */ protected void checkDeviceConnection() { if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { this.smartcardDiscovered(mSmartcardDevice.getTransport()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 451065d6b..29200ac2c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -130,9 +130,9 @@ public class CryptoOperationHelper Date: Sat, 9 Apr 2016 16:03:31 +0600 Subject: OTG: Add/update javadoc; rename methods, exceptions --- .../keychain/smartcard/NfcTransport.java | 21 +++- .../keychain/smartcard/SmartcardDevice.java | 49 ++++---- .../keychain/smartcard/Transport.java | 30 ++++- .../keychain/smartcard/TransportIoException.java | 20 ---- .../smartcard/UsbConnectionDispatcher.java | 54 +++++++++ .../keychain/smartcard/UsbConnectionManager.java | 54 --------- .../keychain/smartcard/UsbTransport.java | 123 ++++++++++++++------- .../keychain/smartcard/UsbTransportException.java | 20 ++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 21 +--- 9 files changed, 225 insertions(+), 167 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index e47ba5360..e3c6d12da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -19,13 +19,23 @@ public class NfcTransport implements Transport { this.mTag = tag; } + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + public byte[] transceive(final byte[] data) throws IOException { return mIsoCard.transceive(data); } + /** + * Disconnect and release connection + */ @Override public void release() { + // Not supported } @Override @@ -33,13 +43,18 @@ public class NfcTransport implements Transport { return mIsoCard != null && mIsoCard.isConnected(); } + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ @Override - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return false; } /** - * Handle NFC communication and return a result. + * Connect to NFC device. *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index 286a38d1f..7bc53a10a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -39,7 +39,6 @@ public class SmartcardDevice { return LazyHolder.mSmartcardDevice; } - // METHOD UPDATED [OK] private String getHolderName(String name) { try { String slength; @@ -80,6 +79,7 @@ public class SmartcardDevice { this.mAdminPin = adminPin; } + // NEW MY METHOD public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); @@ -102,7 +102,7 @@ public class SmartcardDevice { putData(keyType.getTimestampObjectId(), timestampBytes); } - public boolean containsKey(KeyType keyType) throws IOException { + private boolean containsKey(KeyType keyType) throws IOException { return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } @@ -110,10 +110,17 @@ public class SmartcardDevice { return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); } - // METHOD UPDATED OK + /** + * Connect to device and select pgp applet + * + * @throws IOException + */ public void connectToDevice() throws IOException { + // Connect on transport layer mTransport.connect(); + // Connect on smartcard layer + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; @@ -164,7 +171,6 @@ public class SmartcardDevice { * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. * @param newPin The new PW1 or PW3. */ - // METHOD UPDATED[OK] public void modifyPin(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; @@ -210,7 +216,6 @@ public class SmartcardDevice { * @param encryptedSessionKey the encoded session key * @return the decoded session key */ - // METHOD UPDATED [OK] public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) @@ -280,7 +285,6 @@ public class SmartcardDevice { * @param dataObject The data object to be stored. * @param data The data to store in the object */ - // METHOD UPDATED [OK] public void putData(int dataObject, byte[] data) throws IOException { if (data.length > 254) { throw new IOException("Cannot PUT DATA with length > 254"); @@ -315,7 +319,6 @@ public class SmartcardDevice { * 0xB8: Decipherment Key * 0xA4: Authentication Key */ - // METHOD UPDATED [OK] public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { @@ -426,10 +429,9 @@ public class SmartcardDevice { * * @return The fingerprints of all subkeys in a contiguous byte array. */ - // METHOD UPDATED [OK] public byte[] getFingerprints() throws IOException { String data = "00CA006E00"; - byte[] buf = mTransport.sendAndReceive(Hex.decode(data)); + byte[] buf = mTransport.transceive(Hex.decode(data)); Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); @@ -446,19 +448,16 @@ public class SmartcardDevice { * * @return Seven bytes in fixed format, plus 0x9000 status word at the end. */ - // METHOD UPDATED [OK] private byte[] getPwStatusBytes() throws IOException { String data = "00CA00C400"; - return mTransport.sendAndReceive(Hex.decode(data)); + return mTransport.transceive(Hex.decode(data)); } - // METHOD UPDATED [OK] public byte[] getAid() throws IOException { String info = "00CA004F00"; - return mTransport.sendAndReceive(Hex.decode(info)); + return mTransport.transceive(Hex.decode(info)); } - // METHOD UPDATED [OK] public String getUserId() throws IOException { String info = "00CA006500"; return getHolderName(communicate(info)); @@ -470,7 +469,6 @@ public class SmartcardDevice { * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ - // METHOD UPDATED [OK] public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { if (!mPw1ValidatedForSignature) { verifyPin(0x81); // (Verify PW1 with mode 81 for signing) @@ -568,29 +566,23 @@ public class SmartcardDevice { return Hex.decode(signature); } - public boolean isConnected() { - return mTransport != null && mTransport.isConnected(); - } - /** * Transceive data via NFC encoded as Hex */ - // METHOD UPDATED [OK] - private String communicate(String apdu) throws IOException, TransportIoException { - return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + private String communicate(String apdu) throws IOException { + return getHex(mTransport.transceive(Hex.decode(apdu))); } public Transport getTransport() { return mTransport; } - // NEW METHOD [OK] public boolean isFidesmoToken() { if (isConnected()) { // Check if we can still talk to the card try { // By trying to select any apps that have the Fidesmo AID prefix we can // see if it is a Fidesmo device or not - byte[] mSelectResponse = mTransport.sendAndReceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); + byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); // Compare the status returned by our select with the OK status code return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); } catch (IOException e) { @@ -614,7 +606,6 @@ public class SmartcardDevice { * @return the public key data objects, in TLV format. For RSA this will be the public modulus * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. */ - // NEW METHOD [OK] public byte[] generateKey(int slot) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); @@ -641,7 +632,6 @@ public class SmartcardDevice { return Hex.decode(publicKeyData); } - // NEW METHOD [OK][OK] private String getDataField(String output) { return output.substring(0, output.length() - 4); } @@ -665,7 +655,6 @@ public class SmartcardDevice { * This works by entering a wrong PIN and then Admin PIN 4 times respectively. * Afterwards, the token is reactivated. */ - // NEW METHOD [OK] public void resetAndWipeToken() throws IOException { String accepted = "9000"; @@ -725,7 +714,11 @@ public class SmartcardDevice { } public boolean isPersistentConnectionAllowed() { - return mTransport != null && mTransport.allowPersistentConnection(); + return mTransport != null && mTransport.isPersistentConnectionAllowed(); + } + + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); } private static class LazyHolder { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index 5252a95d0..fa8b0695a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -2,14 +2,40 @@ package org.sufficientlysecure.keychain.smartcard; import java.io.IOException; +/** + * Abstraction for transmitting APDU commands + */ public interface Transport { - byte[] sendAndReceive(byte[] data) throws IOException; + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + byte[] transceive(byte[] data) throws IOException; + /** + * Disconnect and release connection + */ void release(); + /** + * Check if device is was connected to and still is connected + * @return connection status + */ boolean isConnected(); - boolean allowPersistentConnection(); + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + boolean isPersistentConnectionAllowed(); + + /** + * Connect to device + * @throws IOException + */ void connect() throws IOException; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java deleted file mode 100644 index 544dd4045..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import java.io.IOException; - -public class TransportIoException extends IOException { - public TransportIoException() { - } - - public TransportIoException(final String detailMessage) { - super(detailMessage); - } - - public TransportIoException(final String message, final Throwable cause) { - super(message, cause); - } - - public TransportIoException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java new file mode 100644 index 000000000..c3068916e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java @@ -0,0 +1,54 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class UsbConnectionDispatcher { + private Activity mActivity; + + private OnDiscoveredUsbDeviceListener mListener; + /** + * Receives broadcast when a supported USB device get permission. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + mListener.usbDeviceDiscovered(usbDevice); + } + } + } + }; + + public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + } + + public void onStart() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + + mActivity.registerReceiver(mUsbReceiver, intentFilter); + } + + public void onStop() { + mActivity.unregisterReceiver(mUsbReceiver); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java deleted file mode 100644 index 8a6971fe6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; -import org.sufficientlysecure.keychain.util.Log; - -public class UsbConnectionManager { - private Activity mActivity; - - private OnDiscoveredUsbDeviceListener mListener; - /** - * Receives broadcast when a supported USB device get permission. - */ - private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); - } - } - } - }; - - public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - } - - public void onStart() { - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - - mActivity.registerReceiver(mUsbReceiver, intentFilter); - } - - public void onStop() { - mActivity.unregisterReceiver(mUsbReceiver); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 2a21c10dd..86f4a687d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -19,9 +19,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +/** + * Based on USB CCID Specification rev. 1.1 + * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf + * Implements small subset of these features + */ public class UsbTransport implements Transport { - private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 2 s + private static final int USB_CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 20s private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; @@ -29,29 +34,24 @@ public class UsbTransport implements Transport { private UsbEndpoint mBulkIn; private UsbEndpoint mBulkOut; private UsbDeviceConnection mConnection; - private byte mCounter = 0; + private byte mCounter; - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) { + public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { mUsbDevice = usbDevice; mUsbManager = usbManager; } - private void powerOff() throws TransportIoException { - final byte[] iccPowerOff = { - 0x63, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOff); - receive(); - } - void powerOn() throws TransportIoException { + /** + * Manage ICC power, Yubikey requires to power on ICC + * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff + * + * @param on true to turn ICC on, false to turn it off + * @throws UsbTransportException + */ + private void iccPowerSet(boolean on) throws UsbTransportException { final byte[] iccPowerOn = { - 0x62, + (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, 0x00, mCounter++, @@ -63,22 +63,28 @@ public class UsbTransport implements Transport { } /** - * Get first class 11 (Chip/Smartcard) interface for the device + * Get first class 11 (Chip/Smartcard) interface of the device * * @param device {@link UsbDevice} which will be searched * @return {@link UsbInterface} of smartcard or null if it doesn't exist */ @Nullable - private static UsbInterface getSmartCardInterface(final UsbDevice device) { + private static UsbInterface getSmartCardInterface(UsbDevice device) { for (int i = 0; i < device.getInterfaceCount(); i++) { - final UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { return anInterface; } } return null; } + /** + * Get device's bulk-in and bulk-out endpoints + * + * @param usbInterface usb device interface + * @return pair of builk-in and bulk-out endpoints respectively + */ @NonNull private static Pair getIoEndpoints(final UsbInterface usbInterface) { UsbEndpoint bulkIn = null, bulkOut = null; @@ -97,43 +103,77 @@ public class UsbTransport implements Transport { return new Pair<>(bulkIn, bulkOut); } + /** + * Release interface and disconnect + */ @Override public void release() { mConnection.releaseInterface(mUsbInterface); mConnection.close(); + mConnection = null; } + /** + * Check if device is was connected to and still is connected + * @return true if device is connected + */ @Override public boolean isConnected() { - // TODO: redo return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); } + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ @Override - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return true; } + /** + * Connect to OTG device + * @throws IOException + */ @Override public void connect() throws IOException { + mCounter = 0; mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null + if (mUsbInterface == null) { + // Shouldn't happen as we whitelist only class 11 devices + throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + } + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); mBulkIn = ioEndpoints.first; mBulkOut = ioEndpoints.second; - // throw if any endpoint is null + + if (mBulkIn == null || mBulkOut == null) { + throw new UsbTransportException("USB error: invalid class 11 interface"); + } mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result + if (mConnection == null) { + throw new UsbTransportException("USB error: failed to connect to device"); + } + + if (!mConnection.claimInterface(mUsbInterface, true)) { + throw new UsbTransportException("USB error: failed to claim interface"); + } - powerOn(); + iccPowerSet(true); Log.d(Constants.TAG, "Usb transport connected"); } + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws UsbTransportException + */ @Override - public byte[] sendAndReceive(byte[] data) throws TransportIoException { + public byte[] transceive(byte[] data) throws UsbTransportException { send(data); byte[] bytes; do { @@ -141,10 +181,11 @@ public class UsbTransport implements Transport { } while (isXfrBlockNotReady(bytes)); checkXfrBlockResult(bytes); + // Discard header return Arrays.copyOfRange(bytes, 10, bytes.length); } - public void send(byte[] d) throws TransportIoException { + private void send(byte[] d) throws UsbTransportException { int l = d.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, @@ -163,7 +204,7 @@ public class UsbTransport implements Transport { } } - public byte[] receive() throws TransportIoException { + private byte[] receive() throws UsbTransportException { byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; byte[] result = null; int readBytes = 0, totalBytes = 0; @@ -171,11 +212,11 @@ public class UsbTransport implements Transport { do { int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); + throw new UsbTransportException("USB error: failed to receive response " + res); } if (result == null) { if (res < 10) { - throw new TransportIoException("USB error, failed to receive ccid header"); + throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); } totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; result = new byte[totalBytes]; @@ -187,10 +228,10 @@ public class UsbTransport implements Transport { return result; } - private void sendRaw(final byte[] data) throws TransportIoException { + private void sendRaw(final byte[] data) throws UsbTransportException { final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); if (tr1 != data.length) { - throw new TransportIoException("USB error, failed to send data " + tr1); + throw new UsbTransportException("USB error: failed to transmit data " + tr1); } } @@ -198,10 +239,10 @@ public class UsbTransport implements Transport { return (byte) ((bytes[7] >> 6) & 0x03); } - private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + private void checkXfrBlockResult(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { - throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } @@ -209,10 +250,6 @@ public class UsbTransport implements Transport { return getStatus(bytes) == 2; } - public UsbDevice getUsbDevice() { - return mUsbDevice; - } - @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java new file mode 100644 index 000000000..27635137f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class UsbTransportException extends IOException { + public UsbTransportException() { + } + + public UsbTransportException(final String detailMessage) { + super(detailMessage); + } + + public UsbTransportException(final String message, final Throwable cause) { + super(message, cause); + } + + public UsbTransportException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index e138af895..f3c3cbe75 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -40,11 +40,12 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.smartcard.CardException; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.Transport; -import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; +import org.sufficientlysecure.keychain.smartcard.UsbConnectionDispatcher; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -72,7 +73,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); protected TagDispatcher mTagDispatcher; - protected UsbConnectionManager mUsbDispatcher; + protected UsbConnectionDispatcher mUsbDispatcher; private boolean mTagHandlingEnabled; private byte[] mSmartcardFingerprints; @@ -201,7 +202,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onCreate(savedInstanceState); mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); - mUsbDispatcher = new UsbConnectionManager(this, this); + mUsbDispatcher = new UsbConnectionDispatcher(this, this); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -423,20 +424,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } - public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - - } - /** * Ask user if she wants to install PGP onto her Fidesmo token */ -- cgit v1.2.3 From 409c38ba2ab79cac97a914d89f9cb9a14ebe08c1 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 17:28:44 +0600 Subject: OTG: UsbTransport minor refactoring --- .../keychain/smartcard/UsbTransport.java | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 86f4a687d..4b4f2c35c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -50,7 +50,7 @@ public class UsbTransport implements Transport { * @throws UsbTransportException */ private void iccPowerSet(boolean on) throws UsbTransportException { - final byte[] iccPowerOn = { + final byte[] iccPowerCommand = { (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, 0x00, @@ -58,8 +58,13 @@ public class UsbTransport implements Transport { 0x00, 0x00, 0x00 }; - sendRaw(iccPowerOn); - receive(); + + sendRaw(iccPowerCommand); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + checkDataBlockResponse(receive()); } /** @@ -174,19 +179,25 @@ public class UsbTransport implements Transport { */ @Override public byte[] transceive(byte[] data) throws UsbTransportException { - send(data); + sendXfrBlock(data); byte[] bytes; do { bytes = receive(); - } while (isXfrBlockNotReady(bytes)); + } while (isDataBlockNotReady(bytes)); - checkXfrBlockResult(bytes); + checkDataBlockResponse(bytes); // Discard header return Arrays.copyOfRange(bytes, 10, bytes.length); } - private void send(byte[] d) throws UsbTransportException { - int l = d.length; + /** + * Transmits XfrBlock + * 6.1.4 PC_to_RDR_XfrBlock + * @param payload payload to transmit + * @throws UsbTransportException + */ + private void sendXfrBlock(byte[] payload) throws UsbTransportException { + int l = payload.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), @@ -194,7 +205,7 @@ public class UsbTransport implements Transport { mCounter++, 0x00, 0x00, 0x00}, - d); + payload); int send = 0; while (send < data.length) { @@ -239,14 +250,14 @@ public class UsbTransport implements Transport { return (byte) ((bytes[7] >> 6) & 0x03); } - private void checkXfrBlockResult(byte[] bytes) throws UsbTransportException { + private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } - private boolean isXfrBlockNotReady(byte[] bytes) { + private boolean isDataBlockNotReady(byte[] bytes) { return getStatus(bytes) == 2; } -- cgit v1.2.3 From b37b171908339e0a2a79c5811aa092fc47ed79b7 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 17:45:17 +0600 Subject: OTG: move UsbConnectionDispatcher to utls package --- .../smartcard/OnDiscoveredUsbDeviceListener.java | 7 --- .../smartcard/UsbConnectionDispatcher.java | 54 -------------------- .../ui/base/BaseSecurityTokenNfcActivity.java | 5 +- .../keychain/util/UsbConnectionDispatcher.java | 58 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 64 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java deleted file mode 100644 index 46b503b42..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.hardware.usb.UsbDevice; - -public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java deleted file mode 100644 index c3068916e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; -import org.sufficientlysecure.keychain.util.Log; - -public class UsbConnectionDispatcher { - private Activity mActivity; - - private OnDiscoveredUsbDeviceListener mListener; - /** - * Receives broadcast when a supported USB device get permission. - */ - private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); - } - } - } - }; - - public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - } - - public void onStart() { - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - - mActivity.registerReceiver(mUsbReceiver, intentFilter); - } - - public void onStop() { - mActivity.unregisterReceiver(mUsbReceiver); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index f3c3cbe75..57da687c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -42,10 +42,9 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.smartcard.CardException; import org.sufficientlysecure.keychain.smartcard.NfcTransport; -import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.Transport; -import org.sufficientlysecure.keychain.smartcard.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -64,7 +63,7 @@ import nordpol.android.OnDiscoveredTagListener; import nordpol.android.TagDispatcher; public abstract class BaseSecurityTokenNfcActivity extends BaseActivity - implements OnDiscoveredTagListener, OnDiscoveredUsbDeviceListener { + implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java new file mode 100644 index 000000000..e48ba7be0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -0,0 +1,58 @@ +package org.sufficientlysecure.keychain.util; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class UsbConnectionDispatcher { + private Activity mActivity; + + private OnDiscoveredUsbDeviceListener mListener; + /** + * Receives broadcast when a supported USB device get permission. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + mListener.usbDeviceDiscovered(usbDevice); + } + } + } + }; + + public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + } + + public void onStart() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + + mActivity.registerReceiver(mUsbReceiver, intentFilter); + } + + public void onStop() { + mActivity.unregisterReceiver(mUsbReceiver); + } + + public interface OnDiscoveredUsbDeviceListener { + void usbDeviceDiscovered(UsbDevice usbDevice); + } +} -- cgit v1.2.3 From 0c9ead62a0f6c7cb59811891059eeb8b5330ee79 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 18:24:31 +0600 Subject: OTG: Remove obsolete Pin classes, reenable changeKey method --- .../keychain/smartcard/PinException.java | 7 --- .../keychain/smartcard/PinType.java | 16 ------- .../keychain/smartcard/SmartcardDevice.java | 40 +++++++++------- .../ui/SecurityTokenOperationActivity.java | 54 +--------------------- 4 files changed, 24 insertions(+), 93 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java deleted file mode 100644 index 58a7a31c9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -public class PinException extends CardException { - public PinException(final String detailMessage, final short responseCode) { - super(detailMessage, responseCode); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java deleted file mode 100644 index 7601edcf3..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -public enum PinType { - BASIC(0x81), - ADMIN(0x83),; - - private final int mMode; - - PinType(final int mode) { - this.mMode = mode; - } - - public int getmMode() { - return mMode; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index 7bc53a10a..a9358ee84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -17,6 +17,11 @@ import java.security.interfaces.RSAPrivateCrtKey; import nordpol.Apdu; +/** + * 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 SmartcardDevice { // Fidesmo constants private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; @@ -30,7 +35,6 @@ public class SmartcardDevice { private boolean mPw1ValidatedForSignature; private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? private boolean mPw3Validated; - private boolean mTagHandlingEnabled; protected SmartcardDevice() { } @@ -39,6 +43,10 @@ public class SmartcardDevice { return LazyHolder.mSmartcardDevice; } + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + private String getHolderName(String name) { try { String slength; @@ -59,10 +67,6 @@ public class SmartcardDevice { } } - private static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - public Passphrase getPin() { return mPin; } @@ -79,7 +83,6 @@ public class SmartcardDevice { this.mAdminPin = adminPin; } - // NEW MY METHOD public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); @@ -90,8 +93,9 @@ public class SmartcardDevice { } // Slot is empty, or contains this key already. PUT KEY operation is safe - boolean canPutKey = !containsKey(keyType) + boolean canPutKey = isSlotEmpty(keyType) || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); + if (!canPutKey) { throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", keyType.toString())); @@ -102,8 +106,12 @@ public class SmartcardDevice { putData(keyType.getTimestampObjectId(), timestampBytes); } - private boolean containsKey(KeyType keyType) throws IOException { - return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + private boolean isSlotEmpty(KeyType keyType) throws IOException { + // Note: special case: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true + if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; + + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { @@ -248,7 +256,6 @@ public class SmartcardDevice { * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. * For PW3 (Admin PIN), mode is 0x83. */ - // METHOD UPDATED [OK] private void verifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { @@ -285,7 +292,7 @@ public class SmartcardDevice { * @param dataObject The data object to be stored. * @param data The data to store in the object */ - public void putData(int dataObject, byte[] data) throws IOException { + private void putData(int dataObject, byte[] data) throws IOException { if (data.length > 254) { throw new IOException("Cannot PUT DATA with length > 254"); } @@ -319,7 +326,7 @@ public class SmartcardDevice { * 0xB8: Decipherment Key * 0xA4: Authentication Key */ - public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) + private void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); @@ -577,6 +584,10 @@ public class SmartcardDevice { return mTransport; } + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + } + public boolean isFidesmoToken() { if (isConnected()) { // Check if we can still talk to the card try { @@ -636,7 +647,6 @@ public class SmartcardDevice { return output.substring(0, output.length() - 4); } - // NEW METHOD [OK] private String tryPin(int mode, byte[] pin) throws IOException { // Command APDU for VERIFY command (page 32) String login = @@ -709,10 +719,6 @@ public class SmartcardDevice { return fp; } - public void setTransport(Transport mTransport) { - this.mTransport = mTransport; - } - public boolean isPersistentConnectionAllowed() { return mTransport != null && mTransport.isPersistentConnectionAllowed(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ed6e3faf3..12843cbcd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -69,8 +69,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private RequiredInputParcel mRequiredInput; - private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - private CryptoInputParcel mInputParcel; @Override @@ -226,10 +224,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); - - long keyGenerationTimestampMillis = key.getCreationTime().getTime(); - long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; - byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); Passphrase passphrase; @@ -240,33 +234,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity throw new IOException("Unable to get cached passphrase!"); } - if (key.canSign() || key.canCertify()) { - if (shouldPutKey(key.getFingerprint(), 0)) { - mSmartcardDevice.putKey(0xB6, key, passphrase); - mSmartcardDevice.putData(0xCE, timestampBytes); - mSmartcardDevice.putData(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)) { - mSmartcardDevice.putKey(0xB8, key, passphrase); - mSmartcardDevice.putData(0xCF, timestampBytes); - mSmartcardDevice.putData(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)) { - mSmartcardDevice.putKey(0xA4, key, passphrase); - mSmartcardDevice.putData(0xD0, timestampBytes); - mSmartcardDevice.putData(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."); - } + mSmartcardDevice.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); @@ -357,24 +325,4 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); } - - private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = mSmartcardDevice.getMasterKeyFingerprint(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; - } - } -- cgit v1.2.3 From 0077f3a3b8a3a62578a48ec8b096cbb85c189246 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 18:35:32 +0600 Subject: OTG: Add/Update copyrights --- .../keychain/smartcard/CardException.java | 17 +++++++++++++++++ .../keychain/smartcard/KeyType.java | 17 +++++++++++++++++ .../keychain/smartcard/NfcTransport.java | 17 +++++++++++++++++ .../keychain/smartcard/SmartcardDevice.java | 22 ++++++++++++++++++++++ .../keychain/smartcard/Transport.java | 17 +++++++++++++++++ .../keychain/smartcard/UsbTransport.java | 17 +++++++++++++++++ .../keychain/smartcard/UsbTransportException.java | 17 +++++++++++++++++ .../ui/SecurityTokenOperationActivity.java | 1 + .../ui/base/BaseSecurityTokenNfcActivity.java | 1 + .../keychain/util/UsbConnectionDispatcher.java | 17 +++++++++++++++++ 10 files changed, 143 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java index 9ea67f711..06445e988 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java index 625e1e669..29c7fce96 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index e3c6d12da..a743c753a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import android.nfc.Tag; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java index a9358ee84..58cf9b51a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -1,3 +1,25 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * 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.smartcard; import org.bouncycastle.bcpg.HashAlgorithmTags; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index fa8b0695a..5b942ee5c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 4b4f2c35c..b08908aa5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import android.hardware.usb.UsbConstants; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java index 27635137f..7bf713bf9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 12843cbcd..ac12c2a4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov * * 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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 57da687c7..c8022a776 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov * * 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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index e48ba7be0..697db0811 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.util; import android.app.Activity; -- cgit v1.2.3 From 7caceee92f2b73c51923bec20545a1609b466716 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 22:29:45 +0600 Subject: OTG: fix icc powering method --- .../java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index b08908aa5..43496b31c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -81,7 +81,7 @@ public class UsbTransport implements Transport { do { bytes = receive(); } while (isDataBlockNotReady(bytes)); - checkDataBlockResponse(receive()); + checkDataBlockResponse(bytes); } /** -- cgit v1.2.3 From 8cb94c446bf1d490978cfe49adcefb29d2fbca5b Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 23:08:52 +0600 Subject: OTG: Add copyright --- .../keychain/ui/UsbEventReceiverActivity.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java index 9df5800b5..05b30b1ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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; -- cgit v1.2.3 From 4e543e5368ae37afd474ebf0f04bd869d12be755 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 23:44:52 +0600 Subject: OTG: rescan devices before next operation --- .../keychain/smartcard/UsbTransport.java | 14 ++++-- .../ui/base/BaseSecurityTokenNfcActivity.java | 10 ++-- .../keychain/util/UsbConnectionDispatcher.java | 53 ++++++++++++++++++---- 3 files changed, 58 insertions(+), 19 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 43496b31c..8d661b9fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -130,9 +130,13 @@ public class UsbTransport implements Transport { */ @Override public void release() { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - mConnection = null; + if (mConnection != null) { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + mConnection = null; + } + + Log.d(Constants.TAG, "Usb transport disconnected"); } /** @@ -292,4 +296,8 @@ public class UsbTransport implements Transport { public int hashCode() { return mUsbDevice != null ? mUsbDevice.hashCode() : 0; } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index c8022a776..5ad542526 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -24,7 +24,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; @@ -144,13 +143,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity smartcardDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbDevice device) { + public void usbDeviceDiscovered(final UsbTransport transport) { // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - smartcardDiscovered(new UsbTransport(device, usbManager)); + smartcardDiscovered(transport); } public void smartcardDiscovered(final Transport transport) { @@ -481,8 +479,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * persistent connections */ protected void checkDeviceConnection() { - if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { - this.smartcardDiscovered(mSmartcardDevice.getTransport()); - } + mUsbDispatcher.rescanDevices(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 697db0811..09b029523 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,13 +26,15 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; -import org.sufficientlysecure.keychain.util.Log; public class UsbConnectionDispatcher { private Activity mActivity; private OnDiscoveredUsbDeviceListener mListener; + private UsbTransport mLastUsedUsbTransport; + private UsbManager mUsbManager; /** * Receives broadcast when a supported USB device get permission. */ @@ -41,13 +43,27 @@ public class UsbConnectionDispatcher { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); + switch (action) { + case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + + mLastUsedUsbTransport = new UsbTransport(usbDevice, mUsbManager); + mListener.usbDeviceDiscovered(mLastUsedUsbTransport); + } + break; + } + case UsbManager.ACTION_USB_DEVICE_DETACHED: { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + + if (mLastUsedUsbTransport != null && mLastUsedUsbTransport.getUsbDevice().equals(usbDevice)) { + mLastUsedUsbTransport.release(); + mLastUsedUsbTransport = null; + } + break; } } } @@ -56,11 +72,13 @@ public class UsbConnectionDispatcher { public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; this.mListener = listener; + this.mUsbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE); } public void onStart() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mActivity.registerReceiver(mUsbReceiver, intentFilter); } @@ -69,7 +87,24 @@ public class UsbConnectionDispatcher { mActivity.unregisterReceiver(mUsbReceiver); } + /** + * Rescans devices and triggers {@link OnDiscoveredUsbDeviceListener} + */ + public void rescanDevices() { + // Note: we don't check devices VID/PID because + // we check for permisssion instead. + // We should have permission only for matching devices + for (UsbDevice device : mUsbManager.getDeviceList().values()) { + if (mUsbManager.hasPermission(device)) { + if (mListener != null) { + mListener.usbDeviceDiscovered(new UsbTransport(device, mUsbManager)); + } + break; + } + } + } + public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); + void usbDeviceDiscovered(UsbTransport usbTransport); } } -- cgit v1.2.3 From df57ecde4755b6d2bf39c486cc7bb8d521d3268b Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Tue, 12 Apr 2016 00:02:59 +0600 Subject: OTG: rename Smartcard -> SecurityToken --- .../keychain/securitytoken/CardException.java | 34 + .../keychain/securitytoken/KeyType.java | 65 ++ .../keychain/securitytoken/NfcTransport.java | 113 +++ .../securitytoken/SecurityTokenHelper.java | 755 +++++++++++++++++++++ .../keychain/securitytoken/Transport.java | 58 ++ .../keychain/securitytoken/UsbTransport.java | 303 +++++++++ .../securitytoken/UsbTransportException.java | 37 + .../keychain/smartcard/CardException.java | 34 - .../keychain/smartcard/KeyType.java | 65 -- .../keychain/smartcard/NfcTransport.java | 113 --- .../keychain/smartcard/SmartcardDevice.java | 755 --------------------- .../keychain/smartcard/Transport.java | 58 -- .../keychain/smartcard/UsbTransport.java | 303 --------- .../keychain/smartcard/UsbTransportException.java | 37 - .../keychain/ui/CreateKeyActivity.java | 34 +- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 32 +- .../keychain/ui/ViewKeyActivity.java | 33 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 123 ++-- .../keychain/util/UsbConnectionDispatcher.java | 2 +- 20 files changed, 1479 insertions(+), 1481 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java new file mode 100644 index 000000000..905deca90 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java new file mode 100644 index 000000000..0e28f022b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java new file mode 100644 index 000000000..3b2dd838d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import android.nfc.Tag; + +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; + +import java.io.IOException; + +import nordpol.IsoCard; +import nordpol.android.AndroidCard; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final Tag mTag; + private IsoCard mIsoCard; + + public NfcTransport(Tag tag) { + this.mTag = tag; + } + + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + @Override + public byte[] transceive(final byte[] data) throws IOException { + return mIsoCard.transceive(data); + } + + /** + * Disconnect and release connection + */ + @Override + public void release() { + // Not supported + } + + @Override + public boolean isConnected() { + return mIsoCard != null && mIsoCard.isConnected(); + } + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + @Override + public boolean isPersistentConnectionAllowed() { + return false; + } + + /** + * Connect to NFC device. + *

+ * 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. + */ + @Override + public void connect() throws IOException { + mIsoCard = AndroidCard.get(mTag); + if (mIsoCard == null) { + throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + } + + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final NfcTransport that = (NfcTransport) o; + + if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; + if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = mTag != null ? mTag.hashCode() : 0; + result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java new file mode 100644 index 000000000..e3f280e18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -0,0 +1,755 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * 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.securitytoken; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Iso7816TLV; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateCrtKey; + +import nordpol.Apdu; + +/** + * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant + * devices. + * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + */ +public class SecurityTokenHelper { + // Fidesmo constants + private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; + + 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 Transport mTransport; + + private Passphrase mPin; + private Passphrase mAdminPin; + private boolean mPw1ValidForMultipleSignatures; + private boolean mPw1ValidatedForSignature; + private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + private boolean mPw3Validated; + + protected SecurityTokenHelper() { + } + + public static SecurityTokenHelper getInstance() { + return LazyHolder.SECURITY_TOKEN_HELPER; + } + + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + private String getHolderName(String name) { + try { + String slength; + int ilength; + name = name.substring(6); + slength = name.substring(0, 2); + ilength = Integer.parseInt(slength, 16) * 2; + name = name.substring(2, ilength + 2); + name = (new String(Hex.decode(name))).replace('<', ' '); + return name; + } catch (IndexOutOfBoundsException e) { + // try-catch for https://github.com/FluffyKaon/OpenPGP-Card + // Note: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! + + Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); + return ""; + } + } + + public Passphrase getPin() { + return mPin; + } + + public void setPin(final Passphrase pin) { + this.mPin = pin; + } + + public Passphrase getAdminPin() { + return mAdminPin; + } + + public void setAdminPin(final Passphrase adminPin) { + this.mAdminPin = adminPin; + } + + public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { + long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; + byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); + KeyType keyType = KeyType.from(secretKey); + + if (keyType == null) { + throw new IOException("Inappropriate key flags for smart card key."); + } + + // Slot is empty, or contains this key already. PUT KEY operation is safe + boolean canPutKey = isSlotEmpty(keyType) + || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); + + if (!canPutKey) { + throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", + keyType.toString())); + } + + putKey(keyType.getmSlot(), secretKey, passphrase); + putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + putData(keyType.getTimestampObjectId(), timestampBytes); + } + + private boolean isSlotEmpty(KeyType keyType) throws IOException { + // Note: special case: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true + if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; + + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + } + + public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); + } + + /** + * Connect to device and select pgp applet + * + * @throws IOException + */ + public void connectToDevice() throws IOException { + // Connect on transport layer + mTransport.connect(); + + // Connect on smartcard layer + + // 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 = communicate(opening); // activate connection + if (!response.endsWith(accepted)) { + throw new CardException("Initialization failed!", parseCardStatus(response)); + } + + byte[] pwStatusBytes = getPwStatusBytes(); + mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); + mPw1ValidatedForSignature = false; + mPw1ValidatedForDecrypt = false; + mPw3Validated = false; + } + + /** + * 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. + */ + private 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; + } + } + + /** + * 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 modifyPin(int pw, byte[] newPin) throws IOException { + final int MAX_PW1_LENGTH_INDEX = 1; + final int MAX_PW3_LENGTH_INDEX = 3; + + byte[] pwStatusBytes = getPwStatusBytes(); + + 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 = communicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { + throw new CardException("Failed to change PIN", parseCardStatus(response)); + } + } + + /** + * Call DECIPHER command + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { + if (!mPw1ValidatedForDecrypt) { + verifyPin(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]; + } + + communicate(firstApdu + getHex(one)); + String second = communicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = getDataField(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. + */ + private void verifyPin(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; + } + } + } + + /** + * 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 + */ + private void putData(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) { + verifyPin(0x82); // (Verify PW1 for non-signing operations) + } + } else if (!mPw3Validated) { + verifyPin(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 = communicate(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 + */ + private void putKey(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) { + verifyPin(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 = communicate( + putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) + ); + offset += 254; + } else { + int length = dataToSend.length - offset; + response = communicate( + 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); + } + + /** + * 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[] getFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mTransport.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. + */ + private byte[] getPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mTransport.transceive(Hex.decode(data)); + } + + public byte[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.transceive(Hex.decode(info)); + } + + public String getUserId() throws IOException { + String info = "00CA006500"; + return getHolderName(communicate(info)); + } + + /** + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value + * + * @param hash the hash for signing + * @return a big integer representing the MPI for the given hash + */ + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { + if (!mPw1ValidatedForSignature) { + verifyPin(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 = communicate(apdu); + + if (response.length() < 4) { + throw new CardException("Bad response", (short) 0); + } + // 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 = communicate("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); + } + + /** + * Transceive data via NFC encoded as Hex + */ + private String communicate(String apdu) throws IOException { + return getHex(mTransport.transceive(Hex.decode(apdu))); + } + + public Transport getTransport() { + return mTransport; + } + + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + } + + public boolean isFidesmoToken() { + if (isConnected()) { // Check if we can still talk to the card + try { + // By trying to select any apps that have the Fidesmo AID prefix we can + // see if it is a Fidesmo device or not + byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); + // Compare the status returned by our select with the OK status code + return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); + } catch (IOException e) { + Log.e(Constants.TAG, "Card communication failed!", e); + } + } + return false; + } + + /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + public byte[] generateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = communicate(generateKeyApdu); + String second = communicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + 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 communicate(login); + } + + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + public void resetAndWipeToken() 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 = communicate(reactivate1); + String response2 = communicate(reactivate2); + if (!response1.equals(accepted) || !response2.equals(accepted)) { + throw new CardException("Reactivating failed!", parseCardStatus(response1)); + } + + } + + /** + * 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[] getMasterKeyFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + 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 boolean isPersistentConnectionAllowed() { + return mTransport != null && mTransport.isPersistentConnectionAllowed(); + } + + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); + } + + private static class LazyHolder { + private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java new file mode 100644 index 000000000..294eaa9ee --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import java.io.IOException; + +/** + * Abstraction for transmitting APDU commands + */ +public interface Transport { + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + byte[] transceive(byte[] data) throws IOException; + + /** + * Disconnect and release connection + */ + void release(); + + /** + * Check if device is was connected to and still is connected + * @return connection status + */ + boolean isConnected(); + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + boolean isPersistentConnectionAllowed(); + + + /** + * Connect to device + * @throws IOException + */ + void connect() throws IOException; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java new file mode 100644 index 000000000..016117feb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Based on USB CCID Specification rev. 1.1 + * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf + * Implements small subset of these features + */ +public class UsbTransport implements Transport { + private static final int USB_CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 20s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private UsbInterface mUsbInterface; + private UsbEndpoint mBulkIn; + private UsbEndpoint mBulkOut; + private UsbDeviceConnection mConnection; + private byte mCounter; + + public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + } + + + /** + * Manage ICC power, Yubikey requires to power on ICC + * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff + * + * @param on true to turn ICC on, false to turn it off + * @throws UsbTransportException + */ + private void iccPowerSet(boolean on) throws UsbTransportException { + final byte[] iccPowerCommand = { + (byte) (on ? 0x62 : 0x63), + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + + sendRaw(iccPowerCommand); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + checkDataBlockResponse(bytes); + } + + /** + * Get first class 11 (Chip/Smartcard) interface of the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + /** + * Get device's bulk-in and bulk-out endpoints + * + * @param usbInterface usb device interface + * @return pair of builk-in and bulk-out endpoints respectively + */ + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + /** + * Release interface and disconnect + */ + @Override + public void release() { + if (mConnection != null) { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + mConnection = null; + } + + Log.d(Constants.TAG, "Usb transport disconnected"); + } + + /** + * Check if device is was connected to and still is connected + * @return true if device is connected + */ + @Override + public boolean isConnected() { + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + @Override + public boolean isPersistentConnectionAllowed() { + return true; + } + + /** + * Connect to OTG device + * @throws IOException + */ + @Override + public void connect() throws IOException { + mCounter = 0; + mUsbInterface = getSmartCardInterface(mUsbDevice); + if (mUsbInterface == null) { + // Shouldn't happen as we whitelist only class 11 devices + throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + } + + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + + if (mBulkIn == null || mBulkOut == null) { + throw new UsbTransportException("USB error: invalid class 11 interface"); + } + + mConnection = mUsbManager.openDevice(mUsbDevice); + if (mConnection == null) { + throw new UsbTransportException("USB error: failed to connect to device"); + } + + if (!mConnection.claimInterface(mUsbInterface, true)) { + throw new UsbTransportException("USB error: failed to claim interface"); + } + + iccPowerSet(true); + Log.d(Constants.TAG, "Usb transport connected"); + } + + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws UsbTransportException + */ + @Override + public byte[] transceive(byte[] data) throws UsbTransportException { + sendXfrBlock(data); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + + checkDataBlockResponse(bytes); + // Discard header + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + /** + * Transmits XfrBlock + * 6.1.4 PC_to_RDR_XfrBlock + * @param payload payload to transmit + * @throws UsbTransportException + */ + private void sendXfrBlock(byte[] payload) throws UsbTransportException { + int l = payload.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + payload); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } + } + + private byte[] receive() throws UsbTransportException { + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new UsbTransportException("USB error: failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; + } + + private void sendRaw(final byte[] data) throws UsbTransportException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new UsbTransportException("USB error: failed to transmit data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isDataBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final UsbTransport that = (UsbTransport) o; + + return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; + } + + @Override + public int hashCode() { + return mUsbDevice != null ? mUsbDevice.hashCode() : 0; + } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java new file mode 100644 index 000000000..6d9212d9f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * 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.securitytoken; + +import java.io.IOException; + +public class UsbTransportException extends IOException { + public UsbTransportException() { + } + + public UsbTransportException(final String detailMessage) { + super(detailMessage); + } + + public UsbTransportException(final String message, final Throwable cause) { + super(message, cause); + } + + public UsbTransportException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java deleted file mode 100644 index 06445e988..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import java.io.IOException; - -public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java deleted file mode 100644 index 29c7fce96..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; - -public enum KeyType { - SIGN(0, 0xB6, 0xCE, 0xC7), - ENCRYPT(1, 0xB8, 0xCF, 0xC8), - AUTH(2, 0xA4, 0xD0, 0xC9),; - - private final int mIdx; - private final int mSlot; - private final int mTimestampObjectId; - private final int mFingerprintObjectId; - - KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { - this.mIdx = idx; - this.mSlot = slot; - this.mTimestampObjectId = timestampObjectId; - this.mFingerprintObjectId = fingerprintObjectId; - } - - public static KeyType from(final CanonicalizedSecretKey key) { - if (key.canSign() || key.canCertify()) { - return SIGN; - } else if (key.canEncrypt()) { - return ENCRYPT; - } else if (key.canAuthenticate()) { - return AUTH; - } - return null; - } - - public int getIdx() { - return mIdx; - } - - public int getmSlot() { - return mSlot; - } - - public int getTimestampObjectId() { - return mTimestampObjectId; - } - - public int getmFingerprintObjectId() { - return mFingerprintObjectId; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java deleted file mode 100644 index a743c753a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import android.nfc.Tag; - -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; - -import java.io.IOException; - -import nordpol.IsoCard; -import nordpol.android.AndroidCard; - -public class NfcTransport implements Transport { - // timeout is set to 100 seconds to avoid cancellation during calculation - private static final int TIMEOUT = 100 * 1000; - private final Tag mTag; - private IsoCard mIsoCard; - - public NfcTransport(Tag tag) { - this.mTag = tag; - } - - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws IOException - */ - @Override - public byte[] transceive(final byte[] data) throws IOException { - return mIsoCard.transceive(data); - } - - /** - * Disconnect and release connection - */ - @Override - public void release() { - // Not supported - } - - @Override - public boolean isConnected() { - return mIsoCard != null && mIsoCard.isConnected(); - } - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - @Override - public boolean isPersistentConnectionAllowed() { - return false; - } - - /** - * Connect to NFC device. - *

- * 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. - */ - @Override - public void connect() throws IOException { - mIsoCard = AndroidCard.get(mTag); - if (mIsoCard == null) { - throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final NfcTransport that = (NfcTransport) o; - - if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; - if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = mTag != null ? mTag.hashCode() : 0; - result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); - return result; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java deleted file mode 100644 index 58cf9b51a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * 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.smartcard; - -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.util.Iso7816TLV; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.interfaces.RSAPrivateCrtKey; - -import nordpol.Apdu; - -/** - * 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 SmartcardDevice { - // Fidesmo constants - private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; - - 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 Transport mTransport; - - private Passphrase mPin; - private Passphrase mAdminPin; - private boolean mPw1ValidForMultipleSignatures; - private boolean mPw1ValidatedForSignature; - private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - private boolean mPw3Validated; - - protected SmartcardDevice() { - } - - public static SmartcardDevice getInstance() { - return LazyHolder.mSmartcardDevice; - } - - private static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - - private String getHolderName(String name) { - try { - String slength; - int ilength; - name = name.substring(6); - slength = name.substring(0, 2); - ilength = Integer.parseInt(slength, 16) * 2; - name = name.substring(2, ilength + 2); - name = (new String(Hex.decode(name))).replace('<', ' '); - return name; - } catch (IndexOutOfBoundsException e) { - // try-catch for https://github.com/FluffyKaon/OpenPGP-Card - // Note: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! - - Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); - return ""; - } - } - - public Passphrase getPin() { - return mPin; - } - - public void setPin(final Passphrase pin) { - this.mPin = pin; - } - - public Passphrase getAdminPin() { - return mAdminPin; - } - - public void setAdminPin(final Passphrase adminPin) { - this.mAdminPin = adminPin; - } - - public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { - long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; - byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - KeyType keyType = KeyType.from(secretKey); - - if (keyType == null) { - throw new IOException("Inappropriate key flags for smart card key."); - } - - // Slot is empty, or contains this key already. PUT KEY operation is safe - boolean canPutKey = isSlotEmpty(keyType) - || keyMatchesFingerPrint(keyType, secretKey.getFingerprint()); - - if (!canPutKey) { - throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.", - keyType.toString())); - } - - putKey(keyType.getmSlot(), secretKey, passphrase); - putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); - putData(keyType.getTimestampObjectId(), timestampBytes); - } - - private boolean isSlotEmpty(KeyType keyType) throws IOException { - // Note: special case: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; - - return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); - } - - public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); - } - - /** - * Connect to device and select pgp applet - * - * @throws IOException - */ - public void connectToDevice() throws IOException { - // Connect on transport layer - mTransport.connect(); - - // Connect on smartcard layer - - // 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 = communicate(opening); // activate connection - if (!response.endsWith(accepted)) { - throw new CardException("Initialization failed!", parseCardStatus(response)); - } - - byte[] pwStatusBytes = getPwStatusBytes(); - mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); - mPw1ValidatedForSignature = false; - mPw1ValidatedForDecrypt = false; - mPw3Validated = false; - } - - /** - * 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. - */ - private 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; - } - } - - /** - * 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 modifyPin(int pw, byte[] newPin) throws IOException { - final int MAX_PW1_LENGTH_INDEX = 1; - final int MAX_PW3_LENGTH_INDEX = 3; - - byte[] pwStatusBytes = getPwStatusBytes(); - - 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 = communicate(changeReferenceDataApdu); // change PIN - if (!response.equals("9000")) { - throw new CardException("Failed to change PIN", parseCardStatus(response)); - } - } - - /** - * Call DECIPHER command - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - */ - public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { - if (!mPw1ValidatedForDecrypt) { - verifyPin(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]; - } - - communicate(firstApdu + getHex(one)); - String second = communicate(secondApdu + getHex(two) + le); - - String decryptedSessionKey = getDataField(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. - */ - private void verifyPin(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; - } - } - } - - /** - * 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 - */ - private void putData(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) { - verifyPin(0x82); // (Verify PW1 for non-signing operations) - } - } else if (!mPw3Validated) { - verifyPin(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 = communicate(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 - */ - private void putKey(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) { - verifyPin(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 = communicate( - putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254) - ); - offset += 254; - } else { - int length = dataToSend.length - offset; - response = communicate( - 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); - } - - /** - * 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[] getFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mTransport.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. - */ - private byte[] getPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mTransport.transceive(Hex.decode(data)); - } - - public byte[] getAid() throws IOException { - String info = "00CA004F00"; - return mTransport.transceive(Hex.decode(info)); - } - - public String getUserId() throws IOException { - String info = "00CA006500"; - return getHolderName(communicate(info)); - } - - /** - * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value - * - * @param hash the hash for signing - * @return a big integer representing the MPI for the given hash - */ - public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { - if (!mPw1ValidatedForSignature) { - verifyPin(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 = communicate(apdu); - - if (response.length() < 4) { - throw new CardException("Bad response", (short) 0); - } - // 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 = communicate("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); - } - - /** - * Transceive data via NFC encoded as Hex - */ - private String communicate(String apdu) throws IOException { - return getHex(mTransport.transceive(Hex.decode(apdu))); - } - - public Transport getTransport() { - return mTransport; - } - - public void setTransport(Transport mTransport) { - this.mTransport = mTransport; - } - - public boolean isFidesmoToken() { - if (isConnected()) { // Check if we can still talk to the card - try { - // By trying to select any apps that have the Fidesmo AID prefix we can - // see if it is a Fidesmo device or not - byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); - // Compare the status returned by our select with the OK status code - return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU); - } catch (IOException e) { - Log.e(Constants.TAG, "Card communication failed!", e); - } - } - return false; - } - - /** - * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), - * this command also has the effect of resetting the digital signature counter. - * NOTE: This does not set the key fingerprint data object! After calling this command, you - * must construct a public key packet using the returned public key data objects, compute the - * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint()) - * - * @param slot The slot on the card where the key should be generated: - * 0xB6: Signature Key - * 0xB8: Decipherment Key - * 0xA4: Authentication Key - * @return the public key data objects, in TLV format. For RSA this will be the public modulus - * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. - */ - public byte[] generateKey(int slot) throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - if (!mPw3Validated) { - verifyPin(0x83); // (Verify PW3 with mode 83) - } - - String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; - String getResponseApdu = "00C00000"; - - String first = communicate(generateKeyApdu); - String second = communicate(getResponseApdu); - - if (!second.endsWith("9000")) { - throw new IOException("On-card key generation failed"); - } - - String publicKeyData = getDataField(first) + getDataField(second); - - Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); - - return Hex.decode(publicKeyData); - } - - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - - 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 communicate(login); - } - - /** - * Resets security token, which deletes all keys and data objects. - * This works by entering a wrong PIN and then Admin PIN 4 times respectively. - * Afterwards, the token is reactivated. - */ - public void resetAndWipeToken() 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 = communicate(reactivate1); - String response2 = communicate(reactivate2); - if (!response1.equals(accepted) || !response2.equals(accepted)) { - throw new CardException("Reactivating failed!", parseCardStatus(response1)); - } - - } - - /** - * 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[] getMasterKeyFingerprint(int idx) throws IOException { - byte[] data = getFingerprints(); - 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 boolean isPersistentConnectionAllowed() { - return mTransport != null && mTransport.isPersistentConnectionAllowed(); - } - - public boolean isConnected() { - return mTransport != null && mTransport.isConnected(); - } - - private static class LazyHolder { - private static final SmartcardDevice mSmartcardDevice = new SmartcardDevice(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java deleted file mode 100644 index 5b942ee5c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import java.io.IOException; - -/** - * Abstraction for transmitting APDU commands - */ -public interface Transport { - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws IOException - */ - byte[] transceive(byte[] data) throws IOException; - - /** - * Disconnect and release connection - */ - void release(); - - /** - * Check if device is was connected to and still is connected - * @return connection status - */ - boolean isConnected(); - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - boolean isPersistentConnectionAllowed(); - - - /** - * Connect to device - * @throws IOException - */ - void connect() throws IOException; -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java deleted file mode 100644 index 8d661b9fa..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Pair; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Based on USB CCID Specification rev. 1.1 - * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf - * Implements small subset of these features - */ -public class UsbTransport implements Transport { - private static final int USB_CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 20s - - private final UsbManager mUsbManager; - private final UsbDevice mUsbDevice; - private UsbInterface mUsbInterface; - private UsbEndpoint mBulkIn; - private UsbEndpoint mBulkOut; - private UsbDeviceConnection mConnection; - private byte mCounter; - - public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { - mUsbDevice = usbDevice; - mUsbManager = usbManager; - } - - - /** - * Manage ICC power, Yubikey requires to power on ICC - * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff - * - * @param on true to turn ICC on, false to turn it off - * @throws UsbTransportException - */ - private void iccPowerSet(boolean on) throws UsbTransportException { - final byte[] iccPowerCommand = { - (byte) (on ? 0x62 : 0x63), - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - - sendRaw(iccPowerCommand); - byte[] bytes; - do { - bytes = receive(); - } while (isDataBlockNotReady(bytes)); - checkDataBlockResponse(bytes); - } - - /** - * Get first class 11 (Chip/Smartcard) interface of the device - * - * @param device {@link UsbDevice} which will be searched - * @return {@link UsbInterface} of smartcard or null if it doesn't exist - */ - @Nullable - private static UsbInterface getSmartCardInterface(UsbDevice device) { - for (int i = 0; i < device.getInterfaceCount(); i++) { - UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { - return anInterface; - } - } - return null; - } - - /** - * Get device's bulk-in and bulk-out endpoints - * - * @param usbInterface usb device interface - * @return pair of builk-in and bulk-out endpoints respectively - */ - @NonNull - private static Pair getIoEndpoints(final UsbInterface usbInterface) { - UsbEndpoint bulkIn = null, bulkOut = null; - for (int i = 0; i < usbInterface.getEndpointCount(); i++) { - final UsbEndpoint endpoint = usbInterface.getEndpoint(i); - if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue; - } - - if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint; - } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint; - } - } - return new Pair<>(bulkIn, bulkOut); - } - - /** - * Release interface and disconnect - */ - @Override - public void release() { - if (mConnection != null) { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - mConnection = null; - } - - Log.d(Constants.TAG, "Usb transport disconnected"); - } - - /** - * Check if device is was connected to and still is connected - * @return true if device is connected - */ - @Override - public boolean isConnected() { - return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); - } - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - @Override - public boolean isPersistentConnectionAllowed() { - return true; - } - - /** - * Connect to OTG device - * @throws IOException - */ - @Override - public void connect() throws IOException { - mCounter = 0; - mUsbInterface = getSmartCardInterface(mUsbDevice); - if (mUsbInterface == null) { - // Shouldn't happen as we whitelist only class 11 devices - throw new UsbTransportException("USB error: device doesn't have class 11 interface"); - } - - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - - if (mBulkIn == null || mBulkOut == null) { - throw new UsbTransportException("USB error: invalid class 11 interface"); - } - - mConnection = mUsbManager.openDevice(mUsbDevice); - if (mConnection == null) { - throw new UsbTransportException("USB error: failed to connect to device"); - } - - if (!mConnection.claimInterface(mUsbInterface, true)) { - throw new UsbTransportException("USB error: failed to claim interface"); - } - - iccPowerSet(true); - Log.d(Constants.TAG, "Usb transport connected"); - } - - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws UsbTransportException - */ - @Override - public byte[] transceive(byte[] data) throws UsbTransportException { - sendXfrBlock(data); - byte[] bytes; - do { - bytes = receive(); - } while (isDataBlockNotReady(bytes)); - - checkDataBlockResponse(bytes); - // Discard header - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - /** - * Transmits XfrBlock - * 6.1.4 PC_to_RDR_XfrBlock - * @param payload payload to transmit - * @throws UsbTransportException - */ - private void sendXfrBlock(byte[] payload) throws UsbTransportException { - int l = payload.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - payload); - - int send = 0; - while (send < data.length) { - final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); - sendRaw(Arrays.copyOfRange(data, send, send + len)); - send += len; - } - } - - private byte[] receive() throws UsbTransportException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new UsbTransportException("USB error: failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); - } - totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; - result = new byte[totalBytes]; - } - System.arraycopy(buffer, 0, result, readBytes, res); - readBytes += res; - } while (readBytes < totalBytes); - - return result; - } - - private void sendRaw(final byte[] data) throws UsbTransportException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new UsbTransportException("USB error: failed to transmit data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isDataBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final UsbTransport that = (UsbTransport) o; - - return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; - } - - @Override - public int hashCode() { - return mUsbDevice != null ? mUsbDevice.hashCode() : 0; - } - - public UsbDevice getUsbDevice() { - return mUsbDevice; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java deleted file mode 100644 index 7bf713bf9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * 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.smartcard; - -import java.io.IOException; - -public class UsbTransportException extends IOException { - public UsbTransportException() { - } - - public UsbTransportException(final String detailMessage) { - super(detailMessage); - } - - public UsbTransportException(final String message, final Throwable cause) { - super(message, cause); - } - - public UsbTransportException(final Throwable cause) { - super(cause); - } -} 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 a9d259b00..83beccb2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -47,9 +47,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin"; public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin"; - public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; - public static final String EXTRA_NFC_AID = "nfc_aid"; - public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id"; + public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid"; + public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints"; public static final String FRAGMENT_TAG = "currentFragment"; @@ -66,8 +66,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { byte[] mScannedFingerprints; - byte[] mNfcAid; - String mNfcUserId; + byte[] mSecurityTokenAid; + String mSecurityTokenUserId; @Override public void onCreate(Bundle savedInstanceState) { @@ -107,10 +107,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false); - if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { - byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); - String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); - byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) { + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); if (containsKeys(nfcFingerprints)) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( @@ -143,19 +143,19 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; } - mScannedFingerprints = mSmartcardDevice.getFingerprints(); - mNfcAid = mSmartcardDevice.getAid(); - mNfcUserId = mSmartcardDevice.getUserId(); + mScannedFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); } @Override - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); return; @@ -169,15 +169,15 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( - mScannedFingerprints, mNfcAid, mNfcUserId); + mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index aba06ac47..7a27e4acc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -249,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.getSmartcardDevice().getFingerprints(); - mTokenAid = mCreateKeyActivity.getSmartcardDevice().getAid(); - mTokenUserId = mCreateKeyActivity.getSmartcardDevice().getUserId(); + mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); + mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); + mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ac12c2a4a..ddc1a7ca0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -172,20 +172,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onSmartcardPreExecute() { + public void onSecurityTokenPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { case SMARTCARD_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -196,15 +196,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mSmartcardDevice.calculateSignature(hash, algo); + byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } case SMARTCARD_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mSmartcardDevice.setPin(new Passphrase("123456")); - mSmartcardDevice.setAdminPin(new Passphrase("12345678")); + mSecurityTokenHelper.setPin(new Passphrase("123456")); + mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -225,7 +225,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); - byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16); Passphrase passphrase; try { @@ -235,20 +235,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity throw new IOException("Unable to get cached passphrase!"); } - mSmartcardDevice.changeKey(key, passphrase); + mSecurityTokenHelper.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); } // change PINs afterwards - mSmartcardDevice.modifyPin(0x81, newPin); - mSmartcardDevice.modifyPin(0x83, newAdminPin); + mSecurityTokenHelper.modifyPin(0x81, newPin); + mSecurityTokenHelper.modifyPin(0x83, newAdminPin); break; } case SMARTCARD_RESET_CARD: { - mSmartcardDevice.resetAndWipeToken(); + mSecurityTokenHelper.resetAndWipeToken(); break; } @@ -260,7 +260,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected final void onSmartcardPostExecute() { + protected final void onSecurityTokenPostExecute() { handleResult(mInputParcel); // show finish @@ -268,7 +268,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - if (mSmartcardDevice.isPersistentConnectionAllowed()) { + if (mSecurityTokenHelper.isPersistentConnectionAllowed()) { // Just close finish(); } else { @@ -309,7 +309,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void onSmartcardError(String error) { + protected void onSecurityTokenError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); @@ -319,8 +319,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onSmartcardPinError(String error) { - onSmartcardError(error); + public void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( 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 dd753a431..e47ca1db9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,7 +32,6 @@ import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; @@ -172,9 +171,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements private byte[] mFingerprint; private String mFingerprintString; - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; @SuppressLint("InflateParams") @Override @@ -647,17 +646,17 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { - mNfcFingerprints = mSmartcardDevice.getFingerprints(); - mNfcUserId = mSmartcardDevice.getUserId(); - mNfcAid = mSmartcardDevice.getAid(); + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } @Override - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { - long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { @@ -668,7 +667,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { - showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid); + showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid); return; } @@ -681,9 +680,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } @@ -696,9 +695,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 5ad542526..4aaf2aba0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -24,7 +24,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; @@ -40,12 +39,12 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.smartcard.CardException; -import org.sufficientlysecure.keychain.smartcard.NfcTransport; -import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; -import org.sufficientlysecure.keychain.smartcard.Transport; +import org.sufficientlysecure.keychain.securitytoken.CardException; +import org.sufficientlysecure.keychain.securitytoken.NfcTransport; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; -import org.sufficientlysecure.keychain.smartcard.UsbTransport; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -70,36 +69,36 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); + protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); protected TagDispatcher mTagDispatcher; protected UsbConnectionDispatcher mUsbDispatcher; private boolean mTagHandlingEnabled; - private byte[] mSmartcardFingerprints; - private String mSmartcardUserId; - private byte[] mSmartcardAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; /** - * Override to change UI before NFC handling (UI thread) + * Override to change UI before SecurityToken handling (UI thread) */ - protected void onSmartcardPreExecute() { + protected void onSecurityTokenPreExecute() { } /** - * Override to implement NFC operations (background thread) + * Override to implement SecurityToken operations (background thread) */ - protected void doSmartcardInBackground() throws IOException { - mSmartcardFingerprints = mSmartcardDevice.getFingerprints(); - mSmartcardUserId = mSmartcardDevice.getUserId(); - mSmartcardAid = mSmartcardDevice.getAid(); + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } /** - * Override to handle result of NFC operations (UI thread) + * Override to handle result of SecurityToken operations (UI thread) */ - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSmartcardFingerprints); + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( @@ -108,15 +107,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSmartcardAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSmartcardUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSmartcardFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } catch (PgpKeyNotFoundException e) { Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mSmartcardAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mSmartcardUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mSmartcardFingerprints); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } } @@ -124,15 +123,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to use something different than Notify (UI thread) */ - protected void onSmartcardError(String error) { + protected void onSecurityTokenError(String error) { Notify.create(this, error, Style.WARN).show(); } /** * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) */ - protected void onSmartcardPinError(String error) { - onSmartcardError(error); + protected void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); } public void tagDiscovered(final Tag tag) { @@ -140,7 +139,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if (!mTagHandlingEnabled) return; - smartcardDiscovered(new NfcTransport(tag)); + securityTokenDiscovered(new NfcTransport(tag)); } public void usbDeviceDiscovered(final UsbTransport transport) { @@ -148,10 +147,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if (!mTagHandlingEnabled) return; - smartcardDiscovered(transport); + securityTokenDiscovered(transport); } - public void smartcardDiscovered(final Transport transport) { + public void securityTokenDiscovered(final Transport transport) { // Actual Smartcard operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; @@ -159,7 +158,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity @Override protected void onPreExecute() { super.onPreExecute(); - onSmartcardPreExecute(); + onSecurityTokenPreExecute(); } @Override @@ -182,7 +181,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } - onSmartcardPostExecute(); + onSecurityTokenPostExecute(); } }.execute(); } @@ -237,12 +236,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { - onSmartcardError(getString(R.string.security_token_error_tag_lost)); + onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); return; } if (e instanceof IsoDepNotSupportedException) { - onSmartcardError(getString(R.string.security_token_error_iso_dep_not_supported)); + onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported)); return; } @@ -257,7 +256,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if ((status & (short) 0xFFF0) == 0x63C0) { int tries = status & 0x000F; // hook to do something different when PIN is wrong - onSmartcardPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); + onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); return; } @@ -266,61 +265,61 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // 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: { - onSmartcardError(getString(R.string.security_token_error_bad_data)); + onSecurityTokenError(getString(R.string.security_token_error_bad_data)); break; } case 0x6883: { - onSmartcardError(getString(R.string.security_token_error_chaining_error)); + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); break; } case 0x6B00: { - onSmartcardError(getString(R.string.security_token_error_header, "P1/P2")); + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); break; } case 0x6D00: { - onSmartcardError(getString(R.string.security_token_error_header, "INS")); + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); break; } case 0x6E00: { - onSmartcardError(getString(R.string.security_token_error_header, "CLA")); + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - onSmartcardError(getString(R.string.security_token_error_terminated)); + onSecurityTokenError(getString(R.string.security_token_error_terminated)); break; } case 0x6700: { - onSmartcardPinError(getString(R.string.security_token_error_wrong_length)); + onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); break; } case 0x6982: { - onSmartcardError(getString(R.string.security_token_error_security_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); break; } case 0x6983: { - onSmartcardError(getString(R.string.security_token_error_authentication_blocked)); + onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); break; } case 0x6985: { - onSmartcardError(getString(R.string.security_token_error_conditions_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - onSmartcardError(getString(R.string.security_token_error_data_not_found)); + onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the security token. case 0x6F00: { - onSmartcardError(getString(R.string.security_token_error_unknown)); + onSecurityTokenError(getString(R.string.security_token_error_unknown)); break; } // 6A82 app not installed on security token! case 0x6A82: { - if (mSmartcardDevice.isFidesmoToken()) { + if (mSecurityTokenHelper.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -328,12 +327,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity promptFidesmoAppInstall(); } } else { // Other (possibly) compatible hardware - onSmartcardError(getString(R.string.security_token_error_pgp_app_not_installed)); + onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); } break; } default: { - onSmartcardError(getString(R.string.security_token_error, e.getMessage())); + onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); break; } } @@ -367,7 +366,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mSmartcardDevice.setPin(passphrase); + mSecurityTokenHelper.setPin(passphrase); return; } @@ -392,7 +391,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mSmartcardDevice.setPin(input.getPassphrase()); + mSecurityTokenHelper.setPin(input.getPassphrase()); break; } default: @@ -402,16 +401,16 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!(mSmartcardDevice.isConnected() - && mSmartcardDevice.getTransport().equals(transport))) { - mSmartcardDevice.setTransport(transport); - mSmartcardDevice.connectToDevice(); + if (!(mSecurityTokenHelper.isConnected() + && mSecurityTokenHelper.getTransport().equals(transport))) { + mSecurityTokenHelper.setTransport(transport); + mSecurityTokenHelper.connectToDevice(); } - doSmartcardInBackground(); + doSecurityTokenInBackground(); } public boolean isSmartcardConnected() { - return mSmartcardDevice.isConnected(); + return mSecurityTokenHelper.isConnected(); } public static class IsoDepNotSupportedException extends IOException { @@ -470,8 +469,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mUsbDispatcher.onStart(); } - public SmartcardDevice getSmartcardDevice() { - return mSmartcardDevice; + public SecurityTokenHelper getSecurityTokenHelper() { + return mSecurityTokenHelper; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 09b029523..7055b2633 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,7 +26,7 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.smartcard.UsbTransport; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; public class UsbConnectionDispatcher { -- cgit v1.2.3 From 784bf2322cc37befb4857f0c440f889d43f89a48 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Tue, 12 Apr 2016 00:06:21 +0600 Subject: OTG: Replace colons with dashes in usb error messages --- .../keychain/securitytoken/UsbTransport.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java index 016117feb..10043e675 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -168,7 +168,7 @@ public class UsbTransport implements Transport { mUsbInterface = getSmartCardInterface(mUsbDevice); if (mUsbInterface == null) { // Shouldn't happen as we whitelist only class 11 devices - throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + throw new UsbTransportException("USB error - device doesn't have class 11 interface"); } final Pair ioEndpoints = getIoEndpoints(mUsbInterface); @@ -176,16 +176,16 @@ public class UsbTransport implements Transport { mBulkOut = ioEndpoints.second; if (mBulkIn == null || mBulkOut == null) { - throw new UsbTransportException("USB error: invalid class 11 interface"); + throw new UsbTransportException("USB error - invalid class 11 interface"); } mConnection = mUsbManager.openDevice(mUsbDevice); if (mConnection == null) { - throw new UsbTransportException("USB error: failed to connect to device"); + throw new UsbTransportException("USB error - failed to connect to device"); } if (!mConnection.claimInterface(mUsbInterface, true)) { - throw new UsbTransportException("USB error: failed to claim interface"); + throw new UsbTransportException("USB error - failed to claim interface"); } iccPowerSet(true); @@ -244,11 +244,11 @@ public class UsbTransport implements Transport { do { int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); if (res < 0) { - throw new UsbTransportException("USB error: failed to receive response " + res); + throw new UsbTransportException("USB error - failed to receive response " + res); } if (result == null) { if (res < 10) { - throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); + throw new UsbTransportException("USB-CCID error - failed to receive CCID header"); } totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; result = new byte[totalBytes]; @@ -263,7 +263,7 @@ public class UsbTransport implements Transport { private void sendRaw(final byte[] data) throws UsbTransportException { final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); if (tr1 != data.length) { - throw new UsbTransportException("USB error: failed to transmit data " + tr1); + throw new UsbTransportException("USB error - failed to transmit data " + tr1); } } @@ -274,7 +274,7 @@ public class UsbTransport implements Transport { private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { - throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } -- cgit v1.2.3 From 263799ec965669ab027db6b1ad62a26fd6af3bac Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 00:13:33 +0600 Subject: OTG: Fix connection issues --- .../keychain/securitytoken/UsbTransport.java | 7 ++++--- .../ui/base/BaseSecurityTokenNfcActivity.java | 13 +++++++++---- .../keychain/util/UsbConnectionDispatcher.java | 21 ++++----------------- 3 files changed, 17 insertions(+), 24 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java index 10043e675..dfe91427e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -66,7 +66,7 @@ public class UsbTransport implements Transport { * @param on true to turn ICC on, false to turn it off * @throws UsbTransportException */ - private void iccPowerSet(boolean on) throws UsbTransportException { + private void setIccPower(boolean on) throws UsbTransportException { final byte[] iccPowerCommand = { (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, @@ -145,7 +145,8 @@ public class UsbTransport implements Transport { */ @Override public boolean isConnected() { - return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice) && + mConnection.getSerial() != null; } /** @@ -188,7 +189,7 @@ public class UsbTransport implements Transport { throw new UsbTransportException("USB error - failed to claim interface"); } - iccPowerSet(true); + setIccPower(true); Log.d(Constants.TAG, "Usb transport connected"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index 4aaf2aba0..dbb234977 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -22,8 +22,11 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; @@ -142,12 +145,13 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity securityTokenDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbTransport transport) { + public void usbDeviceDiscovered(final UsbDevice usbDevice) { // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - securityTokenDiscovered(transport); + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); } public void securityTokenDiscovered(final Transport transport) { @@ -401,7 +405,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!(mSecurityTokenHelper.isConnected() + if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() + && mSecurityTokenHelper.isConnected() && mSecurityTokenHelper.getTransport().equals(transport))) { mSecurityTokenHelper.setTransport(transport); mSecurityTokenHelper.connectToDevice(); @@ -477,7 +482,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * Run smartcard routines if last used token is connected and supports * persistent connections */ - protected void checkDeviceConnection() { + public void checkDeviceConnection() { mUsbDispatcher.rescanDevices(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 7055b2633..60fc84dba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -33,7 +33,6 @@ public class UsbConnectionDispatcher { private Activity mActivity; private OnDiscoveredUsbDeviceListener mListener; - private UsbTransport mLastUsedUsbTransport; private UsbManager mUsbManager; /** * Receives broadcast when a supported USB device get permission. @@ -45,23 +44,12 @@ public class UsbConnectionDispatcher { switch (action) { case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + android.hardware.usb.UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); if (permission) { Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - - mLastUsedUsbTransport = new UsbTransport(usbDevice, mUsbManager); - mListener.usbDeviceDiscovered(mLastUsedUsbTransport); - } - break; - } - case UsbManager.ACTION_USB_DEVICE_DETACHED: { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - - if (mLastUsedUsbTransport != null && mLastUsedUsbTransport.getUsbDevice().equals(usbDevice)) { - mLastUsedUsbTransport.release(); - mLastUsedUsbTransport = null; + mListener.usbDeviceDiscovered(usbDevice); } break; } @@ -78,7 +66,6 @@ public class UsbConnectionDispatcher { public void onStart() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mActivity.registerReceiver(mUsbReceiver, intentFilter); } @@ -97,7 +84,7 @@ public class UsbConnectionDispatcher { for (UsbDevice device : mUsbManager.getDeviceList().values()) { if (mUsbManager.hasPermission(device)) { if (mListener != null) { - mListener.usbDeviceDiscovered(new UsbTransport(device, mUsbManager)); + mListener.usbDeviceDiscovered(device); } break; } @@ -105,6 +92,6 @@ public class UsbConnectionDispatcher { } public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbTransport usbTransport); + void usbDeviceDiscovered(UsbDevice usbDevice); } } -- cgit v1.2.3 From db57cf3e7ecaae25e9b8c1b468822c28801af129 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 00:37:55 +0600 Subject: OTG: Skip TokenWaitFragmen when otg device is connected --- .../keychain/ui/CreateKeyActivity.java | 19 ++++++++++++------- .../ui/CreateSecurityTokenImportResetFragment.java | 8 ++++---- .../keychain/ui/CreateSecurityTokenWaitFragment.java | 11 +++++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 83beccb2a..44b185c52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -144,8 +144,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { @Override protected void doSecurityTokenInBackground() throws IOException { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground(); return; } @@ -156,11 +156,16 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { @Override protected void onSecurityTokenPostExecute() { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute(); return; } + // We don't want get back to wait activity mainly because it looks weird with otg token + if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { + getSupportFragmentManager().popBackStackImmediate(); + } + if (containsKeys(mScannedFingerprints)) { try { long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); @@ -255,9 +260,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } - interface NfcListenerFragment { - void doNfcInBackground() throws IOException; - void onNfcPostExecute(); + interface SecurityTokenListenerFragment { + void doSecurityTokenInBackground() throws IOException; + void onSecurityTokenPostExecute(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index 7a27e4acc..a716cb20d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; @@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Preferences; public class CreateSecurityTokenImportResetFragment extends QueueingCryptoOperationFragment - implements NfcListenerFragment { + implements SecurityTokenListenerFragment { private static final int REQUEST_CODE_RESET = 0x00005001; @@ -247,7 +247,7 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void doNfcInBackground() throws IOException { + public void doSecurityTokenInBackground() throws IOException { mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); @@ -259,7 +259,7 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void onNfcPostExecute() { + public void onSecurityTokenPostExecute() { setData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index a3ea38e40..5dc2c478b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -26,6 +27,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; public class CreateSecurityTokenWaitFragment extends Fragment { @@ -33,6 +35,15 @@ public class CreateSecurityTokenWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) { + ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection(); + } + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false); -- cgit v1.2.3 From 28a352a288701a6419602e4c2fff3d5da7172cc0 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 02:02:11 +0600 Subject: OTG: Rename smartcard -> security token --- .../keychain/remote/ApiPendingIntentFactory.java | 6 +++--- .../keychain/service/input/RequiredInputParcel.java | 18 +++++++++--------- .../keychain/ui/SecurityTokenOperationActivity.java | 14 +++++++------- .../keychain/ui/base/BaseSecurityTokenNfcActivity.java | 14 +++++++------- .../keychain/ui/base/CryptoOperationHelper.java | 6 +++--- 5 files changed, 29 insertions(+), 29 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 03789f118..46bade950 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -50,9 +50,9 @@ public class ApiPendingIntentFactory { CryptoInputParcel cryptoInput) { switch (requiredInput.mType) { - case SMARTCARD_MOVE_KEY_TO_CARD: - case SMARTCARD_DECRYPT: - case SMARTCARD_SIGN: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: + case SECURITY_TOKEN_DECRYPT: + case SECURITY_TOKEN_SIGN: { return createNfcOperationPendingIntent(data, requiredInput, cryptoInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 24aa6f118..efc574124 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -14,8 +14,8 @@ import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SMARTCARD_SIGN, SMARTCARD_DECRYPT, - SMARTCARD_MOVE_KEY_TO_CARD, SMARTCARD_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, + PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT, + SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, } public Date mSignatureTime; @@ -92,19 +92,19 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN, new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcDecryptOperation( long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_DECRYPT, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_DECRYPT, new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcReset() { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_RESET_CARD, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD, null, null, null, null, null); } @@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable { signAlgos[i] = mSignAlgos.get(i); } - return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN, inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId); } @@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable { if (!mSignatureTime.equals(input.mSignatureTime)) { throw new AssertionError("input times must match, this is a programming error!"); } - if (input.mType != RequiredInputType.SMARTCARD_SIGN) { + if (input.mType != RequiredInputType.SECURITY_TOKEN_SIGN) { throw new AssertionError("operation types must match, this is a progrmming error!"); } @@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable { ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... - return new RequiredInputParcel(RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD, inputData, null, null, mMasterKeyId, buf.getLong()); } @@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); } - if (input.mType != RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD) { + if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) { throw new AssertionError("Operation types must match, this is a programming error!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ddc1a7ca0..39cd74fd2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -136,8 +136,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD - && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_RESET_CARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD + && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { @@ -182,7 +182,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { - case SMARTCARD_DECRYPT: { + case SECURITY_TOKEN_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); @@ -190,7 +190,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case SMARTCARD_SIGN: { + case SECURITY_TOKEN_SIGN: { mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { @@ -201,7 +201,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case SMARTCARD_MOVE_KEY_TO_CARD: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation mSecurityTokenHelper.setPin(new Passphrase("123456")); mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); @@ -247,7 +247,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity break; } - case SMARTCARD_RESET_CARD: { + case SECURITY_TOKEN_RESET_CARD: { mSecurityTokenHelper.resetAndWipeToken(); break; @@ -277,7 +277,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected Void doInBackground(Void... params) { // check all 200ms if Security Token has been taken away while (true) { - if (isSmartcardConnected()) { + if (isSecurityTokenConnected()) { try { Thread.sleep(200); } catch (InterruptedException ignored) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index dbb234977..f4c0a9365 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -155,7 +155,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } public void securityTokenDiscovered(final Transport transport) { - // Actual Smartcard operations are executed in doInBackground to not block the UI thread + // Actual Security Token operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @@ -168,7 +168,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity @Override protected IOException doInBackground(Void... params) { try { - handleSmartcard(transport); + handleSecurityToken(transport); } catch (IOException e) { return e; } @@ -181,7 +181,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onPostExecute(exception); if (exception != null) { - handleSmartcardError(exception); + handleSecurityTokenError(exception); return; } @@ -237,7 +237,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mTagDispatcher.interceptIntent(intent); } - private void handleSmartcardError(IOException e) { + private void handleSecurityTokenError(IOException e) { if (e instanceof TagLostException) { onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); @@ -403,7 +403,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - protected void handleSmartcard(Transport transport) throws IOException { + protected void handleSecurityToken(Transport transport) throws IOException { // Don't reconnect if device was already connected if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() && mSecurityTokenHelper.isConnected() @@ -414,7 +414,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity doSecurityTokenInBackground(); } - public boolean isSmartcardConnected() { + public boolean isSecurityTokenConnected() { return mSecurityTokenHelper.isConnected(); } @@ -479,7 +479,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } /** - * Run smartcard routines if last used token is connected and supports + * Run Security Token routines if last used token is connected and supports * persistent connections */ public void checkDeviceConnection() { 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 29200ac2c..ad15c8f68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -130,9 +130,9 @@ public class CryptoOperationHelper Date: Fri, 15 Apr 2016 01:21:15 +0200 Subject: Handle user input on key creation more generously Allow empty name and do not regex-check email fixes #1825 --- .../sufficientlysecure/keychain/pgp/KeyRing.java | 18 +++++----- .../keychain/ui/CreateKeyEmailFragment.java | 35 +++++--------------- .../keychain/ui/CreateKeyFinalFragment.java | 38 +++++++++++++++++++++- .../keychain/ui/CreateKeyNameFragment.java | 33 +++---------------- .../keychain/ui/widget/EmailEditText.java | 19 +---------- 5 files changed, 60 insertions(+), 83 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 77977b691..d6132869f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -78,18 +78,20 @@ public abstract class KeyRing { } /** - * Returns a composed user id. Returns null if name is null! + * Returns a composed user id. Returns null if name, email and comment are empty. */ public static String createUserId(UserId userId) { - String userIdString = userId.name; // consider name a required value - if (userIdString != null && !TextUtils.isEmpty(userId.comment)) { - userIdString += " (" + userId.comment + ")"; + StringBuilder userIdBuilder = new StringBuilder(); + if (!TextUtils.isEmpty(userId.name)) { + userIdBuilder.append(userId.comment); } - if (userIdString != null && !TextUtils.isEmpty(userId.email)) { - userIdString += " <" + userId.email + ">"; + if (!TextUtils.isEmpty(userId.comment)) { + userIdBuilder.append(" (" + userId.comment + ")"); } - - return userIdString; + if (!TextUtils.isEmpty(userId.email)) { + userIdBuilder.append(" <" + userId.email + ">"); + } + return userIdBuilder.length() == 0 ? null : userIdBuilder.toString(); } public static class UserId implements Serializable { 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 b020a0dba..b871f471c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; public class CreateKeyEmailFragment extends Fragment { private CreateKeyActivity mCreateKeyActivity; @@ -52,10 +51,6 @@ public class CreateKeyEmailFragment extends Fragment { private ArrayList mAdditionalEmailModels = new ArrayList<>(); private EmailAdapter mEmailAdapter; - // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS - // EMAIL_ADDRESS fails for mails with umlauts for example - private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); - /** * Creates new instance of this fragment */ @@ -76,16 +71,15 @@ public class CreateKeyEmailFragment extends Fragment { * @return true if EditText is not empty */ private boolean isMainEmailValid(EditText editText) { - boolean output = true; - if (!checkEmail(editText.getText().toString(), false)) { + if (editText.getText().length() == 0) { editText.setError(getString(R.string.create_key_empty)); editText.requestFocus(); - output = false; - } else { - editText.setError(null); + return false; + } else if (!checkEmail(editText.getText().toString(), false)){ + return false; } - - return output; + editText.setError(null); + return true; } @Override @@ -146,10 +140,9 @@ public class CreateKeyEmailFragment extends Fragment { * @return */ private boolean checkEmail(String email, boolean additionalEmail) { - // check for email format or if the user did any input - if (!isEmailFormatValid(email)) { + if (email.isEmpty()) { Notify.create(getActivity(), - getString(R.string.create_key_email_invalid_email), + getString(R.string.create_key_email_empty_email), Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } @@ -166,18 +159,6 @@ public class CreateKeyEmailFragment extends Fragment { return true; } - /** - * Checks the email format - * Uses the default Android Email Pattern - * - * @param email - * @return - */ - private boolean isEmailFormatValid(String email) { - // check for email format or if the user did any input - return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches()); - } - /** * Checks for duplicated emails inside the additional email adapter. * 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 cbf862074..896df0ad2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -57,6 +57,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.util.Date; import java.util.Iterator; +import java.util.regex.Pattern; public class CreateKeyFinalFragment extends Fragment { @@ -81,6 +82,10 @@ public class CreateKeyFinalFragment extends Fragment { private OperationResult mQueuedFinishResult; private EditKeyResult mQueuedDisplayResult; + // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS + // EMAIL_ADDRESS fails for mails with umlauts for example + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); + public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); frag.setRetainInstance(true); @@ -106,7 +111,11 @@ public class CreateKeyFinalFragment extends Fragment { CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); // set values - mNameEdit.setText(createKeyActivity.mName); + if (createKeyActivity.mName != null) { + mNameEdit.setText(createKeyActivity.mName); + } else { + mNameEdit.setText(getString(R.string.user_id_no_name)); + } if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { String emailText = createKeyActivity.mEmail + ", "; Iterator it = createKeyActivity.mAdditionalEmails.iterator(); @@ -122,6 +131,8 @@ public class CreateKeyFinalFragment extends Fragment { mEmailEdit.setText(createKeyActivity.mEmail); } + checkEmailValidity(); + mCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -309,6 +320,31 @@ public class CreateKeyFinalFragment extends Fragment { return saveKeyringParcel; } + private void checkEmailValidity() { + CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); + + boolean emailsValid = true; + if (!EMAIL_PATTERN.matcher(createKeyActivity.mEmail).matches()) { + emailsValid = false; + } + if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { + for (Iterator it = createKeyActivity.mAdditionalEmails.iterator(); it.hasNext(); ) { + if (!EMAIL_PATTERN.matcher(it.next().toString()).matches()) { + emailsValid = false; + } + } + } + if (!emailsValid) { + mEmailEdit.setError(getString(R.string.create_key_final_email_valid_warning)); + mEmailEdit.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mNameEdit.requestFocus(); // Workaround to remove focus from email + } + }); + } + } + private void createKey() { CreateKeyActivity activity = (CreateKeyActivity) getActivity(); if (activity == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java index 7480367bb..3332b9cf9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyNameFragment.java @@ -18,13 +18,11 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.EditText; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; @@ -50,27 +48,6 @@ public class CreateKeyNameFragment extends Fragment { 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. - * - * @param context - * @param editText - * @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; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_name_fragment, container, false); @@ -109,13 +86,11 @@ public class CreateKeyNameFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mNameEdit)) { - // save state - mCreateKeyActivity.mName = mNameEdit.getText().toString(); + // save state + mCreateKeyActivity.mName = mNameEdit.getText().length() == 0 ? null : mNameEdit.getText().toString(); - CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); - } + CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java index 49b37692c..55d5aec0c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java @@ -23,15 +23,11 @@ import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.util.AttributeSet; -import android.util.Patterns; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.ContactHelper; -import java.util.regex.Matcher; - public class EmailEditText extends AppCompatAutoCompleteTextView { public EmailEditText(Context context) { @@ -70,20 +66,7 @@ public class EmailEditText extends AppCompatAutoCompleteTextView { @Override public void afterTextChanged(Editable editable) { - String email = editable.toString(); - if (email.length() > 0) { - Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); - if (emailMatcher.matches()) { - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.ic_stat_retyped_ok, 0); - } else { - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.ic_stat_retyped_bad, 0); - } - } else { - // remove drawable if email is empty - EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } + } }; -- cgit v1.2.3 From 38adb9c40ee2227e361ddbf773b219b512f2258f Mon Sep 17 00:00:00 2001 From: Durgesh <007durgesh219@gmail.com> Date: Fri, 15 Apr 2016 15:40:29 +0530 Subject: Fix Wrong file encrypted from cached input Signed-off-by: Durgesh <007durgesh219@gmail.com> --- .../java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 22202a35f..d5c540856 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -729,6 +729,8 @@ public class EncryptFilesFragment // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(new CryptoInputParcel(new Date())); + } else if (resultCode == Activity.RESULT_CANCELED) { + onCryptoOperationCancelled(); } return; } -- cgit v1.2.3 From 163aef4c6b57c9501038e0a63408360e67ccf4a0 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 16 Apr 2016 00:15:04 +0600 Subject: Update obsolete Nfc class prefixes --- .../keychain/operations/CertifyOperation.java | 4 ++-- .../keychain/operations/SignEncryptOperation.java | 6 +++--- .../keychain/pgp/PgpCertifyOperation.java | 4 ++-- .../keychain/pgp/PgpDecryptVerifyOperation.java | 2 +- .../sufficientlysecure/keychain/pgp/PgpKeyOperation.java | 10 +++++----- .../keychain/pgp/PgpSignEncryptOperation.java | 2 +- .../keychain/remote/ApiPendingIntentFactory.java | 4 ++-- .../keychain/service/input/RequiredInputParcel.java | 14 +++++++------- .../ui/CreateSecurityTokenImportResetFragment.java | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 79b42ecc4..b4b27f7ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -47,7 +47,7 @@ import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; @@ -144,7 +144,7 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; - NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder( + SecurityTokenSignOperationsBuilder allRequiredInput = new SecurityTokenSignOperationsBuilder( cryptoInput.getSignatureTime(), masterKeyId, masterKeyId); // Work through all requested certifications diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java index 2ca74063c..5bca372cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; @@ -80,7 +80,7 @@ public class SignEncryptOperation extends BaseOperation { int total = inputBytes != null ? 1 : inputUris.size(), count = 0; ArrayList results = new ArrayList<>(); - NfcSignOperationsBuilder pendingInputBuilder = null; + SecurityTokenSignOperationsBuilder pendingInputBuilder = null; // if signing subkey has not explicitly been set, get first usable subkey capable of signing if (input.getSignatureMasterKeyId() != Constants.key.none @@ -161,7 +161,7 @@ public class SignEncryptOperation extends BaseOperation { return new SignEncryptResult(log, requiredInput, results, cryptoInput); } if (pendingInputBuilder == null) { - pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime, + pendingInputBuilder = new SecurityTokenSignOperationsBuilder(requiredInput.mSignatureTime, input.getSignatureMasterKeyId(), input.getSignatureSubKeyId()); } pendingInputBuilder.addAll(requiredInput); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index aa1c2e037..ae0a31191 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -76,7 +76,7 @@ public class PgpCertifyOperation { // get the master subkey (which we certify for) PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey(); - NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp, + SecurityTokenSignOperationsBuilder requiredInput = new SecurityTokenSignOperationsBuilder(creationTimestamp, publicKey.getKeyID(), publicKey.getKeyID()); try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index e15139a7f..94606bff9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -769,7 +769,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation mSignAlgos = new ArrayList<>(); ArrayList mInputHashes = new ArrayList<>(); long mMasterKeyId; long mSubKeyId; - public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) { + public SecurityTokenSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) { mSignatureTime = signatureTime; mMasterKeyId = masterKeyId; mSubKeyId = subKeyId; @@ -238,13 +238,13 @@ public class RequiredInputParcel implements Parcelable { } - public static class NfcKeyToCardOperationsBuilder { + public static class SecurityTokenKeyToCardOperationsBuilder { ArrayList mSubkeysToExport = new ArrayList<>(); Long mMasterKeyId; byte[] mPin; byte[] mAdminPin; - public NfcKeyToCardOperationsBuilder(Long masterKeyId) { + public SecurityTokenKeyToCardOperationsBuilder(Long masterKeyId) { mMasterKeyId = masterKeyId; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index a716cb20d..6f35fdd38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -230,7 +230,7 @@ public class CreateSecurityTokenImportResetFragment public void resetCard() { Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class); - RequiredInputParcel resetP = RequiredInputParcel.createNfcReset(); + RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset(); intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP); intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel()); startActivityForResult(intent, REQUEST_CODE_RESET); -- cgit v1.2.3 From a87c65c3f480ae06070607f2cd0f5227012d6cd3 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 16 Apr 2016 01:43:13 +0600 Subject: Check if security token keys match required once before signing/decryption --- .../keychain/securitytoken/SecurityTokenHelper.java | 10 +++++----- .../keychain/ui/SecurityTokenOperationActivity.java | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index e3f280e18..ed17fc379 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -131,13 +131,13 @@ public class SecurityTokenHelper { private boolean isSlotEmpty(KeyType keyType) throws IOException { // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; + if (getMasterKeyFingerprint(keyType) == null) return true; return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType), fingerprint); } /** @@ -723,10 +723,10 @@ public class SecurityTokenHelper { * 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. + * @param keyType key.operatio type * @return The fingerprint of the requested key, or null if not found. */ - public byte[] getMasterKeyFingerprint(int idx) throws IOException { + public byte[] getMasterKeyFingerprint(KeyType keyType) throws IOException { byte[] data = getFingerprints(); if (data == null) { return null; @@ -735,7 +735,7 @@ public class SecurityTokenHelper { // return the master key fingerprint ByteBuffer fpbuf = ByteBuffer.wrap(data); byte[] fp = new byte[20]; - fpbuf.position(idx * 20); + fpbuf.position(keyType.getIdx() * 20); fpbuf.get(fp, 0, 20); return fp; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 39cd74fd2..af7246abc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -36,10 +36,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.OrientationUtils; @@ -183,6 +185,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity switch (mRequiredInput.mType) { case SECURITY_TOKEN_DECRYPT: { + long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + mSecurityTokenHelper.getMasterKeyFingerprint(KeyType.SIGN)); + + if (tokenKeyId != mRequiredInput.getMasterKeyId()) { + throw new IOException(getString(R.string.error_wrong_security_token)); + } + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); @@ -191,6 +200,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity break; } case SECURITY_TOKEN_SIGN: { + long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + mSecurityTokenHelper.getMasterKeyFingerprint(KeyType.SIGN)); + + if (tokenKeyId != mRequiredInput.getMasterKeyId()) { + throw new IOException(getString(R.string.error_wrong_security_token)); + } + mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { -- cgit v1.2.3 From 935f88131e47c19df5bb8bb5dd13d20d391f0794 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 16 Apr 2016 15:45:43 +0600 Subject: Check subkey id instead of masterkey id on security token operations Rename ambiguous getMasterKeyFingerprint -> getKeyFingerprint --- .../keychain/securitytoken/SecurityTokenHelper.java | 10 ++++++---- .../keychain/ui/SecurityTokenOperationActivity.java | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index ed17fc379..0040d6958 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -22,6 +22,8 @@ package org.sufficientlysecure.keychain.securitytoken; +import android.support.annotation.NonNull; + import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; @@ -131,13 +133,13 @@ public class SecurityTokenHelper { private boolean isSlotEmpty(KeyType keyType) throws IOException { // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (getMasterKeyFingerprint(keyType) == null) return true; + if (getKeyFingerprint(keyType) == null) return true; return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(getMasterKeyFingerprint(keyType), fingerprint); + return java.util.Arrays.equals(getKeyFingerprint(keyType), fingerprint); } /** @@ -723,10 +725,10 @@ public class SecurityTokenHelper { * Return the fingerprint from application specific data stored on tag, or * null if it doesn't exist. * - * @param keyType key.operatio type + * @param keyType key type * @return The fingerprint of the requested key, or null if not found. */ - public byte[] getMasterKeyFingerprint(KeyType keyType) throws IOException { + public byte[] getKeyFingerprint(@NonNull KeyType keyType) throws IOException { byte[] data = getFingerprints(); if (data == null) { return null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index af7246abc..925ad19d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -186,9 +186,9 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity switch (mRequiredInput.mType) { case SECURITY_TOKEN_DECRYPT: { long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( - mSecurityTokenHelper.getMasterKeyFingerprint(KeyType.SIGN)); + mSecurityTokenHelper.getKeyFingerprint(KeyType.ENCRYPT)); - if (tokenKeyId != mRequiredInput.getMasterKeyId()) { + if (tokenKeyId != mRequiredInput.getSubKeyId()) { throw new IOException(getString(R.string.error_wrong_security_token)); } @@ -201,9 +201,9 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } case SECURITY_TOKEN_SIGN: { long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( - mSecurityTokenHelper.getMasterKeyFingerprint(KeyType.SIGN)); + mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN)); - if (tokenKeyId != mRequiredInput.getMasterKeyId()) { + if (tokenKeyId != mRequiredInput.getSubKeyId()) { throw new IOException(getString(R.string.error_wrong_security_token)); } -- cgit v1.2.3 From 15ed1c9a63fb0505651e4edf4f4ef65644640429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 18 Apr 2016 07:31:31 +0200 Subject: Fix delete_fail --- .../sufficientlysecure/keychain/operations/results/DeleteResult.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java index 1a8f10d4f..7c394fc1e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DeleteResult.java @@ -131,7 +131,8 @@ public class DeleteResult extends InputPendingResult { else if (mFail == 0) { str = activity.getString(R.string.delete_nothing); } else { - str = activity.getResources().getQuantityString(R.plurals.delete_fail, mFail); + str = activity.getResources().getQuantityString( + R.plurals.delete_fail, mFail, mFail); } } -- cgit v1.2.3 From 8e4d68c55a4e3f236cbc7d4664cd48c8fa0857f3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 29 Apr 2016 15:40:35 +0200 Subject: api: allow caching of sessionKey in OpenPgpDecryptResult --- .../pgp/OpenPgpDecryptionResultBuilder.java | 22 +++--- .../keychain/pgp/PgpDecryptVerifyOperation.java | 80 +++++++++++++++------- .../keychain/remote/OpenPgpService.java | 9 +++ .../keychain/service/input/CryptoInputParcel.java | 12 ++-- 4 files changed, 83 insertions(+), 40 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java index c4525e5cd..31a3f91b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java @@ -26,6 +26,8 @@ public class OpenPgpDecryptionResultBuilder { // builder private boolean mInsecure = false; private boolean mEncrypted = false; + private byte[] sessionKey; + private byte[] decryptedSessionKey; public void setInsecure(boolean insecure) { this.mInsecure = insecure; @@ -36,24 +38,26 @@ public class OpenPgpDecryptionResultBuilder { } public OpenPgpDecryptionResult build() { - OpenPgpDecryptionResult result = new OpenPgpDecryptionResult(); - if (mInsecure) { Log.d(Constants.TAG, "RESULT_INSECURE"); - result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE); - return result; + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey); } if (mEncrypted) { Log.d(Constants.TAG, "RESULT_ENCRYPTED"); - result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED); - } else { - Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED"); - result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED); + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey); } - return result; + Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED"); + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED); } + public void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) { + if ((sessionKey == null) != (decryptedSessionKey == null)) { + throw new AssertionError("sessionKey must be null iff decryptedSessionKey is null!"); + } + this.sessionKey = sessionKey; + this.decryptedSessionKey = decryptedSessionKey; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 94606bff9..a27e4a8d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -26,9 +26,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.security.SignatureException; import java.util.Date; import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import android.content.Context; import android.support.annotation.NonNull; @@ -60,7 +63,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; -import org.sufficientlysecure.keychain.util.CharsetVerifier; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -73,6 +75,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.CharsetVerifier; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -197,6 +200,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation cachedSessionKeys = decryptorFactory.getCachedSessionKeys(); + cryptoInput.addCryptoData(cachedSessionKeys); + if (cachedSessionKeys.size() >= 1) { + Entry entry = cachedSessionKeys.entrySet().iterator().next(); + result.sessionKey = entry.getKey().array(); + result.decryptedSessionKey = entry.getValue(); + } } else { // there wasn't even any useful data if (!anyPacketFound) { 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 02d9ba62d..e17310b65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -35,6 +35,7 @@ import android.app.Service; import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -453,6 +454,14 @@ public class OpenPgpService extends Service { cryptoInput.mPassphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); } + if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER)) { + Bundle wrapperBundle = data.getBundleExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER); + wrapperBundle.setClassLoader(getClassLoader()); + OpenPgpDecryptionResult decryptionResult = wrapperBundle.getParcelable(OpenPgpApi.EXTRA_DECRYPTION_RESULT); + if (decryptionResult != null && decryptionResult.hasDecryptedSessionKey()) { + cryptoInput.addCryptoData(decryptionResult.sessionKey, decryptionResult.decryptedSessionKey); + } + } byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 849418905..080c34c04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -17,18 +17,18 @@ package org.sufficientlysecure.keychain.service.input; -import android.os.Parcel; -import android.os.Parcelable; - -import org.sufficientlysecure.keychain.util.ParcelableProxy; -import org.sufficientlysecure.keychain.util.Passphrase; -import java.net.Proxy; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashMap; import java.util.Map; +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Passphrase; + /** * This is a base class for the input of crypto operations. */ -- cgit v1.2.3 From 19e6103e5f6a896d459edc9469411be9e8124cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 29 Apr 2016 17:03:50 +0200 Subject: Update libraries, add proguard filters to be under 64K limit --- .../src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index af60a1d9b..37e01e98f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -90,8 +90,9 @@ public class MainActivity extends BaseSecurityTokenNfcActivity implements FabCon @Override public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { if (drawerItem != null) { + PrimaryDrawerItem item = (PrimaryDrawerItem) drawerItem; Intent intent = null; - switch (drawerItem.getIdentifier()) { + switch ((int) item.getIdentifier()) { case ID_KEYS: onKeysSelected(); break; -- cgit v1.2.3 From a242881da635c60115934d4e54b210e7f3d7f44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 29 Apr 2016 21:50:54 +0200 Subject: Include only add button in dropdown width --- .../keychain/ui/widget/EncryptKeyCompletionView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index fb9e502e3..a5d807313 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -186,7 +186,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - this.setDropDownWidth(this.getLeft()+this.getRight()); - this.setDropDownHorizontalOffset(-this.getLeft()); + // increase width to include add button + this.setDropDownWidth(this.getRight()); } } -- cgit v1.2.3 From 7b24679094b40c0f99f11d728554bf7c288de24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 29 Apr 2016 22:22:10 +0200 Subject: Re-enable move-to-card option, improve strings --- .../keychain/ui/EditKeyFragment.java | 82 ++++++++++------------ .../keychain/ui/ViewKeyAdvSubkeysFragment.java | 82 ++++++++++------------ .../ui/dialog/EditSubkeyDialogFragment.java | 4 +- .../keychain/ui/util/KeyFormattingUtils.java | 2 +- 4 files changed, 81 insertions(+), 89 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 4cbb4724e..9ed8e369d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -35,6 +35,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; @@ -440,50 +441,45 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment Date: Fri, 29 Apr 2016 22:37:52 +0200 Subject: Use StringBuilder.append() according to lint --- .../main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 06b18f8e0..d2384e679 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -95,10 +95,14 @@ public abstract class KeyRing { userIdBuilder.append(userId.comment); } if (!TextUtils.isEmpty(userId.comment)) { - userIdBuilder.append(" (" + userId.comment + ")"); + userIdBuilder.append(" ("); + userIdBuilder.append(userId.comment); + userIdBuilder.append(")"); } if (!TextUtils.isEmpty(userId.email)) { - userIdBuilder.append(" <" + userId.email + ">"); + userIdBuilder.append(" <"); + userIdBuilder.append(userId.email); + userIdBuilder.append(">"); } return userIdBuilder.length() == 0 ? null : userIdBuilder.toString(); } -- cgit v1.2.3 From b4af7a06a51bb65ed43af1c0fcb8035705b2a24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 29 Apr 2016 23:14:24 +0200 Subject: Use proper ImportKeysProxyActivity to scan QR Codes --- .../keychain/ui/RedirectImportKeysActivity.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java index 2abb98a03..5cb680a57 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RedirectImportKeysActivity.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2016 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 @@ -20,7 +22,6 @@ import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -34,15 +35,16 @@ public class RedirectImportKeysActivity extends BaseActivity { } private void startQrCodeCaptureActivity() { - final Intent intent = new Intent(this, QrCodeCaptureActivity.class); + final Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); + scanQrCode.setAction(ImportKeysProxyActivity.ACTION_QR_CODE_API); new AlertDialog.Builder(this) .setTitle(R.string.redirect_import_key_title) .setMessage(R.string.redirect_import_key_message) .setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - // intent directly to ImportKeyChain activity - startActivity(intent); + // directly scan with OpenKeychain + startActivity(scanQrCode); finish(); } }) @@ -52,7 +54,6 @@ public class RedirectImportKeysActivity extends BaseActivity { finish(); } }) - .setIcon(android.R.drawable.ic_dialog_alert) .show(); } } \ No newline at end of file -- cgit v1.2.3 From 5805cd4d0ac05438df5d740f7a1a46d431f65fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Fri, 29 Apr 2016 23:52:01 +0200 Subject: Fix createUserId --- .../src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index d2384e679..1ebab7847 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -92,7 +92,7 @@ public abstract class KeyRing { public static String createUserId(UserId userId) { StringBuilder userIdBuilder = new StringBuilder(); if (!TextUtils.isEmpty(userId.name)) { - userIdBuilder.append(userId.comment); + userIdBuilder.append(userId.name); } if (!TextUtils.isEmpty(userId.comment)) { userIdBuilder.append(" ("); -- cgit v1.2.3 From d72001c6907ff50890673cd91b266c34c73ec84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 30 Apr 2016 15:24:09 +0200 Subject: Fix animation in manage keys for security tokens --- .../keychain/securitytoken/NfcTransport.java | 4 +- .../securitytoken/SecurityTokenHelper.java | 1 - .../keychain/ui/CreateKeyActivity.java | 13 +- .../ui/CreateSecurityTokenWaitFragment.java | 29 +- .../keychain/ui/MainActivity.java | 4 +- .../ui/SecurityTokenOperationActivity.java | 4 +- .../keychain/ui/ViewKeyActivity.java | 4 +- .../ui/base/BaseSecurityTokenActivity.java | 488 +++++++++++++++++++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 488 --------------------- .../keychain/util/UsbConnectionDispatcher.java | 6 +- 10 files changed, 529 insertions(+), 512 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java index 3b2dd838d..ba36ebdf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java @@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.securitytoken; import android.nfc.Tag; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import java.io.IOException; @@ -83,7 +83,7 @@ public class NfcTransport implements Transport { public void connect() throws IOException { mIsoCard = AndroidCard.get(mTag); if (mIsoCard == null) { - throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + throw new BaseSecurityTokenActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } mIsoCard.setTimeout(TIMEOUT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 0040d6958..30893afb6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -341,7 +341,6 @@ public class SecurityTokenHelper { } } - /** * Puts a key on the token in the given slot. * 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 44b185c52..2210a23dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import org.sufficientlysecure.keychain.R; @@ -28,7 +29,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.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -36,7 +37,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; -public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { +public class CreateKeyActivity extends BaseSecurityTokenActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; @@ -77,7 +78,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - mTagDispatcher.interceptIntent(getIntent()); + mNfcTagDispatcher.interceptIntent(getIntent()); setTitle(R.string.title_manage_my_keys); @@ -163,7 +164,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // We don't want get back to wait activity mainly because it looks weird with otg token if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { - getSupportFragmentManager().popBackStackImmediate(); + // hack from http://stackoverflow.com/a/11253987 + CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = true; + getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);// getSupportFragmentManager(). + CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false; } if (containsKeys(mScannedFingerprints)) { @@ -257,7 +261,6 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // do it immediately! getSupportFragmentManager().executePendingTransactions(); - } interface SecurityTokenListenerFragment { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index 5dc2c478b..d5f4cc7af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -17,21 +17,23 @@ package org.sufficientlysecure.keychain.ui; -import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; - +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; public class CreateSecurityTokenWaitFragment extends Fragment { + public static boolean sDisableFragmentAnimations = false; + CreateKeyActivity mCreateKeyActivity; View mBackButton; @@ -39,8 +41,8 @@ public class CreateSecurityTokenWaitFragment extends Fragment { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) { - ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection(); + if (this.getActivity() instanceof BaseSecurityTokenActivity) { + ((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection(); } } @@ -61,9 +63,22 @@ public class CreateSecurityTokenWaitFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } + /** + * hack from http://stackoverflow.com/a/11253987 + */ + @Override + public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { + if (sDisableFragmentAnimations) { + Animation a = new Animation() {}; + a.setDuration(0); + return a; + } + return super.onCreateAnimation(transit, enter, nextAnim); + } + } 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 37e01e98f..13df0b539 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.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; -public class MainActivity extends BaseSecurityTokenNfcActivity implements FabContainer, OnBackStackChangedListener { +public class MainActivity extends BaseSecurityTokenActivity 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/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 925ad19d4..4d07025e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; @@ -58,7 +58,7 @@ import nordpol.android.NfcGuideView; * NFC devices. * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ -public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity { +public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; 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 e47ca1db9..dea4b4eef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -85,7 +85,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.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -103,7 +103,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements +public class ViewKeyActivity extends BaseSecurityTokenActivity implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java new file mode 100644 index 000000000..75e10055d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov + * + * 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 android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.TagLostException; +import android.os.AsyncTask; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +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.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.securitytoken.CardException; +import org.sufficientlysecure.keychain.securitytoken.NfcTransport; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.securitytoken.Transport; +import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; +import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog; +import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; + +import nordpol.android.OnDiscoveredTagListener; +import nordpol.android.TagDispatcher; + +public abstract class BaseSecurityTokenActivity extends BaseActivity + implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { + public static final int REQUEST_CODE_PIN = 1; + + public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; + + private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; + + protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); + protected TagDispatcher mNfcTagDispatcher; + protected UsbConnectionDispatcher mUsbDispatcher; + private boolean mTagHandlingEnabled; + + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; + + /** + * Override to change UI before SecurityToken handling (UI thread) + */ + protected void onSecurityTokenPreExecute() { + } + + /** + * Override to implement SecurityToken operations (background thread) + */ + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); + } + + /** + * Override to handle result of SecurityToken operations (UI thread) + */ + protected void onSecurityTokenPostExecute() { + + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); + + 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, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); + startActivity(intent); + } catch (PgpKeyNotFoundException e) { + Intent intent = new Intent(this, CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); + startActivity(intent); + } + } + + /** + * Override to use something different than Notify (UI thread) + */ + protected void onSecurityTokenError(String error) { + Notify.create(this, error, Style.WARN).show(); + } + + /** + * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) + */ + protected void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); + } + + public void tagDiscovered(final Tag tag) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + securityTokenDiscovered(new NfcTransport(tag)); + } + + public void usbDeviceDiscovered(final UsbDevice usbDevice) { + // Actual USB operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); + } + + public void securityTokenDiscovered(final Transport transport) { + // Actual Security Token operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onSecurityTokenPreExecute(); + } + + @Override + protected IOException doInBackground(Void... params) { + try { + handleSecurityToken(transport); + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(IOException exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleSecurityTokenError(exception); + return; + } + + onSecurityTokenPostExecute(); + } + }.execute(); + } + + protected void pauseTagHandling() { + mTagHandlingEnabled = false; + } + + protected void resumeTagHandling() { + mTagHandlingEnabled = true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mNfcTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); + mUsbDispatcher = new UsbConnectionDispatcher(this, this); + + // Check whether we're recreating a previously destroyed instance + if (savedInstanceState != null) { + // 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) { + mNfcTagDispatcher.interceptIntent(intent); + } + + private void handleSecurityTokenError(IOException e) { + + if (e instanceof TagLostException) { + onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); + return; + } + + if (e instanceof IsoDepNotSupportedException) { + onSecurityTokenError(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 + onSecurityTokenPinError(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: { + onSecurityTokenError(getString(R.string.security_token_error_bad_data)); + break; + } + case 0x6883: { + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); + break; + } + case 0x6B00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); + break; + } + case 0x6D00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); + break; + } + case 0x6E00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); + break; + } + // These error conditions are more likely to be experienced by an end user. + case 0x6285: { + onSecurityTokenError(getString(R.string.security_token_error_terminated)); + break; + } + case 0x6700: { + onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); + break; + } + case 0x6982: { + onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); + break; + } + case 0x6983: { + onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); + break; + } + case 0x6985: { + onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); + break; + } + // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. + case 0x6A88: + case 0x6A83: { + onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); + break; + } + // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an + // unhandled exception on the security token. + case 0x6F00: { + onSecurityTokenError(getString(R.string.security_token_error_unknown)); + break; + } + // 6A82 app not installed on security token! + case 0x6A82: { + if (mSecurityTokenHelper.isFidesmoToken()) { + // Check if the Fidesmo app is installed + if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { + promptFidesmoPgpInstall(); + } else { + promptFidesmoAppInstall(); + } + } else { // Other (possibly) compatible hardware + onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); + } + break; + } + default: { + onSecurityTokenError(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"); + + mNfcTagDispatcher.disableExclusiveNfc(); + } + + /** + * 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"); + mNfcTagDispatcher.enableExclusiveNfc(); + } + + protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { + + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); + if (passphrase != null) { + mSecurityTokenHelper.setPin(passphrase); + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(requiredInput)); + startActivityForResult(intent, REQUEST_CODE_PIN); + } catch (PassphraseCacheService.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); + mSecurityTokenHelper.setPin(input.getPassphrase()); + break; + } + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + protected void handleSecurityToken(Transport transport) throws IOException { + // Don't reconnect if device was already connected + if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() + && mSecurityTokenHelper.isConnected() + && mSecurityTokenHelper.getTransport().equals(transport))) { + mSecurityTokenHelper.setTransport(transport); + mSecurityTokenHelper.connectToDevice(); + } + doSecurityTokenInBackground(); + } + + public boolean isSecurityTokenConnected() { + return mSecurityTokenHelper.isConnected(); + } + + public static class IsoDepNotSupportedException extends IOException { + + public IsoDepNotSupportedException(String detailMessage) { + super(detailMessage); + } + + } + + /** + * Ask user if she wants to install PGP onto her Fidesmo token + */ + private void promptFidesmoPgpInstall() { + FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); + fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); + } + + /** + * Show a Dialog to the user informing that Fidesmo App must be installed and with option + * to launch the Google Play store. + */ + private void promptFidesmoAppInstall() { + FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); + fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); + } + + /** + * Use the package manager to detect if an application is installed on the phone + * + * @param uri an URI identifying the application's package + * @return 'true' if the app is installed + */ + private boolean isAndroidAppInstalled(String uri) { + PackageManager mPackageManager = getPackageManager(); + boolean mAppInstalled; + try { + mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); + mAppInstalled = true; + } catch (PackageManager.NameNotFoundException e) { + Log.e(Constants.TAG, "App not installed on Android device"); + mAppInstalled = false; + } + return mAppInstalled; + } + + @Override + protected void onStop() { + super.onStop(); + mUsbDispatcher.onStop(); + } + + @Override + protected void onStart() { + super.onStart(); + mUsbDispatcher.onStart(); + } + + public SecurityTokenHelper getSecurityTokenHelper() { + return mSecurityTokenHelper; + } + + /** + * Run Security Token routines if last used token is connected and supports + * persistent connections + */ + public void checkDeviceConnection() { + mUsbDispatcher.rescanDevices(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java deleted file mode 100644 index f4c0a9365..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ /dev/null @@ -1,488 +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 - * Copyright (C) 2016 Nikita Mikhailov - * - * 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 android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.TagLostException; -import android.os.AsyncTask; -import android.os.Bundle; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -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.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.securitytoken.CardException; -import org.sufficientlysecure.keychain.securitytoken.NfcTransport; -import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; -import org.sufficientlysecure.keychain.securitytoken.Transport; -import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity; -import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; -import org.sufficientlysecure.keychain.ui.ViewKeyActivity; -import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog; -import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; - -import nordpol.android.OnDiscoveredTagListener; -import nordpol.android.TagDispatcher; - -public abstract class BaseSecurityTokenNfcActivity extends BaseActivity - implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { - public static final int REQUEST_CODE_PIN = 1; - - public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - - private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - - protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); - protected TagDispatcher mTagDispatcher; - protected UsbConnectionDispatcher mUsbDispatcher; - private boolean mTagHandlingEnabled; - - private byte[] mSecurityTokenFingerprints; - private String mSecurityTokenUserId; - private byte[] mSecurityTokenAid; - - /** - * Override to change UI before SecurityToken handling (UI thread) - */ - protected void onSecurityTokenPreExecute() { - } - - /** - * Override to implement SecurityToken operations (background thread) - */ - protected void doSecurityTokenInBackground() throws IOException { - mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); - mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); - mSecurityTokenAid = mSecurityTokenHelper.getAid(); - } - - /** - * Override to handle result of SecurityToken operations (UI thread) - */ - protected void onSecurityTokenPostExecute() { - - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); - - 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, mSecurityTokenAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); - startActivity(intent); - } catch (PgpKeyNotFoundException e) { - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); - startActivity(intent); - } - } - - /** - * Override to use something different than Notify (UI thread) - */ - protected void onSecurityTokenError(String error) { - Notify.create(this, error, Style.WARN).show(); - } - - /** - * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) - */ - protected void onSecurityTokenPinError(String error) { - onSecurityTokenError(error); - } - - public void tagDiscovered(final Tag tag) { - // Actual NFC operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - - securityTokenDiscovered(new NfcTransport(tag)); - } - - public void usbDeviceDiscovered(final UsbDevice usbDevice) { - // Actual USB operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - - UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); - } - - public void securityTokenDiscovered(final Transport transport) { - // Actual Security Token operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - new AsyncTask() { - @Override - protected void onPreExecute() { - super.onPreExecute(); - onSecurityTokenPreExecute(); - } - - @Override - protected IOException doInBackground(Void... params) { - try { - handleSecurityToken(transport); - } catch (IOException e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(IOException exception) { - super.onPostExecute(exception); - - if (exception != null) { - handleSecurityTokenError(exception); - return; - } - - onSecurityTokenPostExecute(); - } - }.execute(); - } - - protected void pauseTagHandling() { - mTagHandlingEnabled = false; - } - - protected void resumeTagHandling() { - mTagHandlingEnabled = true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); - mUsbDispatcher = new UsbConnectionDispatcher(this, this); - - // Check whether we're recreating a previously destroyed instance - if (savedInstanceState != null) { - // 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) { - mTagDispatcher.interceptIntent(intent); - } - - private void handleSecurityTokenError(IOException e) { - - if (e instanceof TagLostException) { - onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); - return; - } - - if (e instanceof IsoDepNotSupportedException) { - onSecurityTokenError(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 - onSecurityTokenPinError(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: { - onSecurityTokenError(getString(R.string.security_token_error_bad_data)); - break; - } - case 0x6883: { - onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); - break; - } - case 0x6B00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); - break; - } - case 0x6D00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); - break; - } - case 0x6E00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); - break; - } - // These error conditions are more likely to be experienced by an end user. - case 0x6285: { - onSecurityTokenError(getString(R.string.security_token_error_terminated)); - break; - } - case 0x6700: { - onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); - break; - } - case 0x6982: { - onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); - break; - } - case 0x6983: { - onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); - break; - } - case 0x6985: { - onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); - break; - } - // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. - case 0x6A88: - case 0x6A83: { - onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); - break; - } - // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an - // unhandled exception on the security token. - case 0x6F00: { - onSecurityTokenError(getString(R.string.security_token_error_unknown)); - break; - } - // 6A82 app not installed on security token! - case 0x6A82: { - if (mSecurityTokenHelper.isFidesmoToken()) { - // Check if the Fidesmo app is installed - if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { - promptFidesmoPgpInstall(); - } else { - promptFidesmoAppInstall(); - } - } else { // Other (possibly) compatible hardware - onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); - } - break; - } - default: { - onSecurityTokenError(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"); - - mTagDispatcher.disableExclusiveNfc(); - } - - /** - * 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"); - mTagDispatcher.enableExclusiveNfc(); - } - - protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { - - try { - Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, - requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); - if (passphrase != null) { - mSecurityTokenHelper.setPin(passphrase); - return; - } - - Intent intent = new Intent(this, PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, - RequiredInputParcel.createRequiredPassphrase(requiredInput)); - startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (PassphraseCacheService.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); - mSecurityTokenHelper.setPin(input.getPassphrase()); - break; - } - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - protected void handleSecurityToken(Transport transport) throws IOException { - // Don't reconnect if device was already connected - if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() - && mSecurityTokenHelper.isConnected() - && mSecurityTokenHelper.getTransport().equals(transport))) { - mSecurityTokenHelper.setTransport(transport); - mSecurityTokenHelper.connectToDevice(); - } - doSecurityTokenInBackground(); - } - - public boolean isSecurityTokenConnected() { - return mSecurityTokenHelper.isConnected(); - } - - public static class IsoDepNotSupportedException extends IOException { - - public IsoDepNotSupportedException(String detailMessage) { - super(detailMessage); - } - - } - - /** - * Ask user if she wants to install PGP onto her Fidesmo token - */ - private void promptFidesmoPgpInstall() { - FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); - fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); - } - - /** - * Show a Dialog to the user informing that Fidesmo App must be installed and with option - * to launch the Google Play store. - */ - private void promptFidesmoAppInstall() { - FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); - fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); - } - - /** - * Use the package manager to detect if an application is installed on the phone - * - * @param uri an URI identifying the application's package - * @return 'true' if the app is installed - */ - private boolean isAndroidAppInstalled(String uri) { - PackageManager mPackageManager = getPackageManager(); - boolean mAppInstalled; - try { - mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); - mAppInstalled = true; - } catch (PackageManager.NameNotFoundException e) { - Log.e(Constants.TAG, "App not installed on Android device"); - mAppInstalled = false; - } - return mAppInstalled; - } - - @Override - protected void onStop() { - super.onStop(); - mUsbDispatcher.onStop(); - } - - @Override - protected void onStart() { - super.onStart(); - mUsbDispatcher.onStart(); - } - - public SecurityTokenHelper getSecurityTokenHelper() { - return mSecurityTokenHelper; - } - - /** - * Run Security Token routines if last used token is connected and supports - * persistent connections - */ - public void checkDeviceConnection() { - mUsbDispatcher.rescanDevices(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 60fc84dba..7a8e65ae4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,7 +26,6 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; public class UsbConnectionDispatcher { @@ -34,6 +33,7 @@ public class UsbConnectionDispatcher { private OnDiscoveredUsbDeviceListener mListener; private UsbManager mUsbManager; + /** * Receives broadcast when a supported USB device get permission. */ @@ -44,7 +44,7 @@ public class UsbConnectionDispatcher { switch (action) { case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { - android.hardware.usb.UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); if (permission) { @@ -79,7 +79,7 @@ public class UsbConnectionDispatcher { */ public void rescanDevices() { // Note: we don't check devices VID/PID because - // we check for permisssion instead. + // we check for permission instead. // We should have permission only for matching devices for (UsbDevice device : mUsbManager.getDeviceList().values()) { if (mUsbManager.hasPermission(device)) { -- cgit v1.2.3 From 1cc0fe558be77c012ecc8630bc3bfe85948ab28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 30 Apr 2016 15:35:20 +0200 Subject: Remove lefover comment --- .../main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 2210a23dd..b71917368 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -166,7 +166,7 @@ public class CreateKeyActivity extends BaseSecurityTokenActivity { if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { // hack from http://stackoverflow.com/a/11253987 CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = true; - getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);// getSupportFragmentManager(). + getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false; } -- cgit v1.2.3 From b97971a29c6a2a80d86ff068ca8f4109d8027646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 30 Apr 2016 16:46:22 +0200 Subject: Fix error codes for ykneo-openpgp 1.0.11 --- .../ui/base/BaseSecurityTokenActivity.java | 73 +++++++++++++++------- 1 file changed, 49 insertions(+), 24 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java index 75e10055d..680613596 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -257,6 +257,8 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity } // Wrong PIN, a status of 63CX indicates X attempts remaining. + // NOTE: Used in ykneo-openpgp version < 1.0.10, changed to 0x6982 in 1.0.11 + // https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410 if ((status & (short) 0xFFF0) == 0x63C0) { int tries = status & 0x000F; // hook to do something different when PIN is wrong @@ -266,50 +268,49 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity // 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: { - onSecurityTokenError(getString(R.string.security_token_error_bad_data)); - break; - } - case 0x6883: { - onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); - break; - } - case 0x6B00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); - break; - } - case 0x6D00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); - break; - } - case 0x6E00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); + + // These error conditions are likely to be experienced by an end user. + + /* OpenPGP Card Spec: Security status not satisfied, PW wrong, + PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */ + // NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN + case 0x6982: { + // hook to do something different when PIN is wrong + onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied)); break; } - // These error conditions are more likely to be experienced by an end user. + /* OpenPGP Card Spec: Selected file in termination state */ case 0x6285: { onSecurityTokenError(getString(R.string.security_token_error_terminated)); break; } + /* OpenPGP Card Spec: Wrong length (Lc and/or Le) */ + // NOTE: Used in ykneo-openpgp < 1.0.10 for too short PIN, changed in 1.0.11 to 0x6A80 for too short PIN + // https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f case 0x6700: { + // hook to do something different when PIN is wrong onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); break; } - case 0x6982: { - onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); + /* OpenPGP Card Spec: Incorrect parameters in the data field */ + // NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN + case 0x6A80: { + // hook to do something different when PIN is wrong + onSecurityTokenPinError(getString(R.string.security_token_error_bad_data)); break; } + /* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */ case 0x6983: { onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); break; } + /* OpenPGP Card Spec: Condition of use not satisfied */ case 0x6985: { onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); break; } - // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. + /* OpenPGP Card Spec: SM data objects incorrect (e.g. wrong TLV-structure in command data) */ + // NOTE: 6A88 is "Not Found" in the spec, but ykneo-openpgp also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); @@ -335,6 +336,30 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity } break; } + + // 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. + + /* OpenPGP Card Spec: Last command of the chain expected */ + case 0x6883: { + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); + break; + } + /* OpenPGP Card Spec: Wrong parameters P1-P2 */ + case 0x6B00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); + break; + } + /* OpenPGP Card Spec: Instruction (INS) not supported */ + case 0x6D00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); + break; + } + /* OpenPGP Card Spec: Class (CLA) not supported */ + case 0x6E00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); + break; + } default: { onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); break; -- cgit v1.2.3 From 0faab067c6effdb49fc9e3567b20837df16cd4e1 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 1 May 2016 22:16:52 +0600 Subject: Fix rotation crash in CreateSecurityTokenWaitFragment --- .../keychain/ui/CreateSecurityTokenWaitFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index d5f4cc7af..782502741 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -38,8 +38,8 @@ public class CreateSecurityTokenWaitFragment extends Fragment { View mBackButton; @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onActivityCreated(@Nullable final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); if (this.getActivity() instanceof BaseSecurityTokenActivity) { ((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection(); -- cgit v1.2.3 From 4c063ebe4683c0ffd0a80ff617967e8134b484fa Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 May 2016 11:36:55 +0200 Subject: api: ACTION_GET_KEY selects by any subkey, rather than just master key id --- .../java/org/sufficientlysecure/keychain/remote/OpenPgpService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 fd58da558..c0343aac6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -621,7 +621,8 @@ public class OpenPgpService extends Service { try { // try to find key, throws NotFoundException if not in db! CanonicalizedPublicKeyRing keyRing = - mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); + mProviderHelper.getCanonicalizedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(masterKeyId)); Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); -- cgit v1.2.3 From 6b7a0597af7aa42b375ca34d8a47515fa8adcc9f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 May 2016 11:45:56 +0200 Subject: api: add a comment explaining use of wrapperBundle --- .../main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java | 1 + 1 file changed, 1 insertion(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 c0343aac6..f975ca9a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -454,6 +454,7 @@ public class OpenPgpService extends Service { new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); } if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER)) { + // this is wrapped in a Bundle to avoid ClassLoader problems Bundle wrapperBundle = data.getBundleExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER); wrapperBundle.setClassLoader(getClassLoader()); OpenPgpDecryptionResult decryptionResult = wrapperBundle.getParcelable(OpenPgpApi.EXTRA_DECRYPTION_RESULT); -- cgit v1.2.3 From 525788359c6821a958ee7306ef3aa34d7b211a6f Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Tue, 15 Mar 2016 10:24:28 +0800 Subject: (WIP) Change password when key is stripped #1692 Approach: Find the first unstripped secret key and use it for passphrase verification All unstripped keys will have their passphrase changed to new passphrase, if possible. Current Progress: Changing the passphrase of keys works fine. Refactoring to combine "modifySecretKeyring" and newly added method, "modifyKeyRingPassword" may be possible if given the go-ahead. --- .../operations/PassphraseChangeOperation.java | 141 +++++++++++++++++++++ .../operations/results/OperationResult.java | 2 + .../keychain/pgp/PgpKeyOperation.java | 72 ++++++++++- .../keychain/service/ChangeUnlockParcel.java | 48 +++++++ .../keychain/service/KeychainService.java | 3 + .../keychain/service/PassphraseChangeParcel.java | 64 ++++++++++ .../keychain/service/SaveKeyringParcel.java | 48 ------- .../keychain/ui/CreateKeyFinalFragment.java | 2 +- .../keychain/ui/EditKeyFragment.java | 2 +- .../keychain/ui/ViewKeyActivity.java | 18 +-- 10 files changed, 336 insertions(+), 64 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java new file mode 100644 index 000000000..e95f35c21 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java @@ -0,0 +1,141 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.util.Iterator; + +/** + * Created by alex on 3/14/16. + */ +public class PassphraseChangeOperation extends BaseOperation { + + + public PassphraseChangeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); + } + + /** + * Finds the first unstripped key & uses that for passphrase verification. + * Might bring in complications + * + * @param passphraseParcel primary input to the operation + * @param cryptoInput input that changes if user interaction is required + * @return the result of the operation + */ + @NonNull + public OperationResult execute(PassphraseChangeParcel passphraseParcel, CryptoInputParcel cryptoInput) { + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_ED, 0); + + if (passphraseParcel == null || passphraseParcel.mMasterKeyId == null) { + log.add(OperationResult.LogType.MSG_ED_ERROR_NO_PARCEL, 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Perform actual modification + PgpEditKeyResult modifyResult; + { + PgpKeyOperation keyOperations = + new PgpKeyOperation(new ProgressScaler(mProgressable, 0, 70, 100), mCancelled); + + try { + log.add(OperationResult.LogType.MSG_ED_FETCHING, 1, + KeyFormattingUtils.convertKeyIdToHex(passphraseParcel.mMasterKeyId)); + + CanonicalizedSecretKeyRing secRing = + mProviderHelper.getCanonicalizedSecretKeyRing(passphraseParcel.mMasterKeyId); + CachedPublicKeyRing cachedRing = + mProviderHelper.getCachedPublicKeyRing(passphraseParcel.mMasterKeyId); + + passphraseParcel.mValidSubkeyId = getFirstValidKeyId(secRing, cachedRing); + + if(passphraseParcel.mValidSubkeyId == null) { + log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, 0); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + modifyResult = keyOperations.modifyKeyRingPassword(secRing, cryptoInput, passphraseParcel); + + if (modifyResult.isPending()) { + log.add(modifyResult, 1); + return new EditKeyResult(log, modifyResult); + } + } catch (ProviderHelper.NotFoundException e) { + log.add(OperationResult.LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + } + + log.add(modifyResult, 1); + + // Check if the action was cancelled + if (checkCancelled()) { + log.add(OperationResult.LogType.MSG_OPERATION_CANCELLED, 0); + return new EditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + if (!modifyResult.success()) { + // error is already logged by modification + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Cannot cancel from here on out! + mProgressable.setPreventCancel(); + + // It's a success, so this must be non-null now + UncachedKeyRing ring = modifyResult.getRing(); + + SaveKeyringResult saveResult = mProviderHelper + .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 70, 95, 100)); + log.add(saveResult, 1); + + // If the save operation didn't succeed, exit here + if (!saveResult.success()) { + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + updateProgress(R.string.progress_done, 100, 100); + log.add(OperationResult.LogType.MSG_ED_SUCCESS, 0); + return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); + + } + + private static Long getFirstValidKeyId (CanonicalizedSecretKeyRing secRing, CachedPublicKeyRing cachedRing) { + + Iterator secretKeyIterator = secRing.secretKeyIterator().iterator(); + + while(secretKeyIterator.hasNext()) { + try { + long keyId = secretKeyIterator.next().getKeyId(); + CanonicalizedSecretKey.SecretKeyType keyType = cachedRing.getSecretKeyType(keyId); + if( keyType == CanonicalizedSecretKey.SecretKeyType.PASSPHRASE + || keyType == CanonicalizedSecretKey.SecretKeyType.PASSPHRASE_EMPTY) { + return keyId; + } + } catch (ProviderHelper.NotFoundException e) { + ; + } + } + + return null; + } +} 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 02256aebd..d3d962808 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 @@ -539,6 +539,7 @@ public abstract class OperationResult implements Parcelable { // secret key modify MSG_MF (LogLevel.START, R.string.msg_mr), MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert), + MSG_MF_ERROR_ALL_KEYS_STRIPPED (LogLevel.ERROR, R.string.msg_mf_error_all_keys_stripped), MSG_MF_ERROR_DIVERT_NEWSUB (LogLevel.ERROR, R.string.msg_mf_error_divert_newsub), MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial), MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode), @@ -552,6 +553,7 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop), MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry), MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master), + MSG_MF_ERROR_PASSPHRASES_UNCHANGED(LogLevel.ERROR, R.string.msg_mf_error_passphrases_unchanged), MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry), MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp), MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted), 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 ce9c30894..abfdf0966 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -72,6 +72,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; @@ -345,6 +347,64 @@ public class PgpKeyOperation { } + + public PgpEditKeyResult modifyKeyRingPassword(CanonicalizedSecretKeyRing wsKR, + CryptoInputParcel cryptoInput, + PassphraseChangeParcel passphraseParcel) { + + OperationLog log = new OperationLog(); + int indent = 0; + + if (passphraseParcel.mMasterKeyId == null || passphraseParcel.mMasterKeyId != wsKR.getMasterKeyId()) { + log.add(LogType.MSG_MF_ERROR_KEYID, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + log.add(LogType.MSG_MF, indent, + KeyFormattingUtils.convertKeyIdToHex(wsKR.getMasterKeyId())); + indent += 1; + progress(R.string.progress_building_key, 0); + + // We work on bouncycastle object level here + PGPSecretKeyRing sKR = wsKR.getRing(); + PGPSecretKey masterSecretKey = sKR.getSecretKey(); + PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); + // Make sure the fingerprint matches + if (passphraseParcel.mFingerprint == null || !Arrays.equals(passphraseParcel.mFingerprint, + masterSecretKey.getPublicKey().getFingerprint())) { + log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (!cryptoInput.hasPassphrase()) { + log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); + + return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase( + masterSecretKey.getKeyID(), passphraseParcel.mValidSubkeyId, + cryptoInput.getSignatureTime()), cryptoInput); + } else { + progress(R.string.progress_modify_passphrase, 70); + log.add(LogType.MSG_MF_PASSPHRASE, indent); + indent += 1; + + try { + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + passphraseParcel.mNewUnlock.mNewPassphrase, log, indent); + if (sKR == null) { + // The error has been logged above, just return a bad state + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } catch (PGPException e) { + throw new UnsupportedOperationException("Failed to build encryptor/decryptor!"); + } + + indent -= 1; + progress(R.string.progress_done, 100); + log.add(LogType.MSG_MF_SUCCESS, indent); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + } + } + /** This method introduces a list of modifications specified by a SaveKeyringParcel to a * WrappedSecretKeyRing. * @@ -1223,6 +1283,7 @@ public class PgpKeyOperation { PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); + int keysModified = 0; for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) { log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, @@ -1236,12 +1297,6 @@ public class PgpKeyOperation { ok = true; } catch (PGPException e) { - // if this is the master key, error! - if (sKey.getKeyID() == masterPublicKey.getKeyID()) { - log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); - return null; - } - // being in here means decrypt failed, likely due to a bad passphrase try // again with an empty passphrase, maybe we can salvage this try { @@ -1264,7 +1319,12 @@ public class PgpKeyOperation { } sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + keysModified++; + } + if(keysModified == 0) { + log.add(LogType.MSG_MF_ERROR_PASSPHRASES_UNCHANGED, indent+1); + return null; } return sKR; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java new file mode 100644 index 000000000..2bfe8254c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.util.Passphrase; + +public class ChangeUnlockParcel implements Parcelable { + + // The new passphrase to use + public final Passphrase mNewPassphrase; + + public ChangeUnlockParcel(Passphrase newPassphrase) { + if (newPassphrase == null) { + throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); + } + mNewPassphrase = newPassphrase; + } + + public ChangeUnlockParcel(Parcel source) { + mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeParcelable(mNewPassphrase, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + public ChangeUnlockParcel createFromParcel(final Parcel source) { + return new ChangeUnlockParcel(source); + } + + public ChangeUnlockParcel[] newArray(final int size) { + return new ChangeUnlockParcel[size]; + } + }; + + public String toString() { + return "passphrase (" + mNewPassphrase + ")"; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index cf51e3b55..e337703d9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.BackupOperation; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation; import org.sufficientlysecure.keychain.operations.InputDataOperation; +import org.sufficientlysecure.keychain.operations.PassphraseChangeOperation; import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; @@ -116,6 +117,8 @@ public class KeychainService extends Service implements Progressable { op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof SaveKeyringParcel) { op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); + } else if (inputParcel instanceof PassphraseChangeParcel) { + op = new PassphraseChangeOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof RevokeKeyringParcel) { op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof CertifyActionsParcel) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java new file mode 100644 index 000000000..8b08aa115 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java @@ -0,0 +1,64 @@ +package org.sufficientlysecure.keychain.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class PassphraseChangeParcel implements Parcelable { + + // the master key id to be edited. + public Long mMasterKeyId; + // the first sub key id that is not stripped. + public Long mValidSubkeyId; + // the key fingerprint, for safety. + public byte[] mFingerprint; + + public ChangeUnlockParcel mNewUnlock; + + + public PassphraseChangeParcel(long masterKeyId, byte[] fingerprint) { + mMasterKeyId = masterKeyId; + mFingerprint = fingerprint; + } + + public PassphraseChangeParcel(Parcel source) { + mValidSubkeyId = source.readInt() != 0 ? source.readLong() : null; + mMasterKeyId = source.readLong(); + mFingerprint = source.createByteArray(); + + mNewUnlock = source.readParcelable(getClass().getClassLoader()); + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeInt(mValidSubkeyId == null ? 0 : 1); + if (mValidSubkeyId != null) { + destination.writeLong(mValidSubkeyId); + } + destination.writeLong(mMasterKeyId); + destination.writeByteArray(mFingerprint); + destination.writeParcelable(mNewUnlock, flags); + } + + public static final Creator CREATOR = new Creator() { + public PassphraseChangeParcel createFromParcel(final Parcel source) { + return new PassphraseChangeParcel(source); + } + + public PassphraseChangeParcel[] newArray(final int size) { + return new PassphraseChangeParcel[size]; + } + }; + + public String toString() { + String out = "mMasterKeyId: " + mMasterKeyId + "\n"; + out += "mNewUnlock: " + mNewUnlock + "\n"; + + return out; + } +} 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 dc892ecc8..563a67b3f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -344,54 +344,6 @@ public class SaveKeyringParcel implements Parcelable { // BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512 } - /** This subclass contains information on how the passphrase should be changed. - * - * If no changes are to be made, this class should NOT be used! - * - * At this point, there must be *exactly one* non-null value here, which specifies the type - * of unlocking mechanism to use. - * - */ - public static class ChangeUnlockParcel implements Parcelable { - - // The new passphrase to use - public final Passphrase mNewPassphrase; - - public ChangeUnlockParcel(Passphrase newPassphrase) { - if (newPassphrase == null) { - throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); - } - mNewPassphrase = newPassphrase; - } - - public ChangeUnlockParcel(Parcel source) { - mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel destination, int flags) { - destination.writeParcelable(mNewPassphrase, flags); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Creator CREATOR = new Creator() { - public ChangeUnlockParcel createFromParcel(final Parcel source) { - return new ChangeUnlockParcel(source); - } - - public ChangeUnlockParcel[] newArray(final int size) { - return new ChangeUnlockParcel[size]; - } - }; - public String toString() { - return "passphrase (" + mNewPassphrase + ")"; - } - - } } 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 896df0ad2..300d6c41a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -44,9 +44,9 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; 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 9ed8e369d..14692f66f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -50,8 +50,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; 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 dea4b4eef..7ddbf4847 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -81,7 +81,9 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; @@ -130,8 +132,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private String mKeyserver; private ArrayList mKeyList; private CryptoOperationHelper mImportOpHelper; - private CryptoOperationHelper mEditOpHelper; - private SaveKeyringParcel mSaveKeyringParcel; + private CryptoOperationHelper mEditOpHelper; + private PassphraseChangeParcel mPassphraseChangeParcel; private TextView mStatusText; private ImageView mStatusImage; @@ -429,13 +431,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } private void changePassword() { - mSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); + mPassphraseChangeParcel = new PassphraseChangeParcel(mMasterKeyId, mFingerprint); - CryptoOperationHelper.Callback editKeyCallback - = new CryptoOperationHelper.Callback() { + CryptoOperationHelper.Callback editKeyCallback + = new CryptoOperationHelper.Callback() { @Override - public SaveKeyringParcel createOperationInput() { - return mSaveKeyringParcel; + public PassphraseChangeParcel createOperationInput() { + return mPassphraseChangeParcel; } @Override @@ -469,7 +471,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Bundle data = message.getData(); // use new passphrase! - mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel( + mPassphraseChangeParcel.mNewUnlock = new ChangeUnlockParcel( (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); -- cgit v1.2.3 From b490be9c1c979fd4a75b5844fb68b0179bcfe598 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Tue, 15 Mar 2016 20:59:42 +0800 Subject: Refactored code to use functions already present in code, reduced liberties taken when modifying functions. Todo: Fix indentation for error messages --- .../operations/PassphraseChangeOperation.java | 28 --------------------- .../keychain/pgp/PgpKeyOperation.java | 29 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java index e95f35c21..fff4ef534 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java @@ -63,15 +63,6 @@ public class PassphraseChangeOperation extends BaseOperation secretKeyIterator = secRing.secretKeyIterator().iterator(); - - while(secretKeyIterator.hasNext()) { - try { - long keyId = secretKeyIterator.next().getKeyId(); - CanonicalizedSecretKey.SecretKeyType keyType = cachedRing.getSecretKeyType(keyId); - if( keyType == CanonicalizedSecretKey.SecretKeyType.PASSPHRASE - || keyType == CanonicalizedSecretKey.SecretKeyType.PASSPHRASE_EMPTY) { - return keyId; - } - } catch (ProviderHelper.NotFoundException e) { - ; - } - } - - return null; - } } 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 abfdf0966..cd4d9e5bb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -72,6 +72,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; @@ -376,6 +377,16 @@ public class PgpKeyOperation { return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } + if (passphraseParcel.mValidSubkeyId == null) { + PGPSecretKey nonDummy = firstNonDummySecretKeyID(sKR); + if(nonDummy== null) { + log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, 0); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } else { + passphraseParcel.mValidSubkeyId = nonDummy.getKeyID(); + } + } + if (!cryptoInput.hasPassphrase()) { log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); @@ -405,6 +416,18 @@ public class PgpKeyOperation { } } + private static PGPSecretKey firstNonDummySecretKeyID(PGPSecretKeyRing secRing) { + Iterator secretKeyIterator = secRing.getSecretKeys(); + + while(secretKeyIterator.hasNext()) { + PGPSecretKey secretKey = secretKeyIterator.next(); + if(!isDummy(secretKey)){ + return secretKey; + } + } + return null; + } + /** This method introduces a list of modifications specified by a SaveKeyringParcel to a * WrappedSecretKeyRing. * @@ -1297,6 +1320,12 @@ public class PgpKeyOperation { ok = true; } catch (PGPException e) { + // if this is the master key, error! + if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) { + log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); + return null; + } + // being in here means decrypt failed, likely due to a bad passphrase try // again with an empty passphrase, maybe we can salvage this try { -- cgit v1.2.3 From dfcde9242d7b39bf1ab9f0b66fc5829fb0af0f8c Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Thu, 17 Mar 2016 08:03:22 +0800 Subject: Removed unrequired code, standardized terms used. --- .../operations/PassphraseChangeOperation.java | 27 +++------------------- .../keychain/pgp/PgpKeyOperation.java | 18 +++++++++------ 2 files changed, 14 insertions(+), 31 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java index fff4ef534..2904c9b5a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java @@ -8,24 +8,17 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.util.Iterator; -/** - * Created by alex on 3/14/16. - */ public class PassphraseChangeOperation extends BaseOperation { @@ -33,14 +26,6 @@ public class PassphraseChangeOperation extends BaseOperation(sKR.getSecretKeys())) { log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, @@ -1321,6 +1323,7 @@ public class PgpKeyOperation { } catch (PGPException e) { // if this is the master key, error! + // skipped when changing key passphrase if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) { log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); return null; @@ -1348,10 +1351,11 @@ public class PgpKeyOperation { } sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); - keysModified++; + keysModified = true; } - if(keysModified == 0) { + if(!keysModified) { + // no passphrase is changed log.add(LogType.MSG_MF_ERROR_PASSPHRASES_UNCHANGED, indent+1); return null; } -- cgit v1.2.3 From f43edcdd7afb1692fab1239c54c3cd535506c9e1 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sun, 17 Apr 2016 11:34:08 +0800 Subject: Refactoring: Removed PassphraseChangeParcel and placed its functionality into ChangeUnlockParcel. --- .../keychain/operations/ChangeUnlockOperation.java | 91 ++++++++++++ .../operations/PassphraseChangeOperation.java | 92 ------------ .../keychain/pgp/PgpKeyOperation.java | 160 ++++++++++----------- .../keychain/service/ChangeUnlockParcel.java | 40 +++++- .../keychain/service/KeychainService.java | 6 +- .../keychain/service/PassphraseChangeParcel.java | 64 --------- .../keychain/service/SaveKeyringParcel.java | 13 +- .../keychain/ui/CreateKeyFinalFragment.java | 8 +- .../keychain/ui/EditKeyFragment.java | 6 +- .../keychain/ui/ViewKeyActivity.java | 21 ++- 10 files changed, 234 insertions(+), 267 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java new file mode 100644 index 000000000..b16957e25 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java @@ -0,0 +1,91 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ProgressScaler; + + +public class ChangeUnlockOperation extends BaseOperation { + + public ChangeUnlockOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { + super(context, providerHelper, progressable); + } + + @NonNull + public OperationResult execute(ChangeUnlockParcel unlockParcel, CryptoInputParcel cryptoInput) { + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_ED, 0); + + if (unlockParcel == null || unlockParcel.mMasterKeyId == null) { + log.add(OperationResult.LogType.MSG_ED_ERROR_NO_PARCEL, 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Perform actual modification + PgpEditKeyResult modifyResult; + { + PgpKeyOperation keyOperations = + new PgpKeyOperation(new ProgressScaler(mProgressable, 0, 70, 100)); + + try { + log.add(OperationResult.LogType.MSG_ED_FETCHING, 1, + KeyFormattingUtils.convertKeyIdToHex(unlockParcel.mMasterKeyId)); + + CanonicalizedSecretKeyRing secRing = + mProviderHelper.getCanonicalizedSecretKeyRing(unlockParcel.mMasterKeyId); + modifyResult = keyOperations.modifyKeyRingPassphrase(secRing, cryptoInput, unlockParcel); + + if (modifyResult.isPending()) { + // obtain original passphrase from user + log.add(modifyResult, 1); + return new EditKeyResult(log, modifyResult); + } + } catch (ProviderHelper.NotFoundException e) { + log.add(OperationResult.LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + } + + log.add(modifyResult, 1); + + if (!modifyResult.success()) { + // error is already logged by modification + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Cannot cancel from here on out! + mProgressable.setPreventCancel(); + + // It's a success, so this must be non-null now + UncachedKeyRing ring = modifyResult.getRing(); + + SaveKeyringResult saveResult = mProviderHelper + .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 70, 95, 100)); + log.add(saveResult, 1); + + // If the save operation didn't succeed, exit here + if (!saveResult.success()) { + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + updateProgress(R.string.progress_done, 100, 100); + log.add(OperationResult.LogType.MSG_ED_SUCCESS, 0); + return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java deleted file mode 100644 index 2904c9b5a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PassphraseChangeOperation.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.sufficientlysecure.keychain.operations; - -import android.content.Context; -import android.support.annotation.NonNull; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; -import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ProgressScaler; - - -public class PassphraseChangeOperation extends BaseOperation { - - - public PassphraseChangeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { - super(context, providerHelper, progressable); - } - - @NonNull - public OperationResult execute(PassphraseChangeParcel passphraseParcel, CryptoInputParcel cryptoInput) { - OperationResult.OperationLog log = new OperationResult.OperationLog(); - log.add(OperationResult.LogType.MSG_ED, 0); - - if (passphraseParcel == null || passphraseParcel.mMasterKeyId == null) { - log.add(OperationResult.LogType.MSG_ED_ERROR_NO_PARCEL, 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - // Perform actual modification - PgpEditKeyResult modifyResult; - { - PgpKeyOperation keyOperations = - new PgpKeyOperation(new ProgressScaler(mProgressable, 0, 70, 100)); - - try { - log.add(OperationResult.LogType.MSG_ED_FETCHING, 1, - KeyFormattingUtils.convertKeyIdToHex(passphraseParcel.mMasterKeyId)); - - CanonicalizedSecretKeyRing secRing = - mProviderHelper.getCanonicalizedSecretKeyRing(passphraseParcel.mMasterKeyId); - modifyResult = keyOperations.modifyKeyRingPassphrase(secRing, cryptoInput, passphraseParcel); - - if (modifyResult.isPending()) { - // obtain original passphrase from user - log.add(modifyResult, 1); - return new EditKeyResult(log, modifyResult); - } - } catch (ProviderHelper.NotFoundException e) { - log.add(OperationResult.LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - } - - log.add(modifyResult, 1); - - if (!modifyResult.success()) { - // error is already logged by modification - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - // Cannot cancel from here on out! - mProgressable.setPreventCancel(); - - // It's a success, so this must be non-null now - UncachedKeyRing ring = modifyResult.getRing(); - - SaveKeyringResult saveResult = mProviderHelper - .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 70, 95, 100)); - log.add(saveResult, 1); - - // If the save operation didn't succeed, exit here - if (!saveResult.success()) { - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); - } - - updateProgress(R.string.progress_done, 100, 100); - log.add(OperationResult.LogType.MSG_ED_SUCCESS, 0); - return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); - - } - -} 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 bfc48ccbe..f98ee0d06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -73,7 +73,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; -import org.sufficientlysecure.keychain.service.PassphraseChangeParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; @@ -347,86 +346,6 @@ public class PgpKeyOperation { } - - public PgpEditKeyResult modifyKeyRingPassphrase(CanonicalizedSecretKeyRing wsKR, - CryptoInputParcel cryptoInput, - PassphraseChangeParcel passphraseParcel) { - - OperationLog log = new OperationLog(); - int indent = 0; - - if (passphraseParcel.mMasterKeyId == null || passphraseParcel.mMasterKeyId != wsKR.getMasterKeyId()) { - log.add(LogType.MSG_MF_ERROR_KEYID, indent); - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); - } - - log.add(LogType.MSG_MF, indent, - KeyFormattingUtils.convertKeyIdToHex(wsKR.getMasterKeyId())); - indent += 1; - progress(R.string.progress_building_key, 0); - - // We work on bouncycastle object level here - PGPSecretKeyRing sKR = wsKR.getRing(); - PGPSecretKey masterSecretKey = sKR.getSecretKey(); - PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); - // Make sure the fingerprint matches - if (passphraseParcel.mFingerprint == null || !Arrays.equals(passphraseParcel.mFingerprint, - masterSecretKey.getPublicKey().getFingerprint())) { - log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); - } - - if (passphraseParcel.mValidSubkeyId == null) { - PGPSecretKey nonDummy = firstNonDummySecretKeyID(sKR); - if(nonDummy== null) { - log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, 0); - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); - } else { - passphraseParcel.mValidSubkeyId = nonDummy.getKeyID(); - } - } - - if (!cryptoInput.hasPassphrase()) { - log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); - - return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase( - masterSecretKey.getKeyID(), passphraseParcel.mValidSubkeyId, - cryptoInput.getSignatureTime()), cryptoInput); - } else { - progress(R.string.progress_modify_passphrase, 70); - log.add(LogType.MSG_MF_PASSPHRASE, indent); - indent += 1; - - try { - sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), - passphraseParcel.mNewUnlock.mNewPassphrase, log, indent); - if (sKR == null) { - // The error has been logged above, just return a bad state - return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); - } - } catch (PGPException e) { - throw new UnsupportedOperationException("Failed to build encryptor/decryptor!"); - } - - indent -= 1; - progress(R.string.progress_done, 100); - log.add(LogType.MSG_MF_SUCCESS, indent); - return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); - } - } - - private static PGPSecretKey firstNonDummySecretKeyID(PGPSecretKeyRing secRing) { - Iterator secretKeyIterator = secRing.getSecretKeys(); - - while(secretKeyIterator.hasNext()) { - PGPSecretKey secretKey = secretKeyIterator.next(); - if(!isDummy(secretKey)){ - return secretKey; - } - } - return null; - } - /** This method introduces a list of modifications specified by a SaveKeyringParcel to a * WrappedSecretKeyRing. * @@ -1135,13 +1054,13 @@ public class PgpKeyOperation { } // 6. If requested, change passphrase - if (saveParcel.mNewUnlock != null) { + if (saveParcel.getChangeUnlockParcel() != null) { progress(R.string.progress_modify_passphrase, 90); log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), - saveParcel.mNewUnlock.mNewPassphrase, log, indent); + saveParcel.getChangeUnlockParcel().mNewPassphrase, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -1274,7 +1193,81 @@ public class PgpKeyOperation { } + public PgpEditKeyResult modifyKeyRingPassphrase(CanonicalizedSecretKeyRing wsKR, + CryptoInputParcel cryptoInput, + ChangeUnlockParcel changeUnlockParcel) { + + OperationLog log = new OperationLog(); + int indent = 0; + + if (changeUnlockParcel.mMasterKeyId == null || changeUnlockParcel.mMasterKeyId != wsKR.getMasterKeyId()) { + log.add(LogType.MSG_MF_ERROR_KEYID, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + log.add(LogType.MSG_MF, indent, + KeyFormattingUtils.convertKeyIdToHex(wsKR.getMasterKeyId())); + indent += 1; + progress(R.string.progress_building_key, 0); + + // We work on bouncycastle object level here + PGPSecretKeyRing sKR = wsKR.getRing(); + PGPSecretKey masterSecretKey = sKR.getSecretKey(); + PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); + // Make sure the fingerprint matches + if (changeUnlockParcel.mFingerprint == null || !Arrays.equals(changeUnlockParcel.mFingerprint, + masterSecretKey.getPublicKey().getFingerprint())) { + log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + // Find the first unstripped secret key + PGPSecretKey nonDummy = firstNonDummySecretKeyID(sKR); + if(nonDummy == null) { + log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + + if (!cryptoInput.hasPassphrase()) { + log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); + + return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase( + masterSecretKey.getKeyID(), nonDummy.getKeyID(), + cryptoInput.getSignatureTime()), cryptoInput); + } else { + progress(R.string.progress_modify_passphrase, 50); + log.add(LogType.MSG_MF_PASSPHRASE, indent); + indent += 1; + + try { + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + changeUnlockParcel.mNewPassphrase, log, indent); + if (sKR == null) { + // The error has been logged above, just return a bad state + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } catch (PGPException e) { + throw new UnsupportedOperationException("Failed to build encryptor/decryptor!"); + } + indent -= 1; + progress(R.string.progress_done, 100); + log.add(LogType.MSG_MF_SUCCESS, indent); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + } + } + + private static PGPSecretKey firstNonDummySecretKeyID(PGPSecretKeyRing secRing) { + Iterator secretKeyIterator = secRing.getSecretKeys(); + + while(secretKeyIterator.hasNext()) { + PGPSecretKey secretKey = secretKeyIterator.next(); + if(!isDummy(secretKey)){ + return secretKey; + } + } + return null; + } /** This method returns true iff the provided keyring has a local direct key signature * with notation data. @@ -1323,7 +1316,6 @@ public class PgpKeyOperation { } catch (PGPException e) { // if this is the master key, error! - // skipped when changing key passphrase if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) { log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); return null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java index 2bfe8254c..fa8fd0841 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014 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.service; import android.os.Parcel; @@ -7,22 +26,36 @@ import org.sufficientlysecure.keychain.util.Passphrase; public class ChangeUnlockParcel implements Parcelable { + // the master key id of keyring. + public Long mMasterKeyId; + // the key fingerprint, for safety. + public byte[] mFingerprint; // The new passphrase to use public final Passphrase mNewPassphrase; - public ChangeUnlockParcel(Passphrase newPassphrase) { + public ChangeUnlockParcel(Long masterKeyId, byte[] fingerprint, Passphrase newPassphrase) { if (newPassphrase == null) { throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); } + + mMasterKeyId = masterKeyId; + mFingerprint = fingerprint; mNewPassphrase = newPassphrase; } public ChangeUnlockParcel(Parcel source) { + mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; + mFingerprint = source.createByteArray(); mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader()); } @Override public void writeToParcel(Parcel destination, int flags) { + destination.writeInt(mMasterKeyId == null ? 0 : 1); + if (mMasterKeyId != null) { + destination.writeLong(mMasterKeyId); + } + destination.writeByteArray(mFingerprint); destination.writeParcelable(mNewPassphrase, flags); } @@ -42,7 +75,10 @@ public class ChangeUnlockParcel implements Parcelable { }; public String toString() { - return "passphrase (" + mNewPassphrase + ")"; + String out = "mMasterKeyId: " + mMasterKeyId + "\n"; + out += "passphrase (" + mNewPassphrase + ")"; + + return out; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index e337703d9..c287f6b38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.operations.BackupOperation; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation; import org.sufficientlysecure.keychain.operations.InputDataOperation; -import org.sufficientlysecure.keychain.operations.PassphraseChangeOperation; +import org.sufficientlysecure.keychain.operations.ChangeUnlockOperation; import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; @@ -117,8 +117,8 @@ public class KeychainService extends Service implements Progressable { op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof SaveKeyringParcel) { op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled); - } else if (inputParcel instanceof PassphraseChangeParcel) { - op = new PassphraseChangeOperation(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof ChangeUnlockParcel) { + op = new ChangeUnlockOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof RevokeKeyringParcel) { op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis); } else if (inputParcel instanceof CertifyActionsParcel) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java deleted file mode 100644 index 8b08aa115..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseChangeParcel.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.sufficientlysecure.keychain.service; - -import android.os.Parcel; -import android.os.Parcelable; - -public class PassphraseChangeParcel implements Parcelable { - - // the master key id to be edited. - public Long mMasterKeyId; - // the first sub key id that is not stripped. - public Long mValidSubkeyId; - // the key fingerprint, for safety. - public byte[] mFingerprint; - - public ChangeUnlockParcel mNewUnlock; - - - public PassphraseChangeParcel(long masterKeyId, byte[] fingerprint) { - mMasterKeyId = masterKeyId; - mFingerprint = fingerprint; - } - - public PassphraseChangeParcel(Parcel source) { - mValidSubkeyId = source.readInt() != 0 ? source.readLong() : null; - mMasterKeyId = source.readLong(); - mFingerprint = source.createByteArray(); - - mNewUnlock = source.readParcelable(getClass().getClassLoader()); - } - - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel destination, int flags) { - destination.writeInt(mValidSubkeyId == null ? 0 : 1); - if (mValidSubkeyId != null) { - destination.writeLong(mValidSubkeyId); - } - destination.writeLong(mMasterKeyId); - destination.writeByteArray(mFingerprint); - destination.writeParcelable(mNewUnlock, flags); - } - - public static final Creator CREATOR = new Creator() { - public PassphraseChangeParcel createFromParcel(final Parcel source) { - return new PassphraseChangeParcel(source); - } - - public PassphraseChangeParcel[] newArray(final int size) { - return new PassphraseChangeParcel[size]; - } - }; - - public String toString() { - String out = "mMasterKeyId: " + mMasterKeyId + "\n"; - out += "mNewUnlock: " + mNewUnlock + "\n"; - - return out; - } -} 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 563a67b3f..32ec48343 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -49,8 +49,6 @@ public class SaveKeyringParcel implements Parcelable { // the key fingerprint, for safety. MUST be null for a new key. public byte[] mFingerprint; - public ChangeUnlockParcel mNewUnlock; - public ArrayList mAddUserIds; public ArrayList mAddUserAttribute; public ArrayList mAddSubKeys; @@ -70,6 +68,9 @@ public class SaveKeyringParcel implements Parcelable { private boolean mUploadAtomic; private String mKeyserver; + // private because we have to set other details like key id + private ChangeUnlockParcel mNewUnlock; + public SaveKeyringParcel() { reset(); } @@ -102,6 +103,14 @@ public class SaveKeyringParcel implements Parcelable { mKeyserver = keysever; } + public void setNewUnlock(Passphrase passphrase) { + mNewUnlock = new ChangeUnlockParcel(mMasterKeyId, mFingerprint, passphrase); + } + + public ChangeUnlockParcel getChangeUnlockParcel() { + return mNewUnlock; + } + public boolean isUpload() { return mUpload; } 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 300d6c41a..d490a5d4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -289,7 +289,7 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase - saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); + saveKeyringParcel.setNewUnlock(new Passphrase()); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -298,9 +298,9 @@ public class CreateKeyFinalFragment extends Fragment { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); - saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase) - : null; + if(createKeyActivity.mPassphrase != null) { + saveKeyringParcel.setNewUnlock(createKeyActivity.mPassphrase); + } } String userId = KeyRing.createUserId( new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) 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 14692f66f..99347c273 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; -import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -339,9 +338,8 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment mKeyList; private CryptoOperationHelper mImportOpHelper; - private CryptoOperationHelper mEditOpHelper; - private PassphraseChangeParcel mPassphraseChangeParcel; + private CryptoOperationHelper mEditOpHelper; + private ChangeUnlockParcel mChangeUnlockParcel; private TextView mStatusText; private ImageView mStatusImage; @@ -431,13 +429,11 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } private void changePassword() { - mPassphraseChangeParcel = new PassphraseChangeParcel(mMasterKeyId, mFingerprint); - - CryptoOperationHelper.Callback editKeyCallback - = new CryptoOperationHelper.Callback() { + CryptoOperationHelper.Callback editKeyCallback + = new CryptoOperationHelper.Callback() { @Override - public PassphraseChangeParcel createOperationInput() { - return mPassphraseChangeParcel; + public ChangeUnlockParcel createOperationInput() { + return mChangeUnlockParcel; } @Override @@ -470,8 +466,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - // use new passphrase! - mPassphraseChangeParcel.mNewUnlock = new ChangeUnlockParcel( + mChangeUnlockParcel = new ChangeUnlockParcel( + mMasterKeyId, + mFingerprint, (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); -- cgit v1.2.3 From 9d35dcb0e924a7f8afcfcaa86752efb40799a456 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sun, 17 Apr 2016 13:33:41 +0800 Subject: Added warning dialog for importing keys --- .../keychain/ui/ImportKeysActivity.java | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 7d2d30c35..6b3e12761 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -38,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -342,6 +344,31 @@ public class ImportKeysActivity extends BaseActivity return; } + showWarningDialog(); + } + + private void showWarningDialog() { + CustomAlertDialogBuilder warningDialog = new CustomAlertDialogBuilder(this); + warningDialog.setTitle(R.string.import_warning_title). + setMessage(R.string.import_warning).setCancelable(true); + warningDialog.setPositiveButton(R.string.btn_import, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + executeImport(); + } + }); + warningDialog.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + }); + warningDialog.show(); + } + + private void executeImport() { + FragmentManager fragMan = getSupportFragmentManager(); + ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); + mOperationHelper = new CryptoOperationHelper<>( 1, this, this, R.string.progress_importing ); -- cgit v1.2.3 From 89cf3bb6d962a5baa6024a5c7d515263c79cc003 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sun, 17 Apr 2016 14:11:26 +0800 Subject: Fixed warnings encountered --- .../java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java | 1 - .../main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 d490a5d4c..97fdd49d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; 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 99347c273..8d5edb3e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -128,7 +128,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment Date: Sun, 17 Apr 2016 18:07:23 +0800 Subject: Edited comments --- .../java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 f98ee0d06..404e07230 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -1315,7 +1315,7 @@ public class PgpKeyOperation { ok = true; } catch (PGPException e) { - // if this is the master key, error! + // if the master key failed && it's not stripped, error! if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) { log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); return null; @@ -1347,7 +1347,7 @@ public class PgpKeyOperation { } if(!keysModified) { - // no passphrase is changed + // no passphrase was changed log.add(LogType.MSG_MF_ERROR_PASSPHRASES_UNCHANGED, indent+1); return null; } -- cgit v1.2.3 From a5a2335673d092bf49983273ad6c54a489fa8d92 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sun, 17 Apr 2016 18:25:15 +0800 Subject: Added license to ChangeUnlockOperation --- .../keychain/operations/ChangeUnlockOperation.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java index b16957e25..f9ae13b1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Alex Fong Jie Wen + * + * 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.operations; import android.content.Context; -- cgit v1.2.3 From bc3aa44b22546fb5b12c5ca9ab438c979a5449f1 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Tue, 19 Apr 2016 09:00:15 +0800 Subject: Refactored to reduce coupling between ChangeUnlockParcel and SaveKeyringParcel --- .../sufficientlysecure/keychain/service/ChangeUnlockParcel.java | 4 ++++ .../sufficientlysecure/keychain/service/SaveKeyringParcel.java | 8 ++++++-- .../sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java | 5 +++-- .../java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java | 5 +++-- 4 files changed, 16 insertions(+), 6 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java index fa8fd0841..974bb2413 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ChangeUnlockParcel.java @@ -33,6 +33,10 @@ public class ChangeUnlockParcel implements Parcelable { // The new passphrase to use public final Passphrase mNewPassphrase; + public ChangeUnlockParcel(Passphrase newPassphrase) { + mNewPassphrase = newPassphrase; + } + public ChangeUnlockParcel(Long masterKeyId, byte[] fingerprint, Passphrase newPassphrase) { if (newPassphrase == null) { throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!"); 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 32ec48343..db6bbcbdb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -103,11 +103,15 @@ public class SaveKeyringParcel implements Parcelable { mKeyserver = keysever; } - public void setNewUnlock(Passphrase passphrase) { - mNewUnlock = new ChangeUnlockParcel(mMasterKeyId, mFingerprint, passphrase); + public void setNewUnlock(ChangeUnlockParcel parcel) { + mNewUnlock = parcel; } public ChangeUnlockParcel getChangeUnlockParcel() { + if(mNewUnlock != null) { + mNewUnlock.mMasterKeyId = mMasterKeyId; + mNewUnlock.mFingerprint = mFingerprint; + } return mNewUnlock; } 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 97fdd49d4..8f486f437 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; @@ -288,7 +289,7 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase - saveKeyringParcel.setNewUnlock(new Passphrase()); + saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase())); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -298,7 +299,7 @@ public class CreateKeyFinalFragment extends Fragment { 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); if(createKeyActivity.mPassphrase != null) { - saveKeyringParcel.setNewUnlock(createKeyActivity.mPassphrase); + saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase)); } } String userId = KeyRing.createUserId( 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 8d5edb3e3..80fea7b23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -50,6 +50,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -338,8 +339,8 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment Date: Thu, 5 May 2016 07:40:08 +0800 Subject: Slight amendments for clarity --- .../org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java | 4 +++- .../main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 8f486f437..eada0b43c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -300,7 +300,9 @@ public class CreateKeyFinalFragment extends Fragment { if(createKeyActivity.mPassphrase != null) { saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase)); - } + } else { + saveKeyringParcel.setNewUnlock(null); + } } String userId = KeyRing.createUserId( new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) 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 66df4e168..1a1e07dec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -466,6 +466,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); + // use new passphrase! mChangeUnlockParcel = new ChangeUnlockParcel( mMasterKeyId, mFingerprint, -- cgit v1.2.3 From 4278aa1bf1614d8b41e240a4f99328f85202041b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 5 May 2016 14:29:56 +0300 Subject: Revert "Added warning dialog for importing keys" This reverts commit 9d35dcb0e924a7f8afcfcaa86752efb40799a456. Conflicts: OpenKeychain/src/main/res/values/strings.xml --- .../keychain/ui/ImportKeysActivity.java | 27 ---------------------- 1 file changed, 27 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 6b3e12761..7d2d30c35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -39,7 +38,6 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -344,31 +342,6 @@ public class ImportKeysActivity extends BaseActivity return; } - showWarningDialog(); - } - - private void showWarningDialog() { - CustomAlertDialogBuilder warningDialog = new CustomAlertDialogBuilder(this); - warningDialog.setTitle(R.string.import_warning_title). - setMessage(R.string.import_warning).setCancelable(true); - warningDialog.setPositiveButton(R.string.btn_import, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - executeImport(); - } - }); - warningDialog.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - } - }); - warningDialog.show(); - } - - private void executeImport() { - FragmentManager fragMan = getSupportFragmentManager(); - ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); - mOperationHelper = new CryptoOperationHelper<>( 1, this, this, R.string.progress_importing ); -- cgit v1.2.3 From eeb6fa5755c77f2e82b7b23a3cd98a17e09b6392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 5 May 2016 14:35:08 +0300 Subject: Fix formatting --- .../org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java | 6 +++--- .../java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') 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 eada0b43c..227d6fce4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -298,11 +298,11 @@ public class CreateKeyFinalFragment extends Fragment { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); - if(createKeyActivity.mPassphrase != null) { + if (createKeyActivity.mPassphrase != null) { saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase)); } else { - saveKeyringParcel.setNewUnlock(null); - } + saveKeyringParcel.setNewUnlock(null); + } } String userId = KeyRing.createUserId( new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) 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 1a1e07dec..ca4a33980 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -466,7 +466,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - // use new passphrase! + // use new passphrase! mChangeUnlockParcel = new ChangeUnlockParcel( mMasterKeyId, mFingerprint, -- cgit v1.2.3 From 5e9de4447c95cd9ed332ec012b4d73ad7a8ef8b2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 6 May 2016 12:54:35 +0200 Subject: external-provider: only allow permission check for caller package names --- .../keychain/remote/ApiPermissionHelper.java | 51 ++++++++++------------ .../keychain/remote/KeychainExternalProvider.java | 32 +++++++++++--- 2 files changed, 47 insertions(+), 36 deletions(-) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index 3af8e70dd..47ecdb21f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -18,6 +18,10 @@ package org.sufficientlysecure.keychain.remote; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Context; @@ -37,11 +41,6 @@ import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; - /** * Abstract service class for remote APIs that handle app registration and user input. @@ -234,35 +233,29 @@ public class ApiPermissionHelper { private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException { Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); - ArrayList allowedPkgs = mApiDao.getRegisteredApiApps(); - Log.d(Constants.TAG, "allowed: " + allowedPkgs); + byte[] storedPackageCert = mApiDao.getApiAppCertificate(packageName); - // check if package is allowed to use our service - if (allowedPkgs.contains(packageName)) { - Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); + boolean isKnownPackage = storedPackageCert != null; + if (!isKnownPackage) { + Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName); + return false; + } + Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); - // check package signature - byte[] currentCert; - try { - currentCert = getPackageCertificate(packageName); - } catch (NameNotFoundException e) { - throw new WrongPackageCertificateException(e.getMessage()); - } + byte[] currentPackageCert; + try { + currentPackageCert = getPackageCertificate(packageName); + } catch (NameNotFoundException e) { + throw new WrongPackageCertificateException(e.getMessage()); + } - byte[] storedCert = mApiDao.getApiAppCertificate(packageName); - if (Arrays.equals(currentCert, storedCert)) { - Log.d(Constants.TAG, - "Package certificate is correct! (equals certificate from database)"); - return true; - } else { - throw new WrongPackageCertificateException( - "PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " + - "equals certificate from database)"); - } + boolean packageCertMatchesStored = Arrays.equals(currentPackageCert, storedPackageCert); + if (packageCertMatchesStored) { + Log.d(Constants.TAG,"Package certificate matches expected."); + return true; } - Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName); - return false; + throw new WrongPackageCertificateException("PACKAGE NOT ALLOWED DUE TO CERTIFICATE MISMATCH!"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 7eb5d558f..455857f00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -24,12 +24,14 @@ import java.util.HashMap; import android.content.ContentProvider; import android.content.ContentValues; +import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; +import android.os.Binder; import android.support.annotation.NonNull; import android.text.TextUtils; @@ -206,16 +208,13 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC break; } - case API_APPS: { - qb.setTables(Tables.API_APPS); - - break; - } - case API_APPS_BY_PACKAGE_NAME: { + String requestedPackageName = uri.getLastPathSegment(); + checkIfPackageBelongsToCaller(getContext(), requestedPackageName); + qb.setTables(Tables.API_APPS); qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); + qb.appendWhereEscapeString(requestedPackageName); break; } @@ -248,6 +247,25 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC return cursor; } + private void checkIfPackageBelongsToCaller(Context context, String requestedPackageName) { + int callerUid = Binder.getCallingUid(); + String[] callerPackageNames = context.getPackageManager().getPackagesForUid(callerUid); + if (callerPackageNames == null) { + throw new IllegalStateException("Failed to retrieve caller package name, this is an error!"); + } + + boolean packageBelongsToCaller = false; + for (String p : callerPackageNames) { + if (p.equals(requestedPackageName)) { + packageBelongsToCaller = true; + break; + } + } + if (!packageBelongsToCaller) { + throw new SecurityException("ExternalProvider may only check status of caller package!"); + } + } + @Override public Uri insert(@NonNull Uri uri, ContentValues values) { throw new UnsupportedOperationException(); -- cgit v1.2.3 From 2f33fe31185f1c12c4688f49c7374a4164fc7d58 Mon Sep 17 00:00:00 2001 From: Alex Fong Date: Sat, 7 May 2016 08:44:59 +0800 Subject: A simple fix to clear the list of detected keys when parsing key file gives an error. --- .../org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java | 1 + 1 file changed, 1 insertion(+) (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 0201318e8..df24e9877 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -141,6 +141,7 @@ public class ImportKeysListLoader OperationResult.OperationLog log = new OperationResult.OperationLog(); log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0); GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log); + mData.clear(); mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); } } -- cgit v1.2.3