mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-12-02 13:32:19 -05:00
remote service: package signature verification, use string for service instead of getClass.getName
This commit is contained in:
parent
3235201cf3
commit
7c3a53d149
@ -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>
|
@ -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>
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user