บทนำ (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) อย่างปลอดภัยดังนี้
|
การสอบทานโปรแกรม (Source code review)
ขั้นตอนที่ 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<String, String> mWhitelists = new HashMap<String, String>(); 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 -> 32 bytes -> 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)
- Android Application Secure Design/Secure CodingGuidebook, February 1st, 2016 Edition, Japan Smartphone Security Association (JSSEC)
- https://developer.android.com/guide/topics/manifest/activity-element.html