一、前言
只允许企业内部程序可使用的Activity。其风险有:第三方应用程序可以读取intent,应保护敏感信息不被第三方读取到敏感信息。注意事项如下:
1、定义内部签名授权。@b@2、不要指定taskAffinity。@b@3、不要指定launchMode。@b@4、请求内部的签名许可。@b@5、不定义intent过滤器,导出属性显式设置为true。@b@6、确认内部签名是由内部程序定义的。@b@7、验证收到的intent。@b@8、敏感信息可以在内部应用程序间返回。@b@9、在导出apk时,使用相同的developer key对apk进行签名。
二、原代码示例
1.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>@b@<manifest xmlns:android="http://schemas.android.com/apk/res/android" _package="org.jssec.android.activity.inhouseactivity" >@b@ @b@<permission@b@ android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION"@b@ android:protectionLevel="signature" />@b@ @b@<application@b@ android:allowBackup="false"@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ @b@ <!-- In-house Activity -->@b@ @b@ <activity@b@ android:name="org.jssec.android.activity.inhouseactivity.InhouseActivity"@b@ android:exported="true"@b@ android:permission="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" />@b@ </application>@b@</manifest>
2.InhouseActivity.java
package org.jssec.android.activity.inhouseactivity;@b@ @b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@import android.app.Activity;@b@import android.content.Context;@b@import android.content.Intent;@b@import android.os.Bundle;@b@import android.view.View;@b@import android.widget.Toast;@b@ @b@public class InhouseActivity extends Activity {@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.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@ @Override@b@ public void onCreate(Bundle savedInstanceState) {@b@ super.onCreate(savedInstanceState);@b@ setContentView(R.layout.main);@b@ @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.",@b@ Toast.LENGTH_LONG).show();@b@ finish();@b@ return;@b@ }@b@ @b@ // *** POINT 7 *** Handle the received intent carefully and securely, even though the intent 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@ String param = getIntent().getStringExtra("PARAM");@b@ Toast.makeText(this, String.format("Received param: "3.2 Handling Input Data Carefully and Secur }@b@ @b@ public void onReturnResultClick(View view) {@b@ // *** POINT 8 *** Sensitive information can be returned since the requesting application is in-house.@b@ Intent intent = new Intent();@b@ intent.putExtra("RESULT", "Sensitive Info");@b@ setResult(RESULT_OK, intent);@b@ finish();@b@ }@b@}
3.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. @b@ 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@}
4.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@ 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@ @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、在目标应用为内部时,敏感信息可以用putExtra()发送。@b@5、使用显式intent来调用内部Activity。@b@6、验证收到的数据。@b@7、当导出apk时,用与目标程序相同的developer key进行签名。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>@b@<manifest xmlns:android="http://schemas.android.com/apk/res/android"@b@ package="org.jssec.android.activity.inhouseuser" >@b@ android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" />@b@ @b@ <application@b@ android:allowBackup="false"@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ @b@ <activity@b@ android:name="org.jssec.android.activity.inhouseuser.InhouseUserActivity"@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>
InhouseUserActivity.java
package org.jssec.android.activity.inhouseuser;@b@ @b@import org.jssec.android.shared.PkgCert;@b@import org.jssec.android.shared.SigPerm;@b@import org.jssec.android.shared.Utils;@b@ @b@import android.app.Activity;@b@import android.content.ActivityNotFoundException;@b@import android.content.Context;@b@import android.content.Intent;@b@import android.os.Bundle;@b@import android.view.View;@b@import android.widget.Toast;@b@ @b@public class InhouseUserActivity extends Activity {@b@ @b@ // Target Activity information@b@ private static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity";@b@ private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.InhouseActivity";@b@ @b@ // In-house Signature Permission@b@ private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.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 int REQUEST_CODE = 1;@b@ @b@ @Override@b@ public void onCreate(Bundle savedInstanceState) {@b@ super.onCreate(savedInstanceState);@b@ setContentView(R.layout.main);@b@ }@b@ @b@ public void onUseActivityClick(View view) {@b@ @b@ // *** POINT 11 *** 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 12 *** Verify that the destination application is signed with the in-house certificate.@b@ if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {@b@ Toast.makeText(this, "Target application is not an in-house application.", Toast.LENGTH_LONG).show();@b@ return;@b@ }@b@ @b@ try {@b@ Intent intent = new Intent();@b@ // *** POINT 13 *** Sensitive information can be sent only by putExtra() since the destination application is in-house.@b@ intent.putExtra("PARAM", "Sensitive Info");@b@ @b@ // *** POINT 14 *** Use explicit intents to call an In-house Activity. i@b@ ntent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);@b@ startActivityForResult(intent, REQUEST_CODE);@b@ }@b@ catch (ActivityNotFoundException e) {@b@ Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show();@b@ }@b@ }@b@ @b@ @Override@b@ public void onActivityResult(int requestCode, int resultCode, Intent data) {@b@ super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return;@b@ @b@ switch (requestCode) {@b@ case REQUEST_CODE:@b@ String result = data.getStringExtra("RESULT");@b@ // *** POINT 15 *** Handle the received 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@ Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show();@b@ break;@b@ }@b@ }@b@}
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@ 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@ @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@ } catch (NameNotFoundException e) {@b@ return null;@b@ }@b@ }@b@}
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@ 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@ 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@}