O Android depende de um Provider
de segurança para oferecer comunicações de rede seguras. No entanto, de tempos em tempos, 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 explorações conhecidas. Ao chamar os métodos do Google Play Services, seu app pode garantir que ele seja executado em um dispositivo com as atualizações mais recentes para que tenha proteção contra explorações conhecidas.
Por exemplo, foi descoberta uma vulnerabilidade no OpenSSL (CVE-2014-0224, link em inglês) que pode deixar os apps vulneráveis a um ataque de intermediário (também conhecido pelo nome em inglês "man-in-the-middle") que descriptografa tráfego seguro sem o conhecimento de nenhum dos lados. Uma correção foi disponibilizada com o Google Play Services versão 5.0, mas é necessário que os apps exijam a instalação dessa correção. Usando os métodos do Google Play Services, é possível 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 android.net.SSLCertificateSocketFactory
. Em vez de usar essa classe, recomendamos que os desenvolvedores de apps utilizem métodos de alto nível para interagir com criptografia. A maioria dos aplicativos usam APIs como a HttpsURLConnection
sem precisar definir um TrustManager
personalizado ou criar um SSLCertificateSocketFactory
.
Aplicar patch no provedor de segurança com ProviderInstaller
Para atualizar o provedor de segurança de um dispositivo, use a classe ProviderInstaller
. Você pode verificar se o provedor de segurança está atualizado (e, se necessário, atualizá-lo) chamando o método installIfNeeded()
(ou installIfNeededAsync()
) dessa classe.
Quando você chama installIfNeeded()
, o ProviderInstaller
faz o seguinte:
- Se a atualização do
Provider
do dispositivo for concluída (ou se ele já estiver atualizado), o método será retornado normalmente. - Se a biblioteca do Google Play Services do dispositivo estiver desatualizada, o método acionará
GooglePlayServicesRepairableException
. O app poderá, então, capturar essa exceção e mostrar ao usuário uma caixa de diálogo apropriada para atualizar o Google Play Services. - Se um erro não recuperável ocorrer, o método acionará
GooglePlayServicesNotAvailableException
para indicar que não é possível atualizar oProvider
. Em seguida, o app poderá capturar a exceção e escolher uma ação apropriada, como exibir o diagrama de fluxo de correção padrão.
O método installIfNeededAsync()
se comporta de forma semelhante, mas, em vez de acionar exceções, ele chama o método de callback apropriado para indicar sucesso ou falha.
Se installIfNeeded()
precisar instalar um novo Provider
, esse processo poderá durar de 30 a 50 ms (em dispositivos mais recentes) até 350 ms (em dispositivos mais antigos). Se o provedor de segurança já estiver atualizado, o método levará um tempo insignificante. Para evitar impactos na experiência do usuário:
- Chame
installIfNeeded()
de 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. - Se a experiência do usuário for afetada pelo bloqueio de linhas de execução, por exemplo, se a chamada for originada de uma atividade na linha de execução da IU, chame a versão assíncrona do método,
installIfNeededAsync()
. Naturalmente, se você fizer isso, será preciso esperar a conclusão da operação antes de tentar realizar comunicações seguras. OProviderInstaller
chama o métodoonProviderInstalled()
do listener para sinalizar 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 explorações conhecidas. Seu app 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 ao android.net.SSLCertificateSocketFactory
, que permanece vulnerável a explorações como a CVE-2014-0224.
Aplicar 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 será apropriada se a experiência do usuário não for afetada pelo bloqueio de linhas de execução enquanto a operação estiver sendo concluída.
Por exemplo, veja aqui a implementação de um adaptador de sincronização que atualiza o provedor de segurança. Como um adaptador de sincronização é 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 adaptador de sincronização chama installIfNeeded()
para atualizar o provedor de segurança. Se o método for retornado normalmente, o adaptador de sincronização saberá que o provedor de segurança está atualizado. Se o método acionar uma exceção, o adaptador de sincronização poderá realizar a ação apropriada, como solicitar que o usuário atualize o Google Play Services.
Kotlin
/** * Sample sync adapter using {@link ProviderInstaller}. */ class SyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) { override fun onPerformSync( account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult ) { 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 SyncManager that a soft error occurred. syncResult.stats.numIoExceptions++ return } catch (e: GooglePlayServicesNotAvailableException) { // Indicates a non-recoverable error; the ProviderInstaller is not able // to install an up-to-date Provider. // Notify the SyncManager that a hard error occurred. syncResult.stats.numAuthExceptions++ return } // If this is reached, you know that the provider was already up-to-date, // or was successfully updated. } }
Java
/** * Sample sync adapter using {@link ProviderInstaller}. */ public class SyncAdapter extends AbstractThreadedSyncAdapter { ... // This is called each time a sync is attempted; this is okay, since the // overhead is negligible if the security provider is up-to-date. @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { 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 SyncManager that a soft error occurred. syncResult.stats.numIoExceptions++; return; } catch (GooglePlayServicesNotAvailableException e) { // Indicates a non-recoverable error; the ProviderInstaller is not able // to install an up-to-date Provider. // Notify the SyncManager that a hard error occurred. syncResult.stats.numAuthExceptions++; return; } // If this is reached, you know that the provider was already up-to-date, // or was successfully updated. } }
Aplicar 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 IU, 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 seu sucesso ou falha chamando callbacks.
Por exemplo, apresentamos aqui um código que atualiza o provedor de segurança em uma atividade na linha de execução de IU. 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 apropriada, 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 will cause the fragment to delay until // onPostResume. retryProviderInstall = true } } /** * On resume, check to see if we flagged that we need to reinstall the * provider. */ override fun onPostResume() { super.onPostResume() if (retryProviderInstall) { // We can now safely retry installation. ProviderInstaller.installIfNeededAsync(this, this) } retryProviderInstall = false } private fun onProviderInstallerNotAvailable() { // This is reached if the provider cannot 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 is not 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 will cause the fragment to delay until // onPostResume. retryProviderInstall = true; } } /** * On resume, check to see if we flagged that we need to reinstall the * provider. */ @Override protected void onPostResume() { super.onPostResume(); if (retryProviderInstall) { // We can now safely retry installation. ProviderInstaller.installIfNeededAsync(this, this); } retryProviderInstall = false; } private void onProviderInstallerNotAvailable() { // This is reached if the provider cannot be updated for some reason. // App should consider all HTTP communication to be vulnerable, and take // appropriate action. } }