4.2.1.3 内部广播接收器

内部广播接收器是广播接收器,它将永远不会收到从内部应用以外发送的任何广播。 它由几个内部应用组成,用于保护内部应用处理的信息或功能。

要点(接收广播):

  1. 定义内部签名权限来接收广播。

  2. 声明使用内部签名权限来接收结果。

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

  4. 需要静态广播接收器定义的内部签名权限。

  5. 需要内部签名来注册动态广播接收器。

  6. 确认内部签名权限是由内部应用定义的。

  7. 尽管广播是从内部应用发送的,但要小心并安全地处理接收到的意图。

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

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

内部广播接收器的示例代码可用于静态和动态广播接收器。

InhouseReceiver.java

  1. package org.jssec.android.broadcast.inhousereceiver;
  2. import org.jssec.android.shared.SigPerm;
  3. import org.jssec.android.shared.Utils;
  4. import android.app.Activity;
  5. import android.content.BroadcastReceiver;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.widget.Toast;
  9. public class InhouseReceiver extends BroadcastReceiver {
  10. // In-house Signature Permission
  11. private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION";
  12. // In-house certificate hash value
  13. private static String sMyCertHash = null;
  14. private static String myCertHash(Context context) {
  15. if (sMyCertHash == null) {
  16. if (Utils.isDebuggable(context)) {
  17. // Certificate hash value of "androiddebugkey" in the debug.keystore.
  18. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  19. } else {
  20. // Certificate hash value of "my company key" in the keystore.
  21. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  22. }
  23. }
  24. return sMyCertHash;
  25. }
  26. private static final String MY_BROADCAST_INHOUSE =
  27. "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";
  28. public boolean isDynamic = false;
  29. private String getName() {
  30. return isDynamic ? "In-house Dynamic Broadcast Receiver" : "In-house Static Broadcast Receiver";
  31. }
  32. @Override
  33. public void onReceive(Context context, Intent intent) {
  34. // *** POINT 6 *** Verify that the in-house signature permission is defined by an in-house application.
  35. if (!SigPerm.test(context, MY_PERMISSION, myCertHash(context))) {
  36. Toast.makeText(context, "The in-house signature permission is not declared by in-house application.",
  37. Toast.LENGTH_LONG).show();
  38. return;
  39. }
  40. // *** POINT 7 *** Handle the received intent carefully and securely,
  41. // even though the Broadcast was sent from an in-house application..
  42. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  43. if (MY_BROADCAST_INHOUSE.equals(intent.getAction())) {
  44. String param = intent.getStringExtra("PARAM");
  45. Toast.makeText(context,
  46. String.format("%s:¥nReceived param: ¥"%s¥"", getName(), param),
  47. Toast.LENGTH_SHORT).show();
  48. }
  49. // *** POINT 8 *** Sensitive information can be returned since the requesting application is inhouse.
  50. setResultCode(Activity.RESULT_OK);
  51. setResultData(String.format("Sensitive Info from %s", getName()));
  52. abortBroadcast();
  53. }
  54. }

静态广播接收器定义在AndroidManifest.xml中。

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.broadcast.inhousereceiver" >
  4. <!-- *** POINT 1 *** Define an in-house signature permission to receive Broadcasts -->
  5. <permission
  6. android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION"
  7. android:protectionLevel="signature" />
  8. <!-- *** POINT 2 *** Declare to use the in-house signature permission to receive results. -->
  9. <uses-permission
  10. android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION" />
  11. <application
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name"
  14. android:allowBackup="false" >
  15. <!-- *** POINT 3 *** Explicitly set the exported attribute to true. -->
  16. <!-- *** POINT 4 *** Require the in-house signature permission by the Static Broadcast Receiver
  17. definition. -->
  18. <receiver
  19. android:name=".InhouseReceiver"
  20. android:permission="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION"
  21. android:exported="true">
  22. <intent-filter>
  23. <action android:name="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE" />
  24. </intent-filter>
  25. </receiver>
  26. <service
  27. android:name=".DynamicReceiverService"
  28. android:exported="false" />
  29. <activity
  30. android:name=".InhouseReceiverActivity"
  31. android:label="@string/app_name"
  32. android:exported="true" >
  33. <intent-filter>
  34. <action android:name="android.intent.action.MAIN" />
  35. <category android:name="android.intent.category.LAUNCHER" />
  36. </intent-filter>
  37. </activity>
  38. </application>
  39. </manifest>

在动态广播接收器中,通过调用程序中的registerReceiver()unregisterReceiver()来执行注册/注销。 为了通过按钮操作执行注册/注销,该按钮PublicReceiverActivity中定义。 由于动态广播接收器实例的作用域比PublicReceiverActivity长,因此不能将其保存为PublicReceiverActivity的成员变量。 在这种情况下,请将动态广播接收器实例保存为DynamicReceiverService的成员变量,然后从PublicReceiverActivity启动/结束DynamicReceiverService,来间接注册/注销动态广播接收器。

InhouseReceiverActivity.java

  1. package org.jssec.android.broadcast.inhousereceiver;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. public class InhouseReceiverActivity extends Activity {
  7. @Override
  8. public void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.main);
  11. }
  12. public void onRegisterReceiverClick(View view) {
  13. Intent intent = new Intent(this, DynamicReceiverService.class);
  14. startService(intent);
  15. }
  16. public void onUnregisterReceiverClick(View view) {
  17. Intent intent = new Intent(this, DynamicReceiverService.class);
  18. stopService(intent);
  19. }
  20. }

DynamicReceiverService.java

  1. package org.jssec.android.broadcast.inhousereceiver;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.content.IntentFilter;
  5. import android.os.IBinder;
  6. import android.widget.Toast;
  7. public class DynamicReceiverService extends Service {
  8. private static final String MY_BROADCAST_INHOUSE =
  9. "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";
  10. private InhouseReceiver mReceiver;
  11. @Override
  12. public IBinder onBind(Intent intent) {
  13. return null;
  14. }
  15. @Override
  16. public void onCreate() {
  17. super.onCreate();
  18. mReceiver = new InhouseReceiver();
  19. mReceiver.isDynamic = true;
  20. IntentFilter filter = new IntentFilter();
  21. filter.addAction(MY_BROADCAST_INHOUSE);
  22. filter.setPriority(1); // Prioritize Dynamic Broadcast Receiver, rather than Static Broadcast Receiver.
  23. // *** POINT 5 *** When registering a dynamic broadcast receiver, require the in-house signature permission.
  24. registerReceiver(mReceiver, filter, "org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION", null);
  25. Toast.makeText(this,
  26. "Registered Dynamic Broadcast Receiver.",
  27. Toast.LENGTH_SHORT).show();
  28. }
  29. @Override
  30. public void onDestroy() {
  31. super.onDestroy();
  32. unregisterReceiver(mReceiver);
  33. mReceiver = null;
  34. Toast.makeText(this,
  35. "Unregistered Dynamic Broadcast Receiver.",
  36. Toast.LENGTH_SHORT).show();
  37. }
  38. }

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. }

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

4.2.1.3.md - 图1

下面,展示了用于向内部广播接收器发送广播的示例代码。

要点(发送广播):

  1. 定义内部签名权限来接收结果。

  2. 声明使用内部签名权限来接收广播。

  3. 确认内部签名权限是由内部应用定义的。

  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.broadcast.inhousesender" >
  4. <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
  5. <!-- *** POINT 10 *** Define an in-house signature permission to receive results. -->
  6. <permission
  7. android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION"
  8. android:protectionLevel="signature" />
  9. <!-- *** POINT 11 *** Declare to use the in-house signature permission to receive Broadcasts. -->
  10. <uses-permission
  11. android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION" />
  12. <application
  13. android:icon="@drawable/ic_launcher"
  14. android:label="@string/app_name"
  15. android:allowBackup="false" >
  16. <activity
  17. android:name="org.jssec.android.broadcast.inhousesender.InhouseSenderActivity"
  18. android:label="@string/app_name"
  19. android:exported="true" >
  20. <intent-filter>
  21. <action android:name="android.intent.action.MAIN" />
  22. <category android:name="android.intent.category.LAUNCHER" />
  23. </intent-filter>
  24. </activity>
  25. </application>
  26. </manifest>

InhouseSenderActivity.java

  1. package org.jssec.android.broadcast.inhousesender;
  2. import org.jssec.android.shared.SigPerm;
  3. import org.jssec.android.shared.Utils;
  4. import android.app.Activity;
  5. import android.content.BroadcastReceiver;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.os.Bundle;
  9. import android.view.View;
  10. import android.widget.TextView;
  11. import android.widget.Toast;
  12. public class InhouseSenderActivity extends Activity {
  13. // In-house Signature Permission
  14. private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousesender.MY_PERMISSION";
  15. // In-house certificate hash value
  16. private static String sMyCertHash = null;
  17. private static String myCertHash(Context context) {
  18. if (sMyCertHash == null) {
  19. if (Utils.isDebuggable(context)) {
  20. // Certificate hash value of "androiddebugkey" in the debug.keystore.
  21. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  22. } else {
  23. // Certificate hash value of "my company key" in the keystore.
  24. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  25. }
  26. }
  27. return sMyCertHash;
  28. }
  29. private static final String MY_BROADCAST_INHOUSE =
  30. "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";
  31. public void onSendNormalClick(View view) {
  32. // *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.
  33. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  34. Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",
  35. Toast.LENGTH_LONG).show();
  36. return;
  37. }
  38. // *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.
  39. Intent intent = new Intent(MY_BROADCAST_INHOUSE);
  40. intent.putExtra("PARAM", "Sensitive Info from Sender");
  41. // *** POINT 14 *** Require the in-house signature permission to limit receivers.
  42. sendBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION");
  43. }
  44. public void onSendOrderedClick(View view) {
  45. // *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.
  46. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  47. Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",
  48. Toast.LENGTH_LONG).show();
  49. return;
  50. }
  51. // *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.
  52. Intent intent = new Intent(MY_BROADCAST_INHOUSE);
  53. intent.putExtra("PARAM", "Sensitive Info from Sender");
  54. // *** POINT 14 *** Require the in-house signature permission to limit receivers.
  55. sendOrderedBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION",
  56. mResultReceiver, null, 0, null, null);
  57. }
  58. private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
  59. @Override
  60. public void onReceive(Context context, Intent intent) {
  61. // *** POINT 15 *** Handle the received result data carefully and securely,
  62. // even though the data came from an in-house application.
  63. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  64. String data = getResultData();
  65. InhouseSenderActivity.this.logLine(String.format("Received result: ¥"%s¥"", data));
  66. }
  67. };
  68. private TextView mLogView;
  69. @Override
  70. public void onCreate(Bundle savedInstanceState) {
  71. super.onCreate(savedInstanceState);
  72. setContentView(R.layout.main);
  73. mLogView = (TextView)findViewById(R.id.logview);
  74. }
  75. private void logLine(String line) {
  76. mLogView.append(line);
  77. mLogView.append("¥n");
  78. }
  79. }

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. }

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

4.2.1.3.md - 图2