Android 依赖于安全 Provider
来提供安全的网络通信。然而,有时会在默认安全提供商中发现漏洞。为了防范这些漏洞,Google Play 服务 提供了一种自动更新设备安全提供商以防范已知漏洞的方法。通过调用 Google Play 服务方法,您可以帮助确保您的应用在已安装最新更新的设备上运行,以防范已知漏洞。
例如,OpenSSL 中发现了一个漏洞 (CVE-2014-0224),该漏洞可能导致应用面临路径攻击,解密安全流量而双方均不知情。Google Play 服务 5.0 版提供了修复,但应用必须检查此修复是否已安装。通过使用 Google Play 服务方法,您可以帮助确保您的应用在已针对该攻击进行保护的设备上运行。
注意:更新设备的安全 Provider
**不**会更新 android.net.SSLCertificateSocketFactory
,后者仍然容易受到攻击。我们鼓励应用开发者使用高级方法与密码学交互(例如 HttpsURLConnection
),而不是使用此已弃用的类。
使用 ProviderInstaller 修补安全提供商
要更新设备的安全提供商,请使用 ProviderInstaller
类。您可以通过调用该类的 installIfNeeded()
(或 installIfNeededAsync()
)方法来验证安全提供商是否最新(如果需要,则更新它)。本节从高层次描述这些选项。后续部分提供更详细的步骤和示例。
当您调用 installIfNeeded()
时,ProviderInstaller
会执行以下操作:
- 如果设备的
Provider
成功更新(或已经最新),该方法将返回而不抛出异常。 - 如果设备的 Google Play 服务库已过期,该方法会抛出
GooglePlayServicesRepairableException
。应用随后可以捕获此异常并向用户显示相应的对话框以更新 Google Play 服务。 - 如果发生不可恢复的错误,该方法会抛出
GooglePlayServicesNotAvailableException
,表示无法更新Provider
。应用随后可以捕获异常并选择适当的操作,例如显示标准的 修复流程图。
installIfNeededAsync()
方法的行为类似,只是它不抛出异常,而是调用相应的回调方法来指示成功或失败。
如果安全提供商已经最新,installIfNeeded()
耗时可忽略不计。如果该方法需要安装新的 Provider
,则可能需要 30-50 毫秒(在新设备上)到 350 毫秒(在旧设备上)。为了避免影响用户体验:
- 立即在后台网络线程加载时调用
installIfNeeded()
,而不是等待线程尝试使用网络。(多次调用此方法没有害处,因为如果安全提供商不需要更新,它会立即返回。) - 如果线程阻塞可能影响用户体验(例如,如果调用来自 UI 线程中的活动),请调用该方法的异步版本
installIfNeededAsync()
。(如果您这样做,您需要等待操作完成,然后才能尝试任何安全通信。ProviderInstaller
会调用您的监听器的onProviderInstalled()
方法来表示成功。)
警告:如果 ProviderInstaller
无法安装更新的 Provider
,您的设备安全提供商可能容易受到已知漏洞的攻击。您的应用应像所有 HTTP 通信都未加密一样运行。
一旦 Provider
更新完成,所有对安全 API(包括 SSL API)的调用都将通过它路由。(但是,这不适用于 android.net.SSLCertificateSocketFactory
,后者仍然容易受到诸如 CVE-2014-0224 等漏洞的攻击。)
同步修补
修补安全提供商的最简单方法是调用同步方法 installIfNeeded()
。如果线程在等待操作完成时阻塞不会影响用户体验,则此方法是合适的。
例如,这是一个更新安全提供商的 worker 的实现。由于 worker 在后台运行,因此线程在等待安全提供商更新时阻塞是没问题的。worker 调用 installIfNeeded()
来更新安全提供商。如果该方法正常返回,则 worker 知道安全提供商是最新的。如果该方法抛出异常,则 worker 可以采取适当的操作(例如提示用户更新 Google Play 服务)。
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(); } }
异步修补
更新安全提供商最多可能需要 350 毫秒(在旧设备上)。如果您在直接影响用户体验的线程(例如 UI 线程)上进行更新,则不应进行同步调用来更新提供商,因为这可能导致应用或设备冻结,直到操作完成。相反,请使用异步方法 installIfNeededAsync()
。该方法通过调用回调来指示成功或失败。
例如,这里有一些在 UI 线程中的活动中更新安全提供商的代码。该活动调用 installIfNeededAsync()
来更新提供商,并将自身指定为接收成功或失败通知的监听器。如果安全提供商是最新的或已成功更新,则会调用活动的 onProviderInstalled()
方法,并且活动知道通信是安全的。如果提供商无法更新,则会调用活动的 onProviderInstallFailed()
方法,并且活动可以采取适当的行动(例如提示用户更新 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 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. } }