אישור פרטי כניסה דיגיטליים אונליין

אפשר לקבל תעודות מזהות דיגיטליות גם בתהליכים באפליקציה וגם בתהליכים באינטרנט. כדי לאשר את פרטי הכניסה מ-Google Wallet, צריך:

  1. מבצעים אינטגרציה דרך האפליקציה או האתר לפי ההוראות שסופקו,
  2. יש למלא את הטופס הזה כדי לבקש את התנאים וההגבלות לקבלת פרטי הכניסה מ-Google Wallet, ולאשר אותם.

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

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

  • קישור לתנאים ולהגבלות
  • לוגו
  • אתר
  • מזהי חבילות אפליקציות (לשילובי אפליקציות ל-Android)
    • כולל גרסאות build למפתחים או לניפוי באגים
  • חתימת האפליקציה
    • $ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
  • מזהה Gmail ששימש להצטרפות לגרסת הבטא הציבורית

פורמטים נתמכים של פרטי כניסה

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

  1. mdocs – מוגדר על ידי ISO.
  2. w3c Verifiable Credentials – מוגדר על ידי W3C.

'מנהל פרטי הכניסה של Android' תומך בשני הפורמטים, אבל כרגע Google Wallet תומך רק בתעודות מזהות דיגיטליות שמבוססות על mdoc.

פרטי כניסה נתמכים

ב-Google Wallet יש תמיכה בשני סוגי פרטי כניסה:

  1. רישיון נהיגה בנייד (mDL)
  2. תעודה מזהה

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

חוויית משתמש

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

המשתמש מתבקש לאמת את הגיל באפליקציה או באתר המשתמש רואה את פרטי הכניסה הזמינים שעומדים בדרישות הדף 'אישור' מוצג למשתמש ב-Google Wallet האימות של המשתמש לאישור השיתוף נתונים שנשלחים לאפליקציה או לאתר
המשתמש מתבקש לאמת את הגיל באפליקציה או באתר המשתמש רואה את פרטי הכניסה הזמינים שעומדים בדרישות הדף 'אישור' מוצג למשתמש ב-Google Wallet האימות של המשתמש לאישור השיתוף נתונים שנשלחים לאפליקציה או לאתר

הערות חשובות

  1. יש גמישות בדרך שבה האפליקציה או האתר יוצרים את נקודת הכניסה ל-API. כפי שמוצג בשלב 1, מומלץ להציג לחצן כללי כמו 'אימות באמצעות תעודה מזהה דיגיטלית', כי עם הזמן צפויות להיות זמינות אפשרויות נוספות מעבר ל-Google Wallet דרך ה-API.
  2. מסך הבורר בשלב 2 מומר על ידי Android. פרטי הכניסה שעומדים בדרישות נקבעים על סמך התאמה בין לוגיקת הרישום שכל ארנק מספק לבין הבקשה שנשלחת על ידי הצד הנסמך.
  3. שלב 3 מעובד על ידי Google Wallet. במסך הזה יוצגו השם, הלוגו ומדיניות הפרטיות שהמפתח מספק.

הוספת תהליך אימות באמצעות תעודה מזהה דיגיטלית

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

המשתמש מתבקש לאמת את הגיל באפליקציה או באתר המשתמש מועבר אל Google Wallet כדי לקבל תעודה מזהה דיגיטלית
המשתמש מתבקש לאמת את הגיל באפליקציה או באתר המשתמש מועבר אל Google Wallet כדי לקבל תעודה מזהה דיגיטלית

אין תעודת מזהה דיגיטלית זמינה

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

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

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

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

לפניכם דוגמה לבקשת requestJson של mdoc לקבלת פרטי כניסה לזהות מכל ארנק במכשיר Android או באינטרנט.

{
      "requests" : [
        {
          "protocol": "openid4vp",
          "data": {<credential_request>} // This is an object, shouldn't be a string.
        }
      ]
}

בקשת הצפנה

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

הפרמטר credential_request ב-requestJson יכלול את השדות הבאים.

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt",
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "cred1",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"  // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ]
          }
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",
          "alg" : "ECDH-ES",
        }
      ]
    },
    "authorization_encrypted_response_alg": "ECDH-ES",
    "authorization_encrypted_response_enc": "A128GCM"
  }
}

אתם יכולים לבקש מספר בלתי מוגבל של מאפיינים נתמכים מכל פרטי כניסה שמאוחסנים ב-Google Wallet.

באפליקציה

כדי לבקש פרטי כניסה לזיהוי מאפליקציות ל-Android:

עדכון יחסי התלות

בקובץ build.gradle של הפרויקט, מעדכנים את יחסי התלות כך שישתמשו ב-Credential Manager (בטא):

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    // optional - needed for credentials support from play services, for devices running Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

הגדרת Credential Manager

כדי להגדיר ולאתחל אובייקט CredentialManager, מוסיפים לוגיקה דומה לזו:

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

מאפייני זהות של בקשות

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

אנחנו ממליצים לשותפים ליצור את הבקשות שלהם בשרת גם בשילובים של אפליקציות ל-Android.

משתמשים ב-requestJson מRequest Format שמכיל את request בקריאה לפונקציה GetDigitalCredentialOption()

// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

אימות ותיקוף התשובה

אחרי שתקבלו תשובה מהארנק, עליכם לוודא שהיא תקינה ומכילה את התגובה credentialJson.

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson) // make a server call to validate the response
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}

התגובה של credentialJson מכילה identityToken (JWT) מוצפן, שמוגדר על ידי W3C. אפליקציית Wallet אחראית על יצירת התגובה הזו.

דוגמה:

"response" : {
  <encrpted_response>
}

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

פיתוח אתרים

כדי לבקש פרטי כניסה לזיהוי באמצעות Digital Credentials API ב-Chrome, צריך להירשם לגרסת המקור לניסיון של Digital Credentials API.

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp",
              data: {<credential_request>} // This is an object, shouldn't be a string.
            }
          ]
        }
      })

שולחים את התשובה מה-API הזה חזרה לשרת כדי לאמת את התשובה של פרטי הכניסה

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

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

  1. פענוח התשובה באמצעות מפתח פרטי

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

    דוגמה ל-Python:

    from jwcrypto import jwe, jwk
    
    # Retrieve the Private Key from Datastore
    reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
    
    # Decrypt the JWE encrypted response from Google Wallet
    jwe_object = jwe.JWE()
    jwe_object.deserialize(encrypted_jwe_response_from_wallet)
    jwe_object.decrypt(reader_private_jwk)
    decrypted_payload_bytes = jwe_object.payload
    decrypted_data = json.loads(decrypted_payload_bytes)
    

    decrypted_data יניב קובץ JSON מסוג vp_token שמכיל את פרטי הכניסה

    {
      "vp_token":
      {
        "cred1": "<credential_token>"
      }
    }
    
  2. יצירת תמליל של הסשן

    השלב הבא הוא ליצור את SessionTranscript לפי ISO/IEC 18013-5:2021 עם מבנה העברה ספציפי ל-Android או לאינטרנט:

    SessionTranscript = [
      null,                // DeviceEngagementBytes not available
      null,                // EReaderKeyBytes not available
      [
        "OpenID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    בשני המקרים של העברה מ-Android או מהאתר, צריך להשתמש באותו מספר חד-פעמי (nonce) שבו השתמשתם כדי ליצור את credential_request.

    Android Handover

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          clientId,           // "android-origin:<app_package_name>",
          nonce,              // nonce that was used to generate credential request
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    העברה לדפדפן

        BrowserHandoverData =[
          origin,               // Origin URL
          clientId,             // "web-origin:<origin>"
          nonce,               //  nonce that was used to generate credential request
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    באמצעות SessionTranscript, צריך לאמת את DeviceResponse בהתאם לסעיף 9 ב-ISO/IEC 18013-5:2021. התהליך כולל כמה שלבים, כמו:

  3. בדיקת אישור המנפיק במדינה איך בודקים את אישורי IACA של המנפיק הנתמך

  4. אימות החתימה של MSO (18013-5, סעיף 9.1.2)

  5. חישוב ובדיקה של ValueDigests לרכיבי נתונים (18013-5 סעיף 9.1.2)

  6. אימות החתימה של deviceSignature (18013-5 סעיף 9.1.3)

{
  "version": "1.0",
  "documents": [
    {
      "docType": "org.iso.18013.5.1.mDL",
      "issuerSigned": {
        "nameSpaces": {...}, // contains data elements
        "issuerAuth": [...]  // COSE_Sign1 w/ issuer PK, mso + sig
      },
      "deviceSigned": {
        "nameSpaces": 24(<< {} >>), // empty
        "deviceAuth": {
          "deviceSignature": [...] // COSE_Sign1 w/ device signature
        }
      }
    }
  ],
  "status": 0
}

בדיקת הפתרון

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

  • שכפול של המאגר של אפליקציות העזר
  • פותחים את הפרויקט ב-Android Studio.
  • יוצרים ומפעילים את היעד appholder במכשיר Android או במהדמ.

אימות מבוסס הוכחת אפס ידע (ZKP)

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

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

מושגים מרכזיים של ZKP בזהות דיגיטלית:

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

המאפיינים המרכזיים של הוכחות ללא ידיעה (ZKP):

  • שלמות: אם ההצהרה נכונה וגם המוכיח וגם המאמת ישרים, המאמת יהיה משוכנע.
  • תקינות: אם ההצהרה שגויה, מוכיח לא ישר לא יכול (בסבירות גבוהה מאוד) לשכנע גורם מאמת ישר שהיא נכונה.
  • אפס ידע: המאמת לא לומד דבר מלבד העובדה שההצהרה נכונה. לא נחשפים נתונים בפועל מהזהות של המאמת.

כדי לקבל חזרה הוכחה ללא ידע מ-Google Wallet, צריך לשנות את פורמט הבקשה ל-mso_mdoc_zk ולהוסיף את zk_system_type לבקשה.

  ...
  "dcql_query": {
    "credentials": [{
      "id": "cred1",
      "format": "mso_mdoc_zk",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
        "zk_system_type": [
         {
          "system": "longfellow-libzk-v1",
           "circuit_hash": "2b8e0c49b08eb1801b9bd7a82aa9eb3736a7519fc2b409asdhj1237034", // This will differ if you need more than 1 attribute.
           "num_attributes": 1,
           "version": 1
         }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...