remote service: package signature verification, use string for service instead of getClass.getName

This commit is contained in:
Dominik Schürmann 2013-12-30 19:16:21 +01:00
parent 3235201cf3
commit 7c3a53d149
15 changed files with 359 additions and 190 deletions

View File

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" > android:orientation="vertical" >
<RelativeLayout <RelativeLayout
@ -33,7 +37,7 @@
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" > android:orientation="horizontal" >
@ -45,7 +49,7 @@
android:text="@string/api_settings_select_key" /> android:text="@string/api_settings_select_key" />
<LinearLayout <LinearLayout
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="16dp" > android:paddingLeft="16dp" >
@ -80,10 +84,10 @@
<LinearLayout <LinearLayout
android:id="@+id/api_app_settings_advanced" android:id="@+id/api_app_settings_advanced"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:visibility="invisible" > android:visibility="gone" >
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -117,6 +121,33 @@
android:id="@+id/api_app_settings_compression" android:id="@+id/api_app_settings_compression"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/api_settings_package_name"
android:textAppearance="?android:attr/textAppearanceMedium" />
<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/api_settings_package_signature"
android:textAppearance="?android:attr/textAppearanceMedium" />
<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>
</LinearLayout> </ScrollView>

View File

@ -329,6 +329,8 @@
<string name="api_settings_save">Save</string> <string name="api_settings_save">Save</string>
<string name="api_settings_cancel">Cancel</string> <string name="api_settings_cancel">Cancel</string>
<string name="api_settings_revoke">Revoke access</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_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_allow">Allow access</string>
<string name="api_register_disallow">Disallow access</string> <string name="api_register_disallow">Disallow access</string>

View File

@ -50,7 +50,6 @@ interface IOpenPgpService {
*/ */
oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback); oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/** /**
* Encrypt * Encrypt
* *

View File

@ -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";
}

View File

@ -40,7 +40,7 @@ public class OpenPgpHelper {
} }
public boolean isAvailable() { 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); List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) { if (!resInfo.isEmpty()) {
return true; return true;

View File

@ -39,22 +39,19 @@ public class OpenPgpListPreference extends DialogPreference {
ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>(); ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
private String mSelectedPackage; private String mSelectedPackage;
public static final int REQUIRED_API_VERSION = 1;
public OpenPgpListPreference(Context context, AttributeSet attrs) { public OpenPgpListPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
List<ResolveInfo> resInfo = List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(
context.getPackageManager().queryIntentServices( new Intent(OpenPgpConstants.SERVICE_INTENT), PackageManager.GET_META_DATA);
new Intent(IOpenPgpService.class.getName()), PackageManager.GET_META_DATA);
if (!resInfo.isEmpty()) { if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) { for (ResolveInfo resolveInfo : resInfo) {
if (resolveInfo.serviceInfo == null) if (resolveInfo.serviceInfo == null)
continue; continue;
String packageName = resolveInfo.serviceInfo.packageName; String packageName = resolveInfo.serviceInfo.packageName;
String simpleName = String.valueOf(resolveInfo.serviceInfo String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(context
.loadLabel(context.getPackageManager())); .getPackageManager()));
Drawable icon = resolveInfo.serviceInfo.loadIcon(context.getPackageManager()); Drawable icon = resolveInfo.serviceInfo.loadIcon(context.getPackageManager());
// get api version // get api version
@ -95,8 +92,8 @@ public class OpenPgpListPreference extends DialogPreference {
TextView tv = (TextView) v.findViewById(android.R.id.text1); TextView tv = (TextView) v.findViewById(android.R.id.text1);
// Put the image on the TextView // Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, null,
null, null, null); null, null);
// Add margin between image and text (support various screen // Add margin between image and text (support various screen
// densities) // densities)
@ -104,13 +101,12 @@ public class OpenPgpListPreference extends DialogPreference {
tv.setCompoundDrawablePadding(dp5); tv.setCompoundDrawablePadding(dp5);
// disable if it has the wrong api_version // 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); tv.setEnabled(true);
} else { } else {
tv.setEnabled(false); tv.setEnabled(false);
tv.setText(tv.getText() + " (API v" tv.setText(tv.getText() + " (API v" + mProviderList.get(position).apiVersion
+ mProviderList.get(position).apiVersion + ", needs v" + ", needs v" + OpenPgpConstants.REQUIRED_API_VERSION + ")");
+ REQUIRED_API_VERSION + ")");
} }
return v; return v;
@ -125,8 +121,8 @@ public class OpenPgpListPreference extends DialogPreference {
mSelectedPackage = mProviderList.get(which).packageName; mSelectedPackage = mProviderList.get(which).packageName;
/* /*
* Clicking on an item simulates the positive button * Clicking on an item simulates the positive button click, and dismisses
* click, and dismisses the dialog. * the dialog.
*/ */
OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE); OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss(); dialog.dismiss();
@ -134,9 +130,8 @@ public class OpenPgpListPreference extends DialogPreference {
}); });
/* /*
* The typical interaction for list-based dialogs is to have * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
* click-on-an-item dismiss the dialog instead of the user having to * dialog instead of the user having to press 'Ok'.
* press 'Ok'.
*/ */
builder.setPositiveButton(null, null); builder.setPositiveButton(null, null);
} }

View File

@ -29,14 +29,12 @@ public class OpenPgpServiceConnection {
private Context mApplicationContext; private Context mApplicationContext;
private IOpenPgpService mService; private IOpenPgpService mService;
private boolean bound; private boolean mBound;
private String cryptoProviderPackageName; private String mCryptoProviderPackageName;
private static final String TAG = "OpenPgpServiceConnection";
public OpenPgpServiceConnection(Context context, String cryptoProviderPackageName) { public OpenPgpServiceConnection(Context context, String cryptoProviderPackageName) {
mApplicationContext = context.getApplicationContext(); this.mApplicationContext = context.getApplicationContext();
this.cryptoProviderPackageName = cryptoProviderPackageName; this.mCryptoProviderPackageName = cryptoProviderPackageName;
} }
public IOpenPgpService getService() { public IOpenPgpService getService() {
@ -44,20 +42,20 @@ public class OpenPgpServiceConnection {
} }
public boolean isBound() { public boolean isBound() {
return bound; return mBound;
} }
private ServiceConnection mCryptoServiceConnection = new ServiceConnection() { private ServiceConnection mCryptoServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
mService = IOpenPgpService.Stub.asInterface(service); mService = IOpenPgpService.Stub.asInterface(service);
Log.d(TAG, "connected to service"); Log.d(OpenPgpConstants.TAG, "connected to service");
bound = true; mBound = true;
} }
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
mService = null; mService = null;
Log.d(TAG, "disconnected from service"); Log.d(OpenPgpConstants.TAG, "disconnected from service");
bound = false; mBound = false;
} }
}; };
@ -67,23 +65,23 @@ public class OpenPgpServiceConnection {
* @return * @return
*/ */
public boolean bindToService() { public boolean bindToService() {
if (mService == null && !bound) { // if not already connected if (mService == null && !mBound) { // if not already connected
try { try {
Log.d(TAG, "not bound yet"); Log.d(OpenPgpConstants.TAG, "not bound yet");
Intent serviceIntent = new Intent(); Intent serviceIntent = new Intent();
serviceIntent.setAction(IOpenPgpService.class.getName()); serviceIntent.setAction(IOpenPgpService.class.getName());
serviceIntent.setPackage(cryptoProviderPackageName); serviceIntent.setPackage(mCryptoProviderPackageName);
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.d(TAG, "Exception", e); Log.d(OpenPgpConstants.TAG, "Exception on binding", e);
return false; return false;
} }
} else { // already connected } else {
Log.d(TAG, "already bound... "); Log.d(OpenPgpConstants.TAG, "already bound");
return true; return true;
} }
} }

View File

@ -55,6 +55,7 @@ public class KeychainContract {
interface ApiAppsColumns { interface ApiAppsColumns {
String PACKAGE_NAME = "package_name"; String PACKAGE_NAME = "package_name";
String PACKAGE_SIGNATURE = "package_signature";
String KEY_ID = "key_id"; // not a database id String KEY_ID = "key_id"; // not a database id
String ENCRYPTION_ALGORITHM = "encryption_algorithm"; String ENCRYPTION_ALGORITHM = "encryption_algorithm";
String HASH_ALORITHM = "hash_algorithm"; String HASH_ALORITHM = "hash_algorithm";

View File

@ -31,7 +31,7 @@ import android.provider.BaseColumns;
public class KeychainDatabase extends SQLiteOpenHelper { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "apg.db"; 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 { public interface Tables {
String KEY_RINGS = "key_rings"; 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 private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, " + ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.PACKAGE_SIGNATURE
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM + " BLOB, " + ApiAppsColumns.KEY_ID + " INT64, " + ApiAppsColumns.ENCRYPTION_ALGORITHM
+ " INTEGER, " + ApiAppsColumns.COMPRESSION + " INTEGER)"; + " INTEGER, " + ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
KeychainDatabase(Context context) { KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -110,6 +111,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
break; break;
case 4: case 4:
db.execSQL(CREATE_API_APPS); 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: default:
break; break;

View File

@ -742,6 +742,7 @@ public class ProviderHelper {
private static ContentValues contentValueForApiApps(AppSettings appSettings) { private static ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
values.put(ApiApps.KEY_ID, appSettings.getKeyId()); values.put(ApiApps.KEY_ID, appSettings.getKeyId());
values.put(ApiApps.COMPRESSION, appSettings.getCompression()); values.put(ApiApps.COMPRESSION, appSettings.getCompression());
values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm()); values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
@ -770,6 +771,8 @@ public class ProviderHelper {
settings = new AppSettings(); settings = new AppSettings();
settings.setPackageName(cur.getString(cur settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); .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.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
settings.setCompression(cur.getInt(cur settings.setCompression(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION))); .getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION)));
@ -781,4 +784,26 @@ public class ProviderHelper {
return settings; 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;
}
} }

View File

@ -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);
}
}

View File

@ -23,6 +23,7 @@ import org.sufficientlysecure.keychain.Id;
public class AppSettings { public class AppSettings {
private String packageName; private String packageName;
private byte[] packageSignature;
private long keyId = Id.key.none; private long keyId = Id.key.none;
private int encryptionAlgorithm; private int encryptionAlgorithm;
private int hashAlgorithm; private int hashAlgorithm;
@ -32,9 +33,10 @@ public class AppSettings {
} }
public AppSettings(String packageName) { public AppSettings(String packageName, byte[] packageSignature) {
super(); super();
this.packageName = packageName; this.packageName = packageName;
this.packageSignature = packageSignature;
// defaults: // defaults:
this.encryptionAlgorithm = PGPEncryptedData.AES_256; this.encryptionAlgorithm = PGPEncryptedData.AES_256;
this.hashAlgorithm = HashAlgorithmTags.SHA512; this.hashAlgorithm = HashAlgorithmTags.SHA512;
@ -49,6 +51,14 @@ public class AppSettings {
this.packageName = packageName; this.packageName = packageName;
} }
public byte[] getPackageSignature() {
return packageSignature;
}
public void setPackageSignature(byte[] packageSignature) {
this.packageSignature = packageSignature;
}
public long getKeyId() { public long getKeyId() {
return keyId; return keyId;
} }

View File

@ -17,8 +17,12 @@
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.service.remote;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@ -67,6 +71,8 @@ public class AppSettingsFragment extends Fragment {
private Spinner mEncryptionAlgorithm; private Spinner mEncryptionAlgorithm;
private Spinner mHashAlgorithm; private Spinner mHashAlgorithm;
private Spinner mCompression; private Spinner mCompression;
private TextView mPackageName;
private TextView mPackageSignature;
KeyValueSpinnerAdapter encryptionAdapter; KeyValueSpinnerAdapter encryptionAdapter;
KeyValueSpinnerAdapter hashAdapter; KeyValueSpinnerAdapter hashAdapter;
@ -79,6 +85,19 @@ public class AppSettingsFragment extends Fragment {
public void setAppSettings(AppSettings appSettings) { public void setAppSettings(AppSettings appSettings) {
this.appSettings = appSettings; this.appSettings = appSettings;
setPackage(appSettings.getPackageName()); 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()); updateSelectedKeyView(appSettings.getKeyId());
mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings
.getEncryptionAlgorithm())); .getEncryptionAlgorithm()));
@ -110,6 +129,8 @@ public class AppSettingsFragment extends Fragment {
.findViewById(R.id.api_app_settings_encryption_algorithm); .findViewById(R.id.api_app_settings_encryption_algorithm);
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm); mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression); 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()); AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
@ -182,7 +203,7 @@ public class AppSettingsFragment extends Fragment {
public void onClick(View v) { public void onClick(View v) {
if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) { if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
mAdvancedSettingsContainer.startAnimation(invisibleAnimation); mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
mAdvancedSettingsContainer.setVisibility(View.INVISIBLE); mAdvancedSettingsContainer.setVisibility(View.GONE);
mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced); mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced);
} else { } else {
mAdvancedSettingsContainer.startAnimation(visibleAnimation); mAdvancedSettingsContainer.startAnimation(visibleAnimation);

View File

@ -18,18 +18,24 @@
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.service.remote;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; 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.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
@ -38,7 +44,7 @@ import android.os.Message;
import android.os.Messenger; 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 { public abstract class RemoteService extends Service {
Context mContext; Context mContext;
@ -98,22 +104,33 @@ public abstract class RemoteService extends Service {
* @param r * @param r
*/ */
protected void checkAndEnqueue(Runnable r) { protected void checkAndEnqueue(Runnable r) {
try {
if (isCallerAllowed(false)) { if (isCallerAllowed(false)) {
mThreadPool.execute(r); mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…"); Log.d(Constants.TAG, "Enqueued runnable…");
} else { } else {
String[] callingPackages = getPackageManager() String[] callingPackages = getPackageManager().getPackagesForUid(
.getPackagesForUid(Binder.getCallingUid()); 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 // TODO: currently simply uses first entry
extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); 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(); RegisterActivityCallback callback = new RegisterActivityCallback();
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback, extras); pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback,
extras);
if (callback.isAllowed()) { if (callback.isAllowed()) {
mThreadPool.execute(r); mThreadPool.execute(r);
@ -122,6 +139,20 @@ public abstract class RemoteService extends Service {
Log.d(Constants.TAG, "User disallowed app!"); 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;
} }
/** /**
@ -200,7 +231,8 @@ public abstract class RemoteService extends Service {
packageName = msg.getData().getString(PACKAGE_NAME); packageName = msg.getData().getString(PACKAGE_NAME);
// resume threads // resume threads
if (isPackageAllowed(packageName, false)) { try {
if (isPackageAllowed(packageName)) {
synchronized (userInputLock) { synchronized (userInputLock) {
userInputLock.notifyAll(); userInputLock.notifyAll();
} }
@ -210,6 +242,10 @@ public abstract class RemoteService extends Service {
Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); Log.e(Constants.TAG, "Should not happen! Emergency shutdown!");
mThreadPool.shutdownNow(); mThreadPool.shutdownNow();
} }
} catch (WrongPackageSignatureException e) {
// TODO: Inform user about wrong signature!
Log.e(Constants.TAG, "RemoteService", e);
}
} else { } else {
allowed = false; allowed = false;
@ -230,15 +266,28 @@ public abstract class RemoteService extends Service {
* @param allowOnlySelf * @param allowOnlySelf
* allow only Keychain app itself * allow only Keychain app itself
* @return true if process is allowed to use this service * @return true if process is allowed to use this service
* @throws WrongPackageSignatureException
*/ */
private boolean isCallerAllowed(boolean allowOnlySelf) { private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); 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? // is calling package allowed to use this service?
for (int i = 0; i < callingPackages.length; i++) { for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i]; String currentPkg = callingPackages[i];
if (isPackageAllowed(currentPkg, allowOnlySelf)) { if (isPackageAllowed(currentPkg)) {
return true; 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 packageName
* @param allowOnlySelf
* allow only Keychain app itself
* @return * @return
* @throws WrongPackageSignatureException
*/ */
private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) { private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
Log.d(Constants.TAG, "packageName: " + packageName); Log.d(Constants.TAG, "packageName: " + packageName);
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext); ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
Log.d(Constants.TAG, "allowed: " + allowedPkgs); Log.d(Constants.TAG, "allowed: " + allowedPkgs);
// check if package is allowed to use our service // 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); Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
return true; // check package signature
} else if (Constants.PACKAGE_NAME.equals(packageName)) { byte[] currentSig;
Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!"); try {
currentSig = getPackageSignature(packageName);
} catch (NameNotFoundException e) {
throw new WrongPackageSignatureException(e.getMessage());
}
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; return true;
} else {
throw new WrongPackageSignatureException(
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not equals signature from database)");
}
} }
return false; return false;

View File

@ -55,6 +55,7 @@ public class RemoteServiceActivity extends SherlockFragmentActivity {
public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
// register action // register action
public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_PACKAGE_NAME = "package_name";
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
// select pub keys action // select pub keys action
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
public static final String EXTRA_MISSING_USER_IDS = "missing_user_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)) { if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow,
@ -166,7 +168,7 @@ public class RemoteServiceActivity extends SherlockFragmentActivity {
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment); R.id.api_app_settings_fragment);
AppSettings settings = new AppSettings(packageName); AppSettings settings = new AppSettings(packageName, packageSignature);
mSettingsFragment.setAppSettings(settings); mSettingsFragment.setAppSettings(settings);
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) { } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);