4.4.1.4 创建/使用内部服务

内部服务是除了内部应用以外的应用禁止使用的服务。 它们用于内部开发的应用,以便安全地共享信息和功能。 以下是使用Messenger绑定类型服务的示例。

要点(创建服务):

  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.service.inhouseservice.messenger" >
  4. <!-- *** POINT 1 *** Define an in-house signature permission -->
  5. <permission
  6. android:name="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION"
  7. android:protectionLevel="signature" />
  8. <application
  9. android:icon="@drawable/ic_launcher"
  10. android:label="@string/app_name"
  11. android:allowBackup="false" >
  12. <!-- Service using Messenger -->
  13. <!-- *** POINT 2 *** Require the in-house signature permission -->
  14. <!-- *** POINT 3 *** Do not define the intent filter and explicitly set the exported attribute to true. -->
  15. <service
  16. android:name="org.jssec.android.service.inhouseservice.messenger.InhouseMessengerService"
  17. android:exported="true"
  18. android:permission="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION" />
  19. </application>
  20. </manifest>

InhouseMessengerService.java

  1. package org.jssec.android.service.inhouseservice.messenger;
  2. import org.jssec.android.shared.SigPerm;
  3. import org.jssec.android.shared.Utils;
  4. import java.lang.reflect.Array;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import android.app.Service;
  8. import android.content.Context;
  9. import android.content.Intent;
  10. import android.os.Bundle;
  11. import android.os.Handler;
  12. import android.os.IBinder;
  13. import android.os.Message;
  14. import android.os.Messenger;
  15. import android.os.RemoteException;
  16. import android.widget.Toast;
  17. public class InhouseMessengerService extends Service{
  18. // In-house signature permission
  19. private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION";
  20. // In-house certificate hash value
  21. private static String sMyCertHash = null;
  22. private static String myCertHash(Context context) {
  23. if (sMyCertHash == null) {
  24. if (Utils.isDebuggable(context)) {
  25. // Certificate hash value of debug.keystore "androiddebugkey"
  26. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  27. } else {
  28. // Certificate hash value of keystore "my company key"
  29. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  30. }
  31. }
  32. return sMyCertHash;
  33. }
  34. // Manage clients(destinations of sending data) in a list
  35. private ArrayList<Messenger> mClients = new ArrayList<Messenger>();
  36. // Messenger used when service receive data from client
  37. private final Messenger mMessenger = new Messenger(new ServiceSideHandler(mClients));
  38. // Handler which handles message received from client
  39. private static class ServiceSideHandler extends Handler{
  40. private ArrayList<Messenger> mClients;
  41. public ServiceSideHandler(ArrayList<Messenger> clients){
  42. mClients = clients;
  43. }
  44. @Override
  45. public void handleMessage(Message msg){
  46. switch(msg.what){
  47. case CommonValue.MSG_REGISTER_CLIENT:
  48. // Add messenger received from client
  49. mClients.add(msg.replyTo);
  50. break;
  51. case CommonValue.MSG_UNREGISTER_CLIENT:
  52. mClients.remove(msg.replyTo);
  53. break;
  54. case CommonValue.MSG_SET_VALUE:
  55. // Send data to client
  56. sendMessageToClients(mClients);
  57. break;
  58. default:
  59. super.handleMessage(msg);
  60. break;
  61. }
  62. }
  63. }
  64. /**
  65. * Send data to client
  66. */
  67. private static void sendMessageToClients(ArrayList<Messenger> mClients){
  68. // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
  69. String sendValue = "Sensitive information (from Service)";
  70. // Send data to the registered client one by one.
  71. // Use iterator to send all clients even though clients are removed in the loop process.
  72. Iterator<Messenger> ite = mClients.iterator();
  73. while(ite.hasNext()){
  74. try {
  75. Message sendMsg = Message.obtain(null, CommonValue.MSG_SET_VALUE, null);
  76. Bundle data = new Bundle();
  77. data.putString("key", sendValue);
  78. sendMsg.setData(data);
  79. Messenger next = ite.next();
  80. next.send(sendMsg);
  81. } catch (RemoteException e) {
  82. // If client does not exits, remove it from a list.
  83. ite.remove();
  84. }
  85. }
  86. }
  87. public IBinder onBind(Intent intent) {
  88. // *** POINT 4 *** Verify that the in-house signature permission is defined by an in-house application.
  89. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  90. Toast.makeText(this, "In-house defined signature permission is not defined by in-house application.", Toast.LENGTH_LONG).show();
  91. return null;
  92. }
  93. // *** POINT 5 *** Handle the received intent carefully and securely,
  94. // even though the intent was sent from an in-house application.
  95. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  96. String param = intent.getStringExtra("PARAM");
  97. Toast.makeText(this, String.format("Received parameter ¥"%s¥".", param), Toast.LENGTH_LONG).show();
  98. return mMessenger.getBinder();
  99. }
  100. @Override
  101. public void onCreate() {
  102. Toast.makeText(this, "Service - onCreate()", Toast.LENGTH_SHORT).show();
  103. }
  104. @Override
  105. public void onDestroy() {
  106. Toast.makeText(this, "Service - onDestroy()", Toast.LENGTH_SHORT).show();
  107. }
  108. }

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.4.1.4.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.service.inhouseservice.messengeruser" >
  4. <!-- *** POINT 8 *** Declare to use the in-house signature permission. -->
  5. <uses-permission
  6. android:name="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name"
  10. android:allowBackup="false" >
  11. <activity
  12. android:name="org.jssec.android.service.inhouseservice.messengeruser.InhouseMessengerUserActivity"
  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>

InhouseMessengerUserActivity.java

  1. package org.jssec.android.service.inhouseservice.messengeruser;
  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.ComponentName;
  7. import android.content.Context;
  8. import android.content.Intent;
  9. import android.content.ServiceConnection;
  10. import android.os.Bundle;
  11. import android.os.Handler;
  12. import android.os.IBinder;
  13. import android.os.Message;
  14. import android.os.Messenger;
  15. import android.os.RemoteException;
  16. import android.view.View;
  17. import android.widget.Toast;
  18. public class InhouseMessengerUserActivity extends Activity {
  19. private boolean mIsBound;
  20. private Context mContext;
  21. // Destination (Requested) service application information
  22. private static final String TARGET_PACKAGE = "org.jssec.android.service.inhouseservice.messenger";
  23. private static final String TARGET_CLASS = "org.jssec.android.service.inhouseservice.messenger.InhouseMessengerService";
  24. // In-house signature permission
  25. private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION";
  26. // In-house certificate hash value
  27. private static String sMyCertHash = null;
  28. private static String myCertHash(Context context) {
  29. if (sMyCertHash == null) {
  30. if (Utils.isDebuggable(context)) {
  31. // Certificate hash value of debug.keystore "androiddebugkey"
  32. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
  33. } else {
  34. // Certificate hash value of keystore "my company key"
  35. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
  36. }
  37. }
  38. return sMyCertHash;
  39. }
  40. // Messenger used when this application receives data from service.
  41. private Messenger mServiceMessenger = null;
  42. // Messenger used when this application sends data to service.
  43. private final Messenger mActivityMessenger = new Messenger(new ActivitySideHandler());
  44. // Handler which handles message received from service
  45. private class ActivitySideHandler extends Handler {
  46. @Override
  47. public void handleMessage(Message msg) {
  48. switch (msg.what) {
  49. case CommonValue.MSG_SET_VALUE:
  50. Bundle data = msg.getData();
  51. String info = data.getString("key");
  52. // *** POINT 13 *** Handle the received result data carefully and securely,
  53. // even though the data came from an in-house application
  54. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
  55. Toast.makeText(mContext, String.format("Received ¥"%s¥" from service.", info),
  56. Toast.LENGTH_SHORT).show();
  57. break;
  58. default:
  59. super.handleMessage(msg);
  60. }
  61. }
  62. }
  63. // Connection used to connect with service. This is necessary when service is implemented with bindSrvice().
  64. private ServiceConnection mConnection = new ServiceConnection() {
  65. // This is called when the connection with the service has been established.
  66. @Override
  67. public void onServiceConnected(ComponentName className, IBinder service) {
  68. mServiceMessenger = new Messenger(service);
  69. Toast.makeText(mContext, "Connect to service", Toast.LENGTH_SHORT).show();
  70. try {
  71. // Send own messenger to service
  72. Message msg = Message.obtain(null, CommonValue.MSG_REGISTER_CLIENT);
  73. msg.replyTo = mActivityMessenger;
  74. mServiceMessenger.send(msg);
  75. } catch (RemoteException e) {
  76. // Service stopped abnormally
  77. }
  78. }
  79. // This is called when the service stopped abnormally and connection is disconnected.
  80. @Override
  81. public void onServiceDisconnected(ComponentName className) {
  82. mServiceMessenger = null;
  83. Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show();
  84. }
  85. };
  86. @Override
  87. public void onCreate(Bundle savedInstanceState) {
  88. super.onCreate(savedInstanceState);
  89. setContentView(R.layout.inhouseservice_activity);
  90. mContext = this;
  91. }
  92. // --- StartService control ---
  93. public void onStartServiceClick(View v) {
  94. // Start bindService
  95. doBindService();
  96. }
  97. public void onGetInfoClick(View v) {
  98. getServiceinfo();
  99. }
  100. public void onStopServiceClick(View v) {
  101. doUnbindService();
  102. }
  103. @Override
  104. protected void onDestroy() {
  105. super.onDestroy();
  106. doUnbindService();
  107. }
  108. /**
  109. * Connect to service
  110. */
  111. void doBindService() {
  112. if (!mIsBound){
  113. // *** POINT 9 *** Verify that the in-house signature permission is defined by an in-house application.
  114. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
  115. Toast.makeText(this, "In-house defined signature permission is not defined by in-house application.", Toast.LENGTH_LONG).show();
  116. return;
  117. }
  118. // *** POINT 10 *** Verify that the destination application is signed with the in-house certificate.
  119. if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
  120. Toast.makeText(this, "Destination(Requested) service application is not in-house application.", Toast.LENGTH_LONG).show();
  121. return;
  122. }
  123. Intent intent = new Intent();
  124. // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
  125. intent.putExtra("PARAM", "Sensitive information");
  126. // *** POINT 12 *** Use the explicit intent to call an in-house service.
  127. intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
  128. bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  129. mIsBound = true;
  130. }
  131. }
  132. /**
  133. * Disconnect service
  134. */
  135. void doUnbindService() {
  136. if (mIsBound) {
  137. unbindService(mConnection);
  138. mIsBound = false;
  139. }
  140. }
  141. /**
  142. * Get information from service
  143. */
  144. void getServiceinfo() {
  145. if (mServiceMessenger != null) {
  146. try {
  147. // Request sending information
  148. Message msg = Message.obtain(null, CommonValue.MSG_SET_VALUE);
  149. mServiceMessenger.send(msg);
  150. } catch (RemoteException e) {
  151. // Service stopped abnormally
  152. }
  153. }
  154. }
  155. }

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

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

4.4.1.4.md - 图2