通过数字凭证验证电话号码

本指南详细介绍了如何使用 DigitalCredential API 获取用户的已验证电话号码。此过程包含两个步骤:

  1. 请求 TS.43 token:您的客户端应用(即“验证方”)向用户的设备请求临时 TS.43 令牌。TS.43 token 是运营商颁发的代表用户身份的凭据。
  2. 将令牌兑换为电话号码:应用的后端会与聚合器或运营商交换 TS.43 token,以获取用户经过验证的电话号码。

前提条件

如需使用 DigitalCredential API 实现电话号码验证,您需要拥有聚合商账号。聚合器与运营商互动,并为您的应用提供必要的 API 接口,通常以可结算的 Cloud 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 请求发送到操作系统。作为响应,您会收到一个包含 TS.43 Digital Credential 的 OpenID4VP 响应对象。此凭据已加密,只能由关联的汇总器解密。收到运营商令牌后,将客户端应用的响应发送到应用的后端。

  4. 验证响应:在应用的后端,验证 OpenID4VP 响应。

  5. 换取电话号码:从应用的后端向聚合平台发送 TS.43 Digital Credential。聚合器验证凭据并返回经过验证的电话号码。

图片:显示电话号码验证请求的流程
图 1.电话号码验证请求的生命周期,从验证器后端向聚合器请求参数开始,到返回已验证的电话号码结束。

从聚合器请求 DCQL 参数

从应用的后端向聚合器发送请求,以获取数字凭据查询语言 (DCQL) 凭据对象。请务必在请求中提供随机数和请求 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 凭据对象放置在嵌套于 dcql_query 对象中的 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_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)