יצירת מפתח גישה

כדי שהמשתמשים יוכלו לאמת את הזהות שלהם באמצעות מפתחות גישה, האפליקציה צריכה קודם לרשום או ליצור את מפתח הגישה לחשבון שלהם.

כדי ליצור את מפתח הגישה, צריך לקבל משרת האפליקציה את הפרטים שנדרשים ליצירת מפתח הגישה, ואז לקרוא ל-API של Credential Manager, שמחזיר זוג מפתחות פרטיים וציבוריים. המפתח הפרטי שמוחזר מאוחסן בספק אישורים, כמו מנהל הסיסמאות של Google, כמפתח גישה. המפתח הציבורי מאוחסן בשרת האפליקציה.

מפתחות הגישה מאוחסנים בספק אישורים, והמפתחות הציבוריים מאוחסנים בשרת האפליקציה
איור 1: יצירת מפתחות גישה

דרישות מוקדמות

מוודאים שהגדרתם Digital Asset Links ושהטירגוט הוא למכשירים עם Android 9 (רמת API ‏28) או גרסה מתקדמת יותר.

סקירה כללית

המדריך הזה מתמקד בשינויים שצריך לבצע באפליקציית הלקוח של הצד המסתמך כדי ליצור מפתח גישה, וכולל סקירה כללית קצרה של ההטמעה של שרת האפליקציה של הצד המסתמך. מידע נוסף על השילוב בצד השרת זמין במאמר בנושא רישום מפתחות גישה בצד השרת.

  1. מוסיפים תלויות לאפליקציה: מוסיפים את הספריות הנדרשות של Credential Manager.
  2. יצירת מופע של Credential Manager: יוצרים מופע של Credential Manager.
  3. קבלת אפשרויות ליצירת אמצעי אימות משרת האפליקציה: משרת האפליקציה, שולחים לאפליקציית הלקוח את הפרטים שנדרשים ליצירת מפתח הגישה, כמו מידע על האפליקציה, על המשתמש, וגם challenge ושדות אחרים.
  4. שליחת בקשה ליצירת מפתח גישה: באפליקציה, משתמשים בפרטים שהתקבלו משרת האפליקציה כדי ליצור אובייקט GetPublicKeyCredentialOption, ומשתמשים באובייקט הזה כדי להפעיל את השיטה credentialManager.getCredential() ליצירת מפתח גישה.
  5. טיפול בתגובה ליצירת מפתח גישה: כשמקבלים את פרטי הכניסה באפליקציית הלקוח, צריך לקודד, לסדר אותם ואז לשלוח את המפתח הציבורי לשרת האפליקציה. צריך גם לטפל בכל החריגים שיכולים להתרחש במקרה של יצירת מפתח גישה.
  6. מאמתים ושומרים את המפתח הציבורי בשרת: משלימים את השלבים בצד השרת כדי לאמת את המקור של פרטי הכניסה, ואז שומרים את המפתח הציבורי.
  7. הודעה למשתמש: מודיעים למשתמש שהסיסמה שלו נוצרה.

הוספת יחסי תלות לאפליקציה

מוסיפים את יחסי התלות הבאים לקובץ build.gradle של מודול האפליקציה:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

יצירת מופע של Credential Manager

משתמשים בהקשר של האפליקציה או הפעילות כדי ליצור אובייקט CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

קבלת אפשרויות ליצירת פרטי כניסה משרת האפליקציות

כשמשתמש לוחץ על לחצן 'יצירת מפתח גישה' או כשמשתמש חדש נרשם, צריך לשלוח בקשה מהאפליקציה לשרת האפליקציה כדי לקבל את המידע שנדרש להתחלת תהליך הרישום של מפתח הגישה.

משתמשים בספרייה שתואמת ל-FIDO בשרת האפליקציות כדי לשלוח לאפליקציית הלקוח את המידע שנדרש ליצירת מפתח גישה, כמו מידע על המשתמש, על האפליקציה ומאפייני הגדרה נוספים. מידע נוסף זמין במאמר בנושא רישום מפתחות גישה בצד השרת.

באפליקציית הלקוח, מפענחים את האפשרויות ליצירת מפתח ציבורי שנשלחו על ידי שרת האפליקציה. האפשרויות האלה מיוצגות בדרך כלל בפורמט JSON. למידע נוסף על פענוח לקוחות אינטרנט, אפשר לעיין במאמר בנושא קידוד ופענוח. באפליקציות לקוח ל-Android, צריך לטפל בפענוח בנפרד.

בקטע הקוד הבא מוצג המבנה של אפשרויות יצירת המפתח הציבורי שנשלחות משרת האפליקציה:

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

בין השדות העיקריים באפשרויות ליצירת מפתח ציבורי:

  • challenge: מחרוזת אקראית שנוצרת על ידי השרת ומשמשת למניעת מתקפות שידור חוזר.
  • rp: פרטים על האפליקציה.
    • rp.name: שם האפליקציה.
    • rp.id: הדומיין או תת-הדומיין של האפליקציה.
  • user: פרטים על המשתמש.
    • id: המזהה הייחודי של המשתמש. הערך הזה לא יכול לכלול פרטים אישיים מזהים, למשל כתובות אימייל או שמות משתמש. אפשר להשתמש בערך אקראי של 16 בייט.
    • name: מזהה ייחודי של החשבון שהמשתמש יזהה, כמו כתובת האימייל או שם המשתמש שלו. השם הזה יוצג בבורר החשבונות. אם משתמשים בשם משתמש, צריך להשתמש באותו ערך כמו באימות באמצעות סיסמה.
    • displayName: שם אופציונלי וידידותי למשתמש של החשבון, שיוצג בבורר החשבונות.
  • authenticatorSelection: פרטים על המכשיר שישמש לאימות.
    • authenticatorAttachment: מציין את אמצעי האימות המועדף. הערכים האפשריים הם:
      • platform: הערך הזה משמש לאמצעי אימות שמוטמע במכשיר של המשתמש, כמו חיישן טביעות אצבע.
      • cross-platform: הערך הזה משמש למכשירים ניידים כמו מפתחי אבטחה. בדרך כלל לא משתמשים בו בהקשר של מפתחות גישה.
      • לא צוין (מומלץ): אם לא מציינים את הערך הזה, המשתמשים יכולים ליצור מפתחות גישה במכשירים המועדפים עליהם. ברוב המקרים, האפשרות הטובה ביותר היא לא לציין את הפרמטר.
    • requireResidentKey: כדי ליצור מפתח גישה, מגדירים את הערך של השדה Boolean הזה ל-true.
    • residentKey: כדי ליצור מפתח גישה, מגדירים את הערך ל-required.
    • userVerification: משמש לציון הדרישות לאימות המשתמש במהלך רישום מפתח גישה. הערכים האפשריים הם:
      • preferred: משתמשים בערך הזה אם חוויית המשתמש חשובה יותר מההגנה, למשל בסביבות שבהן אימות המשתמש גורם לחיכוך רב יותר מאשר הגנה.
      • required: משתמשים בערך הזה אם נדרשת הפעלה של שיטת אימות משתמש שזמינה במכשיר.
      • discouraged: משתמשים בערך הזה אם לא מומלץ להשתמש בשיטת אימות משתמש.
        מידע נוסף על userVerification זמין במאמר userVerification deep dive.
  • excludeCredentials: רשימת מזהי פרטי הכניסה במערך כדי למנוע יצירה של מפתח גישה כפול אם כבר קיים מפתח גישה עם אותו ספק פרטי כניסה.

יצירת מפתח גישה

אחרי שניתחתם את האפשרויות ליצירת מפתח ציבורי בצד השרת, יוצרים מפתח גישה על ידי הוספת האפשרויות האלה לאובייקט CreatePublicKeyCredentialRequest וקריאה ל-createCredential().

createPublicKeyCredentialRequest כולל את:

  • requestJson: האפשרויות ליצירת פרטי הכניסה שנשלחות על ידי שרת האפליקציה.
  • preferImmediatelyAvailableCredentials: זהו שדה בוליאני אופציונלי שמגדיר אם להשתמש רק בפרטי כניסה שזמינים באופן מקומי או בפרטי כניסה שסונכרנו על ידי ספק פרטי הכניסה כדי למלא את הבקשה, במקום בפרטי כניסה ממפתחות אבטחה או בתהליכי עבודה של מפתחות היברידיים. אפשרויות השימוש הן:
    • false (ברירת מחדל): משתמשים בערך הזה אם הקריאה ל-Credential Manager הופעלה על ידי פעולת משתמש מפורשת.
    • true: משתמשים בערך הזה אם מתבצעת קריאה ל-Credential Manager באופן אופורטוניסטי, למשל כשפותחים את האפליקציה בפעם הראשונה.
      אם מגדירים את הערך ל-true ואין פרטי כניסה זמינים באופן מיידי, Credential Manager לא יציג ממשק משתמש והבקשה תיכשל באופן מיידי. במקרה כזה, יוחזר NoCredentialException לבקשות get ו-CreateCredentialNoCreateOptionException לבקשות create.
  • origin: השדה הזה מוגדר באופן אוטומטי לאפליקציות ל-Android. כדי להגדיר את origin בדפדפנים ובאפליקציות עם הרשאות דומות, צריך לעיין במאמר בנושא ביצוע קריאות ל-Credential Manager בשם צדדים אחרים באפליקציות עם הרשאות.
  • isConditional: זהו שדה אופציונלי שערך ברירת המחדל שלו הוא false. למידע נוסף: יצירת מפתח גישה באופן אוטומטי.

הפעלת הפונקציה createCredential() מציגה את ממשק המשתמש המובנה של Credential Manager, שמופיע בחלק התחתון של המסך ומבקש מהמשתמש להשתמש במפתח גישה ולבחור ספק אישורים וחשבון לאחסון. עם זאת, אם הערך של isConditional מוגדר כ-true, ממשק המשתמש לא מוצג ומפתח הגישה נוצר באופן אוטומטי.

יצירה אוטומטית של מפתח גישה

אפשר ליצור מפתח גישה למשתמש באופן אוטומטי אחרי התחברות מוצלחת באמצעות סיסמה. לשם כך, צריך להגדיר את הפרמטר isConditional לערך true ב-CreatePublicKeyCredentialRequest בזמן יצירת מפתח הגישה. אם למשתמש עדיין אין מפתח גישה, האפליקציה תנסה ליצור מפתח גישה באופן אוטומטי ברקע ולשמור אותו בספק האישורים של המשתמש, כמו מנהל הסיסמאות של Google. דוגמה לאופן ההטמעה מופיעה במדגם הציבורי.

דוגמה להתראה שמנהל הסיסמאות של Google מציג אחרי יצירת מפתח גישה
איור 2: התראה ממנהל הסיסמאות של Google

טיפול בתגובה

אחרי שהמשתמש מאומת באמצעות נעילת המסך של המכשיר, נוצר מפתח גישה והוא נשמר אצל ספק פרטי הכניסה שהמשתמש בחר.

התגובה אחרי שקוראים ל-createCredential() בהצלחה היא אובייקט PublicKeyCredential.

האסימון PublicKeyCredential נראה כך:

{
  "id": "<identifier>",
  "type": "public-key",
  "rawId": "<identifier>",
  "response": {
    "clientDataJSON": "<ArrayBuffer encoded object with the origin and signed challenge>",
    "attestationObject": "<ArrayBuffer encoded object with the public key and other information.>"
  },
  "authenticatorAttachment": "platform"
}

באפליקציית הלקוח, מבצעים סריאליזציה של האובייקט ושולחים אותו לשרת האפליקציה.

מוסיפים קוד לטיפול בכשלים כמו בקטע הקוד הבא:

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

אימות ושמירה של המפתח הציבורי בשרת האפליקציות

בשרת האפליקציות, צריך לאמת את פרטי הכניסה של המפתח הציבורי ואז לשמור את המפתח הציבורי.

כדי לאמת את המקור של פרטי הכניסה עם המפתח הציבורי, משווים אותם לרשימת ההיתרים של אפליקציות מאושרות. אם למפתח יש מקור לא מזוהה, דוחים אותו.

כדי לקבל את טביעת האצבע מסוג SHA 256 של האפליקציה:

  1. כדי להדפיס את אישור החתימה של אפליקציית הגרסה, מריצים את הפקודה הבאה בטרמינל:

    keytool -list -keystore <path-to-apk-signing-keystore>
    

    בתגובה, מזהים את טביעת האצבע מסוג SHA 256 של אישור החתימה, שמופיעה כ-Certificate fingerprints block : SHA256.

  2. מקודדים את טביעת האצבע של SHA256 באמצעות קידוד base64url. בדוגמה הזו של Python אפשר לראות איך מקודדים את טביעת האצבע בצורה נכונה:

    import binascii
    import base64
    fingerprint = '<SHA256 finerprint>' # your app's SHA256 fingerprint
    print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
    
  3. מוסיפים android:apk-key-hash: לתחילת הפלט מהשלב הקודם, כך שמתקבל פלט שדומה לזה:

    android:apk-key-hash:<encoded SHA 256 fingerprint>
    

    התוצאה צריכה להיות זהה למקור מותר בשרת האפליקציה. אם יש לכם כמה אישורי חתימה, כמו אישורים לניפוי באגים ולפרסום, או כמה אפליקציות, צריך לחזור על התהליך ולאשר את כל המקורות כחוקיים בשרת האפליקציה.

הודעה למשתמש

אחרי שמפתח הגישה נוצר בהצלחה, צריך להודיע למשתמשים על מפתח הגישה ולעדכן אותם שהם יכולים לנהל את מפתחות הגישה שלהם דרך אפליקציית ספק האישורים או בהגדרות האפליקציה. הודעה למשתמשים באמצעות תיבת דו-שיח, התראה או חלונית Snackbar בהתאמה אישית. אם גורם זדוני יוצר מפתח גישה באופן לא צפוי, צריך לשלוח התראה אבטחה מיידית. לכן, מומלץ להוסיף לשיטות האלה באפליקציה גם אמצעי תקשורת חיצוניים, כמו אימייל.

שיפור חוויית המשתמש

כדי לשפר את חוויית המשתמש כשמטמיעים הרשמה באמצעות Credential Manager, כדאי להוסיף פונקציונליות לשחזור פרטי הכניסה ולהשבית את תיבות הדו-שיח של המילוי האוטומטי.

הוספת פונקציונליות לשחזור פרטי הכניסה במכשיר חדש

כדי לאפשר למשתמשים להיכנס לחשבונות שלהם בצורה חלקה במכשיר חדש, צריך להטמיע את הפונקציה שחזור פרטי הכניסה. הוספת שחזור פרטי הכניסה עם BackupAgent מאפשרת למשתמשים להיכנס לחשבונות שלהם כשהם פותחים את האפליקציה המשוחזרת במכשיר חדש, כך שהם יכולים להשתמש באפליקציה מיד.

השבתת המילוי האוטומטי בשדות של פרטי הכניסה (אופציונלי)

כדי למנוע חפיפה בין תיבות הדו-שיח של המילוי האוטומטי (FillDialog ו-SaveDialog) לבין ממשק המשתמש של הגיליון התחתון של Credential Manager, צריך להוסיף את המאפיין isCredential לשדות של שם המשתמש והסיסמה במסכי האפליקציה שבהם המשתמשים אמורים להשתמש בממשק המשתמש של הגיליון התחתון של Credential Manager לאימות.

המאפיין isCredential נתמך ב-Android מגרסה 14 ואילך.

בדוגמה הבאה אפשר לראות איך מוסיפים את המאפיין isCredential לשדות הרלוונטיים של שם המשתמש והסיסמה בתצוגות הרלוונטיות באפליקציה:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:isCredential="true" />

השלבים הבאים