برای محافظت دربرابر رخنه SSL، ارائه‌دهنده امنیتتان را به‌روز کنید

Android برای ارائه ارتباطات شبکه ایمن به Provider امنیتی متکی است. بااین‌حال، گاهی اوقات، آسیب‌پذیری‌هایی در ارائه‌دهنده امنیتی پیش‌فرض پیدا می‌شود. برای محافظت دربرابر این آسیب‌پذیری‌ها، خدمات Google Play روشی برای به‌روزرسانی خودکار ارائه‌دهنده امنیتی دستگاه ارائه می‌دهد تا از دستگاه دربرابر سوءاستفاده‌های شناخته‌شده محافظت کند. با فراخوانی روش‌های «خدمات Google Play»، می‌توانید کمک کنید مطمئن شوید برنامه‌تان در دستگاهی اجرا می‌شود که جدیدترین به‌روزرسانی‌ها را برای محافظت دربرابر سوءاستفاده‌های شناخته‌شده دارد.

برای مثال، آسیب‌پذیری‌ای در OpenSSL (CVE-2014-0224) کشف شده است که می‌تواند برنامه‌ها را دربرابر حمله «در مسیر» که ترافیک ایمن را بدون اطلاع طرفین رمزگشایی می‌کند، آسیب‌پذیر کند. نسخه ۵.۰ خدمات Google Play اصلاحیه‌ای ارائه می‌دهد، اما برنامه‌ها باید بررسی کنند که این اصلاحیه نصب شده باشد. بااستفاده از روش‌های «خدمات Google Play»، می‌توانید کمک کنید مطمئن شوید برنامه‌تان در دستگاهی اجرا می‌شود که دربرابر آن حمله ایمن است.

احتیاط: به‌روزرسانی امنیت دستگاه Provider android.net.SSLCertificateSocketFactory را به‌روزرسانی نمی‌کند، که همچنان آسیب‌پذیر باقی می‌ماند. به‌جای استفاده از این کلاس منسوخ‌شده، توسعه‌دهندگان برنامه را تشویق می‌کنیم از روش‌های سطح بالا برای تعامل با رمزنگاری استفاده کنند، مانند HttpsURLConnection.

وصله کردن ارائه‌دهنده امنیتی بااستفاده از ProviderInstaller

برای به‌روزرسانی ارائه‌دهنده امنیتی دستگاه، از کلاس ProviderInstaller استفاده کنید. می‌توانید با فراخوانی روش installIfNeeded() (یا installIfNeededAsync()) آن کلاس، درستی‌سنجی کنید که ارائه‌دهنده امنیتی به‌روز است (و درصورت لزوم آن را به‌روز کنید). این بخش این گزینه‌ها را در سطح بالا توضیح می‌دهد. بخش‌های زیر مراحل و مثال‌های دقیق‌تری ارائه می‌دهند.

وقتی با installIfNeeded() تماس می‌گیرید، ProviderInstaller کارهای زیر را انجام می‌دهد:

  • اگر Provider دستگاه با موفقیت به‌روز شود (یا ازقبل به‌روز باشد)، این روش بدون ایجاد استثنا برمی‌گردد.
  • اگر کتابخانه خدمات Google Play دستگاه قدیمی باشد، این روش GooglePlayServicesRepairableException را پرتاب می‌کند. سپس برنامه می‌تواند این استثنا را دریافت کند و چارگوش گفتگوی مناسبی را برای به‌روزرسانی «خدمات Google Play» به کاربر نشان دهد.
  • اگر خطای غیرقابل‌بازیابی رخ دهد، روش GooglePlayServicesNotAvailableException را برمی‌گرداند تا نشان دهد که نمی‌تواند Provider را به‌روز کند. سپس برنامه می‌تواند استثنا را دریافت کند و اقدام مناسبی را انتخاب کند، مثلاً نمایش نمودار جریان رفع مشکل استاندارد.

روش installIfNeededAsync() رفتار مشابهی دارد، با این تفاوت که به‌جای ایجاد استثنا، روش فراخوانی مناسب را برای نشان دادن موفقیت یا شکست فراخوانی می‌کند.

اگر ارائه‌دهنده امنیتی ازقبل به‌روز باشد، installIfNeeded() زمان ناچیزی طول می‌کشد. اگر روش نیاز به نصب Provider جدید داشته باشد، این کار می‌تواند از ۳۰ تا ۵۰ میلی‌ثانیه (در دستگاه‌های جدیدتر) تا ۳۵۰ میلی‌ثانیه (در دستگاه‌های قدیمی‌تر) طول بکشد. برای جلوگیری از تأثیر بر تجربه کاربری:

  • به‌محض بار شدن رشته‌های شبکه‌سازی پس‌زمینه، installIfNeeded() را از این رشته‌ها فراخوانی کنید، به‌جای اینکه منتظر بمانید تا رشته سعی کند از شبکه استفاده کند. (چند بار فراخوانی این روش ضرری ندارد، زیرا اگر ارائه‌دهنده امنیتی نیازی به به‌روزرسانی نداشته باشد، بلافاصله برمی‌گردد.)
  • اگر تجربه کاربری می‌تواند تحت‌تأثیر مسدود شدن رشته قرار گیرد، نسخه ناهمگام روش، installIfNeededAsync()، را فراخوانی کنید—برای مثال، اگر فراخوانی از فعالیتی در رشته میانای کاربر باشد. (اگر این کار را انجام دهید، باید منتظر بمانید تا عملیات به‌پایان برسد سپس برای برقراری ارتباط امن اقدام کنید. ‫ ProviderInstaller روش onProviderInstalled() شنونده شما را برای نشان دادن موفقیت فراخوانی می‌کند.)

هشدار: اگر ProviderInstaller نتواند Provider به‌روزشده را نصب کند، ممکن است ارائه‌دهنده امنیتی دستگاهتان دربرابر سوءاستفاده‌های شناخته‌شده آسیب‌پذیر باشد. برنامه شما باید طوری رفتار کند که گویی همه ارتباطات HTTP رمزگذاری‌نشده است.

پس‌از به‌روزرسانی Provider، همه تماس‌ها با میاناهای برنامه‌سازی کاربردی امنیتی (ازجمله میاناهای برنامه‌سازی کاربردی SSL) ازطریق آن هدایت می‌شوند. (بااین‌حال، این موضوع برای android.net.SSLCertificateSocketFactory اعمال نمی‌شود که همچنان دربرابر سوءاستفاده‌هایی مانند CVE-2014-0224 آسیب‌پذیر است.)

وصله همگام‌سازی

ساده‌ترین راه برای وصله کردن ارائه‌دهنده امنیتی، فراخوانی روش همگام installIfNeeded() است. اگر تجربه کاربر تحت تأثیر مسدود شدن رشته قرار نگیرد، این کار مناسب است درحالی‌که منتظر است عملیات به‌پایان برسد.

برای مثال، در اینجا پیاده‌سازی worker که ارائه‌دهنده امنیتی را به‌روز می‌کند آورده شده است. ازآنجایی‌که یک worker در پس‌زمینه اجرا می‌شود، اگر رشته درحین انتظار برای به‌روزرسانی ارائه‌دهنده امنیتی مسدود شود، مشکلی نیست. کارمند با installIfNeeded() تماس می‌گیرد تا ارائه‌دهنده امنیتی را به‌روز کند. اگر روش به‌طور عادی برگردد، کارگر می‌داند که ارائه‌دهنده امنیتی به‌روز است. اگر روش استثنایی را ایجاد کند، کارگر می‌تواند اقدام مناسب را انجام دهد (مثلاً از کاربر بخواهد سرویس‌های Google Play را به‌روز کند).

کاتلین

/**
 * 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()
    }
}

جاوا

/**
 * 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();
  }
}

وصله غیرهمگام

به‌روزرسانی ارائه‌دهنده امنیتی می‌تواند تا ۳۵۰ میلی‌ثانیه (در دستگاه‌های قدیمی‌تر) طول بکشد. اگر به‌روزرسانی را در رشته‌ای انجام می‌دهید که مستقیماً بر تجربه کاربر تأثیر می‌گذارد، مانند رشته میانای کاربر، نباید برای به‌روزرسانی ارائه‌دهنده تماس هم‌زمان برقرار کنید، زیرا این کار می‌تواند منجر به توقف برنامه یا دستگاه تا پایان عملیات شود. درعوض، از روش ناهمگام installIfNeededAsync() استفاده کنید. این روش موفقیت یا شکست خود را با فراخوانی برگشت‌های فراخوانی نشان می‌دهد.

برای مثال، در اینجا کدی وجود دارد که ارائه‌دهنده امنیتی را در یک فعالیت در رشته میانای کاربر به‌روز می‌کند. فعالیت installIfNeededAsync() را فراخوانی می‌کند تا ارائه‌دهنده را به‌روز کند و خود را به‌عنوان شنونده برای دریافت اعلان‌های موفقیت یا شکست تعیین می‌کند. اگر ارائه‌دهنده امنیتی به‌روز باشد یا با موفقیت به‌روز شود، روش onProviderInstalled() فعالیت فراخوانی می‌شود و فعالیت می‌داند که ارتباط امن است. اگر ارائه‌دهنده به‌روز نشود، روش onProviderInstallFailed() فعالیت فراخوانده می‌شود و فعالیت می‌تواند اقدام مناسب را انجام دهد (مثلاً از کاربر بخواهد خدمات Google Play را به‌روز کند).

کاتلین

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

جاوا

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