4.3.1.4 创建/使用内部内容供应器

内部内容供应器禁止除内部应用以外的应用使用。

下面展示了如何实现内部内容供应器的示例代码。

要点(创建内容供应器):

  1. 定义内部签名权限。

  2. 需要内部签名权限。

  3. 将导出属性显式设置为true

  4. 验证内部签名权限是否由内部应用定义。

  5. 验证参数的安全性,即使这是来自内部应用的请求。

  6. 由于请求应用是内部的,因此可以返回敏感信息。

  7. 导出 APK 时,请使用与请求应用相同的开发者密钥对 APK 进行签名。

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="org.jssec.android.provider.inhouseprovider">
  4. <!-- *** POINT 1 *** Define an in-house signature permission -->
  5. <permission
  6. android:name="org.jssec.android.provider.inhouseprovider.MY_PERMISSION"
  7. android:protectionLevel="signature" />
  8. <application
  9. android:icon="@drawable/ic_launcher"
  10. android:label="@string/app_name" >
  11. <!-- *** POINT 2 *** Require the in-house signature permission -->
  12. <!-- *** POINT 3 *** Explicitly set the exported attribute to true. -->
  13. <provider
  14. android:name=".InhouseProvider"
  15. android:authorities="org.jssec.android.provider.inhouseprovider"
  16. android:permission="org.jssec.android.provider.inhouseprovider.MY_PERMISSION"
  17. android:exported="true" />
  18. </application>
  19. </manifest>

InhouseProvider.java

  1. package org.jssec.android.provider.inhouseprovider;
  2. import org.jssec.android.shared.SigPerm;
  3. import org.jssec.android.shared.Utils;
  4. import android.content.ContentProvider;
  5. import android.content.ContentUris;
  6. import android.content.ContentValues;
  7. import android.content.Context;
  8. import android.content.UriMatcher;
  9. import android.database.Cursor;
  10. import android.database.MatrixCursor;
  11. import android.net.Uri;
  12. public class InhouseProvider extends ContentProvider {
  13. public static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";
  14. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype";
  15. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype";
  16. // Expose the interface that the Content Provider provides.
  17. public interface Download {
  18. public static final String PATH = "downloads";
  19. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  20. }
  21. public interface Address {
  22. public static final String PATH = "addresses";
  23. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  24. }
  25. // UriMatcher
  26. private static final int DOWNLOADS_CODE = 1;
  27. private static final int DOWNLOADS_ID_CODE = 2;
  28. private static final int ADDRESSES_CODE = 3;
  29. private static final int ADDRESSES_ID_CODE = 4;
  30. private static UriMatcher sUriMatcher;
  31. static {
  32. sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  33. sUriMatcher.addURI(AUTHORITY, Download.PATH, DOWNLOADS_CODE);
  34. sUriMatcher.addURI(AUTHORITY, Download.PATH + "/#", DOWNLOADS_ID_CODE);
  35. sUriMatcher.addURI(AUTHORITY, Address.PATH, ADDRESSES_CODE);
  36. sUriMatcher.addURI(AUTHORITY, Address.PATH + "/#", ADDRESSES_ID_CODE);
  37. }
  38. // Since this is a sample program,
  39. // query method returns the following fixed result always without using database.
  40. private static MatrixCursor sAddressCursor = new MatrixCursor(new String[] { "_id", "city" });
  41. static {
  42. sAddressCursor.addRow(new String[] { "1", "New York" });
  43. sAddressCursor.addRow(new String[] { "2", "London" });
  44. sAddressCursor.addRow(new String[] { "3", "Paris" });
  45. }
  46. private static MatrixCursor sDownloadCursor = new MatrixCursor(new String[] { "_id", "path" });
  47. static {
  48. sDownloadCursor.addRow(new String[] { "1", "/sdcard/downloads/sample.jpg" });
  49. sDownloadCursor.addRow(new String[] { "2", "/sdcard/downloads/sample.txt" });
  50. }
  51. // In-house Signature Permission
  52. private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";
  53. // In-house certificate hash value
  54. private static String sMyCertHash = null;
  55. private static String myCertHash(Context context) {
  56. if (sMyCertHash == null) {
  57. if (Utils.isDebuggable(context)) {
  58. // Certificate hash value of "androiddebugkey" in the debug.keystore.
  59. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  60. } else {
  61. // Certificate hash value of "my company key" in the keystore.
  62. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  63. }
  64. }
  65. return sMyCertHash;
  66. }
  67. @Override
  68. public boolean onCreate() {
  69. return true;
  70. }
  71. @Override
  72. public String getType(Uri uri) {
  73. switch (sUriMatcher.match(uri)) {
  74. case DOWNLOADS_CODE:
  75. case ADDRESSES_CODE:
  76. return CONTENT_TYPE;
  77. case DOWNLOADS_ID_CODE:
  78. case ADDRESSES_ID_CODE:
  79. return CONTENT_ITEM_TYPE;
  80. default:
  81. throw new IllegalArgumentException("Invalid URI:" + uri);
  82. }
  83. }
  84. @Override
  85. public Cursor query(Uri uri, String[] projection, String selection,
  86. String[] selectionArgs, String sortOrder) {
  87. // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.
  88. if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
  89. throw new SecurityException("The in-house signature permission is not declared by in-house application.");
  90. }
  91. // *** POINT 5 *** Handle the received request data carefully and securely,
  92. // even though the data came from an in-house application.
  93. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  94. // Checking for other parameters are omitted here, due to sample.
  95. // Refer to "3.2 Handle Input Data Carefully and Securely."
  96. // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
  97. // It depends on application whether the query result has sensitive meaning or not.
  98. switch (sUriMatcher.match(uri)) {
  99. case DOWNLOADS_CODE:
  100. case DOWNLOADS_ID_CODE:
  101. return sDownloadCursor;
  102. case ADDRESSES_CODE:
  103. case ADDRESSES_ID_CODE:
  104. return sAddressCursor;
  105. default:
  106. throw new IllegalArgumentException("Invalid URI:" + uri);
  107. }
  108. }
  109. @Override
  110. public Uri insert(Uri uri, ContentValues values) {
  111. // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.
  112. if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
  113. throw new SecurityException("The in-house signature permission is not declared by in-house application.");
  114. }
  115. // *** POINT 5 *** Handle the received request data carefully and securely,
  116. // even though the data came from an in-house application.
  117. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  118. // Checking for other parameters are omitted here, due to sample.
  119. // Refer to "3.2 Handle Input Data Carefully and Securely."
  120. // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
  121. // It depends on application whether the issued ID has sensitive meaning or not.
  122. switch (sUriMatcher.match(uri)) {
  123. case DOWNLOADS_CODE:
  124. return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
  125. case ADDRESSES_CODE:
  126. return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
  127. default:
  128. throw new IllegalArgumentException("Invalid URI:" + uri);
  129. }
  130. }
  131. @Override
  132. public int update(Uri uri, ContentValues values, String selection,
  133. String[] selectionArgs) {
  134. // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.
  135. if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
  136. throw new SecurityException("The in-house signature permission is not declared by in-house application.");
  137. }
  138. // *** POINT 5 *** Handle the received request data carefully and securely,
  139. // even though the data came from an in-house application.
  140. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  141. // Checking for other parameters are omitted here, due to sample.
  142. // Refer to "3.2 Handle Input Data Carefully and Securely."
  143. // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
  144. // It depends on application whether the number of updated records has sensitive meaning or not.
  145. switch (sUriMatcher.match(uri)) {
  146. case DOWNLOADS_CODE:
  147. return 5; // Return number of updated records
  148. case DOWNLOADS_ID_CODE:
  149. return 1;
  150. case ADDRESSES_CODE:
  151. return 15;
  152. case ADDRESSES_ID_CODE:
  153. return 1;
  154. default:
  155. throw new IllegalArgumentException("Invalid URI:" + uri);
  156. }
  157. }
  158. @Override
  159. public int delete(Uri uri, String selection, String[] selectionArgs) {
  160. // *** POINT 4 *** Verify if the in-house signature permission is defined by an in-house application.
  161. if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
  162. throw new SecurityException("The in-house signature permission is not declared by in-house application.");
  163. }
  164. // *** POINT 5 *** Handle the received request data carefully and securely,
  165. // even though the data came from an in-house application.
  166. // Here, whether uri is within expectations or not, is verified by UriMatcher#match() and switch case.
  167. // Checking for other parameters are omitted here, due to sample.
  168. // Refer to "3.2 Handle Input Data Carefully and Securely."
  169. // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
  170. // It depends on application whether the number of deleted records has sensitive meaning or not.
  171. switch (sUriMatcher.match(uri)) {
  172. case DOWNLOADS_CODE:
  173. return 10; // Return number of deleted records
  174. case DOWNLOADS_ID_CODE:
  175. return 1;
  176. case ADDRESSES_CODE:
  177. return 20;
  178. case ADDRESSES_ID_CODE:
  179. return 1;
  180. default:
  181. throw new IllegalArgumentException("Invalid URI:" + uri);
  182. }
  183. }
  184. }

SigPerm.java

  1. package org.jssec.android.shared;
  2. import android.content.Context;
  3. import android.content.pm.PackageManager;
  4. import android.content.pm.PackageManager.NameNotFoundException;
  5. import android.content.pm.PermissionInfo;
  6. public class SigPerm {
  7. public static boolean test(Context ctx, String sigPermName, String correctHash) {
  8. if (correctHash == null) return false;
  9. correctHash = correctHash.replaceAll(" ", "");
  10. return correctHash.equals(hash(ctx, sigPermName));
  11. }
  12. public static String hash(Context ctx, String sigPermName) {
  13. if (sigPermName == null) return null;
  14. try {
  15. // Get the package name of the application which declares a permission named sigPermName.
  16. PackageManager pm = ctx.getPackageManager();
  17. PermissionInfo pi;
  18. pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);
  19. String pkgname = pi.packageName;
  20. // Fail if the permission named sigPermName is not a Signature Permission
  21. if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;
  22. // Return the certificate hash value of the application which declares a permission named sigPermName.
  23. return PkgCert.hash(ctx, pkgname);
  24. } catch (NameNotFoundException e) {
  25. return null;
  26. }
  27. }
  28. }

PkgCert.java

  1. package org.jssec.android.shared;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import android.content.Context;
  5. import android.content.pm.PackageInfo;
  6. import android.content.pm.PackageManager;
  7. import android.content.pm.PackageManager.NameNotFoundException;
  8. import android.content.pm.Signature;
  9. public class PkgCert {
  10. public static boolean test(Context ctx, String pkgname, String correctHash) {
  11. if (correctHash == null) return false;
  12. correctHash = correctHash.replaceAll(" ", "");
  13. return correctHash.equals(hash(ctx, pkgname));
  14. }
  15. public static String hash(Context ctx, String pkgname) {
  16. if (pkgname == null) return null;
  17. try {
  18. PackageManager pm = ctx.getPackageManager();
  19. PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
  20. if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
  21. Signature sig = pkginfo.signatures[0];
  22. byte[] cert = sig.toByteArray();
  23. byte[] sha256 = computeSha256(cert);
  24. return byte2hex(sha256);
  25. } catch (NameNotFoundException e) {
  26. return null;
  27. }
  28. }
  29. private static byte[] computeSha256(byte[] data) {
  30. try {
  31. return MessageDigest.getInstance("SHA-256").digest(data);
  32. } catch (NoSuchAlgorithmException e) {
  33. return null;
  34. }
  35. }
  36. private static String byte2hex(byte[] data) {
  37. if (data == null) return null;
  38. final StringBuilder hexadecimal = new StringBuilder();
  39. for (final byte b : data) {
  40. hexadecimal.append(String.format("%02X", b));
  41. }
  42. return hexadecimal.toString();
  43. }
  44. }

要点 7:导出 APK 时,请使用与请求应用相同的开发者密钥对 APK 进行签名。

4.3.1.4.md - 图1

下面是使用内部内容供应器的活动示例。

要点(使用内容个供应器):

  1. 声明使用内部签名权限。

  2. 验证内部签名权限是否由内部应用定义。

  3. 验证目标应用是否使用内部证书签名。

  4. 由于目标应用是内部应用,因此可以发送敏感信息。

  5. 即使数据来自内部应用,也要小心并安全地处理收到的结果数据。

  6. 导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="org.jssec.android.provider.inhouseuser">
  4. <!-- *** POINT 8 *** Declare to use the in-house signature permission. -->
  5. <uses-permission
  6. android:name="org.jssec.android.provider.inhouseprovider.MY_PERMISSION" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name" >
  10. <activity
  11. android:name=".InhouseUserActivity"
  12. android:label="@string/app_name"
  13. android:exported="true" >
  14. <intent-filter>
  15. <action android:name="android.intent.action.MAIN" />
  16. <category android:name="android.intent.category.LAUNCHER" />
  17. </intent-filter>
  18. </activity>
  19. </application>
  20. </manifest>

InhouseUserActivity.java

  1. package org.jssec.android.provider.inhouseuser;
  2. import org.jssec.android.shared.PkgCert;
  3. import org.jssec.android.shared.SigPerm;
  4. import org.jssec.android.shared.Utils;
  5. import android.app.Activity;
  6. import android.content.ContentValues;
  7. import android.content.Context;
  8. import android.content.pm.PackageManager;
  9. import android.content.pm.ProviderInfo;
  10. import android.database.Cursor;
  11. import android.net.Uri;
  12. import android.os.Bundle;
  13. import android.view.View;
  14. import android.widget.TextView;
  15. public class InhouseUserActivity extends Activity {
  16. // Target Content Provider Information
  17. private static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";
  18. private interface Address {
  19. public static final String PATH = "addresses";
  20. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
  21. }
  22. // In-house Signature Permission
  23. private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";
  24. // In-house certificate hash value
  25. private static String sMyCertHash = null;
  26. private static String myCertHash(Context context) {
  27. if (sMyCertHash == null) {
  28. if (Utils.isDebuggable(context)) {
  29. // Certificate hash value of "androiddebugkey" in the debug.keystore.
  30. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  31. } else {
  32. // Certificate hash value of "my company key" in the keystore.
  33. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  34. }
  35. }
  36. return sMyCertHash;
  37. }
  38. // Get package name of target content provider.
  39. private static String providerPkgname(Context context, Uri uri) {
  40. String pkgname = null;
  41. PackageManager pm = context.getPackageManager();
  42. ProviderInfo pi = pm.resolveContentProvider(uri.getAuthority(), 0);
  43. if (pi != null) pkgname = pi.packageName;
  44. return pkgname;
  45. }
  46. public void onQueryClick(View view) {
  47. logLine("[Query]");
  48. // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.
  49. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  50. logLine(" The in-house signature permission is not declared by in-house application.");
  51. return;
  52. }
  53. // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate.
  54. String pkgname = providerPkgname(this, Address.CONTENT_URI);
  55. if (!PkgCert.test(this, pkgname, myCertHash(this))) {
  56. logLine(" The target content provider is not served by in-house applications.");
  57. return;
  58. }
  59. Cursor cursor = null;
  60. try {
  61. // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
  62. cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);
  63. // *** POINT 12 *** Handle the received result data carefully and securely,
  64. // even though the data comes from an in-house application.
  65. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  66. if (cursor == null) {
  67. logLine(" null cursor");
  68. } else {
  69. boolean moved = cursor.moveToFirst();
  70. while (moved) {
  71. logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
  72. moved = cursor.moveToNext();
  73. }
  74. }
  75. } finally {
  76. if (cursor != null) cursor.close();
  77. }
  78. }
  79. public void onInsertClick(View view) {
  80. logLine("[Insert]");
  81. // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.
  82. String correctHash = myCertHash(this);
  83. if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
  84. logLine(" The in-house signature permission is not declared by in-house application.");
  85. return;
  86. }
  87. // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate.
  88. String pkgname = providerPkgname(this, Address.CONTENT_URI);
  89. if (!PkgCert.test(this, pkgname, correctHash)) {
  90. logLine(" The target content provider is not served by in-house applications.");
  91. return;
  92. }
  93. // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
  94. ContentValues values = new ContentValues();
  95. values.put("city", "Tokyo");
  96. Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
  97. // *** POINT 12 *** Handle the received result data carefully and securely,
  98. // even though the data comes from an in-house application.
  99. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  100. logLine(" uri:" + uri);
  101. }
  102. public void onUpdateClick(View view) {
  103. logLine("[Update]");
  104. // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.
  105. String correctHash = myCertHash(this);
  106. if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
  107. logLine(" The in-house signature permission is not declared by in-house application.");
  108. return;
  109. }
  110. // *** POINT 10 *** Verify if the destination application is signed with the in-house certificate.
  111. String pkgname = providerPkgname(this, Address.CONTENT_URI);
  112. if (!PkgCert.test(this, pkgname, correctHash)) {
  113. logLine(" The target content provider is not served by in-house applications.");
  114. return;
  115. }
  116. // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
  117. ContentValues values = new ContentValues();
  118. values.put("city", "Tokyo");
  119. String where = "_id = ?";
  120. String[] args = { "4" };
  121. int count = getContentResolver().update(Address.CONTENT_URI, values, where, args);
  122. // *** POINT 12 *** Handle the received result data carefully and securely,
  123. // even though the data comes from an in-house application.
  124. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  125. logLine(String.format(" %s records updated", count));
  126. }
  127. public void onDeleteClick(View view) {
  128. logLine("[Delete]");
  129. // *** POINT 9 *** Verify if the in-house signature permission is defined by an in-house application.
  130. String correctHash = myCertHash(this);
  131. if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
  132. logLine(" The target content provider is not served by in-house applications.");
  133. return;
  134. }
  135. // *** POINT 10 *** Verify if the destination application is signed with the in-house certificat
  136. e.
  137. String pkgname = providerPkgname(this, Address.CONTENT_URI);
  138. if (!PkgCert.test(this, pkgname, correctHash)) {
  139. logLine(" The target content provider is not served by in-house applications.");
  140. return;
  141. }
  142. // *** POINT 11 *** Sensitive information can be sent since the destination application is in-ho
  143. use one.
  144. int count = getContentResolver().delete(Address.CONTENT_URI, null, null);
  145. // *** POINT 12 *** Handle the received result data carefully and securely,
  146. // even though the data comes from an in-house application.
  147. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  148. logLine(String.format(" %s records deleted", count));
  149. }
  150. private TextView mLogView;
  151. @Override
  152. public void onCreate(Bundle savedInstanceState) {
  153. super.onCreate(savedInstanceState);
  154. setContentView(R.layout.main);
  155. mLogView = (TextView)findViewById(R.id.logview);
  156. }
  157. private void logLine(String line) {
  158. mLogView.append(line);
  159. mLogView.append("¥n");
  160. }
  161. }

SigPerm.java

  1. package org.jssec.android.shared;
  2. import android.content.Context;
  3. import android.content.pm.PackageManager;
  4. import android.content.pm.PackageManager.NameNotFoundException;
  5. import android.content.pm.PermissionInfo;
  6. public class SigPerm {
  7. public static boolean test(Context ctx, String sigPermName, String correctHash) {
  8. if (correctHash == null) return false;
  9. correctHash = correctHash.replaceAll(" ", "");
  10. return correctHash.equals(hash(ctx, sigPermName));
  11. }
  12. public static String hash(Context ctx, String sigPermName) {
  13. if (sigPermName == null) return null;
  14. try {
  15. // Get the package name of the application which declares a permission named sigPermName.
  16. PackageManager pm = ctx.getPackageManager();
  17. PermissionInfo pi;
  18. pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);
  19. String pkgname = pi.packageName;
  20. // Fail if the permission named sigPermName is not a Signature Permission
  21. if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;
  22. // Return the certificate hash value of the application which declares a permission named sigPermName.
  23. return PkgCert.hash(ctx, pkgname);
  24. } catch (NameNotFoundException e) {
  25. return null;
  26. }
  27. }
  28. }

PkgCert.java

  1. package org.jssec.android.shared;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import android.content.Context;
  5. import android.content.pm.PackageInfo;
  6. import android.content.pm.PackageManager;
  7. import android.content.pm.PackageManager.NameNotFoundException;
  8. import android.content.pm.Signature;
  9. public class PkgCert {
  10. public static boolean test(Context ctx, String pkgname, String correctHash) {
  11. if (correctHash == null) return false;
  12. correctHash = correctHash.replaceAll(" ", "");
  13. return correctHash.equals(hash(ctx, pkgname));
  14. }
  15. public static String hash(Context ctx, String pkgname) {
  16. if (pkgname == null) return null;
  17. try {
  18. PackageManager pm = ctx.getPackageManager();
  19. PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
  20. if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
  21. Signature sig = pkginfo.signatures[0];
  22. byte[] cert = sig.toByteArray();
  23. byte[] sha256 = computeSha256(cert);
  24. return byte2hex(sha256);
  25. } catch (NameNotFoundException e) {
  26. return null;
  27. }
  28. }
  29. private static byte[] computeSha256(byte[] data) {
  30. try {
  31. return MessageDigest.getInstance("SHA-256").digest(data);
  32. } catch (NoSuchAlgorithmException e) {
  33. return null;
  34. }
  35. }
  36. private static String byte2hex(byte[] data) {
  37. if (data == null) return null;
  38. final StringBuilder hexadecimal = new StringBuilder();
  39. for (final byte b : data) {
  40. hexadecimal.append(String.format("%02X", b));
  41. }
  42. return hexadecimal.toString();
  43. }
  44. }

要点 13:导出 APK 时,请使用与请求应用相同的开发者密钥对 APK 进行签名。

4.3.1.4.md - 图2