Android mengandalkan Provider
keamanan untuk
menyediakan komunikasi jaringan yang aman. Namun, dari waktu ke waktu,
kerentanan ditemukan di penyedia keamanan default. Untuk melindungi dari
kerentanan ini, layanan Google Play menyediakan cara untuk otomatis mengupdate penyedia keamanan perangkat
agar terlindung dari eksploitasi umum. Dengan memanggil metode layanan Google Play, Anda dapat membantu memastikan
aplikasi Anda berjalan di perangkat yang memiliki update terbaru
untuk melindungi dari eksploitasi umum.
Misalnya, ditemukan kerentanan di OpenSSL (CVE-2014-0224) yang dapat membuat aplikasi rentan terhadap serangan di jalur yang mendekripsi traffic aman tanpa diketahui kedua belah pihak. Layanan Google Play versi 5.0 menawarkan perbaikan, tetapi aplikasi harus memeriksa apakah perbaikan ini sudah diinstal atau belum. Dengan metode layanan Google Play, Anda dapat membantu memastikan aplikasi Anda berjalan di perangkat yang aman dari serangan tersebut.
Perhatian: Mengupdate Provider
keamanan perangkat
tidak akan mengupdate
android.net.SSLCertificateSocketFactory
sehingga tetap rentan. Daripada menggunakan class ini, yang tidak digunakan lagi, sebaiknya developer aplikasi
menggunakan metode tingkat tinggi untuk berinteraksi dengan kriptografi, seperti
HttpsURLConnection
.
Menambahkan patch di penyedia keamanan menggunakan ProviderInstaller
Untuk mengupdate penyedia keamanan perangkat, gunakan
class
ProviderInstaller
. Anda dapat memastikan bahwa penyedia keamanan adalah versi terbaru (dan mengupdatenya,
jika perlu) dengan memanggil
metode installIfNeeded()
(atau installIfNeededAsync()
)
class tersebut. Bagian ini menjelaskan opsi tersebut di level tinggi. Bagian berikutnya memberikan
langkah-langkah dan contoh yang lebih detail.
Saat Anda memanggil installIfNeeded()
,
ProviderInstaller
akan melakukan hal-hal berikut:
- Jika
Provider
perangkat berhasil diupdate (atau sudah yang terbaru), metode akan ditampilkan tanpa menampilkan pengecualian. - Jika library layanan Google Play perangkat telah habis masa berlakunya, metode ini
akan menampilkan
GooglePlayServicesRepairableException
. Selanjutnya, aplikasi dapat memahami pengecualian ini dan menunjukkan kotak dialog yang tepat kepada pengguna untuk mengupdate layanan Google Play. - Jika terjadi error yang tidak dapat dipulihkan, metode ini akan menampilkan
GooglePlayServicesNotAvailableException
yang menunjukkan updateProvider
tidak dapat dilakukan. Selanjutnya, aplikasi dapat memahami pengecualian dan memilih tindakan yang tepat, seperti menampilkan diagram alur perbaikan standar.
Metode
installIfNeededAsync()
menunjukkan perilaku yang mirip, tetapi bukannya
menampilkan pengecualian, metode ini akan memanggil metode callback yang tepat untuk menunjukkan
keberhasilan atau kegagalan.
Jika penyedia keamanan sudah yang
terbaru, installIfNeeded()
akan memerlukan
waktu yang sangat singkat. Jika metode ini
perlu menginstal Provider
baru, proses ini dapat memerlukan waktu
dari 30-50 milidetik (di perangkat yang lebih baru) hingga 350 milidetik (di perangkat yang
lebih lama). Agar tidak memengaruhi pengalaman pengguna:
- Panggil
installIfNeeded()
dari thread jaringan latar belakang segera setelah thread dimuat, bukan menunggu sampai thread mencoba menggunakan jaringan. (Tidak ada salahnya memanggil metode beberapa kali, karena metode ini akan langsung ditampilkan jika penyedia keamanan tidak perlu diupdate.) - Panggil versi
asinkron metode tersebut,
installIfNeededAsync()
, jika pengalaman pengguna dapat terpengaruh oleh pemblokiran thread—misalnya, jika panggilan berasal dari aktivitas di UI thread. (Jika menggunakan opsi ini, Anda harus menunggu hingga operasi selesai sebelum mencoba komunikasi aman apa pun.ProviderInstaller
memanggil metodeonProviderInstalled()
pemroses Anda untuk menandakan keberhasilan.)
Peringatan: Jika
ProviderInstaller
tidak dapat menginstal Provider
yang telah diupdate,
penyedia keamanan perangkat Anda mungkin akan rentan terhadap eksploitasi umum. Aplikasi Anda
akan berperilaku seolah-olah semua komunikasi HTTP tidak dienkripsi.
Setelah Provider
diupdate, semua panggilan ke
API keamanan (termasuk API SSL) akan dirutekan melalui penyedia tersebut.
(Namun, hal ini tidak berlaku untuk android.net.SSLCertificateSocketFactory
, yang tetap rentan terhadap
eksploitasi seperti
CVE-2014-0224.)
Menambahkan patch secara sinkron
Cara paling mudah untuk menambahkan patch di penyedia keamanan adalah dengan memanggil metode sinkron
installIfNeeded()
.
Hal ini sesuai jika pengalaman pengguna tidak akan terpengaruh oleh pemblokiran thread
selagi menunggu operasi diselesaikan.
Misalnya, berikut adalah penerapan worker yang mengupdate penyedia keamanan. Karena worker
berjalan di latar belakang, tidak masalah jika thread diblokir selagi menunggu
penyedia keamanan diupdate. Worker memanggil
installIfNeeded()
untuk
mengupdate penyedia keamanan. Jika metode ini ditampilkan secara normal, worker
akan mengetahui bahwa penyedia keamanan adalah versi terbaru. Jika metode ini menampilkan pengecualian,
worker dapat mengambil tindakan yang sesuai (seperti memperingatkan pengguna untuk
mengupdate layanan 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(); } }
Menambahkan patch secara asinkron
Proses update penyedia keamanan dapat memerlukan waktu hingga 350 milidetik (di
perangkat lama). Jika update dilakukan pada thread yang langsung memengaruhi
pengalaman pengguna, seperti UI thread, sebaiknya Anda tidak membuat panggilan sinkron
untuk mengupdate penyedia, karena hal tersebut dapat mengakibatkan aplikasi atau perangkat
berhenti berfungsi sampai operasi selesai. Sebagai gantinya, gunakan metode
asinkron installIfNeededAsync()
. Metode tersebut menunjukkan keberhasilan atau kegagalan dengan memanggil
callback.
Misalnya, berikut adalah kode yang mengupdate penyedia keamanan dalam
aktivitas di UI thread. Aktivitas ini memanggil installIfNeededAsync()
untuk mengupdate penyedia, dan menetapkan dirinya sebagai pemroses untuk menerima notifikasi keberhasilan
atau kegagalan. Jika penyedia keamanan adalah versi terbaru atau
berhasil diupdate, metode
onProviderInstalled()
aktivitas akan dipanggil, dan aktivitas akan mengetahui bahwa komunikasi tersebut aman. Jika
penyedia tidak dapat diupdate, metode
onProviderInstallFailed()
aktivitas akan dipanggil, dan aktivitas dapat mengambil tindakan yang sesuai (seperti
memperingatkan pengguna untuk mengupdate layanan 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. } }