一、前言
partner activity是指定可信应用程序可使用的Activity。通常用于合作伙伴业务之间共享数据和功能。其风险是:第三方应用可以读取信息,应确保没有第三方恶意程序读取,注意事项如下:
1、不指定taskAffinity.@b@2、不指定launchMode@b@3、不定义intent过滤器,导出属性显式设置为true@b@4、通过白名单预设请求应用的证书。@b@5、确认接收的intent来自合作方的应用。@b@6、只返回合作方授权披露的信息。
二、原示例代码
1.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.partneractivity" >@b@ @b@ <application@b@ android:allowBackup="false"@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ @b@ <!-- Partner activity -->@b@ @b@ <activity@b@ android:name=".PartnerActivity"@b@ android:exported="true" />@b@ @b@ </application>@b@</manifest>
2.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、不将FLAG_ACTIVITY_NEW_TASK标志用于启动一项活动的intent。@b@3、仅使用putExtra()发送合作方授权的信息。@b@4、显式调用partner activity。@b@5、使用startActivityForResult() 去调用Partner Activity.@b@6、验证收到的数据。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> @b@ @b@<manifest xmlns:android="http://schemas.android.com/apk/res/android" @b@ _package="org.jssec.android.activity.partneruser" >@b@ @b@ <application@b@ android:allowBackup="false"@b@ android:icon="@drawable/ic_launcher"@b@ android:label="@string/app_name" >@b@ <activity@b@ android:name="org.jssec.android.activity.partneruser.PartnerUserActivity" @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>
package org.jssec.android.activity.partneruser;@b@ @b@import org.jssec.android.shared.PkgCertWhitelists;@b@import org.jssec.android.shared.Utils;@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 PartnerUserActivity extends Activity {@b@ @b@ private static PkgCertWhitelists sWhitelists = null; @b@ private static void buildWhitelists(Context context) {@b@ boolean isdebug = Utils.isDebuggable(context);@b@ sWhitelists = new PkgCertWhitelists();@b@ @b@ // Register the certificate hash value of partner application org.jssec.android.activity.partneractivity. @b@ sWhitelists.add("org.jssec.android.activity.partneractivity", isdebug ? "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA");@b@ @b@ // Register the other partner applications in the same way.@b@ }@b@ private static boolean checkPartner(Context context, String pkgname) {@b@ if (sWhitelists == null) buildWhitelists(context); @b@ return sWhitelists.test(context, pkgname);@b@ } @b@ @b@ private static final int REQUEST_CODE = 1;@b@ @b@ // Information related the target partner activity @b@ private static final String TARGET_PACKAGE = "org.jssec.android.activity.partneractivity"; @b@ private static final String TARGET_ACTIVITY = "org.jssec.android.activity.partneractivity.PartnerActivity";@b@ @b@ @Override @b@ public void onCreate(Bundle savedInstanceState) {@b@ super.onCreate(savedInstanceState);@b@ setContentView(R.layout.main);@b@ }@b@ public void onUseActivityClick(View view) {@b@ @b@ if (!checkPartner(this, TARGET_PACKAGE)) { @b@ Toast.makeText(this, "Target application is not a partner application.",Toast.LENGTH_LONG).show();@b@ return;@b@ }@b@ @b@ try { @b@ Intent intent = new Intent();@b@ intent.putExtra("PARAM", "Info for Partner Apps");@b@ intent.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);@b@ if (resultCode != RESULT_OK) return;@b@ @b@ switch (requestCode) {@b@ case REQUEST_CODE:@b@ String result = data.getStringExtra("RESULT");@b@ @b@ // even though the data comes from a partner 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: 2 Handling Input Data Carefully and Secure ")); @b@ break;@b@ }@b@ }@b@}
package org.jssec.android.shared;@b@ @b@import java.util.HashMap;@b@import java.util.Map;@b@import android.content.Context;@b@ @b@public class PkgCertWhitelists { @b@ private Map<String, String> mWhitelists = new HashMap<String, String>();@b@ @b@ public boolean add(String pkgname, String sha256) {@b@ if (pkgname == null) return false; @b@ if (sha256 == null) return false;@b@ @b@ sha256 = sha256.replaceAll(" ", ""); @b@ if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars @b@ sha256 = sha256.toUpperCase(); @b@ if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char@b@ @b@ mWhitelists.put(pkgname, sha256);@b@ return true;@b@ }@b@ public boolean test(Context ctx, String pkgname) { @b@ // Get the correct hash value which corresponds to pkgname.@b@ String correctHash = mWhitelists.get(pkgname);@b@ // Compare the actual hash value of pkgname with the correct hash value.@b@ return PkgCert.test(ctx, pkgname, correctHash);@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@ 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@}