How to secure the Partner Activities (2)

บทนำ (Overview)

“Partner activities”  เป็น “Activities” ที่สมารถถูกเรียกใช้งานจากเฉพาะ “Application” ระบุไว้เท่านั้น เป้าหมายก็เพื่อสามารถให้โปรแกรมต่างขององค์กรสามารถใช้ข้อมูลหรือฟังก์ชันการทำงานร่วมกันได้อย่างปลอดภัย ในบทความนี้กล่าวถึงส่วนของโปรแกรมที่เป็น Partner มาเรียกใช้ Activity ที่เราจัดเตรียมเอาไว้ให้ (สามารถติดตามได้จากบทความ How to secure the Partner Activities (1))

ระดับของความเสี่ยงและมาตรการป้องกันของการใช้งาน “Activities” ให้ปลอดภัยนั้น ขึ้นอยู่กับการนำไปใช้งาน ซึ่งเราสามารถที่จะจำแนกได้เป็น 4 ประเภทดังต่อไปนี้

เงื่อนไข ประเภท อธิบาย
สำหรับทุก “Application” Public Activity เป็น “Activity” ที่สามารถถูกเรียกใช้งานจาก Application ที่ติดตั้งอยู่ในเครื่องเดียวกัน
เฉพาะ “Application” อนุญาต Partner Activity เป็น “Activity” ที่สามารถเรียกใช้งานเฉพาะ “Application” อนุญาตเท่านั้น
เฉพาะ “Application” ภายใน In-house Activity เป็น “Activity” ที่สามารถใช้งานเฉพาะ Application ที่พัฒนาเฉพาะองค์กรเดียวกันเท่านั้น
ไม่อนุญาต “Application” อื่น Private Activity เป็น “Activity” ที่ไม่สามารถเรียกใช้งานจาก “Application” อื่น ๆ ที่ติดตั้งอยู่ในเครื่องกัน ซึ่งเป็น “Activity” ที่มีความปลอดภัยสูงสุด

ความเสี่ยง (Risks)

อย่างไรก็ตามการใช้งาน Partner Activities อาจมีความเสี่ยงที่โปรแกรมประสงค์ร้ายสามารถที่จะอ่าน “Intent” ที่เรียกใช้งาน “Partner Activities” โดยเฉพาะอย่างยิ่งถ้าภายใน “Intent” มีข้อมูลสำคัญบรรจุอยู่ (Sensitive Information)

การควบคุมความปลอดภัย (Control Activities)

ลำดับ การควบคุม สาเหตุ
ขั้นตอนการใช้งาน “Activity”
1 มีการตรวจสอบว่า “Certificate“ ของ “Application“ ที่ร้องขอใช้งานตรงกับ “White list“ จัดเตรียมไว้ ตรวจสอบให้แน่ใจว่า มีการตรวจสอบว่า “Certificate“ ของ “Application“ ที่ร้องขอใช้งานตรงกับ “White list“ จัดเตรียมไว้
2 จะต้องไม่ตั้งค่า “FLAG_ACTIVITY_NEW_TASK” สำหรับ Intent ที่เรียกใช้งาน “Activity” ตรวจสอบให้แน่ใจว่า “FLAG_ACTIVITY_NEW_TASK” ไม่ได้ถูกตั้งค่าใน “setFlags()” หรือ “addFlags()” ตอนสร้าง “Intent” ซึ่งก็คือ startActivity() หรือ startActivityForResult() เพื่อป้องกันการเปลี่ยนแปลง “launch mode” ของ “Activity” ที่กำหนดอยู่ใน “AndroidManifest.xml” นำไปสู่อ่านข้อมูลสำคัญ (Sensitive information) ที่อยู่ ใน “Intent” จาก “Application” ประสงค์ร้าย
3 ข้อมูลต้องถูกส่งให้ “Partner Activity” เท่านั้น ตรวจสอบให้แน่ใจว่า ข้อมูลต้องถูกส่งให้ Partner Activity เท่านั้น โดยใช้ putExtra().
4 มีการใช้งาน “Explicit Intent” โดยระบุ เพื่อเรียกใช้งาน “Partner Activities” ตรวจสอบให้แน่ใจว่า มีการใช้งาน “Explicit Intent” โดยระบุ เพื่อเรียกใช้งาน “Partner Activities”
5 จัดการข้อมูลตอบกลับอย่างปลอดภัย (Input validation) ตรวจสอบให้แน่ใจว่า ข้อมูลนำเข้ามีการตรวจสอบ (Input validation) อย่างปลอดภัยดังนี้

  • ข้อมูลนำเข้าจะต้องถูกรูปแบบ (Valid Format) เช่น “String” ไม่รวมอักขระพิเศษเป็นต้น ตามที่ตกลงกันไว้ (Business requirement)
  • ขนาดข้อมูลนำเข้า (Value range)

 การสอบทานโปรแกรม (Source code review)

privateActivity04

ขั้นตอนที่ 1 การตั้งค่าใน AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.itselectlab.android.activity.partneruser" >

    <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
        
        <activity android:name=".PartnerUserActivity" 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>

ขั้นตอนที่ 2 การเรียกใช้ “Partner Activity” จาก “Partner Application”


public class PartnerUserActivity extends Activity {

	// Control Activity 1: มีการตรวจสอบว่า "Certificate" ของ "Application" ที่ร้องขอใช้งานตรงกับ "White list" จัดเตรียมไว้
	private static PkgCertWhitelists sWhitelists = null;
	private static void buildWhitelists(Context context) {
		boolean isdebug = Utils.isDebuggable(context);
		sWhitelists = new PkgCertWhitelists();
		
		// เราจะสร้าง Whitelist ของ Application ที่เราอนุญาตโดยใช้ certificate hash value
		sWhitelists.add("org.itselectlab.android.activity.partneractivity", isdebug ?
			// Certificate hash value of "androiddebugkey" in the debug.keystore.
    			"0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" :
			// Certificate hash value of "partner key" in the keystore.
    			"D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA");
		
		// Register the other partner applications in the same way.
	}
	private static boolean checkPartner(Context context, String pkgname) {
		if (sWhitelists == null) buildWhitelists(context);
		return sWhitelists.test(context, pkgname);
	}
	
    private static final int REQUEST_CODE = 1;

    // ข้อมูล Package และ Activities ของ Partner Application
    private static final String TARGET_PACKAGE =  "org.itselectlab.android.activity.partneractivity";
    private static final String TARGET_ACTIVITY = "org.itselectlab.android.activity.partneractivity.PartnerActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    
    public void onUseActivityClick(View view) {

        // Control Activity 1: มีการตรวจสอบว่า "Certificate" ของ "Application" ที่ร้องขอใช้งานตรงกับ "White list" จัดเตรียมไว้
    	if (!checkPartner(this, TARGET_PACKAGE)) {
        	Toast.makeText(this, "Target application is not a partner application.", Toast.LENGTH_LONG).show();
            return;
        }
        
        try {
        	Intent intent = new Intent();
        	
        	// Control Activity 2: “FLAG_ACTIVITY_NEW_TASK” ไม่ได้ถูกตั้งค่าใน “setFlags()” หรือ “addFlags()” ตอนสร้าง “Intent” ซึ่งก็คือ startActivity() หรือ startActivityForResult() 
        	// Control Activity 3:ข้อมูลต้องถูกส่งให้ Partner Activity เท่านั้น โดยใช้ putExtra().
        	intent.putExtra("PARAM", "Info for Partner Apps");
        	
        	// Control Activity 4:  มีการใช้งาน “Explicit Intent” โดยระบุ เพื่อเรียกใช้งาน “Partner Activities”
        	intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
        	startActivityForResult(intent, REQUEST_CODE);
        }
        catch (ActivityNotFoundException e) {
        	Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public 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");
			
        	// Control Activity 5: ข้อมูลนำเข้ามีการตรวจสอบ (Input validation) อย่างปลอดภัย
        	// จากตัวอย่างข้างล่าง
        	// - ข้อมูลนำเข้าจะต้องถูกรูปแบบ (Valid Format) = กำหนดเป็น String
        	// - ขนาดข้อมูลนำเข้า (Value range)
        	Toast.makeText(this,
        			String.format("Target activity not found.", result), Toast.LENGTH_LONG).show();
			break;
		}
    }
}

ขั้นตอนที่ 3 Class ของ “PkgCertWhitelists”

public class PkgCertWhitelists {
    private Map&lt;String, String&gt; mWhitelists = new HashMap&lt;String, String&gt;();
     
    public boolean add(String pkgname, String sha256) {
        if (pkgname == null) return false;
        if (sha256 == null) return false;
         
        sha256 = sha256.replaceAll(" ", "");
        if (sha256.length() != 64) return false;    // SHA-256 -&gt; 32 bytes -&gt; 64 chars
        sha256 = sha256.toUpperCase();
        if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
         
        mWhitelists.put(pkgname, sha256);
        return true;
    }
     
    public boolean test(Context ctx, String pkgname) {
        // Get the correct hash value which corresponds to pkgname.
        String correctHash = mWhitelists.get(pkgname);
         
        // Compare the actual hash value of pkgname with the correct hash value.
        return PkgCert.test(ctx, pkgname, correctHash);
    }
}

ขั้นตอนที่ 4 Class ของ “PkgCert”

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();
    }

เอกสารอ้างอิง (Referrences)

ใส่ความเห็น