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

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 میلی‌ثانیه (در دستگاه‌های قدیمی‌تر) طول بکشد. برای جلوگیری از تأثیرگذاری بر تجربه کاربر:

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

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

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

وصله همزمان

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

به عنوان مثال، در اینجا پیاده سازی یک کارگر است که ارائه دهنده امنیت را به روز می کند. از آنجایی که یک کارگر در پس‌زمینه اجرا می‌شود، اشکالی ندارد که در زمان انتظار برای به‌روزرسانی ارائه‌دهنده امنیت، رشته مسدود شود. کارگر برای به‌روزرسانی ارائه‌دهنده امنیت 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();
  }
}

وصله ناهمزمان

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

به عنوان مثال، در اینجا کدی وجود دارد که ارائه دهنده امنیت را در یک فعالیت در رشته رابط کاربری به روز می کند. این اکتیویتی برای به‌روزرسانی ارائه‌دهنده 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.
  }
}