Android 依靠安全 Provider
提供安全的网络通信。但是,默认安全提供程序中会不时发现漏洞。为了防止这些漏洞引发问题,您可以通过 Google Play 服务自动更新设备的安全提供程序,从而防范已知攻击。通过调用 Google Play 服务方法,可确保您的应用在具有最新更新,从而可防范已知攻击的设备上运行。
例如,我们在 OpenSSL (CVE-2014-0224) 中发现了一个漏洞,该漏洞会让应用受到可以解密安全流量的“中间人”攻击,而通信双方对此却毫不知情。Google Play 服务版本 5.0 提供了该漏洞的修复程序,但应用必须确保已安装此修复程序。通过使用 Google Play 服务方法,可确保您的应用在可以防范该攻击的设备上运行。
注意:更新设备的安全 Provider
不会更新 android.net.SSLCertificateSocketFactory
。我们建议应用开发者使用高级方法与加密进行交互,而不是使用此类。大多数应用都可以使用 HttpsURLConnection
之类的 API,无需设置自定义 TrustManager
或创建 SSLCertificateSocketFactory
。
使用 ProviderInstaller 为安全提供程序打补丁
如需更新设备的安全提供程序,请使用 ProviderInstaller
类。通过调用该类的 installIfNeeded()
(或 installIfNeededAsync()
)方法,您可以验证安全提供程序是否处于最新状态(如果需要,请对其进行更新)。
当您调用 installIfNeeded()
时,ProviderInstaller
将执行以下操作:
- 如果设备的
Provider
已成功更新(或者已经处于最新状态),此方法将正常返回。 - 如果设备的 Google Play 服务库已过期,此方法会抛出
GooglePlayServicesRepairableException
。然后,应用可以捕获此异常,并向用户显示相应的对话框以更新 Google Play 服务。 - 如果出现不可恢复的错误,此方法将抛出
GooglePlayServicesNotAvailableException
以表明它无法更新Provider
。然后,应用可以捕获异常,并选择相应的操作,例如显示标准的修复流程图。
installIfNeededAsync()
方法的行为方式与之相似,只是不会抛出异常,而是会调用相应的回调方法以表明更新成功还是失败。
如果 installIfNeeded()
需要安装新的 Provider
,所需的时间从 30-50 毫秒(在较新的设备上)到 350 毫秒(在较旧的设备上)不等。如果安全提供程序已经处于最新状态,该方法需要的时间微不足道。为避免影响用户体验,请执行以下操作:
- 在加载线程时立即从后台网络线程调用
installIfNeeded()
,而不是等待线程尝试使用网络。(多次调用该方法没有任何坏处,因为如果安全提供程序不需要更新,它会立即返回。) - 如果用户体验受线程拦截影响(例如,如果调用来自界面线程中的某个 Activity),请调用此方法的异步版本,即
installIfNeededAsync()
。(当然,如果执行此操作,您需要等待操作完成后再尝试任何安全的通信。ProviderInstaller
会调用侦听器的onProviderInstalled()
方法以指示成功。)
警告:如果 ProviderInstaller
无法安装已更新的Provider
,您设备的安全提供程序可能会受到已知漏洞攻击。您的应用应表现为好像所有 HTTP 通信都未加密。
在更新 Provider
后,对安全 API(包括 SSL API)的所有调用均通过它进行路由。(但是,这不适用于 android.net.SSLCertificateSocketFactory
,其仍然容易受到类似 CVE-2014-0224 的攻击。)
同步打补丁
为安全提供程序打补丁的最简单方法是调用同步方法 installIfNeeded()
。如果在等待操作完成时用户体验不会受到线程拦截影响,该方法会非常适用。
例如,下面是一个可以更新安全提供程序的同步适配器的实现。由于同步适配器在后台运行,因此,如果正在等待安全提供程序更新,线程就可以实施拦截。同步适配器将调用 installIfNeeded()
以更新安全提供程序。如果该方法正常返回,同步适配器会知道安全提供程序处于最新状态。如果该方法引发异常,则同步适配器可以进行适当的操作(例如提示用户更新 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. } }
异步打补丁
更新安全提供程序最多需要 350 毫秒的时间(在较旧的设备上)。如果在直接影响用户体验的线程(例如界面线程)上进行更新,您一定不希望通过同步调用更新提供程序,因为这会导致应用或设备在相应操作完成前处于冻结状态。相反,您应该使用异步方法 installIfNeededAsync()
。该方法通过调用回调指示其成功还是失败。
例如,下面是可以在界面线程的某个 Activity 中更新安全提供程序的一些代码。此 Activity 会调用 installIfNeededAsync()
以更新提供程序,并将自身指定为接收成功或失败通知的侦听器。如果安全提供程序为最新或已成功更新,将调用此 Activity 的 onProviderInstalled()
方法,且 Activity 知道通信是安全的。如果提供程序无法更新,将调用此 Activity 的 onProviderInstallFailed()
方法,且 Activity 可以进行适当的操作(如提示用户更新 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. } }