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()
, если блокировка потока может повлиять на взаимодействие с пользователем — например, если вызов происходит из действия в потоке пользовательского интерфейса. (Если вы сделаете это, вам придется дождаться завершения операции, прежде чем пытаться установить какую-либо безопасную связь.ProviderInstaller
вызывает методonProviderInstalled()
вашего слушателя, чтобы сигнализировать об успехе.)
Предупреждение. Если ProviderInstaller
не может установить обновленный Provider
, поставщик безопасности вашего устройства может быть уязвим для известных эксплойтов. Ваше приложение должно вести себя так, как будто все соединения HTTP не зашифрованы.
После обновления Provider
все вызовы API безопасности (включая API SSL) маршрутизируются через него. (Однако это не относится к android.net.SSLCertificateSocketFactory
, который остается уязвимым для таких эксплойтов, как CVE-2014-0224 .)
Обновление синхронно
Самый простой способ исправить поставщика безопасности — вызвать синхронный метод installIfNeeded()
. Это подходит, если блокировка потока не повлияет на работу пользователя во время ожидания завершения операции.
Например, вот реализация работника , который обновляет поставщика безопасности. Поскольку рабочий процесс работает в фоновом режиме, ничего страшного, если поток заблокируется во время ожидания обновления поставщика безопасности. Рабочий вызывает installIfNeeded()
, чтобы обновить поставщика безопасности. Если метод возвращается нормально, работник знает, что поставщик безопасности обновлен. Если метод выдает исключение, исполнитель может предпринять соответствующие действия (например, предложить пользователю обновить службы Google Play).
Котлин
/** * 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() } }
Ява
/** * 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 мс (на старых устройствах). Если вы выполняете обновление в потоке, который напрямую влияет на взаимодействие с пользователем, например в потоке пользовательского интерфейса, вам не нужно выполнять синхронный вызов для обновления поставщика, поскольку это может привести к зависанию приложения или устройства до завершения операции. заканчивается. Вместо этого используйте асинхронный метод installIfNeededAsync()
. Этот метод указывает на свой успех или неудачу, вызывая обратные вызовы.
Например, вот код, который обновляет поставщика безопасности в действии в потоке пользовательского интерфейса. Действие вызывает installIfNeededAsync()
для обновления поставщика и назначает себя прослушивателем для получения уведомлений об успехе или сбое. Если поставщик безопасности обновлен или успешно обновлен, вызывается метод действия onProviderInstalled()
, и действие узнает, что связь безопасна. Если поставщик не может быть обновлен, вызывается метод действия onProviderInstallFailed()
, и действие может выполнить соответствующее действие (например, предложить пользователю обновить службы Google Play).
Котлин
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. } }
Ява
/** * 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. } }