使用數位憑證驗證電話號碼

本指南詳細說明如何使用 DigitalCredential API 取得使用者的已驗證電話號碼。這個程序包含兩個步驟:

  1. 要求 TS.43 token:用戶端應用程式 (「驗證者」) 向使用者裝置要求臨時 TS.43 權杖。TS.43 token 是電信業者核發的憑證,代表使用者的身分。
  2. 以權杖換取電話號碼:應用程式後端會與匯總器或電信業者交換 TS.43 token,取得使用者已驗證的電話號碼。

必要條件

如要使用 DigitalCredential API 實作電話號碼驗證,您需要擁有匯總工具帳戶。整合器會與貨運公司互動,並為應用程式提供必要的 API 介面,通常是可計費的雲端 API 端點。

您也需要在 Gradle 建構指令碼中加入下列依附元件:

Kotlin

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

Groovy

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

實作

端對端程序通常會依下列步驟進行:

  1. 向匯總器要求 DCQL (數位憑證查詢語言) 參數:呼叫一或多個匯總器,並要求一組 DCQL 參數。您可以使用 DCQL,指定從各個匯總工具取得的確切數位憑證。
  2. 建立 OpenID4VP 要求:從應用程式後端建立 OpenID4VP 要求,同時加入匯總工具的 DCQL 參數。然後將 OpenID4VP 要求傳送至用戶端應用程式。

  3. 呼叫 Credential Manager API:在用戶端應用程式中,使用 Credential Manager API 將 OpenID4VP 要求傳送至作業系統。您會收到 OpenID4VP 回應物件,其中包含 TS.43 Digital Credential。這項憑證經過加密,只能由相關聯的匯總工具解密。收到貨運公司權杖後,請將用戶端應用程式的回應傳送至應用程式後端。

  4. 驗證回應:在應用程式的後端,驗證 OpenID4VP 回應。

  5. 換取電話號碼:從應用程式後端將 TS.43 Digital Credential 傳送給匯總工具。彙整器會驗證憑證,並傳回已驗證的電話號碼。

圖片:顯示電話號碼驗證要求流程
圖 1:電話號碼驗證要求生命週期,從驗證方後端向匯總工具要求參數開始,到傳回已驗證的電話號碼為止。

向匯總工具索取 DCQL 參數

從應用程式後端向匯總工具傳送要求,索取數位憑證查詢語言 (DCQL) 憑證物件。請務必在要求中提供 Nonce 和要求 ID。匯總工具會傳回 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 憑證物件放在 dcql_query 物件中巢狀結構的 credentials 陣列,如下列範例所示:

"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_typeresponse_mode:常數,分別表示具有固定值 vp_tokendc_api 的要求格式。
  • nonce:後端為每個要求產生的專屬值。匯總器 DCQL 憑證物件中的隨機碼必須與這個隨機碼相符。
  • dcql_query:在這種情況下,請使用 dcql_query 指定要求 TS.43 Digital Credential。您也可以在這裡申請其他數位憑證。

接著,將 OpenID4VP 要求包裝在 DigitalCredential API 要求物件中,並傳送至用戶端應用程式。

{
  "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

在用戶端應用程式中,使用應用程式後端提供的 DigitalCredential API 要求,呼叫 Credential Manager 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 回應。DigitalCredential 結果中的典型憑證 JSON 如下:

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

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

從用戶端應用程式將 DigitalCredential API 回應傳送回後端伺服器,以便驗證並與匯總工具交換經過驗證的電話號碼。

驗證數位憑證回應

以下範例說明如何在應用程式後端剖析回應,並執行驗證步驟:

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)