Для обеспечения безопасности сетевых подключений в Android используется Provider. Однако время от времени в поставщике безопасности по умолчанию обнаруживаются уязвимости. Чтобы защитить устройство от известных уязвимостей, сервисы Google Play автоматически обновляют поставщика услуг безопасности. Вызывая методы сервисов Google Play, вы можете убедиться, что ваше приложение запущено на устройстве с последними обновлениями, защищающими от известных эксплойтов.
Например, в OpenSSL была обнаружена уязвимость (CVE-2014-0224), которая позволяет злоумышленникам расшифровывать защищенный трафик без ведома пользователей. В сервисах 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 безопасности (включая API SSL) будут направляться через него.
Однако это не относится к 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(). Этот метод указывает на успешное или неудачное выполнение путем вызова обратных вызовов.
Ниже приведен пример кода, который обновляет поставщика услуг безопасности в действии в потоке 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. } }