page.title=Updating Your Security Provider to Protect Against SSL Exploits page.tags="network","certificates" page.article=true @jd:body

In this document

  1. Patching the Security Provider with ProviderInstaller
  2. Patching Synchronously
  3. Patching Asynchronously

See also

Android relies on a security {@link java.security.Provider Provider} to provide secure network communications. However, from time to time, vulnerabilities are found in the default security provider. To protect against these vulnerabilities, Google Play services provides a way to automatically update a device's security provider to protect against known exploits. By calling Google Play services methods, your app can ensure that it's running on a device that has the latest updates to protect against known exploits.

For example, a vulnerability was discovered in OpenSSL (CVE-2014-0224) that can leave apps open to a "man-in-the-middle" attack that decrypts secure traffic without either side knowing. With Google Play services version 5.0, a fix is available, but apps must ensure that this fix is installed. By using the Google Play services methods, your app can ensure that it's running on a device that's secured against that attack.

Caution: Updating a device's security {@link java.security.Provider Provider} does not update {@link android.net.SSLCertificateSocketFactory android.net.SSLCertificateSocketFactory}. Rather than using this class, we encourage app developers to use high-level methods for interacting with cryptography. Most apps can use APIs like {@link javax.net.ssl.HttpsURLConnection} without needing to set a custom {@link javax.net.ssl.TrustManager} or create an {@link android.net.SSLCertificateSocketFactory}.

Patching the Security Provider with ProviderInstaller

To update a device's security provider, use the {@code ProviderInstaller} class. You can verify that the security provider is up-to-date (and update it, if necessary) by calling that class's {@code installIfNeeded()} (or {@code installIfNeededAsync()}) method.

When you call {@code installIfNeeded()}, the {@code ProviderInstaller} does the following:

The {@code installIfNeededAsync()} method behaves similarly, except that instead of throwing exceptions, it calls the appropriate callback method to indicate success or failure.

If {@code installIfNeeded()} needs to install a new {@link java.security.Provider Provider}, this can take anywhere from 30-50 milliseconds (on more recent devices) to 350 ms (on older devices). If the security provider is already up-to-date, the method takes a negligible amount of time. To avoid affecting user experience:

Warning: If the {@code ProviderInstaller} is unable to install an updated {@link java.security.Provider Provider}, your device's security provider might be vulnerable to known exploits. Your app should behave as if all HTTP communication is unencrypted.

Once the {@link java.security.Provider Provider} is updated, all calls to security APIs (including SSL APIs) are routed through it. (However, this does not apply to {@link android.net.SSLCertificateSocketFactory android.net.SSLCertificateSocketFactory}, which remains vulnerable to such exploits as CVE-2014-0224.)

Patching Synchronously

The simplest way to patch the security provider is to call the synchronous method {@code installIfNeeded()}. This is appropriate if user experience won't be affected by the thread blocking while it waits for the operation to finish.

For example, here's an implementation of a sync adapter that updates the security provider. Since a sync adapter runs in the background, it's okay if the thread blocks while waiting for the security provider to be updated. The sync adapter calls {@code installIfNeeded()} to update the security provider. If the method returns normally, the sync adapter knows the security provider is up-to-date. If the method throws an exception, the sync adapter can take appropriate action (such as prompting the user to update Google Play services).

/**
 * 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.
      GooglePlayServicesUtil.showErrorNotification(
          e.getConnectionStatusCode(), getContext());

      // 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.
  }
}

Patching Asynchronously

Updating the security provider can take as much as 350 milliseconds (on older devices). If you're doing the update on a thread that directly affects user experience, such as the UI thread, you don't want to make a synchronous call to update the provider, since that can result in the app or device freezing until the operation finishes. Instead, you should use the asynchronous method {@code installIfNeededAsync()}. That method indicates its success or failure by calling callbacks.

For example, here's some code that updates the security provider in an activity in the UI thread. The activity calls {@code installIfNeededAsync()} to update the provider, and designates itself as the listener to receive success or failure notifications. If the security provider is up-to-date or is successfully updated, the activity's {@code onProviderInstalled()} method is called, and the activity knows communication is secure. If the provider cannot be updated, the activity's {@code onProviderInstallFailed()} method is called, and the activity can take appropriate action (such as prompting the user to update Google Play services).

/**
 * Sample activity using {@link ProviderInstaller}.
 */
public class MainActivity extends Activity
    implements ProviderInstaller.ProviderInstallListener {

  private static final int ERROR_DIALOG_REQUEST_CODE = 1;

  private boolean mRetryProviderInstall;

  //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) {
    if (GooglePlayServicesUtil.isUserRecoverableError(errorCode)) {
      // Recoverable error. Show a dialog prompting the user to
      // install/update/enable Google Play services.
      GooglePlayServicesUtil.showErrorDialogFragment(
          errorCode,
          this,
          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 GooglePlayServicesUtil.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.
      mRetryProviderInstall = true;
    }
  }

  /**
   * On resume, check to see if we flagged that we need to reinstall the
   * provider.
   */
  @Override
  protected void onPostResume() {
    super.onPostResult();
    if (mRetryProviderInstall) {
      // We can now safely retry installation.
      ProviderInstall.installIfNeededAsync(this, this);
    }
    mRetryProviderInstall = 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.
  }
}