אימות מספרי טלפון באמצעות אישורים דיגיטליים

במדריך הזה מוסבר איך להשתמש ב-DigitalCredential API כדי לקבל מספרי טלפון מאומתים של המשתמשים. התהליך כולל שני שלבים:

  1. שליחת בקשה ל-TS.43 token: אפליקציית הלקוח (המאמת) שולחת בקשה לאסימון זמני של TS.43 מהמכשיר של המשתמש. ‫TS.43 token הוא אישור שמונפק על ידי הספק ומייצג את זהות המשתמש.
  2. החלפת הטוקן במספר טלפון: הקצה העורפי של האפליקציה מחליף את הטוקן TS.43 token עם צד שלישי או עם ספק סלולר כדי לקבל את מספר הטלפון המאומת של המשתמש.

תאימות לגרסת Android

ממשק ה-API לאימות מספר הטלפון נתמך ב-Android 10 (רמת API‏ 29) ובגרסאות מתקדמות יותר.

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

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

צריך גם להוסיף את יחסי התלות הבאים לסקריפט ה-build של Gradle:

Kotlin

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

Groovy

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

הטמעה

בדרך כלל התהליך המלא כולל את השלבים הבאים:

  1. בקשת פרמטרים של DCQL (שפת שאילתות של מסמכים דיגיטליים) מאגרגטור: מתקשרים לאגרגטור אחד או יותר ומבקשים קבוצה של פרמטרים של DCQL. בעזרת DCQL אפשר לציין את פרטי הכניסה הדיגיטליים המדויקים שאתם צריכים מכל צובר.
  2. יצירת בקשת OpenID4VP: בבק-אנד של האפליקציה, יוצרים את בקשת OpenID4VP, וכוללים בה את הפרמטרים של DCQL מהאגרגטור. לאחר מכן, שולחים את בקשת OpenID4VP לאפליקציית הלקוח.

  3. הפעלת Credential Manager API: באפליקציית הלקוח, משתמשים ב-Credential Manager API כדי לשלוח את בקשת OpenID4VP למערכת ההפעלה. בתגובה, מקבלים אובייקט תגובה של OpenID4VP שמכיל את TS.43 Digital Credential. פרטי הכניסה האלה מוצפנים, ורק המצטבר המשויך יכול לפענח אותם. אחרי שמקבלים את האסימון של חברת התובלה, שולחים את התגובה מאפליקציית הלקוח אל ה-Backend של האפליקציה.

  4. אימות התשובה: בקצה העורפי של האפליקציה, מאמתים את התשובה של OpenID4VP.

  5. Exchange for phone number: מהקצה העורפי של האפליקציה, שולחים את TS.43 Digital Credential למרכז האגרגציה. הספק המצטבר מאמת את פרטי הכניסה ומחזיר את מספר הטלפון המאומת.

תמונה שמציגה את התהליך של בקשה לאימות מספר טלפון
איור 1. תהליך Phone Number Verification, מתחיל בבקשה של בק-אנד של מאמת הפרטים לקבלת פרמטרים מצד אתר אגרגטור ומסתיים בהחזרת מספר טלפון מאומת.

בקשת פרמטרים של DCQL מאגרגטור

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

{
  // The credential ID is mapped to the request ID that is sent in your request to the aggregator.
  "id": "aggregator1",
  "format": "dc-authorization+sd-jwt",
  "meta": {
    "vct_values": [
      "number-verification/device-phone-number/ts43"
    ],
    "credential_authorization_jwt": "..."
  },
  "claims": [
    {
      "path": ["subscription_hint"],
      "values": [1]
    },
    {
      "path": ["phone_number_hint"],
      "values": ["+14155552671"]
    }
  ]
}

יצירת בקשת OpenID4VP

קודם כל, יוצרים אובייקט dcql_query מהקצה העורפי של האפליקציה, על ידי הצבת אובייקט האישורים של DCQL במערך credentials שמוטמע באובייקט dcql_query, כמו בדוגמה הבאה:

"dcql_query": {
  "credentials": [
      "id": "aggregator1",
      "format": "dc-authorization+sd-jwt",
      "meta": {
        "vct_values": [
          "number-verification/device-phone-number/ts43"
        ],
        "credential_authorization_jwt": "..."
      },
      "claims": [
        {
          "path": ["subscription_hint"],
          "values": [1]
        },
        {
          "path": ["phone_number_hint"],
          "values": ["+14155552671"]
        }
      ]
  ]
}

לאחר מכן, יוצרים בקשת OpenID4VP במבנה הבא:

{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "response_type": "vp_token",
    "response_mode": "dc_api",
    "nonce": "...",
    "dcql_query": { ... }
  }
}
  • protocol: צריך להגדיר את הערך openid4vp-v1-unsigned לבקשות לאימות מספר טלפון.
  • response_type ו-response_mode: קבועים שמציינים את הפורמט של הבקשה עם הערכים הקבועים vp_token ו-dc_api, בהתאמה.
  • nonce: ערך ייחודי שנוצר על ידי ה-Backend לכל בקשה. ה-nonce באובייקט של פרטי הכניסה של DCQL במצטבר צריך להיות זהה ל-nonce הזה.
  • dcql_query: במקרה הזה, משתמשים ב-dcql_query כדי לציין שמבוקש TS.43 Digital Credential. אפשר גם לבקש כאן פרטי כניסה דיגיטליים אחרים.

לאחר מכן, עוטפים את בקשת OpenID4VP באובייקט בקשת API של DigitalCredential ושולחים אותו לאפליקציית הלקוח.

{
  "requests":
    [
      {
        "protocol": "openid4vp-v1-unsigned",
        "data": {
          "response_type": "vp_token",
          "response_mode": "dc_api",
          "nonce": "...",
          "dcql_query": { ... }
        }
      }
    ]
}

בקטע הקוד הבא אפשר לראות איך יוצרים את הבקשה ל-DigitalCredential API:

def GenerateDCRequest():
    credentials = []
    aggregator1_dcql = call_aggregator_endpoint(nonce, "aggregator1", additional_params)
    credentials.append(aggregator1_dcql) # You can optionally work with multiple
    # aggregators, or request other types of credentials

    val dc_request =
    {
      "requests":
        [
          {
            "protocol": "openid4vp-v1-unsigned",
            "data": {
              "response_type": "vp_token",
              "response_mode": "dc_api",
              "nonce": "...",
              "dcql_query": {"credentials": credentials}
            }
          }
        ]
    }
    return dc_request

שליחת קריאה ל-Credential Manager API

באפליקציית הלקוח, מבצעים קריאה ל-Credential Manager API, עם בקשת DigitalCredential API שסופקה על ידי הקצה העורפי של האפליקציה.

val requestJson = generateTs43DigitalCredentialRequestFromServer()
val digiCredOption = GetDigitalCredentialOption(requestJson = requestJson)
val getCredRequest = GetCredentialRequest(
    listOf(digiCredOption)
)

coroutineScope.launch {
  try {
    val response = credentialManager.getCredential(
      context = activityContext,
      request = getCredRequest
    )
    val credential = response.credential
    when (credential) {
      is DigitalCredential -> {
        val responseJson = credential.credentialJson
        validateResponseOnServer(responseJson)
      }
      else -> {
        // Catch any unrecognized credential type here.
        Log.e(TAG, "Unexpected type of credential ${credential.type}")
      }
    }
  } catch (e : GetCredentialException) {
      // If user cancels the operation, the feature isn't available, or the
      // SIM doesn't support the feature, a GetCredentialCancellationException
      // will be returned. Otherwise, a GetCredentialUnsupportedException will
      // be returned with details in the exception message.
      handleFailure(e)
  }
}

התשובה של DigitalCredential API מכילה את התשובה של OpenID4VP. דוגמה ל-JSON של פרטי כניסה מתוצאת DigitalCredential:

{
  "protocol": "openid4vp-v1-unsigned",

  "data": {
    "vp_token": {
      "aggregator1": ["eyJhbGciOiAiRVMy..."] # The encrypted TS.43 Digital
                                             # Credential in an array structure.
    }
  }
}

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

במקרים מסוימים, התשובה עשויה להכיל שגיאה מסוג TS.43. תגובת השגיאה היא אובייקט JSON בפורמט תגובת השגיאה של OpenID4VP:

{
  "protocol": "openid4vp-v1-unsigned",

  "data": {
    "error": "<error_code>",
    "error_description": "<Human-readable description of the error>",
  }
}

יש 2 ערכים אפשריים של error_code:

  • invalid_request: מציין שהבקשה הייתה פגומה.
  • server_error: מציין שהיו כשלים במהלך עיבוד הבקשה. יכול להיות שמדובר בשגיאות מקומיות או בבעיות שקשורות לתקן TS.43.

בשדה error_description מופיעים פרטים נוספים על הבעיה.

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

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

def processDigitalCredentialsResponse(response):
  # Step 1: Parse out the TS.43 Digital Credential from the response
  openId4VpResponse = response['data']

  ts43_digital_credential = response['vp_token']["aggregator1"][0]

  # Step 2: Perform response validation
  verifyResponse(ts43_digital_credential)

def verifyResponse(ts43_digital_credential):
  # The returned ts43_digital_credential is an SD-JWT-based Verifiable Credentials
  # (SD-JWT VC) as defined in this IETF spec. The section 3.4 of the specification
  # outlines how to validate the credential. At a high level, the steps involves
  # validating (1) the nonce in the response credential matches the one in the
  # request, (2) the integrity of the credential by checking the credential is
  # signed by the trusted issuer Android Telephony, and (3) other validity
  # properties associated with this credential, such as issue time and expiration
  # time

  # In most cases, you can use an SD-JWT VC library to perform these validations.

  # Some aggregators may also perform the validation logic for you. Check with your
  # aggregator to decide the exact scope of the validation required.

החלפה של מספר טלפון

משרת הקצה העורפי של האפליקציה, שולחים את TS.43 Digital Credential שאומת לנקודת הקצה של המצטבר כדי לאמת את פרטי הכניסה ולקבל את מספר הטלפון המאומת.

def processDigitalCredentialsResponse(response):
  # ... prior steps

  # Step 3: Call aggregator endpoint to exchange the verified phone number
  callAggregatorPnvEndpoint(ts43_digital_credential)