diff options
15 files changed, 349 insertions, 180 deletions
diff --git a/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml index 81b48be72..a40444e0f 100644 --- a/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml +++ b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml @@ -1,122 +1,153 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical" > +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" > - <RelativeLayout + <LinearLayout android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:gravity="center_horizontal" - android:orientation="horizontal" - android:paddingBottom="3dip" > - - <ImageView - android:id="@+id/api_app_settings_app_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_alignParentBottom="true" - android:layout_alignParentTop="true" - android:layout_marginRight="6dp" - android:src="@drawable/icon" /> - - <TextView - android:id="@+id/api_app_settings_app_name" - android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:gravity="center_horizontal" + android:orientation="horizontal" + android:paddingBottom="3dip" > + + <ImageView + android:id="@+id/api_app_settings_app_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentBottom="true" + android:layout_alignParentTop="true" + android:layout_marginRight="6dp" + android:src="@drawable/icon" /> + + <TextView + android:id="@+id/api_app_settings_app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/api_app_settings_app_icon" + android:gravity="center_vertical" + android:orientation="vertical" + android:text="Name (set in-code)" + android:textAppearance="?android:attr/textAppearanceMedium" /> + </RelativeLayout> + + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toRightOf="@+id/api_app_settings_app_icon" - android:gravity="center_vertical" - android:orientation="vertical" - android:text="Name (set in-code)" - android:textAppearance="?android:attr/textAppearanceMedium" /> - </RelativeLayout> + android:orientation="horizontal" > - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" > + <Button + android:id="@+id/api_app_settings_select_key_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/api_settings_select_key" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="16dp" > + + <TextView + android:id="@+id/api_app_settings_user_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:ellipsize="end" + android:singleLine="true" + android:text="@string/api_settings_no_key" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/api_app_settings_user_id_rest" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:ellipsize="end" + android:singleLine="true" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </LinearLayout> + </LinearLayout> <Button - android:id="@+id/api_app_settings_select_key_button" - android:layout_width="wrap_content" + android:id="@+id/api_app_settings_advanced_button" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/api_settings_select_key" /> + android:text="@string/api_settings_show_advanced" /> <LinearLayout - android:layout_width="fill_parent" + android:id="@+id/api_app_settings_advanced" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingLeft="16dp" > + android:visibility="gone" > <TextView - android:id="@+id/api_app_settings_user_id" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="right" - android:ellipsize="end" - android:singleLine="true" - android:text="@string/api_settings_no_key" + android:text="@string/label_encryption_algorithm" android:textAppearance="?android:attr/textAppearanceMedium" /> + <Spinner + android:id="@+id/api_app_settings_encryption_algorithm" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + <TextView - android:id="@+id/api_app_settings_user_id_rest" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="right" - android:ellipsize="end" - android:singleLine="true" - android:text="" - android:textAppearance="?android:attr/textAppearanceSmall" /> - </LinearLayout> - </LinearLayout> - - <Button - android:id="@+id/api_app_settings_advanced_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/api_settings_show_advanced" /> + android:text="@string/label_hash_algorithm" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <LinearLayout - android:id="@+id/api_app_settings_advanced" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="invisible" > + <Spinner + android:id="@+id/api_app_settings_hash_algorithm" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/label_encryption_algorithm" - android:textAppearance="?android:attr/textAppearanceMedium" /> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/label_message_compression" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <Spinner - android:id="@+id/api_app_settings_encryption_algorithm" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + <Spinner + android:id="@+id/api_app_settings_compression" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/label_hash_algorithm" - android:textAppearance="?android:attr/textAppearanceMedium" /> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/api_settings_package_name" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <Spinner - android:id="@+id/api_app_settings_hash_algorithm" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + <TextView + android:id="@+id/api_app_settings_package_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="com.example" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/label_message_compression" - android:textAppearance="?android:attr/textAppearanceMedium" /> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/api_settings_package_signature" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <Spinner - android:id="@+id/api_app_settings_compression" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + <TextView + android:id="@+id/api_app_settings_package_signature" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Base64 encoded signature" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </LinearLayout> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</ScrollView>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/strings.xml b/OpenPGP-Keychain/res/values/strings.xml index 63f267277..da82e7553 100644 --- a/OpenPGP-Keychain/res/values/strings.xml +++ b/OpenPGP-Keychain/res/values/strings.xml @@ -313,7 +313,7 @@ <string name="import_qr_code_missing">Missing QR Codes: %1$s</string> <string name="import_qr_code_wrong">QR Code malformed! Please try again!</string> <string name="import_qr_code_finished">QR Code scanning finished!</string> - + <!-- Intent labels --> <string name="intent_decrypt_file">OpenPGP: Decrypt File</string> <string name="intent_import_key">OpenPGP: Import Key</string> @@ -329,6 +329,8 @@ <string name="api_settings_save">Save</string> <string name="api_settings_cancel">Cancel</string> <string name="api_settings_revoke">Revoke access</string> + <string name="api_settings_package_name">Package Name</string> + <string name="api_settings_package_signature">SHA-256 of Package Signature</string> <string name="api_register_text">The following application requests access to OpenPGP Keychain\'s API.\n\nAllow permanent access?</string> <string name="api_register_allow">Allow access</string> <string name="api_register_disallow">Disallow access</string> diff --git a/OpenPGP-Keychain/src/org/openintents/openpgp/IOpenPgpService.aidl b/OpenPGP-Keychain/src/org/openintents/openpgp/IOpenPgpService.aidl index 7cbf96b56..8f9e8a0fd 100644 --- a/OpenPGP-Keychain/src/org/openintents/openpgp/IOpenPgpService.aidl +++ b/OpenPGP-Keychain/src/org/openintents/openpgp/IOpenPgpService.aidl @@ -50,7 +50,6 @@ interface IOpenPgpService { */ oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback); - /** * Encrypt * diff --git a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpConstants.java b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpConstants.java new file mode 100644 index 000000000..b1ca1bfe6 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpConstants.java @@ -0,0 +1,10 @@ +package org.openintents.openpgp; + +public class OpenPgpConstants { + + public static final String TAG = "OpenPgp API"; + + public static final int REQUIRED_API_VERSION = 1; + public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService"; + +} diff --git a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpHelper.java b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpHelper.java index 56c4a4dca..7305c47ce 100644 --- a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpHelper.java +++ b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpHelper.java @@ -40,7 +40,7 @@ public class OpenPgpHelper { } public boolean isAvailable() { - Intent intent = new Intent(IOpenPgpService.class.getName()); + Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT); List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0); if (!resInfo.isEmpty()) { return true; diff --git a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpListPreference.java b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpListPreference.java index 551401b18..4fef3f32c 100644 --- a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpListPreference.java +++ b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpListPreference.java @@ -39,22 +39,19 @@ public class OpenPgpListPreference extends DialogPreference { ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>(); private String mSelectedPackage; - public static final int REQUIRED_API_VERSION = 1; - public OpenPgpListPreference(Context context, AttributeSet attrs) { super(context, attrs); - List<ResolveInfo> resInfo = - context.getPackageManager().queryIntentServices( - new Intent(IOpenPgpService.class.getName()), PackageManager.GET_META_DATA); + List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices( + new Intent(OpenPgpConstants.SERVICE_INTENT), PackageManager.GET_META_DATA); if (!resInfo.isEmpty()) { for (ResolveInfo resolveInfo : resInfo) { if (resolveInfo.serviceInfo == null) continue; String packageName = resolveInfo.serviceInfo.packageName; - String simpleName = String.valueOf(resolveInfo.serviceInfo - .loadLabel(context.getPackageManager())); + String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(context + .getPackageManager())); Drawable icon = resolveInfo.serviceInfo.loadIcon(context.getPackageManager()); // get api version @@ -95,8 +92,8 @@ public class OpenPgpListPreference extends DialogPreference { TextView tv = (TextView) v.findViewById(android.R.id.text1); // Put the image on the TextView - tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, - null, null, null); + tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, null, + null, null); // Add margin between image and text (support various screen // densities) @@ -104,13 +101,12 @@ public class OpenPgpListPreference extends DialogPreference { tv.setCompoundDrawablePadding(dp5); // disable if it has the wrong api_version - if (mProviderList.get(position).apiVersion == REQUIRED_API_VERSION) { + if (mProviderList.get(position).apiVersion == OpenPgpConstants.REQUIRED_API_VERSION) { tv.setEnabled(true); } else { tv.setEnabled(false); - tv.setText(tv.getText() + " (API v" - + mProviderList.get(position).apiVersion + ", needs v" - + REQUIRED_API_VERSION + ")"); + tv.setText(tv.getText() + " (API v" + mProviderList.get(position).apiVersion + + ", needs v" + OpenPgpConstants.REQUIRED_API_VERSION + ")"); } return v; @@ -125,8 +121,8 @@ public class OpenPgpListPreference extends DialogPreference { mSelectedPackage = mProviderList.get(which).packageName; /* - * Clicking on an item simulates the positive button - * click, and dismisses the dialog. + * Clicking on an item simulates the positive button click, and dismisses + * the dialog. */ OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE); dialog.dismiss(); @@ -134,9 +130,8 @@ public class OpenPgpListPreference extends DialogPreference { }); /* - * The typical interaction for list-based dialogs is to have - * click-on-an-item dismiss the dialog instead of the user having to - * press 'Ok'. + * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the + * dialog instead of the user having to press 'Ok'. */ builder.setPositiveButton(null, null); } diff --git a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpServiceConnection.java b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpServiceConnection.java index 56a922d73..f7ba06aaf 100644 --- a/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpServiceConnection.java +++ b/OpenPGP-Keychain/src/org/openintents/openpgp/OpenPgpServiceConnection.java @@ -29,14 +29,12 @@ public class OpenPgpServiceConnection { private Context mApplicationContext; private IOpenPgpService mService; - private boolean bound; - private String cryptoProviderPackageName; - - private static final String TAG = "OpenPgpServiceConnection"; + private boolean mBound; + private String mCryptoProviderPackageName; public OpenPgpServiceConnection(Context context, String cryptoProviderPackageName) { - mApplicationContext = context.getApplicationContext(); - this.cryptoProviderPackageName = cryptoProviderPackageName; + this.mApplicationContext = context.getApplicationContext(); + this.mCryptoProviderPackageName = cryptoProviderPackageName; } public IOpenPgpService getService() { @@ -44,20 +42,20 @@ public class OpenPgpServiceConnection { } public boolean isBound() { - return bound; + return mBound; } private ServiceConnection mCryptoServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { mService = IOpenPgpService.Stub.asInterface(service); - Log.d(TAG, "connected to service"); - bound = true; + Log.d(OpenPgpConstants.TAG, "connected to service"); + mBound = true; } public void onServiceDisconnected(ComponentName name) { mService = null; - Log.d(TAG, "disconnected from service"); - bound = false; + Log.d(OpenPgpConstants.TAG, "disconnected from service"); + mBound = false; } }; @@ -67,23 +65,23 @@ public class OpenPgpServiceConnection { * @return */ public boolean bindToService() { - if (mService == null && !bound) { // if not already connected + if (mService == null && !mBound) { // if not already connected try { - Log.d(TAG, "not bound yet"); + Log.d(OpenPgpConstants.TAG, "not bound yet"); Intent serviceIntent = new Intent(); serviceIntent.setAction(IOpenPgpService.class.getName()); - serviceIntent.setPackage(cryptoProviderPackageName); + serviceIntent.setPackage(mCryptoProviderPackageName); mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, Context.BIND_AUTO_CREATE); return true; } catch (Exception e) { - Log.d(TAG, "Exception", e); + Log.d(OpenPgpConstants.TAG, "Exception on binding", e); return false; } - } else { // already connected - Log.d(TAG, "already bound... "); + } else { + Log.d(OpenPgpConstants.TAG, "already bound"); return true; } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java index ea4ca377c..82bb473f6 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -55,6 +55,7 @@ public class KeychainContract { interface ApiAppsColumns { String PACKAGE_NAME = "package_name"; + String PACKAGE_SIGNATURE = "package_signature"; String KEY_ID = "key_id"; // not a database id String ENCRYPTION_ALGORITHM = "encryption_algorithm"; String HASH_ALORITHM = "hash_algorithm"; diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 0f962967d..60c5c91a8 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -31,7 +31,7 @@ import android.provider.BaseColumns; public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "apg.db"; - private static final int DATABASE_VERSION = 5; + private static final int DATABASE_VERSION = 6; public interface Tables { String KEY_RINGS = "key_rings"; @@ -66,9 +66,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, " - + ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM - + " INTEGER, " + ApiAppsColumns.COMPRESSION + " INTEGER)"; + + ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.PACKAGE_SIGNATURE + + " BLOB, " + ApiAppsColumns.KEY_ID + " INT64, " + ApiAppsColumns.ENCRYPTION_ALGORITHM + + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM + " INTEGER, " + + ApiAppsColumns.COMPRESSION + " INTEGER)"; KeychainDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -110,6 +111,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { break; case 4: db.execSQL(CREATE_API_APPS); + case 5: + // new column: package_signature + db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS); + db.execSQL(CREATE_API_APPS); default: break; diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 7ef61c15b..f12048277 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -742,6 +742,7 @@ public class ProviderHelper { private static ContentValues contentValueForApiApps(AppSettings appSettings) { ContentValues values = new ContentValues(); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); + values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature()); values.put(ApiApps.KEY_ID, appSettings.getKeyId()); values.put(ApiApps.COMPRESSION, appSettings.getCompression()); values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm()); @@ -770,6 +771,8 @@ public class ProviderHelper { settings = new AppSettings(); settings.setPackageName(cur.getString(cur .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); + settings.setPackageSignature(cur.getBlob(cur + .getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID))); settings.setCompression(cur.getInt(cur .getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION))); @@ -781,4 +784,26 @@ public class ProviderHelper { return settings; } + + public static byte[] getApiAppSignature(Context context, String packageName) { + Uri queryUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName); + + String[] projection = new String[] { ApiApps.PACKAGE_SIGNATURE }; + + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(queryUri, projection, null, null, null); + + byte[] signature = null; + if (cursor != null && cursor.moveToFirst()) { + int signatureCol = 0; + + signature = cursor.getBlob(signatureCol); + } + + if (cursor != null) { + cursor.close(); + } + + return signature; + } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java new file mode 100644 index 000000000..cef002265 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java @@ -0,0 +1,10 @@ +package org.sufficientlysecure.keychain.service.exception; + +public class WrongPackageSignatureException extends Exception { + + private static final long serialVersionUID = -8294642703122196028L; + + public WrongPackageSignatureException(String message) { + super(message); + } +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettings.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettings.java index 381a4065c..9da4c8392 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettings.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettings.java @@ -23,6 +23,7 @@ import org.sufficientlysecure.keychain.Id; public class AppSettings { private String packageName; + private byte[] packageSignature; private long keyId = Id.key.none; private int encryptionAlgorithm; private int hashAlgorithm; @@ -32,9 +33,10 @@ public class AppSettings { } - public AppSettings(String packageName) { + public AppSettings(String packageName, byte[] packageSignature) { super(); this.packageName = packageName; + this.packageSignature = packageSignature; // defaults: this.encryptionAlgorithm = PGPEncryptedData.AES_256; this.hashAlgorithm = HashAlgorithmTags.SHA512; @@ -49,6 +51,14 @@ public class AppSettings { this.packageName = packageName; } + public byte[] getPackageSignature() { + return packageSignature; + } + + public void setPackageSignature(byte[] packageSignature) { + this.packageSignature = packageSignature; + } + public long getKeyId() { return keyId; } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java index 942f8eba8..e592f5d57 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java @@ -17,8 +17,12 @@ package org.sufficientlysecure.keychain.service.remote; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; @@ -67,6 +71,8 @@ public class AppSettingsFragment extends Fragment { private Spinner mEncryptionAlgorithm; private Spinner mHashAlgorithm; private Spinner mCompression; + private TextView mPackageName; + private TextView mPackageSignature; KeyValueSpinnerAdapter encryptionAdapter; KeyValueSpinnerAdapter hashAdapter; @@ -79,6 +85,19 @@ public class AppSettingsFragment extends Fragment { public void setAppSettings(AppSettings appSettings) { this.appSettings = appSettings; setPackage(appSettings.getPackageName()); + mPackageName.setText(appSettings.getPackageName()); + + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(appSettings.getPackageSignature()); + byte[] digest = md.digest(); + String signature = new String(Hex.encode(digest)); + + mPackageSignature.setText(signature); + } catch (NoSuchAlgorithmException e) { + Log.e(Constants.TAG, "Should not happen!", e); + } + updateSelectedKeyView(appSettings.getKeyId()); mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings .getEncryptionAlgorithm())); @@ -110,6 +129,8 @@ public class AppSettingsFragment extends Fragment { .findViewById(R.id.api_app_settings_encryption_algorithm); mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm); mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression); + mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name); + mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature); AlgorithmNames algorithmNames = new AlgorithmNames(getActivity()); @@ -182,7 +203,7 @@ public class AppSettingsFragment extends Fragment { public void onClick(View v) { if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) { mAdvancedSettingsContainer.startAnimation(invisibleAnimation); - mAdvancedSettingsContainer.setVisibility(View.INVISIBLE); + mAdvancedSettingsContainer.setVisibility(View.GONE); mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced); } else { mAdvancedSettingsContainer.startAnimation(visibleAnimation); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java index 4e8c4678a..0f28d96f6 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java @@ -18,18 +18,24 @@ package org.sufficientlysecure.keychain.service.remote; import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -38,7 +44,7 @@ import android.os.Message; import android.os.Messenger; /** - * Abstract service for remote APIs that handle app registration and user input. + * Abstract service class for remote APIs that handle app registration and user input. */ public abstract class RemoteService extends Service { Context mContext; @@ -98,32 +104,57 @@ public abstract class RemoteService extends Service { * @param r */ protected void checkAndEnqueue(Runnable r) { - if (isCallerAllowed(false)) { - mThreadPool.execute(r); - - Log.d(Constants.TAG, "Enqueued runnable…"); - } else { - String[] callingPackages = getPackageManager() - .getPackagesForUid(Binder.getCallingUid()); - - Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!"); - Bundle extras = new Bundle(); - // TODO: currently simply uses first entry - extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); - - RegisterActivityCallback callback = new RegisterActivityCallback(); - - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback, extras); - - if (callback.isAllowed()) { + try { + if (isCallerAllowed(false)) { mThreadPool.execute(r); + Log.d(Constants.TAG, "Enqueued runnable…"); } else { - Log.d(Constants.TAG, "User disallowed app!"); + String[] callingPackages = getPackageManager().getPackagesForUid( + Binder.getCallingUid()); + // TODO: currently simply uses first entry + String packageName = callingPackages[0]; + + byte[] packageSignature; + try { + packageSignature = getPackageSignature(packageName); + } catch (NameNotFoundException e) { + Log.e(Constants.TAG, "Should not happen, returning!", e); + return; + } + Log.e(Constants.TAG, + "Not allowed to use service! Starting activity for registration!"); + Bundle extras = new Bundle(); + extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); + extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); + RegisterActivityCallback callback = new RegisterActivityCallback(); + + pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback, + extras); + + if (callback.isAllowed()) { + mThreadPool.execute(r); + Log.d(Constants.TAG, "Enqueued runnable…"); + } else { + Log.d(Constants.TAG, "User disallowed app!"); + } } + } catch (WrongPackageSignatureException e) { + // TODO: Inform user about wrong signature! + Log.e(Constants.TAG, "RemoteService", e); } } + private byte[] getPackageSignature(String packageName) throws NameNotFoundException { + PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, + PackageManager.GET_SIGNATURES); + Signature[] signatures = pkgInfo.signatures; + // TODO: Only first signature?! + byte[] packageSignature = signatures[0].toByteArray(); + + return packageSignature; + } + /** * Locks current thread and pauses execution of runnables and starts activity for user input * @@ -200,15 +231,20 @@ public abstract class RemoteService extends Service { packageName = msg.getData().getString(PACKAGE_NAME); // resume threads - if (isPackageAllowed(packageName, false)) { - synchronized (userInputLock) { - userInputLock.notifyAll(); + try { + if (isPackageAllowed(packageName)) { + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + } else { + // Should not happen! + Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); + mThreadPool.shutdownNow(); } - mThreadPool.resume(); - } else { - // Should not happen! - Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); - mThreadPool.shutdownNow(); + } catch (WrongPackageSignatureException e) { + // TODO: Inform user about wrong signature! + Log.e(Constants.TAG, "RemoteService", e); } } else { allowed = false; @@ -230,15 +266,28 @@ public abstract class RemoteService extends Service { * @param allowOnlySelf * allow only Keychain app itself * @return true if process is allowed to use this service + * @throws WrongPackageSignatureException */ - private boolean isCallerAllowed(boolean allowOnlySelf) { - String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException { + return isUidAllowed(Binder.getCallingUid(), allowOnlySelf); + } + + private boolean isUidAllowed(int uid, boolean allowOnlySelf) + throws WrongPackageSignatureException { + if (android.os.Process.myUid() == uid) { + return true; + } + if (allowOnlySelf) { // barrier + return false; + } + + String[] callingPackages = getPackageManager().getPackagesForUid(uid); // is calling package allowed to use this service? for (int i = 0; i < callingPackages.length; i++) { String currentPkg = callingPackages[i]; - if (isPackageAllowed(currentPkg, allowOnlySelf)) { + if (isPackageAllowed(currentPkg)) { return true; } } @@ -248,28 +297,39 @@ public abstract class RemoteService extends Service { } /** - * Checks if packageName is a registered app for the API. + * Checks if packageName is a registered app for the API. Does not return true for own package! * * @param packageName - * @param allowOnlySelf - * allow only Keychain app itself * @return + * @throws WrongPackageSignatureException */ - private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) { + private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException { Log.d(Constants.TAG, "packageName: " + packageName); - ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext); + ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this); Log.d(Constants.TAG, "allowed: " + allowedPkgs); // check if package is allowed to use our service - if (allowedPkgs.contains(packageName) && (!allowOnlySelf)) { + if (allowedPkgs.contains(packageName)) { Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); - return true; - } else if (Constants.PACKAGE_NAME.equals(packageName)) { - Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!"); + // check package signature + byte[] currentSig; + try { + currentSig = getPackageSignature(packageName); + } catch (NameNotFoundException e) { + throw new WrongPackageSignatureException(e.getMessage()); + } - return true; + byte[] storedSig = ProviderHelper.getApiAppSignature(this, packageName); + if (Arrays.equals(currentSig, storedSig)) { + Log.d(Constants.TAG, + "Package signature is correct! (equals signature from database)"); + return true; + } else { + throw new WrongPackageSignatureException( + "PACKAGE NOT ALLOWED! Signature wrong! (Signature not equals signature from database)"); + } } return false; diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java index 2c4bb4e97..bba8176b1 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java @@ -55,6 +55,7 @@ public class RemoteServiceActivity extends SherlockFragmentActivity { public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; // register action public static final String EXTRA_PACKAGE_NAME = "package_name"; + public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature"; // select pub keys action public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; @@ -110,6 +111,7 @@ public class RemoteServiceActivity extends SherlockFragmentActivity { */ if (ACTION_REGISTER.equals(action)) { final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); // Inflate a "Done"/"Cancel" custom action bar view ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, @@ -166,7 +168,7 @@ public class RemoteServiceActivity extends SherlockFragmentActivity { mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( R.id.api_app_settings_fragment); - AppSettings settings = new AppSettings(packageName); + AppSettings settings = new AppSettings(packageName, packageSignature); mSettingsFragment.setAppSettings(settings); } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); |