4.1.1.4 创建/使用内部活动

内部活动是禁止其他内部应用以外的应用使用的活动。 它们用于内部开发的应用,以便安全地共享信息和功能。

第三方应用可能会读取用于启动活动的意图。 因此,如果你将敏感信息放入用于启动活动的意图中,有必要采取对策来确保它不会被恶意第三方读取。

下面展示了创建内部活动的示例代码。

要点(创建活动):

  1. 定义内部签名权限。

  2. 不要指定taskAffinity

  3. 不要指定launchMode

  4. 需要内部签名权限。

  5. 不要定义意图过滤器,并将导出属性显式设为true

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

  7. 尽管意图是从内部应用发送的,仔细和安全地处理接收到的意图。

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

  9. 导出 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.activity.inhouseactivity" >
  4. <!-- *** POINT 1 *** Define an in-house signature permission -->
  5. <permission
  6. android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION"
  7. android:protectionLevel="signature" />
  8. <application
  9. android:allowBackup="false"
  10. android:icon="@drawable/ic_launcher"
  11. android:label="@string/app_name" >
  12. <!-- In-house Activity -->
  13. <!-- *** POINT 2 *** Do not specify taskAffinity -->
  14. <!-- *** POINT 3 *** Do not specify launchMode -->
  15. <!-- *** POINT 4 *** Require the in-house signature permission -->
  16. <!-- *** POINT 5 *** Do not define the intent filter and explicitly set the exported attribute to
  17. true -->
  18. <activity
  19. android:name="org.jssec.android.activity.inhouseactivity.InhouseActivity"
  20. android:exported="true"
  21. android:permission="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" />
  22. </application>
  23. </manifest>

InhouseActivity.java

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

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

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

4.1.1.4.md - 图1

使用内部活动的代码如下:

要点(使用活动):

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

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

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

  4. 由于目标应用是内部的,所以敏感信息只能由putExtra()发送。

  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.activity.inhouseuser" >
  4. <!-- *** POINT 10 *** Declare to use the in-house signature permission -->
  5. <uses-permission
  6. android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" />
  7. <application
  8. android:allowBackup="false"
  9. android:icon="@drawable/ic_launcher"
  10. android:label="@string/app_name" >
  11. <activity
  12. android:name="org.jssec.android.activity.inhouseuser.InhouseUserActivity"
  13. android:label="@string/app_name"
  14. android:exported="true" >
  15. <intent-filter>
  16. <action android:name="android.intent.action.MAIN" />
  17. <category android:name="android.intent.category.LAUNCHER" />
  18. </intent-filter>
  19. </activity>
  20. </application>
  21. </manifest>

InhouseUserActivity.java

  1. package org.jssec.android.activity.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.ActivityNotFoundException;
  7. import android.content.Context;
  8. import android.content.Intent;
  9. import android.os.Bundle;
  10. import android.view.View;
  11. import android.widget.Toast;
  12. public class InhouseUserActivity extends Activity {
  13. // Target Activity information
  14. private static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity";
  15. private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.InhouseActivity";
  16. // In-house Signature Permission
  17. private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION";
  18. // In-house certificate hash value
  19. private static String sMyCertHash = null;
  20. private static String myCertHash(Context context) {
  21. if (sMyCertHash == null) {
  22. if (Utils.isDebuggable(context)) {
  23. // Certificate hash value of "androiddebugkey" in the debug.keystore.
  24. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  25. } else {
  26. // Certificate hash value of "my company key" in the keystore.
  27. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  28. }
  29. }
  30. return sMyCertHash;
  31. }
  32. private static final int REQUEST_CODE = 1;
  33. @Override
  34. public void onCreate(Bundle savedInstanceState) {
  35. super.onCreate(savedInstanceState);
  36. setContentView(R.layout.main);
  37. }
  38. public void onUseActivityClick(View view) {
  39. // *** POINT 11 *** Verify that the in-house signature permission is defined by an in-house application.
  40. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  41. Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",
  42. Toast.LENGTH_LONG).show();
  43. return;
  44. }
  45. // ** POINT 12 *** Verify that the destination application is signed with the in-house certificate.
  46. if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
  47. Toast.makeText(this, "Target application is not an in-house application.", Toast.LENGTH_LONG).show();
  48. return;
  49. }
  50. try {
  51. Intent intent = new Intent();
  52. // *** POINT 13 *** Sensitive information can be sent only by putExtra() since the destination application is in-house.
  53. intent.putExtra("PARAM", "Sensitive Info");
  54. // *** POINT 14 *** Use explicit intents to call an In-house Activity.
  55. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
  56. startActivityForResult(intent, REQUEST_CODE);
  57. } catch (ActivityNotFoundException e) {
  58. Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show();
  59. }
  60. }
  61. @Override
  62. public void onActivityResult(int requestCode, int resultCode, Intent data) {
  63. super.onActivityResult(requestCode, resultCode, data);
  64. if (resultCode != RESULT_OK) return;
  65. switch (requestCode) {
  66. case REQUEST_CODE:
  67. String result = data.getStringExtra("RESULT");
  68. // *** POINT 15 *** Handle the received data carefully and securely,
  69. // even though the data came from an in-house application.
  70. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  71. Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show();
  72. break;
  73. }
  74. }
  75. }

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

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

4.1.1.4.md - 图2