אישור אימות דיגיטלי פנים אל פנים (אופליין)

במדריך הזה מוסבר איך גורמים מסתמכים (RP) ומפתחי קוראים יכולים להטמיע אימות פנים אל פנים (אופליין) של פרטי כניסה דיגיטליים שמוצגים מ-Google Wallet, בהתאם לתקן הבינלאומי ISO/IEC 18013-5.

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

סקירה כללית על הצגה אופליין (ISO/IEC 18013-5)

תקן ISO/IEC 18013-5 מגדיר פרוטוקול סטנדרטי שמאפשר פעולה הדדית להצגה אופליין בין מחזיק (המכשיר הנייד של המשתמש שבו פועלת אפליקציית Google Wallet) לבין קורא / מאמת (מסוף פיזי או אפליקציה נלווית לנייד).

תהליך ההצגה מתרחש בשלבים נפרדים:

  1. אינטראקציה עם המכשיר: הקורא וה-Wallet יוצרים קשר ראשוני באמצעות הצמדה ב-NFC (העברה סטטית או העברה בהסכמה) או סריקת קוד QR. בשלב הזה, מתבצעת החלפה של המטא-נתונים של מעורבות המכשיר והמפתח הציבורי הזמני של הקורא (EReaderKey).
  2. חיבור להעברת נתונים: מתבצע משא ומתן על ערוץ Bluetooth Low Energy (BLE) מאובטח ומוצפן (כשהקורא פועל במצב Central Client או Peripheral Server).
  3. בקשת מכשיר: הקורא משדר DeviceRequest בקידוד CBOR שמציין את סוג המסמך המבוקש (כמו org.iso.18013.5.1.mDL) ואת מרחבי השמות ורכיבי הנתונים הספציפיים שהתבקשו.
  4. הסכמת המשתמש ואימות המכשיר: אפליקציית Google Wallet מבקשת מהמשתמש לבדוק את רכיבי הנתונים המבוקשים ולאשר את השיתוף באמצעות אימות ביומטרי או נעילת מסך.
  5. תגובת המכשיר ואימות קריפטוגרפי: הארנק שולח בחזרה DeviceResponse בקידוד CBOR שמכיל את אובייקט האבטחה לנייד (MSO) החתום ואת רכיבי הנתונים החתומים על ידי המכשיר. הקורא מאמת את החתימות הקריפטוגרפיות מול אישורי בסיס מהימנים.

‫Multipaz Open-Source SDK

כדי להטמיע אפליקציה לקריאה או לאימות, Google ממליצה להשתמש ב-Multipaz, ערכת SDK בקוד פתוח ל-Kotlin Multiplatform (KMP) שפותחה במקור על ידי Google ונתרמה ל-OpenWallet Foundation (OWF).

‫Multipaz מספקת הטמעה מוכנה לייצור של פרוטוקולים של קורא וארנק ISO/IEC 18013-5, צינורות עיבוד נתונים (pipeline) של אימות קריפטוגרפי, קידוד/פענוח של CBOR וסכימות של סוגי מסמכים שניתן להרחיב.

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

השלבים הבאים מדגימים איך לשלב את Multipaz SDK באפליקציית קורא ל-Android.

שלב 1: הוספת יחסי תלות

ספריות Multipaz מתפרסמות ב-Maven Central. מוסיפים את המודולים הנדרשים לקובץ build.gradle.kts של האפליקציה:

// build.gradle.kts
dependencies {
    // Core Multipaz library (protocol engine, CBOR, crypto)
    implementation("org.multipaz:multipaz:0.100.0")

    // Android-specific platform bindings (NFC, BLE, Keystore)
    implementation("org.multipaz:multipaz-android:0.100.0")

    // Standardized document types (mDL, EU PID, etc.)
    implementation("org.multipaz:multipaz-doctypes:0.100.0")
}

שלב 2: מגדירים את ההרשאות ב-Android

כדי לאמת את הזהות פנים אל פנים, צריך לתת הרשאות חומרה ל-NFC, לסריקה במצלמה (לסריקת קוד QR) ולהעברת נתונים באמצעות Bluetooth עם צריכת אנרגיה נמוכה (BLE). מוסיפים את ההרשאות הבאות לקובץ AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- NFC Engagement -->
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature android:name="android.hardware.nfc" android:required="false" />

    <!-- Camera for QR Code Engagement -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />

    <!-- Bluetooth Low Energy Transport (Android 12+) -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

    <!-- Legacy Bluetooth Permissions for Android 11 and lower -->
    <uses-permission android:name="android.permission.BLUETOOTH"
                     android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                     android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     android:maxSdkVersion="30" />
</manifest>

שלב 3: הפעלת מנוע האימות

משתמשים בהפשטות של Multipaz לחיבור VerificationHelper או Reader כדי לטפל באירועי מעורבות ולנהל את מחזור החיים של תקשורת BLE:

import org.multipaz.verification.VerificationHelper
import org.multipaz.cbor.Cbor
import org.multipaz.crypto.Crypto

class ReaderManager(private val context: android.content.Context) {

    private var verificationHelper: VerificationHelper? = null

    fun startListeningForEngagement() {
        verificationHelper = VerificationHelper.Builder(
            context = context,
            listener = object : VerificationHelper.Listener {
                override fun onDeviceConnected() {
                    // BLE channel established, send request
                    sendDeviceRequest()
                }

                override fun onResponseReceived(deviceResponseBytes: ByteArray) {
                    // Process and verify the received credential payload
                    handleDeviceResponse(deviceResponseBytes)
                }

                override fun onError(error: Throwable) {
                    // Handle transport or protocol errors
                }

                override fun onDeviceDisconnected(transportTransportSpecificTermination: Boolean) {
                    // Connection closed
                }
            }
        ).build()
    }
}

שלב 4: יצירת DeviceRequest

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

fun sendDeviceRequest() {
    // Specify the DocType and requested elements
    val docType = "org.iso.18013.5.1.mDL"
    val namespace = "org.iso.18013.5.1"

    // Map of requested elements: elementName -> intentToRetain
    val requestedElements = mapOf(
        "family_name" to false,
        "given_name" to false,
        "age_over_21" to false,
        "portrait" to false,
        "driving_privileges" to false
    )

    // Build ISO/IEC 18013-5 DeviceRequest structure
    val deviceRequestBytes = verificationHelper?.buildDeviceRequest(
        docType = docType,
        itemsToRequest = mapOf(namespace to requestedElements)
    )

    if (deviceRequestBytes != null) {
        verificationHelper?.sendDeviceRequest(deviceRequestBytes)
    }
}

תמיכה בסוגי מסמכים נוספים (DocTypes)

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

1. סוגי מסמכים סטנדרטיים מובנים

הספרייה multipaz-doctypes מספקת מודלים של סכימות שהוגדרו מראש לפרטי כניסה סטנדרטיים:

מזהה סוג המסמך תקן / היקף מרחב שמות אופייני רכיבים נפוצים
org.iso.18013.5.1.mDL רישיון נהיגה בנייד לפי תקן ISO/IEC 18013-5 org.iso.18013.5.1 ‫family_name, given_name, birth_date, issue_date, expiry_date, issuing_authority, document_number, portrait, driving_privileges, age_over_18, age_over_21
eu.europa.ec.eudi.pid.1 נתוני זיהוי אישיים בארנק הזהות הדיגיטלית של האיחוד האירופי (EUDIW) eu.europa.ec.eudi.pid.1 ‫family_name, first_name, birth_date, nationality, issuing_country, issuing_authority, personal_administrative_number
com.google.wallet.idcard.1 מסמכי זיהוי ב-Google Wallet / תעודות מזהות לבדיקה com.google.wallet.idcard.1 given_name,‏ family_name,‏ birth_date,‏ document_number,‏ portrait

2. בקשה לסוגי מסמכים בהתאמה אישית

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

// Example: Requesting a custom event ticket credential
val customDocType = "com.example.events.ticket"
val customNamespace = "com.example.events.ticket.1"

val customRequestedItems = mapOf(
    "ticket_id" to false,
    "event_name" to false,
    "seat_section" to false,
    "vip_access" to false
)

val multiDocRequestBytes = verificationHelper?.buildMultiDocDeviceRequest(
    documents = listOf(
        DocumentRequest(
            docType = customDocType,
            namespaces = mapOf(customNamespace to customRequestedItems)
        )
    )
)

אימות קריפטוגרפי וניהול אמון

קבלת מטען ייעודי (payload) של תגובה היא רק השלב הראשון. קוראים חייבים לבצע אימות קריפטוגרפי בן ארבעה שלבים כדי לאמת את האותנטיות והתקינות של התעודה המוצגת.

שלב האימות יעד האימות
1. אימות המנפיק אימות של IssuerAuth (COSE_Sign1) מול אישורי בסיס מהימנים של IACA
2. בדיקת חלון התוקף צריך לוודא שהשעה הנוכחית היא בין validFrom ל-validUntil
3. בדיקת תקינות הנתונים חישוב תמציות SHA-256 של רכיבים שהוחזרו והתאמה ל-MSO ValueDigests
4. אימות המכשיר אימות חתימה או MAC של DeviceSigned באמצעות DeviceKey שקשור ל-SessionTranscript

1. תהליך האימות ב-4 שלבים

  1. אימות המנפיק (IssuerAuth):
    • אובייקט האבטחה לנייד (MSO) חתום על ידי הרשות המנפיקה (מטען ייעודי (payload) של IssuerAuth).
    • הקורא מאמת את החתימה של COSE_Sign1 באמצעות אישור החתימה על המסמך, ומוודא שהאישור מקושר לאישור בסיס מהימן של רשות האישורים המנפיקה (IACA).
  2. אימות חלון התוקף:
    • הקורא בודק את חותמות הזמן validityInfo.validFrom ו-validityInfo.validUntil ב-MSO בהשוואה לשעון הנוכחי של הקורא כדי לוודא שתוקף פרטי הכניסה לא פג.
  3. אימות תקינות הנתונים (ValueDigests):
    • על כל IssuerSignedItem שמתקבל, הקורא מחשב את התקציר שלו (למשל SHA-256) ומוודא שהוא תואם לרשומת הגיבוב המתאימה במילון ValueDigests של ה-MSO.
  4. אימות מכשיר (DeviceSigned):
    • הקורא מוודא שהמכשיר שמציג את פרטי הכניסה מחזיק במפתח הפרטי שתואם ל-DeviceKey שפורסם בתוך ה-MSO החתום.
    • כדי לעשות את זה, מאמתים את DeviceAuth (DeviceSignature או DeviceMac) דרך SessionTranscript, מקשרים את הסשן למפתח הזמני של הקורא ומונעים התקפות מסוג "הפעלה מחדש" ו"אדם באמצע".

2. ניהול אישורי בסיס מהימנים של IACA

קוראים בסביבת הייצור חייבים לכלול מאגר אישורים מקומי ומאובטח שמכיל אישורי בסיס מהימנים של IACA:

  • אישורי IACA בסביבת הייצור: מורידים ומגדירים אישורי בסיס מרשויות הנפקה רשמיות. עיינו ברשימה של מנפיקים נתמכים ואישורי IACA.
  • ‫AAMVA VICAL: בתחומי השיפוט של ארה"ב, מערכות קוראים יכולות להשתלב עם שירות Verified Issuer Certificate Authority List ‏ (VICAL) של American Association of Motor Vehicle Administrators ‏ (AAMVA) כדי לסנכרן באופן אוטומטי ישויות עוגן אמינות של המדינה.
  • שורשי בדיקה של ארגז חול: כשמבצעים בדיקה מול פרטי כניסה של ארגז חול, צריך לוודא שהקורא נותן אמון ב-Google Sandbox IACA Root.
import org.multipaz.crypto.X509Cert

// Configure trusted IACA certificates in the trust store
val trustedCertificates = mutableListOf<X509Cert>()

// Add official state IACA certificates
trustedCertificates.add(X509Cert.fromPem(sampleStateIacaPem))

// Add Google Sandbox IACA root certificate for testing
trustedCertificates.add(X509Cert.fromPem(googleSandboxIacaPem))

val verifier = MultipazVerifier(trustStore = trustedCertificates)
val verificationResult = verifier.verify(deviceResponseBytes, sessionTranscript)

if (verificationResult.isIssuerAuthorized && verificationResult.isDeviceAuthenticated) {
    // Credential is valid and authentic
} else {
    // Reject presentation: cryptographic validation failed
}

אימות קוראים (מומלץ)

אימות קורא מאפשר לאפליקציית הקורא להוכיח את הזהות שלה ל-Google Wallet באופן קריפטוגרפי על ידי חתימה על המבנה של ReaderAuthentication באמצעות אישור קורא מורשה מסוג X.509.

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

כלי בדיקה ופיתוח

כדי להאיץ את השילוב, אפשר להשתמש בכלים למפתחים ובדוגמאות להטמעה הבאים:

  1. אפליקציות לדוגמה של Multipaz:
    • משכפלים את מאגר Multipaz ומריצים את האפליקציה לדוגמה IdentityReader ל-Android כדי לבדוק תהליכי אימות פיזיים.
  2. איך יוצרים תעודה מזהה לבדיקה ב-Google Wallet:
  3. בדיקת מאמת מבוסס-אינטרנט:
    • אפשר להשתמש ב-verifier.multipaz.org כדי לבדוק בקשות CBOR, לבחון שאילתות של הצהרות ולבדוק מצגות מבוססות-אינטרנט של W3C או ISO 18013-7.

פתרון בעיות ואבחון בשטח

בטבלה הבאה מפורטות בעיות נפוצות שנתקלים בהן במהלך אימות אופליין, והפתרונות המומלצים:

הבעיה או התסמין שורש הבעיה הפתרון המומלץ
הזמן הקצוב לתפוגה של חיבור BLE / כשל בחיבור
  • הפרעות בתדר רדיו בסביבות עם צפיפות גבוהה.
  • חוסר תאימות בין מצב היקפי למצב מרכזי בחומרה ספציפית של קורא.
  • זמן קצוב לתפוגה של סריקה.
  • מוודאים שהקורא תומך גם במצב BLE Central Client וגם במצב Peripheral Server.
  • כוונון חלון הסריקה והמרווח של BLE לסריקה אגרסיבית במהלך מעורבות פעילה.
  • מוודאים שהמשא ומתן על גודל ה-MTU הושלם בהצלחה.
האינטראקציה בטאץ' ב-NFC נכשלת או נקטעת המשתמש מרחיק את המכשיר הנייד מהאנטנה של הקורא לפני שהרשומה של העברת הנתונים ב-BLE מועברת במלואה.
  • המסוף צריך לספק משוב חזותי, קולי או פיזי מיידי ברגע שמתחיל השימוש ב-NFC.
  • הנחו את המשתמשים להחזיק את הטלפון יציב מול יעד ה-NFC עד שנוצר חיבור BLE.
UNTRUSTED_ISSUER / כשל בשרשרת האישורים האישור של חותם המסמך לא מקושר לאף אישור IACA מהימן במאגר האישורים המקומי של הקורא.
  • מוודאים שאישור הבסיס של המנפיק נטען למאגר האישורים של הקורא.
  • אם הבדיקה מתבצעת בארגז חול, צריך לוודא שנטען שורש IACA של ארגז החול של Google.
  • לוודא שרשימות האישורים של IACA (למשל, AAMVA VICAL) מתעדכנות מדי פעם.
INVALID_VALIDITY_INFO / Expired MSO
  • השעון של מערכת הקורא לא מסונכרן.
  • פג התוקף של החתימה של ה-MSO.
  • מוודאים שהמכשיר של הקורא מסנכרן את זמן המערכת שלו באופן קבוע באמצעות NTP.
  • מבקשים מהמשתמש לפתוח את Google Wallet כשהוא מחובר לאינטרנט כדי לרענן את אסימוני פרטי הכניסה.
DEVICE_AUTHENTICATION_FAILED חוסר התאמה בתמליל הסשן בין הקורא לבין Wallet, או חתימת מכשיר זמנית לא תקינה.
  • חשוב לוודא שהבייטים הגולמיים המדויקים של DeviceEngagementBytes ו-EReaderKeyBytes נשמרים במבנה של SessionTranscript בלי קידוד מחדש.
קריסה שקשורה להרשאות ב-Android מגרסה 12 ואילך האפליקציה ניסתה לסרוק או לפרסם באמצעות BLE בלי הרשאות בתחילת ההפעלה.
  • לפני שמתחילים סשנים של קורא, צריך לבדוק את ההרשאות BLUETOOTH_SCAN, ‏ BLUETOOTH_CONNECT ו-BLUETOOTH_ADVERTISE ולבקש אותן בזמן הריצה.

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

כשמעצבים קוראים פיזיים ואפליקציות נלוות:

  • השתמשו בחשיפה סלקטיבית בממשק המשתמש: הציגו למפעיל רק את ההחלטה או את המאפיין המינימלי הנדרש (לדוגמה, הציגו סימן וי ירוק בולט ו"גיל 21 ומעלה אומת" במקום להציג את תאריך הלידה המלא, הכתובת ומספר הרישיון של המשתמש).
  • אינדיקטורים ברורים לאינטראקציה פיזית: סמנו בבירור את אזור היעד של ה-NFC והציגו רמזים חזותיים (כמו אנימציות או סרגלי התקדמות) שמראים כל שלב: הצמדה / סריקה ← מתחבר ← מאמת ← הושלם.
  • טיפול בנתונים זמניים: אסור לאחסן או לתעד רכיבי מידע אישי שמתקבלים מהארנק, אלא אם הדבר נדרש במפורש על פי הדין החל וצוין ב-intentToRetain = true.