Как запросить цифровое удостоверение

Проверка цифровых удостоверений в приложениях Android может использоваться для аутентификации и авторизации личности пользователя (например, с помощью официального удостоверения личности), подтверждения его свойств (например, с помощью водительского удостоверения, диплома или таких атрибутов, как возраст или адрес) или в других сценариях, когда необходимо выдать и проверить удостоверение, чтобы подтвердить подлинность объекта.

Verifier API поддерживается на устройствах с Android 9 (API уровня 28) и более поздними версиями.

Реализация

Чтобы проверить цифровые удостоверения в проекте для Android, выполните следующие действия:

  1. Добавьте зависимости в скрипт сборки приложения и инициализируйте класс CredentialManager.
  2. Создайте запрос цифрового удостоверения личности и используйте его для инициализации DigitalCredentialOption, а затем создайте GetCredentialRequest.
  3. Запустите процесс getCredential с созданным запросом, чтобы получить успешный ответ GetCredentialResponse или обработать возможные исключения. После успешного получения проверьте ответ.

Добавьте зависимости и инициализируйте SDK

Добавьте в скрипт сборки Gradle следующие зависимости:

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

Затем инициализируйте экземпляр класса CredentialManager.

val credentialManager = CredentialManager.create(context)

Как создать запрос цифрового удостоверения

Создайте запрос цифрового удостоверения личности и используйте его для инициализации DigitalCredentialOption.

// The request in the JSON format to conform with
// the JSON-ified Credential Manager - Verifier 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)
)

Ниже приведен пример запроса OpenId4Vp1. Подробные сведения о полях в этом запросе можно найти в документации по OpenID.

{
  "requests": [
    {
      "protocol": "openid4vp-v1-unsigned",
      "data": {
        "response_type": "vp_token",
        "response_mode": "dc_api",
        "nonce": "OD8eP8BYfr0zyhgq4QCVEGN3m7C1Ht_No9H5fG5KJFk",
        "dcql_query": {
          "credentials": [
            {
              "id": "cred1",
              "format": "mso_mdoc",
              "meta": {
                "doctype_value": "org.iso.18013.5.1.mDL"
              },
              "claims": [
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "family_name"
                  ]
                },
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "given_name"
                  ]
                },
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "age_over_21"
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  ]
}

Получить сертификат

Запустите процесс getCredential с созданным запросом. Вы получите либо сообщение об успешном выполнении GetCredentialResponse, либо сообщение GetCredentialException, если запрос не удался.

Процесс getCredential запускает системные диалоговые окна Android, в которых пользователю предлагаются доступные варианты учетных данных и собирается информация о его выборе. Затем приложение кошелька, содержащее выбранный вариант удостоверения, отобразит пользовательский интерфейс для получения согласия и выполнения действий, необходимых для создания ответа цифрового удостоверения.

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.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}")
        }
    }
}

// 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}")
    }
}
Изображение, показывающее отображаемый селектор цифровых учетных данных при вызове getCredential.
Рисунок 1. Окно выбора цифровых учетных данных, которое появляется при вызове getCredential.

Гибкие запросы нескольких учетных данных

Digital Credentials API также поддерживает запрос нескольких типов удостоверений одновременно с помощью OpenID4VP:

  • Одновременные запросы. Запрашивайте несколько учетных данных в рамках одной транзакции пользователя. Например, приложение для путешествий может одновременно запросить посадочный талон и удостоверение личности, чтобы пользователь мог проверить и подтвердить оба документа одним нажатием.
  • Гибкое выполнение. Принимайте любые из нескольких типов учетных данных или эмитентов. Например, для подтверждения возраста можно использовать водительское удостоверение или паспорт. Пользователь может выбрать любой из этих документов, если он сохранен в держателе.

Вот пример запроса нескольких учетных данных в одном запросе:

{
  "credentials": [
    {
      "id": "gov_id1",
      "format": "mso_mdoc",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
      },
      "claims": [
        {
          "path": [
            "org.iso.18013.5.1",
            "age_over_21"
          ]
        }
      ]
    },
    {
      "id": "boarding_pass1",
      "format": "dc+sd-jwt",
      "meta": {
        "vct_values": [
          "https://credentials.example.airline/boarding-pass"
        ]
      },
      "claims": [
        {
          "path": [
            "flight_number"
          ]
        },
        {
          "path": [
            "seat"
          ]
        }
      ]
    }
  ]
}
Изображение, на котором показано окно выбора цифрового удостоверения при вызове нескольких типов удостоверений.
Рисунок 1. Окно выбора цифрового удостоверения, которое показывается при вызове нескольких типов удостоверений.

Обработка ответного сообщения

После отправки запроса на получение учетных данных обработайте объект GetCredentialResponse, возвращенный методом CredentialManager.

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.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}")
        }
    }
}

Обработка исключений

Приложение должно обрабатывать возможные ошибки, в том числе отсутствие учетных данных на устройстве или отмену операции пользователем.

// 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.
        }
        is CreateCredentialUnknownException -> {
            // An unknown, usually unexpected, error has occurred. Check the
            // message error for any additional debugging information.
        }
        is CreateCredentialCustomException -> {
            // You have encountered a custom error thrown by the wallet.
            // If you made the API call with a request object that's a
            // subclass of CreateCustomCredentialRequest using a 3rd-party SDK,
            // then you should check for any custom exception type constants
            // within that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}