소지자 앱으로 사용자 인증 정보 제시

인증 관리자 소지자 API를 사용하면 Android 소지자('지갑'이라고도 함) 앱에서 디지털 사용자 인증 정보를 관리하고 검증 도구에 제시할 수 있습니다. 이 가이드에서는 인증 관리자에 소지자 앱의 사용자 인증 정보를 등록하는 방법과 사용자가 사용자 인증 정보를 선택할 때 프레젠테이션 요청을 처리하는 방법을 설명합니다. Holder API는 Android 6 (API 수준 23) 이상에서 지원됩니다.

종속 항목 추가

Credential Manager Holder API를 사용하려면 앱 모듈의 build.gradle 스크립트에 다음 종속 항목을 추가하세요.

dependencies {
    // Used to implement credential registries
    implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-digitalcredentials-openid:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-digitalcredentials-sdjwtvc:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha05"
}

사용자 인증 정보 등록

먼저 OpenId4VpRegistry 요청을 등록하는 데 사용할 RegistryManager 인스턴스를 만듭니다.

// Create the registry manager
val registryManager = RegistryManager.create(context)

OpenId4VpRegistry 요청 빌드

검증자의 OpenID4VP 요청을 처리하려면 OpenId4VpRegistry를 등록해야 합니다. 월렛 사용자 인증 정보 (예: sdJwtsFromStorage 및 mdocsFromStorage)가 로드된 로컬 데이터 유형이 있다고 가정하면 이를 Jetpack DigitalCredentialEntry에 상응하는 SdJwtEntry 및 MdocEntry로 변환해야 합니다.

레지스트리에 SD-JWT 추가

각 로컬 SD-JWT 사용자 인증 정보를 레지스트리의 SdJwtEntry에 매핑합니다.

fun mapToSdJwtEntries(sdJwtsFromStorage: List<StoredSdJwtEntry>): List<SdJwtEntry> {
    val list = mutableListOf<SdJwtEntry>()

    for (sdJwt in sdJwtsFromStorage) {
        list.add(
            SdJwtEntry(
                verifiableCredentialType = sdJwt.getVCT(),
                claims = sdJwt.getClaimsList(),
                entryDisplayPropertySet = sdJwt.toDisplayProperties(),
                id = sdJwt.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

레지스트리에 mdoc 추가

로컬 mdoc 사용자 인증 정보를 Jetpack 유형 MdocEntry에 매핑합니다.

fun mapToMdocEntries(mdocsFromStorage: List<StoredMdocEntry>): List<MdocEntry> {
    val list = mutableListOf<MdocEntry>()

    for (mdoc in mdocsFromStorage) {
        list.add(
            MdocEntry(
                docType = mdoc.retrieveDocType(),
                fields = mdoc.getFields(),
                entryDisplayPropertySet = mdoc.toDisplayProperties(),
                id = mdoc.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

코드에 관한 핵심 사항

  • 암호화된 ID: ID 필드를 구성하는 한 가지 방법은 암호화된 사용자 인증 정보 식별자를 등록하여 나만 값을 복호화할 수 있도록 하는 것입니다.
  • 현지화: 두 형식의 UI 표시 필드를 현지화해야 합니다.

요청 결합 및 등록

변환된 항목을 결합하고 RegistryManager에 요청을 등록합니다.

val credentialEntries =
    mapToSdJwtEntries(sdJwtsFromStorage) + mapToMdocEntries(mdocsFromStorage)

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    id = "my-wallet-openid-registry-v1" // A stable, unique ID to identify your registry record.
)

선택사항: 인라인 발급

사용자에게 요청된 유형의 기존 사용자 인증 정보가 없는 경우 소지자에게 사용자 인증 정보를 추가하는 옵션을 표시할 수 있습니다. 이렇게 하려면 지원되는 사용자 인증 정보가 포함된 inlineIssuanceEntries 목록을 OpenId4VpRegistry에 추가합니다.

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    inlineIssuanceEntries = emptyList(),
                listOf(
                MdocInlineIssuanceEntry(
                    id = "Issuance",
                    display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
                        subtitle = "Mobile Drivers License, State Id, and Others",
                    ),
                    supportedMdocs = setOf(
                        MdocInlineIssuanceEntry.SupportedMdoc(
                        "eu.europa.ec.eudi.pid.1"
                        ),
                        MdocInlineIssuanceEntry.SupportedMdoc(
                        "org.iso.18013.5.1.mDL1"
                        ),
                    )
                ),
                SdJwtInlineIssuanceEntry(
                    id = "sd-jwt-issuance",
                    display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
                        subtitle = "Mobile Drivers License, State Id, and Others",
                    ),
                    supportedSdJwts = setOf(
                        SdJwtInlineIssuanceEntry.SupportedSdJwt("urn:openid:interop:id:1")
                    )
                )
            ),
    id = "my-wallet-openid-registry-v1"
)

CredentialManager에 사용자 인증 정보를 등록합니다.

try {
    val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
    // Handle failure
}

앱 메타데이터 관리

소지자 앱이 CredentialManager에 등록하는 메타데이터에는 다음과 같은 속성이 있습니다.

  • 지속성: 정보가 로컬에 저장되어 재부팅 후에도 유지됩니다.
  • 사일로화된 저장소: 각 앱의 레지스트리 레코드는 별도로 저장되므로 한 앱이 다른 앱의 레지스트리 레코드를 변경할 수 없습니다.
  • 키가 지정된 업데이트: 각 앱의 레지스트리 레코드는 id로 키가 지정되므로 레코드를 다시 식별, 업데이트 또는 삭제할 수 있습니다.
  • 메타데이터 업데이트: 앱이 변경되거나 처음 로드될 때마다 유지된 메타데이터를 업데이트하는 것이 좋습니다. 동일한 id에서 레지스트리가 여러 번 호출되면 최신 호출이 이전의 모든 레코드를 덮어씁니다. 업데이트하려면 먼저 이전 레코드를 삭제할 필요 없이 다시 등록합니다.

선택사항: 일치자 만들기

매처는 인증 관리자가 샌드박스에서 실행하여 수신되는 검증 도구 요청에 대해 등록된 사용자 인증 정보를 필터링하는 Wasm 바이너리입니다.

  • 기본 매처: OpenId4VpRegistry 클래스를 인스턴스화하면 기본 OpenId4VP 매처(OpenId4VpDefaults.DEFAULT_MATCHER)가 자동으로 포함됩니다. 모든 표준 OpenID4VP 사용 사례의 경우 라이브러리가 일치를 처리합니다.
  • 커스텀 일치자: 자체 일치 로직이 필요한 비표준 프로토콜을 지원하는 경우에만 커스텀 일치자를 구현합니다.

선택한 사용자 인증 정보 처리

사용자가 사용자 인증 정보를 선택하면 소지자 앱에서 요청을 처리해야 합니다. androidx.credentials.registry.provider.action.GET_CREDENTIAL 인텐트 필터를 수신 대기하는 Activity를 정의해야 합니다. 샘플 월렛에서 이 절차를 보여줍니다.

인텐트는 인증 도구 요청 및 호출 출처를 사용하여 활동을 실행합니다. 이는 PendingIntentHandler.retrieveProviderGetCredentialRequest 함수를 사용하여 추출합니다. 그러면 인증자 요청과 관련된 모든 정보가 포함된 ProviderGetCredentialRequest가 반환됩니다. 세 가지 주요 구성요소가 있습니다.

다음 코드를 사용하여 프레젠테이션 요청을 처리할 수 있습니다.

request.credentialOptions.forEach { option ->
    if (option is GetDigitalCredentialOption) {
        Log.i(TAG, "Got DC request: ${option.requestJson}")
        processRequest(option.requestJson)
    }
}

이에 관한 예는 샘플 월렛에서 확인할 수 있습니다.

확인자 신원 확인

먼저 인텐트에서 ProviderGetCredentialRequest를 추출합니다.

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

권한이 있는 앱 확인: 권한이 있는 앱 (예: 웹브라우저)은 출처 매개변수를 설정하여 다른 인증자를 대신하여 호출할 수 있습니다. 이 출처를 가져오려면 권한이 있고 신뢰할 수 있는 호출자 목록 (JSON 형식의 허용 목록)을 CallingAppInfo getOrigin API에 전달합니다.

val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

origin이 비어 있지 않은 경우: packageName 및 인증서 지문이 허용 목록의 앱과 일치하면 출처가 반환됩니다. 이를 권한이 있는 호출로 간주하고 호출 앱의 서명을 사용하여 출처를 계산하는 대신 OpenID4VP 응답에 이 출처를 설정합니다.

origin가 비어 있는 경우: 인증자 요청이 Android 앱에서 전송됩니다. OpenID4VP 응답에 매핑된 출처는 android:apk-key-hash:<encoded SHA 256 fingerprint>로 계산되어야 합니다.

val appSigningInfo =
    request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.get(0)?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
    Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"

소지자 UI 렌더링

사용자 인증 정보가 선택되면 소지자 앱이 호출되어 앱의 UI를 통해 사용자를 안내합니다. 이 워크플로를 처리하는 방법에는 두 가지가 있습니다.

  • 사용자 인증 정보를 공개하는 데 추가 사용자 인증이 필요한 경우 BiometricPrompt API를 사용합니다. 이는 샘플에 나와 있습니다.
  • 그렇지 않으면 많은 월렛이 데이터를 호출 앱에 즉시 다시 전달하는 빈 활동을 렌더링하여 자동 반환을 선택합니다. 이렇게 하면 사용자 클릭이 최소화되고 더 원활한 환경이 제공됩니다.

사용자 인증 정보 응답 반환

소지자 앱이 결과를 다시 전송할 준비가 되면 사용자 인증 정보 응답으로 활동을 완료합니다.

PendingIntentHandler.setGetCredentialResponse(
    resultData,
    GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()

예외가 있는 경우 다음과 같이 사용자 인증 정보 예외를 전송할 수 있습니다.

PendingIntentHandler.setGetCredentialException(
    resultData,
    GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()

컨텍스트에서 사용자 인증 정보 응답을 반환하는 전체 예는 샘플 앱을 참고하세요.