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

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

  1. משלבים את התכונה באמצעות אפליקציה או אתר, בהתאם להוראות שמופיעות.
  2. כדי לבדוק את התהליך באמצעות ארגז החול של Google Wallet, צריך להשתמש בתעודה מזהה לבדיקה.
  3. כדי להתחיל בשידור חי, צריך למלא את הטופס הזה כדי לבקש גישה ולאשר את התנאים וההגבלות של אישורי הכניסה ל-Google Wallet. חובה למלא את הטופס לכל אחת מהישויות העסקיות שלכם. הצוות שלנו ייצור איתך קשר אחרי שתמלא את הטופס.
  4. אם יש לך שאלות, אפשר לפנות אלwallet-identity-rp-support@google.com.

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

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

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

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

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

‫Google Wallet תומך ב-2 סוגים של פרטי כניסה:

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

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

חוויית משתמש

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

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

הערות חשובות

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

הוספת תהליך של תעודה מזהה דיגיטלית

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

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

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

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

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

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

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

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

{
      "requests" : [
        {
          "protocol": "openid4vp-v1-unsigned",
          "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", // change this to dc_api if you want to demo with a non encrypted response.
  "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"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
  },
  "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",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "isserauth_alg_values": [
          -7
        ]
      }
    }
  }
}

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

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

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "mdl-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      },
      {  // Credential type 2
        "id": "id_pass-request",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "com.google.wallet.idcard.1"
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ],
            "intent_to_retain": false // set this to true if you are saving the value of the field
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ],
            "intent_to_retain": false
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ],
            "intent_to_retain": false
          }
        ]
      }
    ]
    credential_sets : [
      {
        "options": [
          [ "mdl-request" ],
          [ "id_pass-request" ]
        ]
      }
    ]
  },
  "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",  // This is required
          "alg" : "ECDH-ES",  // This is required
        }
      ]
    },
    "vp_formats_supported": {
      "mso_mdoc": {
        "deviceauth_alg_values": [
          -7
        ],
        "isserauth_alg_values": [
          -7
        ]
      }
    }
  }
}

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

באפליקציה

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

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

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

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

הגדרת מנהל פרטי הכניסה

כדי להגדיר ולהפעיל אובייקט 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 אחראית ליצירת התגובה הזו.

דוגמה:

{
  "protocol" : "openid4vp-v1-unsigned",
  "data" : {
    <encrpted_response>
  }
}

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

פיתוח אתרים

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

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp-v1-unsigned",
              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)
    # Save public key thumbprint for session transcript
    encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()
    
    # 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": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
      }
    }
    
  2. יצירת תמליל של הסשן

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

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

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

    העברה בין מכשירים ב-Android

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

    העברה של הדפדפן

        BrowserHandoverData =[
          origin,               // Origin URL
          nonce,               //  nonce that was used to generate credential request
          encryption_public_jwk_thumbprint,  // Encryption public key (JWK) Thumbprint
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

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

  3. בודקים את האישור של המנפיק במדינה. כאן מפורטים אישורי ה-IACA של המנפיקים הנתמכים.

  4. אימות חתימת MSO (18013-5 Section 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
}

יצירת הפתרון

כדי לבנות את הפתרון, אפשר לעיין ביישום העזר של כלי אימות הזהויות ב-GitHub

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

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

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

מושגי מפתח בנושא 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": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 5,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
        {
          "system": "longfellow-libzk-v1",
          "circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
          "num_attributes": 1, // number of attributes (in claims) this has can support
          "version": 6,
          "block_enc_hash": 4096,
          "block_enc_sig": 2945,
        }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...
      "client_metadata": {
        "jwks": {
          "keys": [ // sample request encryption key
            {
              ...

תקבלו מהארנק הוכחה מוצפנת של אפס ידע. אפשר לאמת את ההוכחה הזו מול אישורי IACA של מנפיקים באמצעות הספרייה longfellow-zk של Google.

השירות לאימות מכיל שרת מבוסס-Docker שמוכן לפריסה, שמאפשר לכם לאמת את התגובה מול אישורים מסוימים של IACA שהונפקו על ידי הרשות המנפיקה.

אפשר לשנות את certs.pem כדי לנהל אישורי הנפקה של IACA שרוצים לתת בהם אמון.

לפרטים נוספים אפשר לפנות לתמיכה באימייל: wallet-identity-rp-support@google.com