Android ใช้Provider
เพื่อการสื่อสารที่ปลอดภัยในเครือข่าย อย่างไรก็ตาม บางครั้งก็พบช่องโหว่ในผู้ให้บริการรักษาความปลอดภัยเริ่มต้น เพื่อป้องกัน
ช่องโหว่เหล่านี้ Google Play
บริการช่วยให้คุณสามารถอัปเดตผู้ให้บริการความปลอดภัยของอุปกรณ์โดยอัตโนมัติ
เพื่อป้องกันการเจาะช่องโหว่ที่ทราบ การเรียกวิธีการของบริการ Google Play ช่วยให้คุณมั่นใจได้ว่า
ที่แอปของคุณกำลังทำงานในอุปกรณ์ที่มีการอัปเดตล่าสุด
จากการโจมตีที่ทราบแล้ว
ตัวอย่างเช่น มีการค้นพบช่องโหว่ใน OpenSSL (CVE-2014-0224) ซึ่งอาจทําให้แอปเปิดรับการโจมตีระหว่างเส้นทางที่ถอดรหัสการเข้าชมที่ปลอดภัยโดยที่ทั้ง 2 ฝ่ายไม่รู้ บริการ Google Play เวอร์ชัน 5.0 มีวิธีแก้ไข แต่แอปต้องตรวจสอบว่าได้ติดตั้งการแก้ไขนี้แล้ว การใช้วิธีการของบริการ Google Play จะช่วยให้มั่นใจได้ว่าแอปของคุณจะทำงานในอุปกรณ์ที่ปลอดภัยจากการโจมตีดังกล่าว
ข้อควรระวัง: การอัปเดตความปลอดภัยของอุปกรณ์Provider
ไม่ได้อัปเดตandroid.net.SSLCertificateSocketFactory
ซึ่งยังคงมีช่องโหว่ แทนที่จะใช้คลาสที่เลิกใช้งานแล้วนี้ เราขอแนะนำให้นักพัฒนาแอป
ใช้วิธีการระดับสูงสำหรับการโต้ตอบกับวิทยาการเข้ารหัส เช่น
HttpsURLConnection
แพตช์ผู้ให้บริการการรักษาความปลอดภัยโดยใช้ ProviderInstaller
หากต้องการอัปเดตผู้ให้บริการความปลอดภัยของอุปกรณ์ ให้ใช้คลาส ProviderInstaller
คุณสามารถยืนยันว่าผู้ให้บริการรักษาความปลอดภัยเป็นเวอร์ชันล่าสุด (และอัปเดตหากจำเป็น) ได้โดยเรียกใช้เมธอด installIfNeeded()
(หรือ installIfNeededAsync()
) ของคลาสนั้น ส่วนนี้จะอธิบายตัวเลือกเหล่านี้อย่างกว้างๆ ส่วนต่อไปนี้จะแสดง
ขั้นตอนและตัวอย่างที่ละเอียดยิ่งขึ้น
เมื่อคุณเรียกใช้ installIfNeeded()
ProviderInstaller
จะดำเนินการต่อไปนี้
- หาก
Provider
ของอุปกรณ์สำเร็จ อัปเดตแล้ว (หรือเป็นปัจจุบันแล้ว) เมธอดจะกลับมาโดยไม่ใส่ข้อยกเว้น - หากไลบรารีบริการ Google Play ของอุปกรณ์
ไม่ใช่เวอร์ชันล่าสุด
ขว้าง
GooglePlayServicesRepairableException
จากนั้นแอปจะจับข้อยกเว้นนี้และแสดงกล่องโต้ตอบที่เหมาะสมให้ผู้ใช้อัปเดตบริการ Google Play - หากเกิดข้อผิดพลาดที่กู้คืนไม่ได้ วิธีการจะแสดงข้อผิดพลาด
GooglePlayServicesNotAvailableException
เพื่อระบุว่าอัปเดตProvider
ไม่ได้ จากนั้นแอปจะจับข้อยกเว้นและเลือกการดำเนินการที่เหมาะสม เช่น แสดงผังขั้นตอนแก้ไขมาตรฐาน
เมธอด installIfNeededAsync()
ทำงานคล้ายกัน ยกเว้นว่าแทนที่จะส่งข้อยกเว้น ระบบจะเรียกเมธอดการเรียกคืนที่เหมาะสมเพื่อบ่งบอกความสําเร็จหรือไม่สําเร็จ
หากผู้ให้บริการรักษาความปลอดภัยเป็นเวอร์ชันล่าสุดแล้ว installIfNeeded()
จะใช้เวลา
ที่ไม่สำคัญเลยทีเดียว หากเมธอด
ต้องติดตั้ง Provider
ใหม่ ซึ่งอาจใช้เวลาสักครู่
ระยะเวลาตั้งแต่ 30-50 มิลลิวินาที (ในอุปกรณ์รุ่นใหม่) ไปจนถึง 350 มิลลิวินาที (ในอุปกรณ์รุ่นเก่ากว่า)
อุปกรณ์) วิธีหลีกเลี่ยงการส่งผลกระทบต่อประสบการณ์ของผู้ใช้
- โทร
installIfNeeded()
จากเธรดเครือข่ายที่ทำงานอยู่เบื้องหลังทันทีเมื่อเทรดโหลดขึ้น แทนที่จะต้องรอให้เทรดลองใช้เครือข่าย (ไม่เป็นอันตรายใดๆ ในการเรียกใช้เมธอดหลายครั้ง เพราะระบบจะแสดงผลทันทีหาก ไม่จำเป็นต้องอัปเดต) - เรียกใช้แบบอะซิงโครนัส
ของเมธอด
installIfNeededAsync()
หากชุดข้อความอาจส่งผลต่อประสบการณ์ของผู้ใช้ การบล็อก เช่น หากการเรียกมาจากกิจกรรมในเธรด UI (หากดำเนินการนี้ คุณจะต้องรอให้การดำเนินการเสร็จสมบูรณ์ก่อนจึงจะสื่อสารอย่างปลอดภัยได้ProviderInstaller
เรียกonProviderInstalled()
วิธีของผู้ฟังเพื่อส่งสัญญาณว่าสำเร็จ)
คำเตือน: หาก
ProviderInstaller
ไม่สามารถติดตั้ง Provider
ที่อัปเดตแล้ว
ผู้ให้บริการรักษาความปลอดภัยของอุปกรณ์อาจมีช่องโหว่
ต่อการเจาะช่องโหว่ที่ทราบ แอปของคุณควรทํางานราวกับว่าการสื่อสาร HTTP ทั้งหมดไม่เข้ารหัส
เมื่ออัปเดต Provider
แล้ว การโทรทั้งหมดไปยัง
ระบบจะกำหนดเส้นทาง API การรักษาความปลอดภัย (รวมถึง SSL API) ผ่าน API ดังกล่าว
(อย่างไรก็ตาม ข้อกำหนดนี้ไม่มีผลกับ android.net.SSLCertificateSocketFactory
ซึ่งยังคงมีช่องโหว่ต่อการโจมตี เช่น CVE-2014-0224)
ติดตั้งแพตช์แบบซิงค์
วิธีที่ง่ายที่สุดในการปักหมุดผู้ให้บริการรักษาความปลอดภัยคือการเรียกใช้เมธอดแบบซิงค์ installIfNeeded()
ซึ่งเหมาะสมในกรณีที่ประสบการณ์ของผู้ใช้จะไม่ได้รับผลกระทบจากการบล็อกเธรดขณะรอให้การดำเนินการเสร็จสมบูรณ์
เช่น ต่อไปนี้คือการใช้งานผู้ปฏิบัติงานที่อัปเดตผู้ให้บริการด้านความปลอดภัย เนื่องจาก Worker ทำงานในเบื้องหลัง จึงไม่เป็นไรหากเธรดบล็อกขณะรอการอัปเดตผู้ให้บริการรักษาความปลอดภัย ผู้ปฏิบัติงานกำลังโทรหา
installIfNeeded()
ถึง
อัปเดตผู้ให้บริการความปลอดภัย หากเมธอดแสดงผลตามปกติ เจ้าหน้าที่จะทราบว่าผู้ให้บริการรักษาความปลอดภัยเป็นเวอร์ชันล่าสุด หากเมธอดแสดงข้อยกเว้น เวิร์กเกอร์จะดําเนินการที่เหมาะสมได้ (เช่น แจ้งให้ผู้ใช้อัปเดตบริการ Google Play)
Kotlin
/** * Sample patch Worker using {@link ProviderInstaller}. */ class PatchWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) { override fun doWork(): Result { try { ProviderInstaller.installIfNeeded(context) } catch (e: GooglePlayServicesRepairableException) { // Indicates that Google Play services is out of date, disabled, etc. // Prompt the user to install/update/enable Google Play services. GoogleApiAvailability.getInstance() .showErrorNotification(context, e.connectionStatusCode) // Notify the WorkManager that a soft error occurred. return Result.failure() } catch (e: GooglePlayServicesNotAvailableException) { // Indicates a non-recoverable error; the ProviderInstaller can't // install an up-to-date Provider. // Notify the WorkManager that a hard error occurred. return Result.failure() } // If this is reached, you know that the provider was already up to date // or was successfully updated. return Result.success() } }
Java
/** * Sample patch Worker using {@link ProviderInstaller}. */ public class PatchWorker extends Worker { ... @Override public Result doWork() { try { ProviderInstaller.installIfNeeded(getContext()); } catch (GooglePlayServicesRepairableException e) { // Indicates that Google Play services is out of date, disabled, etc. // Prompt the user to install/update/enable Google Play services. GoogleApiAvailability.getInstance() .showErrorNotification(context, e.connectionStatusCode) // Notify the WorkManager that a soft error occurred. return Result.failure(); } catch (GooglePlayServicesNotAvailableException e) { // Indicates a non-recoverable error; the ProviderInstaller can't // install an up-to-date Provider. // Notify the WorkManager that a hard error occurred. return Result.failure(); } // If this is reached, you know that the provider was already up to date // or was successfully updated. return Result.success(); } }
แพตช์แบบไม่พร้อมกัน
การอัปเดตผู้ให้บริการความปลอดภัยอาจใช้เวลานานถึง 350 มิลลิวินาที (เปิด
อุปกรณ์รุ่นเก่า) หากคุณอัปเดตในเธรดที่ส่งผลต่อประสบการณ์ของผู้ใช้โดยตรง เช่น เธรด UI คุณไม่ควรเรียกใช้แบบซิงค์เพื่ออัปเดตผู้ให้บริการ เนื่องจากอาจส่งผลให้แอปหรืออุปกรณ์ค้างจนกว่าการดำเนินการจะเสร็จสมบูรณ์ แต่ให้ใช้พารามิเตอร์แบบอะซิงโครนัส
เมธอด installIfNeededAsync()
วิธีการนี้ระบุว่าการเรียกใช้สำเร็จหรือล้มเหลว
Callback
ลองดูตัวอย่างโค้ดต่อไปนี้ซึ่งอัปเดตผู้ให้บริการรักษาความปลอดภัยใน
กิจกรรมในชุดข้อความ UI กิจกรรมเรียก installIfNeededAsync()
เพื่ออัปเดตผู้ให้บริการ และกำหนดตัวเองเป็นผู้ฟังเพื่อรับการแจ้งเตือนความสําเร็จหรือความล้มเหลว หากผู้ให้บริการรักษาความปลอดภัยเป็นเวอร์ชันล่าสุดหรืออัปเดตสำเร็จ ระบบจะเรียกใช้เมธอด onProviderInstalled()
ของกิจกรรม และกิจกรรมจะทราบว่าการสื่อสารนั้นปลอดภัย หาก
ไม่สามารถอัปเดตผู้ให้บริการ กิจกรรม
onProviderInstallFailed()
และกิจกรรมสามารถดำเนินการที่เหมาะสม (เช่น
โดยแจ้งให้ผู้ใช้อัปเดตบริการ Google Play)
Kotlin
private const val ERROR_DIALOG_REQUEST_CODE = 1 /** * Sample activity using {@link ProviderInstaller}. */ class MainActivity : Activity(), ProviderInstaller.ProviderInstallListener { private var retryProviderInstall: Boolean = false // Update the security provider when the activity is created. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ProviderInstaller.installIfNeededAsync(this, this) } /** * This method is only called if the provider is successfully updated * (or is already up to date). */ override fun onProviderInstalled() { // Provider is up to date; app can make secure network calls. } /** * This method is called if updating fails. The error code indicates * whether the error is recoverable. */ override fun onProviderInstallFailed(errorCode: Int, recoveryIntent: Intent) { GoogleApiAvailability.getInstance().apply { if (isUserResolvableError(errorCode)) { // Recoverable error. Show a dialog prompting the user to // install/update/enable Google Play services. showErrorDialogFragment(this@MainActivity, errorCode, ERROR_DIALOG_REQUEST_CODE) { // The user chose not to take the recovery action. onProviderInstallerNotAvailable() } } else { onProviderInstallerNotAvailable() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == ERROR_DIALOG_REQUEST_CODE) { // Adding a fragment via GoogleApiAvailability.showErrorDialogFragment // before the instance state is restored throws an error. So instead, // set a flag here, which causes the fragment to delay until // onPostResume. retryProviderInstall = true } } /** * On resume, check whether a flag indicates that the provider needs to be * reinstalled. */ override fun onPostResume() { super.onPostResume() if (retryProviderInstall) { // It's safe to retry installation. ProviderInstaller.installIfNeededAsync(this, this) } retryProviderInstall = false } private fun onProviderInstallerNotAvailable() { // This is reached if the provider can't be updated for some reason. // App should consider all HTTP communication to be vulnerable and take // appropriate action. } }
Java
/** * Sample activity using {@link ProviderInstaller}. */ public class MainActivity extends Activity implements ProviderInstaller.ProviderInstallListener { private static final int ERROR_DIALOG_REQUEST_CODE = 1; private boolean retryProviderInstall; // Update the security provider when the activity is created. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ProviderInstaller.installIfNeededAsync(this, this); } /** * This method is only called if the provider is successfully updated * (or is already up to date). */ @Override protected void onProviderInstalled() { // Provider is up to date; app can make secure network calls. } /** * This method is called if updating fails. The error code indicates * whether the error is recoverable. */ @Override protected void onProviderInstallFailed(int errorCode, Intent recoveryIntent) { GoogleApiAvailability availability = GoogleApiAvailability.getInstance(); if (availability.isUserRecoverableError(errorCode)) { // Recoverable error. Show a dialog prompting the user to // install/update/enable Google Play services. availability.showErrorDialogFragment( this, errorCode, ERROR_DIALOG_REQUEST_CODE, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { // The user chose not to take the recovery action. onProviderInstallerNotAvailable(); } }); } else { // Google Play services isn't available. onProviderInstallerNotAvailable(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == ERROR_DIALOG_REQUEST_CODE) { // Adding a fragment via GoogleApiAvailability.showErrorDialogFragment // before the instance state is restored throws an error. So instead, // set a flag here, which causes the fragment to delay until // onPostResume. retryProviderInstall = true; } } /** * On resume, check whether a flag indicates that the provider needs to be * reinstalled. */ @Override protected void onPostResume() { super.onPostResume(); if (retryProviderInstall) { // It's safe to retry installation. ProviderInstaller.installIfNeededAsync(this, this); } retryProviderInstall = false; } private void onProviderInstallerNotAvailable() { // This is reached if the provider can't be updated for some reason. // App should consider all HTTP communication to be vulnerable and take // appropriate action. } }