4.1.1.4 创建/使用内部活动
内部活动是禁止其他内部应用以外的应用使用的活动。 它们用于内部开发的应用,以便安全地共享信息和功能。
第三方应用可能会读取用于启动活动的意图。 因此,如果你将敏感信息放入用于启动活动的意图中,有必要采取对策来确保它不会被恶意第三方读取。
下面展示了创建内部活动的示例代码。
要点(创建活动):
定义内部签名权限。
不要指定
taskAffinity。不要指定
launchMode。需要内部签名权限。
不要定义意图过滤器,并将导出属性显式设为
true。确认内部签名权限是由内部应用的。
尽管意图是从内部应用发送的,仔细和安全地处理接收到的意图。
由于请求的应用是内部的,因此可以返回敏感信息。
导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.activity.inhouseactivity" ><!-- *** POINT 1 *** Define an in-house signature permission --><permissionandroid:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION"android:protectionLevel="signature" /><applicationandroid:allowBackup="false"android:icon="@drawable/ic_launcher"android:label="@string/app_name" ><!-- In-house Activity --><!-- *** POINT 2 *** Do not specify taskAffinity --><!-- *** POINT 3 *** Do not specify launchMode --><!-- *** POINT 4 *** Require the in-house signature permission --><!-- *** POINT 5 *** Do not define the intent filter and explicitly set the exported attribute totrue --><activityandroid:name="org.jssec.android.activity.inhouseactivity.InhouseActivity"android:exported="true"android:permission="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" /></application></manifest>
InhouseActivity.java
package org.jssec.android.activity.inhouseactivity;import org.jssec.android.shared.SigPerm;import org.jssec.android.shared.Utils;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Toast;public class InhouseActivity extends Activity {// In-house Signature Permissionprivate static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION";// In-house certificate hash valueprivate static String sMyCertHash = null;private static String myCertHash(Context context) {if (sMyCertHash == null) {if (Utils.isDebuggable(context)) {// Certificate hash value of "androiddebugkey" in the debug.keystore.sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";} else {// Certificate hash value of "my company key" in the keystore.sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";}}return sMyCertHash;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);// *** POINT 6 *** Verify that the in-house signature permission is defined by an in-house application.if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",Toast.LENGTH_LONG).show();finish();return;}// *** POINT 7 *** Handle the received intent carefully and securely, even though the intent was sent from an in-house application.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."String param = getIntent().getStringExtra("PARAM");Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show();}public void onReturnResultClick(View view) {// *** POINT 8 *** Sensitive information can be returned since the requesting application is inhouse.Intent intent = new Intent();intent.putExtra("RESULT", "Sensitive Info");setResult(RESULT_OK, intent);finish();}}
SigPerm.java
package org.jssec.android.shared;import android.content.Context;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.content.pm.PermissionInfo;public class SigPerm {public static boolean test(Context ctx, String sigPermName, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, sigPermName));}public static String hash(Context ctx, String sigPermName) {if (sigPermName == null) return null;try {// Get the package name of the application which declares a permission named sigPermName.PackageManager pm = ctx.getPackageManager();PermissionInfo pi;pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);String pkgname = pi.packageName;// Fail if the permission named sigPermName is not a Signature Permissionif (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;// Return the certificate hash value of the application which declares a permission named sigPermName.return PkgCert.hash(ctx, pkgname);} catch (NameNotFoundException e) {return null;}}}
PkgCert.java
package org.jssec.android.shared;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.content.pm.Signature;public class PkgCert {public static boolean test(Context ctx, String pkgname, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, pkgname));}public static String hash(Context ctx, String pkgname) {if (pkgname == null) return null;try {PackageManager pm = ctx.getPackageManager();PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.Signature sig = pkginfo.signatures[0];byte[] cert = sig.toByteArray();byte[] sha256 = computeSha256(cert);return byte2hex(sha256);} catch (NameNotFoundException e) {return null;}}private static byte[] computeSha256(byte[] data) {try {return MessageDigest.getInstance("SHA-256").digest(data);} catch (NoSuchAlgorithmException e) {return null;}}private static String byte2hex(byte[] data) {if (data == null) return null;final StringBuilder hexadecimal = new StringBuilder();for (final byte b : data) {hexadecimal.append(String.format("%02X", b));}return hexadecimal.toString();}}
要点 9:导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。

使用内部活动的代码如下:
要点(使用活动):
声明你要使用内部签名权限。
确认内部签名权限是由内部应用定义的。
验证目标应用是否使用内部证书签名。
由于目标应用是内部的,所以敏感信息只能由
putExtra()发送。使用显式意图调用内部活动。
即使数据来自内部应用,也要小心并安全地处理接收到的数据。
导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.activity.inhouseuser" ><!-- *** POINT 10 *** Declare to use the in-house signature permission --><uses-permissionandroid:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" /><applicationandroid:allowBackup="false"android:icon="@drawable/ic_launcher"android:label="@string/app_name" ><activityandroid:name="org.jssec.android.activity.inhouseuser.InhouseUserActivity"android:label="@string/app_name"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
InhouseUserActivity.java
package org.jssec.android.activity.inhouseuser;import org.jssec.android.shared.PkgCert;import org.jssec.android.shared.SigPerm;import org.jssec.android.shared.Utils;import android.app.Activity;import android.content.ActivityNotFoundException;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Toast;public class InhouseUserActivity extends Activity {// Target Activity informationprivate static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity";private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.InhouseActivity";// In-house Signature Permissionprivate static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION";// In-house certificate hash valueprivate static String sMyCertHash = null;private static String myCertHash(Context context) {if (sMyCertHash == null) {if (Utils.isDebuggable(context)) {// Certificate hash value of "androiddebugkey" in the debug.keystore.sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";} else {// Certificate hash value of "my company key" in the keystore.sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";}}return sMyCertHash;}private static final int REQUEST_CODE = 1;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);}public void onUseActivityClick(View view) {// *** POINT 11 *** Verify that the in-house signature permission is defined by an in-house application.if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",Toast.LENGTH_LONG).show();return;}// ** POINT 12 *** Verify that the destination application is signed with the in-house certificate.if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {Toast.makeText(this, "Target application is not an in-house application.", Toast.LENGTH_LONG).show();return;}try {Intent intent = new Intent();// *** POINT 13 *** Sensitive information can be sent only by putExtra() since the destination application is in-house.intent.putExtra("PARAM", "Sensitive Info");// *** POINT 14 *** Use explicit intents to call an In-house Activity.intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);startActivityForResult(intent, REQUEST_CODE);} catch (ActivityNotFoundException e) {Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show();}}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode != RESULT_OK) return;switch (requestCode) {case REQUEST_CODE:String result = data.getStringExtra("RESULT");// *** POINT 15 *** Handle the received data carefully and securely,// even though the data came from an in-house application.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show();break;}}}
SigPerm.java
package org.jssec.android.shared;import android.content.Context;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.content.pm.PermissionInfo;public class SigPerm {public static boolean test(Context ctx, String sigPermName, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, sigPermName));}public static String hash(Context ctx, String sigPermName) {if (sigPermName == null) return null;try {// Get the package name of the application which declares a permission named sigPermName.PackageManager pm = ctx.getPackageManager();PermissionInfo pi;pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);String pkgname = pi.packageName;// Fail if the permission named sigPermName is not a Signature Permissionif (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;// Return the certificate hash value of the application which declares a permission named sigPermName.return PkgCert.hash(ctx, pkgname);} catch (NameNotFoundException e) {return null;}}}
PkgCert.java
package org.jssec.android.shared;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.content.pm.Signature;public class PkgCert {public static boolean test(Context ctx, String pkgname, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, pkgname));}public static String hash(Context ctx, String pkgname) {if (pkgname == null) return null;try {PackageManager pm = ctx.getPackageManager();PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.Signature sig = pkginfo.signatures[0];byte[] cert = sig.toByteArray();byte[] sha256 = computeSha256(cert);return byte2hex(sha256);} catch (NameNotFoundException e) {return null;}}private static byte[] computeSha256(byte[] data) {try {return MessageDigest.getInstance("SHA-256").digest(data);} catch (NoSuchAlgorithmException e) {return null;}}private static String byte2hex(byte[] data) {if (data == null) return null;final StringBuilder hexadecimal = new StringBuilder();for (final byte b : data) {hexadecimal.append(String.format("%02X", b));}return hexadecimal.toString();}}
要点 16:导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。

