一、注意事项
1、定义一个内部签名权限来接收广播。
2、定义使用内部签名的许可来接收结果。
3、定义exported属性为true。
4、使用静态Broadcat Receiver请求一个内部签名许可。
5、请求内部签名许可来注册Dynamic Broadcast Receiver。
6、验证内部签名许可的正确性。
7、处理收到的intent,验证其正确性。
8、敏感信息可以在内部应用中返回。
9、当导出apk的时候,用相同的developer key进行签名。
二、原代码示例
1.InhouseReceiver.java
package org.jssec.android.broadcast.inhousereceiver;@b@ @b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@ @b@import android.app.Activity;@b@import android.content.BroadcastReceiver;@b@import android.content.Context;@b@import android.content.Intent;@b@import android.widget.Toast;@b@ @b@public class InhouseReceiver extends BroadcastReceiver {@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousereceiver.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@ return sMyCertHash;@b@ }@b@ @b@ private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";@b@ @b@ public boolean isDynamic = false;@b@ private String getName() {@b@ return isDynamic ? "In-house Dynamic Broadcast Receiver" : "In-house Static Broadcast Receiver"; }@b@ @b@ @Override@b@ public void onReceive(Context context, Intent intent) {@b@ @b@ // *** POINT 6 *** Verify that the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(context, MY_PERMISSION, myCertHash(context))) {@b@ Toast.makeText(context, "The in-house signature permission is not declared by in-house application.", Toast.LENGTH_LONG).show();@b@ return;@b@ }@b@ @b@ // *** POINT 7 *** Handle the received intent carefully and securely,@b@ // even though the Broadcast was sent 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 (MY_BROADCAST_INHOUSE.equals(intent.getAction())) {@b@ String param = intent.getStringExtra("PARAM");@b@ Toast.makeText(context,String.format("%s:¥nReceived param: ¥"%s¥"", getName(), param), Toast.LENGTH_SHORT).show();@b@ }@b@ @b@ // *** POINT 8 *** Sensitive information can be returned since the requesting application is in-house.@b@ setResultCode(Activity.RESULT_OK);@b@ setResultData(String.format("Sensitive Info from %s", getName()));@b@ abortBroadcast();@b@ }@b@}
2.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>@b@<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.broadcast.inhousereceiver" >@b@ <!-- *** POINT 1 *** Define an in-house signature permission to receive Broadcasts -->@b@ <permission android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION" android:protectionLevel="signature" />@b@ @b@ <!-- *** POINT 2 *** Declare to use the in-house signature permission to receive results. -->@b@ <uses-permission android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION" />@b@ @b@ <application@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name"@b@ android:allowBackup="false" >@b@ @b@ <!-- *** POINT 3 *** Explicitly set the exported attribute to true. -->@b@ <!-- *** POINT 4 *** Require the in-house signature permission by the Static Broadcast Receiver definition. -->@b@ @b@ <receiver@b@ android:name=".InhouseReceiver"@b@ android:permission="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION"@b@ android:exported="true">@b@ <intent-filter>@b@ <action android:name="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE" />@b@ </intent-filter>@b@ </receiver>@b@ @b@ <service android:name=".DynamicReceiverService" android:exported="false" />@b@ @b@ <activity@b@ android:name=".InhouseReceiverActivity"@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@ @b@</manifest>
3.InhouseReceiverActivity.java
package org.jssec.android.broadcast.inhousereceiver;@b@import android.app.Activity;@b@import android.content.Intent;@b@import android.os.Bundle;@b@import android.view.View;@b@ @b@public class InhouseReceiverActivity extends Activity {@b@ @Override@b@ public void onCreate(Bundle savedInstanceState) {@b@ super.onCreate(savedInstanceState);@b@ setContentView(R.layout.main);@b@ }@b@ @b@ public void onRegisterReceiverClick(View view) {@b@ Intent intent = new Intent(this, DynamicReceiverService.class);@b@ startService(intent);@b@ }@b@ @b@ public void onUnregisterReceiverClick(View view) {@b@ Intent intent = new Intent(this, DynamicReceiverService.class);@b@ stopService(intent);@b@ }@b@}
4.DynamicReceiverService.java
package org.jssec.android.broadcast.inhousereceiver;@b@ @b@import android.app.Service;@b@import android.content.Intent;@b@import android.content.IntentFilter;@b@import android.os.IBinder;@b@import android.widget.Toast;@b@ @b@public class DynamicReceiverService extends Service {@b@ @b@ private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";@b@ @b@ private InhouseReceiver mReceiver;@b@ @b@ @Override@b@ public IBinder onBind(Intent intent) {@b@ return null;@b@ }@b@ @b@ @Override@b@ public void onCreate() {@b@ super.onCreate();@b@ @b@ mReceiver = new InhouseReceiver();@b@ mReceiver.isDynamic = true;@b@ IntentFilter filter = new IntentFilter();@b@ filter.addAction(MY_BROADCAST_INHOUSE);@b@ filter.setPriority(1); // Prioritize Dynamic Broadcast Receiver, rather than Static Broadcast Receiver.@b@ @b@ // *** POINT 5 *** When registering a dynamic broadcast receiver, require the in-house signature permission.@b@ registerReceiver(mReceiver, filter, "org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION", null);@b@ @b@ Toast.makeText(this,"Registered Dynamic Broadcast Receiver.", Toast.LENGTH_SHORT).show();@b@ }@b@ @b@ @Override@b@ public void onDestroy() {@b@ super.onDestroy();@b@ unregisterReceiver(mReceiver);@b@ mReceiver = null;@b@ Toast.makeText(this,"Unregistered Dynamic Broadcast Receiver.", Toast.LENGTH_SHORT).show();@b@ }@b@}
5.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. 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@ @b@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@}
6.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;@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@ 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、处理收到的数据。@b@7、当导出APk,用与目标应用相同的developer key进行签名。
AndroidManifest.xml@b@ @b@<?xml version="1.0" encoding="utf-8"?>@b@<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.broadcast.inhousesender" >@b@ @b@ <uses-permission android:name="android.permission.BROADCAST_STICKY"/>@b@ @b@ <!-- *** POINT 10 *** Define an in-house signature permission to receive results. -->@b@ <permission@b@ android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION"@b@ android:protectionLevel="signature" />@b@ @b@ <!-- *** POINT 11 *** Declare to use the in-house signature permission to receive Broadcasts. -->@b@ <uses-permission android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION" />@b@ @b@ <application@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name"@b@ android:allowBackup="false" >@b@ @b@ <activity@b@ android:name="org.jssec.android.broadcast.inhousesender.InhouseSenderActivity"@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>
InhouseSenderActivity.java@b@ @b@package org.jssec.android.broadcast.inhousesender;@b@ @b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@ @b@import android.app.Activity;@b@import android.content.BroadcastReceiver;@b@import android.content.Context;@b@import android.content.Intent;@b@import android.os.Bundle;@b@import android.view.View;@b@import android.widget.TextView;@b@import android.widget.Toast;@b@ @b@public class InhouseSenderActivity extends Activity {@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousesender.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@ private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";@b@ @b@ public void onSendNormalClick(View view) {@b@ @b@ // *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {@b@ Toast.makeText(this, "The in-house signature permission is not declared by in-house application.", Toast.LENGTH_LONG).show();@b@ return;@b@ }@b@ @b@ // *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.@b@ Intent intent = new Intent(MY_BROADCAST_INHOUSE);@b@ intent.putExtra("PARAM", "Sensitive Info from Sender");@b@ @b@ // *** POINT 14 *** Require the in-house signature permission to limit receivers.@b@ sendBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION");@b@ }@b@ @b@ public void onSendOrderedClick(View view) {@b@ // *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.@b@ if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {@b@ Toast.makeText(this, "The in-house signature permission is not declared by in-house application.", Toast.LENGTH_LONG).show();@b@ return;@b@ }@b@ // *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.@b@ Intent intent = new Intent(MY_BROADCAST_INHOUSE);@b@ intent.putExtra("PARAM", "Sensitive Info from Sender");@b@ @b@ // *** POINT 14 *** Require the in-house signature permission to limit receivers.@b@ sendOrderedBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION",@b@ mResultReceiver, null, 0, null, null);@b@ }@b@ @b@ private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {@b@ @Override@b@ public void onReceive(Context context, Intent intent) {@b@ @b@ // *** POINT 15 *** Handle the received result data carefully and securely,@b@ // even though the data came from an in-house application.@b@ // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."@b@ String data = getResultData();@b@ InhouseSenderActivity.this.logLine(String.format("Received result: ¥"%s¥"", data));@b@ }@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@ 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@ @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@ // 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@ @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;@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@ // Will not handle multiple signatures.@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@}