O Android depende de um Provider
de segurança para
oferecer comunicações de rede seguras. No entanto, periodicamente,
são encontradas vulnerabilidades no provedor de segurança padrão. Para se proteger contra
essas vulnerabilidades, o Google Play
Services oferece uma maneira de atualizar automaticamente o provedor de segurança de um dispositivo,
proporcionando proteção contra exploits conhecidos. Ao chamar os métodos do Google Play Services, você pode ajudar a garantir
que seu app seja executado em um dispositivo com as atualizações mais recentes para
se proteger contra exploits conhecidos.
Por exemplo, foi descoberta uma vulnerabilidade no OpenSSL (CVE-2014-0224, link em inglês) que pode deixar os apps abertos a um ataque no caminho que descriptografa tráfego seguro sem nenhum dos lados ficar sabendo. A versão 5.0 do Google Play Services oferece uma correção, mas os apps precisam verificar se essa correção está instalada. Ao usar os métodos do Google Play Services, você pode ajudar a garantir que seu app seja executado em um dispositivo protegido contra esse ataque.
Cuidado: a atualização do Provider
de segurança
de um dispositivo não atualiza a
android.net.SSLCertificateSocketFactory
,
que permanece vulnerável. Em vez de usar essa classe descontinuada, incentivamos os desenvolvedores de apps a
usar métodos de alto nível para interagir com criptografia, como
HttpsURLConnection
.
Aplicar o patch no provedor de segurança usando o ProviderInstaller
Para atualizar o provedor de segurança de um dispositivo, use a
classe
ProviderInstaller
. Você pode conferir se o provedor de segurança está atualizado (e, se necessário,
atualizá-lo) chamando
o método installIfNeeded()
(ou installIfNeededAsync()
) dessa classe. Esta seção descreve essas opções detalhadamente. As seções abaixo fornecem
etapas e exemplos mais detalhados.
Quando você chama installIfNeeded()
, o
ProviderInstaller
faz o seguinte:
- Se o
Provider
do dispositivo for atualizado (ou já estiver atualizado), o método vai retornar sem gerar uma exceção. - Se a biblioteca do Google Play Services do dispositivo estiver desatualizada, o método
vai gerar uma
GooglePlayServicesRepairableException
. O app poderá capturar essa exceção e mostrar ao usuário uma caixa de diálogo para atualizar o Google Play Services. - Se um erro não recuperável ocorrer, o método vai gerar uma
GooglePlayServicesNotAvailableException
para indicar que não é possível atualizar oProvider
. Em seguida, o app poderá capturar a exceção e escolher uma ação adequada, como mostrar o diagrama de fluxo de correção padrão.
O método
installIfNeededAsync()
se comporta de forma semelhante, mas, em vez de
gerar exceções, ele chama o método de callback adequado para indicar
sucesso ou falha.
Se o provedor de segurança já estiver atualizado, installIfNeeded()
levará um
tempo insignificante. Se o método
precisar instalar um novo Provider
, isso pode levar
de 30 a 50 ms (em dispositivos mais recentes) até 350 ms (em dispositivos
mais antigos). Para evitar impactos na experiência do usuário:
- Chame
installIfNeeded()
em linhas de execução de rede em segundo plano imediatamente quando elas forem carregadas, em vez de esperar que a linha de execução tente usar a rede. Não há problema em chamar o método várias vezes, já que ele será retornado imediatamente se o provedor de segurança não precisar ser atualizado. - Chame a versão
assíncrona do método,
installIfNeededAsync()
, se a experiência do usuário puder ser afetada pelo bloqueio de linhas de execução, por exemplo, se a chamada for de uma atividade na linha de execução de interface. Naturalmente, se você fizer isso, será necessário esperar a conclusão da operação antes de tentar realizar comunicações seguras. OProviderInstaller
chama o métodoonProviderInstalled()
do listener para indicar que o processo foi bem-sucedido.
Aviso: se o ProviderInstaller
não conseguir instalar um Provider
atualizado,
o provedor de segurança do dispositivo poderá ficar vulnerável a exploits conhecidos. Seu app
vai se comportar como se todas as comunicações HTTP estivessem descriptografadas.
Quando o Provider
for atualizado, todas as chamadas para
APIs de segurança (incluindo APIs SSL) serão roteadas por ele.
No entanto, isso não se aplica à android.net.SSLCertificateSocketFactory
, que permanece vulnerável a
exploits como
CVE-2014-0224 (em inglês).
Aplicar o patch de forma síncrona
A maneira mais simples de aplicar um patch no provedor de segurança é chamar o
método síncrono installIfNeeded()
.
Essa ação é adequada quando a experiência do usuário não é afetada pelo bloqueio de linhas de execução
enquanto a operação estiver em andamento.
Por exemplo, confira uma implementação de um worker que atualiza o provedor de segurança. Como um worker
é executado em segundo plano, não haverá problema se a linha de execução for bloqueada enquanto
a atualização do provedor de segurança estiver sendo realizada. O worker chama
installIfNeeded()
para
atualizar o provedor de segurança. Se o método retornar normalmente, o worker
vai saber que o provedor de segurança está atualizado. Se o método gerar uma exceção,
o worker poderá realizar uma ação adequada, como solicitar que o usuário
atualize o Google Play Services.
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(); } }
Aplicar o patch de forma assíncrona
A atualização do provedor de segurança pode levar até 350 ms (em
dispositivos mais antigos). Se você estiver realizando a atualização em uma linha de execução que afeta diretamente
a experiência do usuário, como a linha de execução de interface, não é recomendável realizar uma chamada síncrona
para atualizar o provedor, já que isso poderá fazer com que o app ou o dispositivo
congele até que a operação seja concluída. Em vez disso, use o método
assíncrono installIfNeededAsync()
. Esse método indica o sucesso ou falha chamando
callbacks.
Por exemplo, apresentamos abaixo um código que atualiza o provedor de segurança em uma
atividade na linha de execução de interface. A atividade chama installIfNeededAsync()
para atualizar o provedor e se designa como listener para receber notificações
de sucesso ou falha. Se o provedor de segurança estiver atualizado ou se a
atualização dele for concluída, o método
onProviderInstalled()
da atividade será chamado, e a atividade saberá que as comunicações estão seguras. Se não
for possível atualizar o provedor, o método
onProviderInstallFailed()
da atividade será chamado e a atividade poderá realizar a ação adequada, como
solicitar que o usuário atualize o Google Play Services.
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. } }