一、注意事项和编码步骤
1、定义一个内部签名许可。@b@2、请求一个内部签名许可。@b@3、显性设置exported属性为true。@b@4、验证内部签名许可是内部应用定义的。@b@5、来自内部应用的请求也要验证其参数的安全性。@b@6、在内部程序内,敏感数据可以返回。@b@7、当导出APK时,用与请求应用相同的developer key进行签名。
二、原代码示例
1.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>@b@<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.provider.inhouseprovider">@b@ @b@<!-- *** POINT 1 *** Define an in-house signature permission -->@b@ <permission@b@ android:name="org.jssec.android.provider.inhouseprovider.MY_PERMISSION"@b@ android:protectionLevel="signature" />@b@ @b@ <application@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ @b@ <!-- *** POINT 2 *** Require the in-house signature permission -->@b@ <!-- *** POINT 3 *** Explicitly set the exported attribute to true. -->@b@ @b@ <provider@b@ android:name=".InhouseProvider"@b@ android:authorities="org.jssec.android.provider.inhouseprovider"@b@ android:permission="org.jssec.android.provider.inhouseprovider.MY_PERMISSION"@b@ android:exported="true" />@b@ </application>@b@</manifest>
2.InhouseProvider.java
package org.jssec.android.provider.inhouseprovider;@b@ @b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@ @b@import android.content.ContentProvider;@b@import android.content.ContentUris;@b@import android.content.ContentValues;@b@import android.content.Context;@b@import android.content.UriMatcher;@b@import android.database.Cursor;@b@import android.database.MatrixCursor;@b@import android.net.Uri;@b@ @b@public class InhouseProvider extends ContentProvider {@b@ @b@ public static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";@b@ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";@b@ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";@b@ @b@ // Expose the interface that the Content Provider provides.@b@ public interface Download {@b@ public static final String PATH = "downloads";@b@ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); }@b@ @b@ public interface Address {@b@ public static final String PATH = "addresses";@b@ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);@b@ }@b@ @b@ // UriMatcher@b@ private static final int DOWNLOADS_CODE = 1;@b@ private static final int DOWNLOADS_ID_CODE = 2;@b@ private static final int ADDRESSES_CODE = 3;@b@ private static final int ADDRESSES_ID_CODE = 4;@b@ private static UriMatcher sUriMatcher;@b@ static {@b@ sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);@b@ sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);@b@ sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);@b@ sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);@b@ sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);@b@ }@b@ @b@ // Since this is a sample program,@b@ // query method returns the following fixed result always without using database.@b@ private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });@b@ static {@b@ sAddressCursor.addRow(new String[] { "1", "New York" });@b@ sAddressCursor.addRow(new String[] { "2", "London" });@b@ sAddressCursor.addRow(new String[] { "3", "Paris" });@b@ }@b@ private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });@b@ static {@b@ sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });@b@ sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });@b@ }@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";@b@ @b@ // In-house certificate hash value@b@ private static String sMyCertHash = null;@b@ private static String myCertHash(Context context) {@b@ if (sMyCertHash == null) {@b@ if (Utils.isDebuggable(context)) {@b@ // Certificate hash value of "androiddebugkey" in the debug.keystore.@b@ sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";@b@ } else {@b@ // Certificate hash value of "my company key" in the keystore.@b@ sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";@b@ }@b@ }@b@ return sMyCertHash;@b@ }@b@ @b@ @Override@b@ public boolean onCreate() {@b@ return true;@b@ }@b@ @b@ @Override@b@ public String getType(Uri uri) {@b@ @b@ switch (sUriMatcher.match(uri)) {@b@ case DOWNLOADS_CODE:@b@ case ADDRESSES_CODE:@b@ return CONTENT_TYPE;@b@ @b@ case DOWNLOADS_ID_CODE:@b@ case ADDRESSES_ID_CODE:@b@ return CONTENT_ITEM_TYPE;@b@ @b@ default:@b@ throw new IllegalArgumentException("Invalid URI:" + uri);@b@ }@b@ }@b@ @b@ @Override@b@ public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)@b@ {@b@ // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {@b@ throw new SecurityException("The in-house signature permission is not declared by in-house application.")@b@ }@b@ @b@ // *** POINT 5 *** Handle the received request data carefully and securely,@b@ // even though the data came from an in-house application.@b@ // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.@b@ // Checking for other parameters are omitted here, due to sample.@b@ // Refer to "3.2 Handle Input Data Carefully and Securely."@b@ // *** POINT 6 *** Sensitive information can be returned since the requesting application is in-house.@b@ // It depends on application whether the query result has sensitive meaning or not.@b@ switch (sUriMatcher.match(uri)) {@b@ case DOWNLOADS_CODE:@b@ case DOWNLOADS_ID_CODE:@b@ return sDownloadCursor;@b@ @b@ case ADDRESSES_CODE:@b@ case ADDRESSES_ID_CODE:@b@ return sAddressCursor;@b@ @b@ default:@b@ throw new IllegalArgumentException("Invalid URI:" + uri);@b@ }@b@ }@b@ @b@ @Override@b@ public Uri insert(Uri uri, ContentValues values) {@b@ @b@ // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {@b@ throw new SecurityException("The in-house signature permission is not declared by in-house application.") }@b@ @b@ // *** POINT 5 *** Handle the received request data carefully and securely,@b@ // even though the data came from an in-house application.@b@ // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.@b@ // Checking for other parameters are omitted here, due to sample.@b@ // Refer to "3.2 Handle Input Data Carefully and Securely."@b@ // *** POINT 6 *** Sensitive information can be returned since the requesting application is in-house.@b@ // It depends on application whether the issued ID has sensitive meaning or not.@b@ switch (sUriMatcher.match(uri)) {@b@ case DOWNLOADS_CODE:@b@ return ContentUris.withAppendedId(Download.CONTENT_URI, 3);@b@ @b@ case ADDRESSES_CODE:@b@ return ContentUris.withAppendedId(Address.CONTENT_URI, 4);@b@ @b@ default:@b@ throw new IllegalArgumentException("Invalid URI:" + uri);@b@ }@b@ }@b@ @b@ @b@ @Override@b@ public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs)@b@ {@b@ @b@ // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {@b@ throw new SecurityException("The in-house signature permission is not declared by in-house application.")@b@ }@b@ @b@ // *** POINT 5 *** Handle the received request data carefully and securely,@b@ // even though the data came from an in-house application.@b@ // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.@b@ // Checking for other parameters are omitted here, due to sample.@b@ // Refer to "3.2 Handle Input Data Carefully and Securely."@b@ @b@ // *** POINT 6 *** Sensitive information can be returned since the requesting application is in-house.@b@ // It depends on application whether the number of updated records has sensitive meaning or not.@b@ switch (sUriMatcher.match(uri)) {@b@ case DOWNLOADS_CODE:@b@ return 5; // Return number of updated records@b@ @b@ case DOWNLOADS_ID_CODE:@b@ return 1;@b@ @b@ case ADDRESSES_CODE:@b@ return 15;@b@ @b@ case ADDRESSES_ID_CODE:@b@ return 1;@b@ default:@b@ throw new IllegalArgumentException("Invalid URI:" + uri);@b@ }@b@ }@b@ @Override@b@ public int delete(Uri uri, String selection, String[] selectionArgs) {@b@ @b@ // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {@b@ throw new SecurityException("The in-house signature permission is not declared by in-house application.") }@b@ @b@ // *** POINT 5 *** Handle the received request data carefully and securely,@b@ // even though the data came from an in-house application.@b@ // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.@b@ // Checking for other parameters are omitted here, due to sample.@b@ // Refer to "3.2 Handle Input Data Carefully and Securely."@b@ // *** POINT 6 *** Sensitive information can be returned since the requesting application is in-house.@b@ // It depends on application whether the number of deleted records has sensitive meaning or not.@b@ @b@ switch (sUriMatcher.match(uri)) {@b@ case DOWNLOADS_CODE:@b@ return 10; // Return number of deleted records@b@ @b@ case DOWNLOADS_ID_CODE:@b@ return 1;@b@ @b@ case ADDRESSES_CODE:@b@ return 20;@b@ @b@ case ADDRESSES_ID_CODE:@b@ return 1;@b@ @b@ default:@b@ throw new IllegalArgumentException("Invalid URI:" + uri);@b@ }@b@ }@b@}
3.SigPerm.java
package org.jssec.android.shared;@b@ @b@import android.content.Context;@b@import android.content.pm.PackageManager;@b@import android.content.pm.PackageManager.NameNotFoundException;@b@import android.content.pm.PermissionInfo;@b@ @b@public class SigPerm {@b@ @b@ public static boolean test(Context ctx, String sigPermName, String correctHash) {@b@ if (correctHash == null) return false;@b@ correctHash = correctHash.replaceAll(" ", "");@b@ return correctHash.equals(hash(ctx, sigPermName));@b@ }@b@ @b@ public static String hash(Context ctx, String sigPermName) {@b@ if (sigPermName == null) return null;@b@ try {@b@ // Get the package name of the application which declares a permission named sigPermName.@b@ PackageManager pm = ctx.getPackageManager();@b@ PermissionInfo pi;@b@ pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);@b@ String pkgname = pi.packageName;@b@ @b@ // Fail if the permission named sigPermName is not a Signature Permission@b@ if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;@b@ @b@ // Return the certificate hash value of the application which declares a permission named sigPermName.@b@ return PkgCert.hash(ctx, pkgname);@b@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@}
4.PkgCert.java
package org.jssec.android.shared;@b@ @b@import java.security.MessageDigest;@b@import java.security.NoSuchAlgorithmException;@b@ @b@import android.content.Context;@b@import android.content.pm.PackageInfo;@b@import android.content.pm.PackageManager;@b@import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature;@b@ @b@public class PkgCert {@b@ @b@ public static boolean test(Context ctx, String pkgname, String correctHash) {@b@ if (correctHash == null) return false;@b@ correctHash = correctHash.replaceAll(" ", "");@b@ return correctHash.equals(hash(ctx, pkgname));@b@ }@b@ @b@ public static String hash(Context ctx, String pkgname) {@b@ if (pkgname == null) return null;@b@ try {@b@ PackageManager pm = ctx.getPackageManager();@b@ PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);@b@ if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.@b@ Signature sig = pkginfo.signatures[0];@b@ byte[] cert = sig.toByteArray();@b@ byte[] sha256 = computeSha256(cert);@b@ return byte2hex(sha256);@b@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@ @b@ private static byte[] computeSha256(byte[] data) {@b@ try {@b@ return MessageDigest.getInstance("SHA-256").digest(data);@b@ } catch (NoSuchAlgorithmException e) {@b@ return null;@b@ }@b@ }@b@ @b@ private static String byte2hex(byte[] data) {@b@ if (data == null) return null;@b@ final StringBuilder hexadecimal = new StringBuilder();@b@ for (final byte b : data) {@b@ hexadecimal.append(String.format("%02X", b));@b@ }@b@ return hexadecimal.toString();@b@ }@b@}
三、安全代码示例
1、声明使用内部签名许可。@b@2、验证内部签名许可是否由内部应用定义。@b@3、难目标应用是由内部证书签名的。@b@4、敏感数据可以发送给内部应用。@b@5、处理收到的结果数据,验证真实性。@b@6、当导出一个APK,使用与目标程序相同的developerkey。
AndroidManifest.xml@b@ @b@<?xml version="1.0" encoding="utf-8"?>@b@ @b@<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.provider.inhouseuser">@b@ @b@ <!-- *** POINT 8 *** Declare to use the in-house signature permission. -->@b@ <uses-permission@b@ android:name="org.jssec.android.provider.inhouseprovider.MY_PERMISSION" />@b@ @b@ <application@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ <activity@b@ android:name=".InhouseUserActivity"@b@ android:label="@string/app_name"@b@ android:exported="true" >@b@ <intent-filter>@b@ <action android:name="android.intent.action.MAIN" />@b@ <category android:name="android.intent.category.LAUNCHER" />@b@ </intent-filter>@b@ </activity>@b@ </application>@b@</manifest>
InhouseUserActivity.java@b@ @b@package org.jssec.android.provider.inhouseuser;@b@ @b@import org.jssec.android.shared.PkgCert;@b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@ @b@import android.app.Activity;@b@import android.content.ContentValues;@b@import android.content.Context;@b@import android.content.pm.PackageManager;@b@import android.content.pm.ProviderInfo;@b@import android.database.Cursor;@b@import android.net.Uri;@b@import android.os.Bundle;@b@import android.view.View;@b@import android.widget.TextView;@b@ @b@public class InhouseUserActivity extends Activity {@b@ @b@ // Target Content Provider Information@b@ private static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";@b@ private interface Address {@b@ public static final String PATH = "addresses";@b@ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); }@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";@b@ @b@ // In-house certificate hash value@b@ private static String sMyCertHash = null;@b@ private static String myCertHash(Context context) {@b@ if (sMyCertHash == null) {@b@ if (Utils.isDebuggable(context)) {@b@ // Certificate hash value of "androiddebugkey" in the debug.keystore.@b@ sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";@b@ } else {@b@ // Certificate hash value of "my company key" in the keystore.@b@ sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";@b@ }@b@ }@b@ return sMyCertHash;@b@ }@b@ @b@ // Get package name of target content provider.@b@ private static String providerPkgname(Context context, Uri uri) {@b@ String pkgname = null;@b@ PackageManager pm = context.getPackageManager();@b@ ProviderInfo pi = pm.resolveContentProvider(uri.getAuthority(), 0);@b@ if (pi != null) pkgname = pi.packageName;@b@ return pkgname;@b@ }@b@ @b@ public void onQueryClick(View view) {@b@ @b@ logLine("[Query]");@b@ @b@ // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {@b@ logLine(" The in-house signature permission is not declared by in-house application.");@b@ return;@b@ }@b@ @b@ // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate.@b@ String pkgname = providerPkgname(this, Address.CONTENT_URI);@b@ if (!PkgCert.test(this, pkgname, myCertHash(this))) {@b@ logLine(" The target content provider is not served by in-house applications.");@b@ return;@b@ }@b@ @b@ Cursor cursor = null;@b@ try {@b@ // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.@b@ cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);@b@ // *** POINT 12 *** Handle the received result data carefully and securely,@b@ // even though the data comes from an in-house application.@b@ // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."@b@ if (cursor == null) {@b@ logLine(" null cursor");@b@ } else {@b@ boolean moved = cursor.moveToFirst();@b@ while (moved) {@b@ logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));@b@ moved = cursor.moveToNext();@b@ }@b@ }@b@ }@b@ @b@ finally {@b@ if (cursor != null) cursor.close();@b@ }@b@ }@b@ @b@ public void onInsertClick(View view) {@b@ logLine("[Insert]");@b@ @b@ // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.@b@ String correctHash = myCertHash(this);@b@ if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {@b@ logLine(" The in-house signature permission is not declared by in-house application.");@b@ return;@b@ }@b@ @b@ // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate. String pkgname = providerPkgname(this, Address.CONTENT_URI);@b@ if (!PkgCert.test(this, pkgname, correctHash)) {@b@ logLine(" The target content provider is not served by in-house applications.");@b@ return;@b@ }@b@ @b@ // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.@b@ ContentValues values = new ContentValues();@b@ values.put("city", "Tokyo");@b@ Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);@b@ @b@ // *** POINT 12 *** Handle the received result data carefully and securely,@b@ // even though the data comes from an in-house application.@b@ // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."@b@ logLine(" uri:" + uri);@b@ }@b@ @b@ public void onUpdateClick(View view) {@b@ @b@ logLine("[Update]");@b@ @b@ // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.@b@ String correctHash = myCertHash(this);@b@ if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {@b@ logLine(" The in-house signature permission is not declared by in-house application.");@b@ return;@b@ }@b@ @b@ // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate. @b@ String pkgname = providerPkgname(this, Address.CONTENT_URI);@b@ if (!PkgCert.test(this, pkgname, correctHash)) {@b@ logLine(" The target content provider is not served by in-house applications.");@b@ return;@b@ }@b@ @b@ // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.@b@ ContentValues values = new ContentValues();@b@ values.put("city", "Tokyo");@b@ String where = "_id = ?";@b@ String[] args = { "4" };@b@ int count = getContentResolver().update(Address.CONTENT_URI, values, where, args);@b@ @b@ // *** POINT 12 *** Handle the received result data carefully and securely,@b@ // even though the data comes from an in-house application.@b@ // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."@b@ logLine(String.format(" %s records updated", count));@b@ }@b@ @b@ public void onDeleteClick(View view) {@b@ @b@ logLine("[Delete]");@b@ @b@ // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.@b@ String correctHash = myCertHash(this);@b@ if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {@b@ logLine(" The target content provider is not served by in-house applications.");@b@ return;@b@ }@b@ @b@ // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate.@b@ String pkgname = providerPkgname(this, Address.CONTENT_URI);@b@ if (!PkgCert.test(this, pkgname, correctHash)) {@b@ logLine(" The target content provider is not served by in-house applications.");@b@ return;@b@ }@b@ @b@ // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.@b@ int count = getContentResolver().delete(Address.CONTENT_URI, null, null);@b@ @b@ // *** POINT 12 *** Handle the received result data carefully and securely,@b@ // even though the data comes from an in-house application.@b@ // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."@b@ logLine(String.format(" %s records deleted", count));@b@ }@b@ @b@ private TextView mLogView;@b@ @b@ @Override@b@ public void onCreate(Bundle savedInstanceState) {@b@ super.onCreate(savedInstanceState);@b@ setContentView(R.layout.main);@b@ mLogView = (TextView)findViewById(R.id.logview);@b@ }@b@ @b@ private void logLine(String line) {@b@ mLogView.append(line);@b@ mLogView.append("¥n");@b@ }@b@}
SigPerm.java@b@ @b@package org.jssec.android.shared;@b@import android.content.Context;@b@import android.content.pm.PackageManager;@b@import android.content.pm.PackageManager.NameNotFoundException;@b@import android.content.pm.PermissionInfo;@b@ @b@public class SigPerm {@b@ @b@ public static boolean test(Context ctx, String sigPermName, String correctHash) {@b@ if (correctHash == null) return false;@b@ correctHash = correctHash.replaceAll(" ", "");@b@ return correctHash.equals(hash(ctx, sigPermName));@b@ }@b@ @b@ public static String hash(Context ctx, String sigPermName) {@b@ if (sigPermName == null) return null;@b@ try {@b@ // Get the package name of the application which declares a permission named sigPermName.@b@ PackageManager pm = ctx.getPackageManager();@b@ PermissionInfo pi;@b@ pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);@b@ String pkgname = pi.packageName;@b@ @b@ // Fail if the permission named sigPermName is not a Signature Permission@b@ if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;@b@ @b@ // Return the certificate hash value of the application which declares a permission named sigPermName.@b@ return PkgCert.hash(ctx, pkgname);@b@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@}
PkgCert.java@b@ @b@package org.jssec.android.shared;@b@ @b@import java.security.MessageDigest;@b@import java.security.NoSuchAlgorithmException;@b@ @b@import android.content.Context;@b@import android.content.pm.PackageInfo;@b@import android.content.pm.PackageManager;@b@import android.content.pm.PackageManager.NameNotFoundException;@b@import android.content.pm.Signature;@b@ @b@public class PkgCert {@b@ @b@ public static boolean test(Context ctx, String pkgname, String correctHash) {@b@ if (correctHash == null) return false;@b@ correctHash = correctHash.replaceAll(" ", "");@b@ return correctHash.equals(hash(ctx, pkgname));@b@ }@b@ @b@ public static String hash(Context ctx, String pkgname) {@b@ if (pkgname == null) return null;@b@ try {@b@ PackageManager pm = ctx.getPackageManager();@b@ PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);@b@ if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.@b@ Signature sig = pkginfo.signatures[0];@b@ byte[] cert = sig.toByteArray();@b@ byte[] sha256 = computeSha256(cert);@b@ return byte2hex(sha256);@b@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@ @b@ private static byte[] computeSha256(byte[] data) {@b@ try {@b@ return MessageDigest.getInstance("SHA-256").digest(data);@b@ } catch (NoSuchAlgorithmException e) {@b@ return null;@b@ }@b@ }@b@ @b@ private static String byte2hex(byte[] data) {@b@ if (data == null) return null;@b@ final StringBuilder hexadecimal = new StringBuilder();@b@ for (final byte b : data) {@b@ hexadecimal.append(String.format("%02X", b));@b@ }@b@ return hexadecimal.toString();@b@ }@b@}