Aggiornare il fornitore di sicurezza per proteggersi dagli exploit SSL

Android si affida a un Provider di sicurezza per per garantire comunicazioni di rete sicure. Tuttavia, di tanto in tanto, nel provider di sicurezza predefinito. Per proteggere da queste vulnerabilità, Google Play servizi consente di aggiornare automaticamente il fornitore di servizi di sicurezza da exploit noti. Chiamando i metodi di Google Play Services, puoi contribuire a garantire che la tua app sia in esecuzione su un dispositivo con gli ultimi aggiornamenti proteggere dagli exploit noti.

Ad esempio, è stata scoperta una vulnerabilità in OpenSSL (CVE-2014-0224) che può lasciare le app esposte a un attacco on-path che decripta il traffico sicuro a insaputa di entrambe le parti. Versione di Google Play Services 5.0 offre una soluzione, ma le app devono verificare che la correzione sia installata. Di utilizzando i metodi di Google Play Services, puoi contribuire a garantire che la tua app sia in esecuzione su un dispositivo protetto contro l'attacco.

Attenzione: l'aggiornamento della sicurezza di un dispositivoProvider non aggiornaandroid.net.SSLCertificateSocketFactory, che rimane vulnerabile. Anziché utilizzare questo corso deprecato, invitiamo gli sviluppatori di app a utilizzano metodi ad alto livello per interagire con la crittografia, come HttpsURLConnection.

Applica la patch al provider di sicurezza utilizzando ProviderInstaller

Per aggiornare il provider di sicurezza di un dispositivo, utilizza la classe ProviderInstaller. Puoi verificare che il provider di sicurezza sia aggiornato (e aggiornarlo, se necessario) chiamando il metodo installIfNeeded() (o installIfNeededAsync()) della classe. Questa sezione descrive queste opzioni a livello generale. Le sezioni che seguono forniscono passaggi ed esempi più dettagliati.

Quando chiami installIfNeeded(), ProviderInstaller svolge le seguenti operazioni:

  • Se Provider del dispositivo viene aggiornato correttamente (o è già aggiornato), il metodo restituisce senza generare un'eccezione.
  • Se la libreria di Google Play Services del dispositivo è obsoleta, il metodo lanci GooglePlayServicesRepairableException L'app può quindi rilevare questa eccezione e mostrare all'utente una finestra di dialogo appropriata per aggiornare Google Play Services.
  • Se si verifica un errore non recuperabile, il metodo genera GooglePlayServicesNotAvailableException per indicare che non è in grado di aggiornare Provider. L'app può quindi rilevare l'eccezione e scegliere un'azione appropriata, ad esempio visualizzare il diagramma di flusso per la risoluzione dei problemi standard.

La installIfNeededAsync() si comporta in modo simile, tranne che invece di generando eccezioni, chiama il metodo di callback appropriato per indicare un successo o un insuccesso.

Se il fornitore di servizi di sicurezza è già aggiornato, installIfNeeded() richiede una trascurabile. Se il metodo deve installare un nuovo Provider, l'operazione può richiedere da 30-50 ms (sui dispositivi più recenti) a 350 ms (sui dispositivi meno recenti) dispositivi). Per evitare di compromettere l'esperienza utente:

  • Chiama installIfNeeded() dai thread di networking in background subito quando i thread vengono caricati, anziché attendere il thread per provare a utilizzare la rete. Non c'è alcun problema a chiamare il metodo più volte, poiché restituisce immediatamente se il fornitore di servizi di sicurezza non necessita di aggiornamento.
  • Chiama il pulsante più recente del metodo, installIfNeededAsync(), se il thread può influire sull'esperienza utente blocco, ad esempio se la chiamata proviene da un'attività nel thread dell'interfaccia utente. Se lo fai, dovrai attendere il completamento dell'operazione prima di tentare qualsiasi comunicazione sicura. ProviderInstaller chiama il metodo onProviderInstalled() dell'ascoltatore per segnalare il successo.

Avviso: se ProviderInstaller non è in grado di installare un Provider aggiornato, il fornitore di servizi di sicurezza del tuo dispositivo potrebbe essere vulnerabile a exploit noti. La tua app dovrebbero comportarsi come se tutte le comunicazioni HTTP non fossero criptate.

Una volta aggiornato Provider, tutte le chiamate a le API di sicurezza (incluse le API SSL) vengono indirizzate tramite questo. Tuttavia, questo non si applica a android.net.SSLCertificateSocketFactory, che rimane vulnerabile a exploit come CVE-2014-0224.

Applica patch in modo sincrono

Il modo più semplice per applicare la patch al provider di sicurezza è chiamare il metodo installIfNeeded() sincrono. Questa opzione è appropriata se l'esperienza utente non è interessata dal blocco dei thread mentre attende il completamento dell'operazione.

Ad esempio, ecco un'implementazione di un worker che aggiorna il provider di sicurezza. Poiché un worker viene eseguito in background, va bene che il thread si blocchi durante l'attesa per aggiornare il fornitore di sicurezza. Il worker chiama Da installIfNeeded() a aggiorna il provider di sicurezza. Se il metodo restituisce normalmente, sa che il fornitore di servizi di sicurezza è aggiornato. Se il metodo genera un'eccezione, il worker può intraprendere l'azione appropriata (ad esempio chiedere all'utente di aggiornare 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();
  }
}

Esegui il patch in modo asincrono

L'aggiornamento del provider di sicurezza può richiedere fino a 350 ms (su dispositivi meno recenti). Se esegui l'aggiornamento su un thread che interessa direttamente esperienza utente, ad esempio il thread dell'interfaccia utente, non vuoi rendere sincrona per aggiornare il fornitore, dato che può causare l'accesso all'app o al dispositivo si blocca fino al termine dell'operazione. Utilizza invece il metodo asincrono installIfNeededAsync(). Questo metodo indica se l'operazione è riuscita o meno mediante la chiamata i callback.

Ad esempio, di seguito è riportato del codice che aggiorna il provider di sicurezza in un'attività nel thread dell'interfaccia utente. L'attività chiama installIfNeededAsync() aggiornare il provider e si autodefinisce come listener per l'esito positivo o notifiche di errore. Se il fornitore di servizi di sicurezza è aggiornato aggiornate correttamente, l'attività onProviderInstalled() e l'attività sa che la comunicazione è sicura. Se il fornitore non può essere aggiornato, viene chiamato il metodo onProviderInstallFailed() dell'attività, che può intraprendere l'azione appropriata (ad esempio chiedere all'utente di aggiornare 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.
  }
}