Android מסתמך על Provider
אבטחה כדי
לספק תקשורת רשת מאובטחת. אבל מעת לעת,
נקודות החולשה נמצאות בספק האבטחה המוגדר כברירת מחדל. להגנה מפני
את נקודות החולשה האלה, Google Play
מספק דרך לעדכן באופן אוטומטי את ספק האבטחה של המכשיר
להגנה מפני ניצול לרעה. הפעלה של שירותי Google Play Services עוזרת לוודא
שהאפליקציה פועלת במכשיר שיש בו את העדכונים האחרונים
להגן עליכם מפני ניצול לרעה.
לדוגמה, התגלתה נקודת חולשה ב-OpenSSL (CVE-2014-0224) שעלולה לאפשר לאפליקציות להיחשף להתקפה בנתיב שמפענחת תעבורת נתונים מאובטחת בלי שצד כלשהו ידע על כך. בגרסה 5.0 של Google Play Services יש תיקון, אבל האפליקציות צריכות לבדוק אם התיקון הזה מותקן. על ידי באמצעות השיטות של Google Play Services, תוכלו לוודא שהאפליקציה שלכם פועלת במכשיר שמאובטח מפני מתקפה זו.
זהירות: עדכון האבטחה של המכשיר Provider
לא מעדכן את android.net.SSLCertificateSocketFactory
, שעדיין חשוף להתקפות. במקום להשתמש בכיתה הזו שהוצאה משימוש, אנחנו מעודדים את מפתחי האפליקציות
להשתמש בשיטות ברמה גבוהה לאינטראקציה עם קריפטוגרפיה, כמו
HttpsURLConnection
תיקון ספק האבטחה באמצעות ProviderInstaller
כדי לעדכן את ספק האבטחה של המכשיר, משתמשים במחלקה ProviderInstaller
. כדי לוודא שספק האבטחה עדכני (ולעדכן אותו במקרה הצורך), אפשר להפעיל את השיטה installIfNeeded()
(או installIfNeededAsync()
) של הכיתה הזו. בקטע הזה מתוארות האפשרויות האלה באופן כללי. בקטעים הבאים מפורטות דוגמאות ומוסבר בהרחבה איך עושים את זה.
כשאתם קוראים ל-installIfNeeded()
, ה-ProviderInstaller
מבצע את הפעולות הבאות:
- אם ה-
Provider
של המכשיר מתעדכן בהצלחה (או שכבר מעודכן), השיטה מחזירה בלי להפעיל חריגה. - אם ספריית Google Play Services במכשיר לא עדכנית, השיטה
זורקות
GooglePlayServicesRepairableException
לאחר מכן, האפליקציה יכולה לזהות את החריג הזה ולהציג למשתמש תיבת דו-שיח מתאימה לעדכון Google Play Services. - אם מתרחשת שגיאה שלא ניתן לשחזר, ה-method יזרוק את הערך
GooglePlayServicesNotAvailableException
כדי לציין שהוא לא יכול לעדכן אתProvider
. לאחר מכן, האפליקציה יכולה לזהות את החריגה ולבחור את קו ההתנהלות המתאים, למשל הצגת תרשים תהליך סטנדרטי לתיקון.
השיטה installIfNeededAsync()
פועלת באופן דומה, אלא שבמקום להפעיל חריגים, היא קוראת לשיטת ה-callback המתאימה כדי לציין הצלחה או כישלון.
אם ספק האבטחה כבר עדכני, הפונקציה installIfNeeded()
לוקחת
פרק זמן זניח. אם השיטה
צריך להתקין Provider
חדש, הפעולה הזו עשויה להימשך
בין 30 ל-50 אלפיות השנייה (במכשירים חדשים יותר) עד 350 אלפיות שנייה (בגרסאות ישנות יותר)
מכשירים). כדי לא להשפיע על חוויית המשתמש:
- צריך להפעיל את
installIfNeeded()
משרשורי הרשת ברקע מיד אחרי שהם נטענים, במקום להמתין עד שהשרשור ינסה להשתמש ברשת. (אין נזק בקריאה ל-method כמה פעמים, כי הוא חוזר מיד אם ספק האבטחה לא צריך עדכון). - צריך לקרוא לגרסה האסינכרונית של השיטה,
installIfNeededAsync()
, אם חוויית המשתמש עשויה להיות מושפעת מהנעילה של השרשור. לדוגמה, אם הקריאה מגיעה מפעילות בשרשור של ממשק המשתמש. (אם תעשו זאת, תצטרכו להמתין לסיום הפעולה לפני שתנסו לבצע תקשורת מאובטחת. הפונקציהProviderInstaller
קורא לשיטהonProviderInstalled()
של המאזין כדי לסמן הצלחה).
אזהרה: אם
ProviderInstaller
לא ניתן להתקין גרסה מעודכנת של Provider
,
ספק האבטחה של המכשיר עלול להיות חשוף למתקפות ידועות. האפליקציה צריכה לפעול כאילו כל התקשורת ב-HTTP לא מוצפנת.
אחרי העדכון של Provider
, כל השיחות אל
ממשקי API של אבטחה (כולל ממשקי API של SSL) מנותבים דרכם.
(עם זאת, הכלל הזה לא חל על android.net.SSLCertificateSocketFactory
, שעדיין חשופה ל
מנצל כמו
CVE-2014-0224.)
תיקון באופן סינכרוני
הדרך הפשוטה ביותר לתקן את ספק האבטחה היא להפעיל
אמצעי תשלום installIfNeeded()
.
הפעולות האלה מתאימות אם חסימת השרשור לא משפיעה על חוויית המשתמש
בזמן שהוא ממתין לסיום הפעולה.
לדוגמה, הנה הטמעה של worker שמעדכן את ספק האבטחה. מכיוון שה-worker פועל ברקע, אין בעיה אם השרשור נחסם בזמן ההמתנה לעדכון של ספק האבטחה. העובד קורא ל-installIfNeeded()
כדי לעדכן את ספק האבטחה. אם השיטה מחזירה תשובה רגילה, העובד יודע שספק האבטחה מעודכן. אם השיטה מפעילה חריגה, העובד יכול לבצע פעולה מתאימה (למשל, להציע למשתמש לעדכן את Google Play Services).
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 אלפיות שנייה (במכשירים ישנים יותר). אם מבצעים את העדכון בשרשור שמשפיע ישירות על חוויית המשתמש, כמו שרשור ממשק המשתמש, לא כדאי לבצע קריאה סינכרונית לעדכון הספק, כי היא עלולה לגרום להקפאה של האפליקציה או המכשיר עד שהפעולה תסתיים. במקום זאת, השתמשו בתבנית
אמצעי תשלום installIfNeededAsync()
. שהשיטה הזו מצביעה על הצלחה או כישלון
קריאה חוזרת (callback).
לדוגמה, הנה קוד שמעדכן את ספק האבטחה
בפעילות ב-thread של ממשק המשתמש. הפעילות קוראת ל-installIfNeededAsync()
כדי לעדכן את הספק, ומגדירה את עצמה כמאזין כדי לקבל התראות על הצלחה או על כשל. אם ספק האבטחה מעודכן או מתעדכן בהצלחה, מתבצעת קריאה ל-method onProviderInstalled()
של הפעילות, והפעילות יודעת שהתקשורת מאובטחת. אם
לא ניתן לעדכן את הספק, הפעילות
onProviderInstallFailed()
נקראת, והפעילות יכולה לנקוט פעולה מתאימה (כמו
בקשה מהמשתמש לעדכן את Google Play Services).
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. } }