อัปเดตผู้ให้บริการรักษาความปลอดภัยของคุณเพื่อป้องกันการแสวงหาประโยชน์จาก SSL

Android ใช้Providerเพื่อการสื่อสารที่ปลอดภัยในเครือข่าย อย่างไรก็ตาม บางครั้งก็พบช่องโหว่ในผู้ให้บริการรักษาความปลอดภัยเริ่มต้น เพื่อป้องกัน ช่องโหว่เหล่านี้ Google Play บริการช่วยให้คุณสามารถอัปเดตผู้ให้บริการความปลอดภัยของอุปกรณ์โดยอัตโนมัติ เพื่อป้องกันการเจาะช่องโหว่ที่ทราบ การเรียกวิธีการของบริการ Google Play ช่วยให้คุณมั่นใจได้ว่า ที่แอปของคุณกำลังทำงานในอุปกรณ์ที่มีการอัปเดตล่าสุด จากการโจมตีที่ทราบแล้ว

ตัวอย่างเช่น มีการค้นพบช่องโหว่ใน OpenSSL (CVE-2014-0224) ซึ่งอาจทําให้แอปเปิดรับการโจมตีระหว่างเส้นทางที่ถอดรหัสการเข้าชมที่ปลอดภัยโดยที่ทั้ง 2 ฝ่ายไม่รู้ บริการ 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() จากเธรดเครือข่ายที่ทำงานอยู่เบื้องหลังทันทีเมื่อเทรดโหลดขึ้น แทนที่จะต้องรอให้เทรดลองใช้เครือข่าย (ไม่เป็นอันตรายใดๆ ในการเรียกใช้เมธอดหลายครั้ง เพราะระบบจะแสดงผลทันทีหาก ไม่จำเป็นต้องอัปเดต)
  • เรียกใช้แบบอะซิงโครนัส ของเมธอด installIfNeededAsync() หากชุดข้อความอาจส่งผลต่อประสบการณ์ของผู้ใช้ การบล็อก เช่น หากการเรียกมาจากกิจกรรมในเธรด UI (หากดำเนินการนี้ คุณจะต้องรอให้การดำเนินการเสร็จสมบูรณ์ก่อนจึงจะสื่อสารอย่างปลอดภัยได้ ProviderInstaller เรียกonProviderInstalled()วิธีของผู้ฟังเพื่อส่งสัญญาณว่าสำเร็จ)

คำเตือน: หาก ProviderInstaller ไม่สามารถติดตั้ง Provider ที่อัปเดตแล้ว ผู้ให้บริการรักษาความปลอดภัยของอุปกรณ์อาจมีช่องโหว่ ต่อการเจาะช่องโหว่ที่ทราบ แอปของคุณควรทํางานราวกับว่าการสื่อสาร HTTP ทั้งหมดไม่เข้ารหัส

เมื่ออัปเดต Provider แล้ว การโทรทั้งหมดไปยัง ระบบจะกำหนดเส้นทาง API การรักษาความปลอดภัย (รวมถึง SSL API) ผ่าน API ดังกล่าว (อย่างไรก็ตาม ข้อกำหนดนี้ไม่มีผลกับ android.net.SSLCertificateSocketFactory ซึ่งยังคงมีช่องโหว่ต่อการโจมตี เช่น CVE-2014-0224)

ติดตั้งแพตช์แบบซิงค์

วิธีที่ง่ายที่สุดในการปักหมุดผู้ให้บริการรักษาความปลอดภัยคือการเรียกใช้เมธอดแบบซิงค์ installIfNeeded() ซึ่งเหมาะสมในกรณีที่ประสบการณ์ของผู้ใช้จะไม่ได้รับผลกระทบจากการบล็อกเธรดขณะรอให้การดำเนินการเสร็จสมบูรณ์

เช่น ต่อไปนี้คือการใช้งานผู้ปฏิบัติงานที่อัปเดตผู้ให้บริการด้านความปลอดภัย เนื่องจาก Worker ทำงานในเบื้องหลัง จึงไม่เป็นไรหากเธรดบล็อกขณะรอการอัปเดตผู้ให้บริการรักษาความปลอดภัย ผู้ปฏิบัติงานกำลังโทรหา installIfNeeded() ถึง อัปเดตผู้ให้บริการความปลอดภัย หากเมธอดแสดงผลตามปกติ เจ้าหน้าที่จะทราบว่าผู้ให้บริการรักษาความปลอดภัยเป็นเวอร์ชันล่าสุด หากเมธอดแสดงข้อยกเว้น เวิร์กเกอร์จะดําเนินการที่เหมาะสมได้ (เช่น แจ้งให้ผู้ใช้อัปเดตบริการ 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() วิธีการนี้ระบุว่าการเรียกใช้สำเร็จหรือล้มเหลว Callback

ลองดูตัวอย่างโค้ดต่อไปนี้ซึ่งอัปเดตผู้ให้บริการรักษาความปลอดภัยใน กิจกรรมในชุดข้อความ 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.
  }
}