Android se basa en un objeto Provider
de seguridad para proporcionar comunicaciones de red seguras. No obstante, de vez en cuando, se detectan vulnerabilidades en el proveedor de seguridad predeterminado. Con el objetivo de brindar protección contra estas vulnerabilidades, los Servicios de Google Play proporcionan un método para actualizar automáticamente el proveedor de seguridad de un dispositivo a fin de brindar protección contra vulnerabilidades conocidas. Si llamas a los métodos de los Servicios de Google Play, podrás garantizar que tu app se ejecute en un dispositivo que cuente con las actualizaciones de protección más recientes contra vulnerabilidades conocidas.
Por ejemplo, se detectó una vulnerabilidad en OpenSSL (CVE-2014-0224) que puede dejar las apps expuestas a ataques de intermediarios que desencriptan tráfico seguro sin que ninguna de las partes correspondientes lo sepa. En la versión 5.0 de los Servicios de Google Play, se ofrece una solución, pero las apps deben controlar que realmente se instale este parche. Al usar los métodos de los Servicios de Google Play, podrás garantizar que tu app se ejecute en un dispositivo protegido contra ese ataque.
Precaución: La actualización del objeto Provider
no actualiza android.net.SSLCertificateSocketFactory
. En lugar de usar esta clase, recomendamos a los desarrolladores de apps que utilicen métodos de alto nivel para interactuar con la criptografía. En la mayoría de las apps, se usan API como HttpsURLConnection
sin necesidad de configurar un TrustManager
o crear un SSLCertificateSocketFactory
.
Cómo aplicarle un parche al proveedor de seguridad con un objeto ProviderInstaller
Para actualizar el proveedor de seguridad de un dispositivo, usa la clase ProviderInstaller
. Puedes verificar si el proveedor de seguridad está actualizado (y, de ser necesario, actualizarlo) llamando al método installIfNeeded()
(o installIfNeededAsync()
) de esa clase.
Cuando llamas a installIfNeeded()
, el ProviderInstaller
realiza lo siguiente:
- Si el objeto
Provider
del dispositivo se actualiza correctamente (o ya está actualizado), el método mostrará una respuesta como lo hace habitualmente. - Si la biblioteca de los Servicios de Google Play del dispositivo está desactualizada, el método generará una excepción del tipo
GooglePlayServicesRepairableException
. Luego, la app podrá usarla para mostrarle al usuario el cuadro de diálogo correspondiente para actualizar los Servicios de Google Play. - Si se produce un error irrecuperable, el método genera una
GooglePlayServicesNotAvailableException
para indicar que no puede actualizar elProvider
. Luego, la app podrá obtener la excepción y elegir un procedimiento adecuado, como mostrar el diagrama de flujo de fix-it estándar.
El método installIfNeededAsync()
se comporta de forma similar, pero, en lugar de generar excepciones, llama al método de devolución de llamada correspondiente para indicar el éxito o el fracaso de la operación.
Si installIfNeeded()
necesita instalar un nuevo Provider
, esto puede tardar entre 30 y 50 milisegundos en dispositivos más recientes, y hasta 350 en dispositivos más antiguos. Si ya está actualizado el proveedor de seguridad, el tiempo que requerirá el método será insignificante. Para no afectar negativamente la experiencia del usuario, haz lo siguiente:
- Llama de inmediato a
installIfNeeded()
desde subprocesos de red en segundo plano cuando estos se carguen, en lugar de esperar a que el subproceso intente usar la red. (Se puede llamar al método varias veces, ya que muestra una respuesta al instante si el proveedor de seguridad no necesita actualización). - Si se prevé que la experiencia del usuario se verá afectada por el bloqueo del subproceso (por ejemplo, si la llamada proviene de una actividad en el subproceso de la IU), llama a la versión asincrónica del método,
installIfNeededAsync()
. (Por supuesto, si haces esto, deberás esperar a que finalice la operación para intentar establecer comunicaciones seguras. ElProviderInstaller
llama al métodoonProviderInstalled()
de tu receptor para indicar que la operación fue exitosa).
Advertencia: Si el ProviderInstaller
no puede instalar un Provider
actualizado, el proveedor de seguridad del dispositivo puede ser vulnerable a ataques conocidos. Tu app debe comportarse como si todas las comunicaciones HTTP no estuvieran encriptadas.
Una vez que se actualice el objeto Provider
, todas las llamadas a las API de seguridad (incluidas las API de SSL) se transmitirán a través de él.
(No obstante, esto no se aplica a android.net.SSLCertificateSocketFactory
, que continúa siendo vulnerable a ataques como CVE-2014-0224.)
Cómo aplicar parches de forma sincrónica
La forma más simple de aplicar una revisión al proveedor de seguridad es llamar al método sincrónico installIfNeeded()
.
Es correcto hacer esto cuando la experiencia del usuario no se vea afectada por el bloqueo del subproceso mientras se espera a que finalice la operación.
Por ejemplo, a continuación, te mostramos la implementación de un adaptador de sincronización que actualiza el proveedor de seguridad. Dado que un adaptador de sincronización se ejecuta en segundo plano, no hay problema si el subproceso se bloquea mientras se espera a que se actualice el proveedor de seguridad. El adaptador de sincronización llama a installIfNeeded()
para actualizar el proveedor de seguridad. Si el método muestra una respuesta como lo hace habitualmente, el adaptador de sincronización reconocerá que el proveedor está actualizado. Si el método genera una excepción, el adaptador podrá tomar una medida necesaria (como solicitar al usuario que actualice los Servicios de Google Play).
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. } }
Cómo aplicar parches de forma asincrónica
La actualización del proveedor de seguridad puede tardar hasta 350 milisegundos en dispositivos más antiguos. Si realizas la actualización en un subproceso que afecta directamente la experiencia del usuario, como el de la IU, no te convendrá realizar una llamada sincrónica para actualizar el proveedor, ya que podría hacer que la app o el dispositivo se bloqueen hasta que finalice la operación. Como alternativa, debes usar el método asincrónico installIfNeededAsync()
,
que indica el éxito o fracaso realizando devoluciones de llamada.
Por ejemplo, a continuación, te mostramos parte de un código con el que se actualiza el proveedor de seguridad en una actividad del procesamiento de IU. La actividad llama a installIfNeededAsync()
a fin de actualizar el proveedor y se designa a sí misma como el objeto de escucha para recibir notificaciones de éxito o fracaso. Si el proveedor de seguridad está actualizado o se actualizó correctamente, se llama al método onProviderInstalled()
de la actividad y esta reconoce que la comunicación es segura. Si no se puede actualizar el proveedor, se llama al método onProviderInstallFailed()
de la actividad y esta puede tomar una medida adecuada (como indicar al usuario que actualice los Servicios de 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 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. } }