mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-12-24 07:58:50 -05:00
Fixes and clarifications to app signature (or better certificate) pinning
This commit is contained in:
parent
6e326fb000
commit
e14a2efcad
@ -73,7 +73,7 @@ public class KeychainContract {
|
|||||||
|
|
||||||
interface ApiAppsColumns {
|
interface ApiAppsColumns {
|
||||||
String PACKAGE_NAME = "package_name";
|
String PACKAGE_NAME = "package_name";
|
||||||
String PACKAGE_SIGNATURE = "package_signature";
|
String PACKAGE_CERTIFICATE = "package_signature";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiAppsAccountsColumns {
|
interface ApiAppsAccountsColumns {
|
||||||
|
@ -148,7 +148,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
"CREATE TABLE IF NOT EXISTS " + Tables.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 NOT NULL UNIQUE, "
|
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
|
||||||
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB"
|
+ ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
private static final String CREATE_API_APPS_ACCOUNTS =
|
private static final String CREATE_API_APPS_ACCOUNTS =
|
||||||
|
@ -45,7 +45,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
|||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||||
@ -1415,7 +1414,7 @@ public class ProviderHelper {
|
|||||||
private ContentValues contentValueForApiApps(AppSettings appSettings) {
|
private 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.PACKAGE_CERTIFICATE, appSettings.getPackageSignature());
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1462,7 +1461,7 @@ public class ProviderHelper {
|
|||||||
settings.setPackageName(cursor.getString(
|
settings.setPackageName(cursor.getString(
|
||||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
||||||
settings.setPackageSignature(cursor.getBlob(
|
settings.setPackageSignature(cursor.getBlob(
|
||||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
|
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
@ -1575,10 +1574,10 @@ public class ProviderHelper {
|
|||||||
return fingerprints;
|
return fingerprints;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getApiAppSignature(String packageName) {
|
public byte[] getApiAppCertificate(String packageName) {
|
||||||
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
||||||
|
|
||||||
String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};
|
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null);
|
Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null);
|
||||||
try {
|
try {
|
||||||
|
@ -37,6 +37,8 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|||||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -45,10 +47,10 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public abstract class RemoteService extends Service {
|
public abstract class RemoteService extends Service {
|
||||||
|
|
||||||
public static class WrongPackageSignatureException extends Exception {
|
public static class WrongPackageCertificateException extends Exception {
|
||||||
private static final long serialVersionUID = -8294642703122196028L;
|
private static final long serialVersionUID = -8294642703122196028L;
|
||||||
|
|
||||||
public WrongPackageSignatureException(String message) {
|
public WrongPackageCertificateException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,9 +76,9 @@ public abstract class RemoteService extends Service {
|
|||||||
String packageName = getCurrentCallingPackage();
|
String packageName = getCurrentCallingPackage();
|
||||||
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
|
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
|
||||||
|
|
||||||
byte[] packageSignature;
|
byte[] packageCertificate;
|
||||||
try {
|
try {
|
||||||
packageSignature = getPackageSignature(packageName);
|
packageCertificate = getPackageCertificate(packageName);
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException e) {
|
||||||
Log.e(Constants.TAG, "Should not happen, returning!", e);
|
Log.e(Constants.TAG, "Should not happen, returning!", e);
|
||||||
// return error
|
// return error
|
||||||
@ -91,7 +93,7 @@ public abstract class RemoteService extends Service {
|
|||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||||
intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
|
intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
@ -105,7 +107,7 @@ public abstract class RemoteService extends Service {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} catch (WrongPackageSignatureException e) {
|
} catch (WrongPackageCertificateException e) {
|
||||||
Log.e(Constants.TAG, "wrong signature!", e);
|
Log.e(Constants.TAG, "wrong signature!", e);
|
||||||
|
|
||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||||
@ -127,14 +129,24 @@ public abstract class RemoteService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getPackageSignature(String packageName) throws NameNotFoundException {
|
private byte[] getPackageCertificate(String packageName) throws NameNotFoundException {
|
||||||
PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
|
PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName,
|
||||||
PackageManager.GET_SIGNATURES);
|
PackageManager.GET_SIGNATURES);
|
||||||
Signature[] signatures = pkgInfo.signatures;
|
// NOTE: Silly Android API naming: Signatures are actually certificates
|
||||||
// TODO: Only first signature?!
|
Signature[] certificates = pkgInfo.signatures;
|
||||||
byte[] packageSignature = signatures[0].toByteArray();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
for (Signature cert : certificates) {
|
||||||
|
try {
|
||||||
|
outputStream.write(cert.toByteArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Should not happen! Writing ByteArrayOutputStream to concat certificates failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return packageSignature;
|
// Even if an apk has several certificates, these certificates should never change
|
||||||
|
// Google Play does not allow the introduction of new certificates into an existing apk
|
||||||
|
// Also see this attack: http://stackoverflow.com/a/10567852
|
||||||
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,9 +156,12 @@ public abstract class RemoteService extends Service {
|
|||||||
* @return package name
|
* @return package name
|
||||||
*/
|
*/
|
||||||
protected String getCurrentCallingPackage() {
|
protected String getCurrentCallingPackage() {
|
||||||
// TODO:
|
|
||||||
// callingPackages contains more than one entry when sharedUserId has been used...
|
|
||||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||||
|
|
||||||
|
// NOTE: No support for sharedUserIds
|
||||||
|
// callingPackages contains more than one entry when sharedUserId has been used
|
||||||
|
// No plans to support sharedUserIds due to many bugs connected to them:
|
||||||
|
// http://java-hamster.blogspot.de/2010/05/androids-shareduserid.html
|
||||||
String currentPkg = callingPackages[0];
|
String currentPkg = callingPackages[0];
|
||||||
Log.d(Constants.TAG, "currentPkg: " + currentPkg);
|
Log.d(Constants.TAG, "currentPkg: " + currentPkg);
|
||||||
|
|
||||||
@ -155,12 +170,12 @@ public abstract class RemoteService extends Service {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* DEPRECATED API
|
* DEPRECATED API
|
||||||
*
|
* <p/>
|
||||||
* Retrieves AccountSettings from database for the application calling this remote service
|
* Retrieves AccountSettings from database for the application calling this remote service
|
||||||
*/
|
*/
|
||||||
protected AccountSettings getAccSettings(String accountName) {
|
protected AccountSettings getAccSettings(String accountName) {
|
||||||
String currentPkg = getCurrentCallingPackage();
|
String currentPkg = getCurrentCallingPackage();
|
||||||
Log.d(Constants.TAG, "getAccSettings accountName: "+ accountName);
|
Log.d(Constants.TAG, "getAccSettings accountName: " + accountName);
|
||||||
|
|
||||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||||
|
|
||||||
@ -198,14 +213,14 @@ public abstract class RemoteService extends Service {
|
|||||||
*
|
*
|
||||||
* @param allowOnlySelf allow only Keychain app itself
|
* @param allowOnlySelf 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
|
* @throws WrongPackageCertificateException
|
||||||
*/
|
*/
|
||||||
private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageSignatureException {
|
private boolean isCallerAllowed(boolean allowOnlySelf) throws WrongPackageCertificateException {
|
||||||
return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
|
return isUidAllowed(Binder.getCallingUid(), allowOnlySelf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUidAllowed(int uid, boolean allowOnlySelf)
|
private boolean isUidAllowed(int uid, boolean allowOnlySelf)
|
||||||
throws WrongPackageSignatureException {
|
throws WrongPackageCertificateException {
|
||||||
if (android.os.Process.myUid() == uid) {
|
if (android.os.Process.myUid() == uid) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -229,11 +244,9 @@ public abstract class RemoteService extends Service {
|
|||||||
/**
|
/**
|
||||||
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
||||||
*
|
*
|
||||||
* @param packageName
|
* @throws WrongPackageCertificateException
|
||||||
* @return
|
|
||||||
* @throws WrongPackageSignatureException
|
|
||||||
*/
|
*/
|
||||||
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
|
private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
|
||||||
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
||||||
|
|
||||||
ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
|
ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
|
||||||
@ -244,22 +257,22 @@ public abstract class RemoteService extends Service {
|
|||||||
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
||||||
|
|
||||||
// check package signature
|
// check package signature
|
||||||
byte[] currentSig;
|
byte[] currentCert;
|
||||||
try {
|
try {
|
||||||
currentSig = getPackageSignature(packageName);
|
currentCert = getPackageCertificate(packageName);
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException e) {
|
||||||
throw new WrongPackageSignatureException(e.getMessage());
|
throw new WrongPackageCertificateException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] storedSig = mProviderHelper.getApiAppSignature(packageName);
|
byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName);
|
||||||
if (Arrays.equals(currentSig, storedSig)) {
|
if (Arrays.equals(currentCert, storedCert)) {
|
||||||
Log.d(Constants.TAG,
|
Log.d(Constants.TAG,
|
||||||
"Package signature is correct! (equals signature from database)");
|
"Package certificate is correct! (equals certificate from database)");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new WrongPackageSignatureException(
|
throw new WrongPackageCertificateException(
|
||||||
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not " +
|
"PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " +
|
||||||
"equals signature from database)");
|
"equals certificate from database)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user