From f7b4677ab746db522be13a2f2a6914d807905711 Mon Sep 17 00:00:00 2001 From: ryousuke Date: Tue, 2 Jul 2024 17:23:46 +0900 Subject: [PATCH] Organize Presentation.kt --- .../oid/OpenIdProvider.kt | 89 ++--------- .../tw2023_wallet_android/oid/Presentation.kt | 142 ++++++++++++++---- .../ui/shared/KeyBindingImpl.kt | 4 +- .../oid/OpenIdProviderTest.kt | 2 +- 4 files changed, 127 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt index 2b93609..8339c0c 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt @@ -282,25 +282,11 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( val vpTokens = credentials.mapNotNull { it -> when (it.format) { "vc+sd-jwt" -> { - Pair( - it.id, - createPresentationSubmissionSdJwtVc( - it, - authRequest, - presentationDefinition - ) - ) + createPresentationSubmissionSdJwtVc(it, authRequest, presentationDefinition) } "jwt_vc_json" -> { - Pair( - it.id, - createPresentationSubmissionJwtVc( - it, - authRequest, - presentationDefinition - ) - ) + createPresentationSubmissionJwtVc(it, authRequest) } else -> { @@ -365,76 +351,31 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( credential: SubmissionCredential, authRequest: RequestObjectPayload, presentationDefinition: PresentationDefinition - ): Triple> { + ): Pair>> { val sdJwt = credential.credential val (_, selectedDisclosures) = selectDisclosure(sdJwt, presentationDefinition)!! - val keyBindingJwt = keyBinding.generateJwt( - sdJwt, + val presentation = SdJwtVcPresentation.createPresentation( + credential, selectedDisclosures, - authRequest.clientId!!, - authRequest.nonce!!, + authRequest, + keyBinding ) - // 絞ったdisclosureでチルダ連結してsd-jwtを構成 - val parts = sdJwt.split('~') - val issuerSignedJwt = parts[0] - val vpToken = - issuerSignedJwt + "~" + selectedDisclosures.joinToString("~") { it.disclosure } + "~" + keyBindingJwt - - val dm = DescriptorMap( - id = credential.inputDescriptor.id, - format = credential.format, - path = "$" - ) - val disclosedClaims = - selectedDisclosures.map { DisclosedClaim(credential.id, credential.types, it.key!!) } - return Triple(vpToken, dm, disclosedClaims) + return Pair( credential.id, presentation ) } private fun createPresentationSubmissionJwtVc( credential: SubmissionCredential, authRequest: RequestObjectPayload, - presentationDefinition: PresentationDefinition - ): Triple> { + ): Pair>> { if (authRequest.responseMode != ResponseMode.DIRECT_POST) { throw IllegalArgumentException("Unsupported response mode: ${authRequest.responseMode}") } - val objectMapper = jacksonObjectMapper() - try { - val (_, payload, _) = JWT.decodeJwt(jwt = credential.credential) - val disclosedClaims = payload.mapNotNull { (key, value) -> - if (key == "vc") { - val vcMap = objectMapper.readValue(value as String, Map::class.java) - vcMap.mapNotNull { (vcKey, vcValue) -> - if (vcKey == "credentialSubject") { - (vcValue as Map).mapNotNull { (subKey, subValue) -> - DisclosedClaim(id = credential.id, types = credential.types, name = subKey as String) - } - } else { - null - } - }.flatten() - } else { - null - } - }.flatten() - val vpToken = this.jwtVpJsonGenerator.generateJwt( - credential.credential, - HeaderOptions(), - JwtVpJsonPayloadOptions( - aud = authRequest.clientId!!, - nonce = authRequest.nonce!! - ) - ) - - return Triple( - first = vpToken, - second = JwtVpJsonPresentation.genDescriptorMap(presentationDefinition.inputDescriptors[0].id), - third = disclosedClaims - ) - - } catch (error: Exception) { - throw error - } + val presentation = JwtVpJsonPresentation.createPresentation( + credential, + authRequest, + jwtVpJsonGenerator + ) + return Pair( credential.id, presentation ) } } diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/Presentation.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/Presentation.kt index 4a4e7ab..be5837b 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/Presentation.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/Presentation.kt @@ -1,6 +1,8 @@ package com.ownd_project.tw2023_wallet_android.oid import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.ownd_project.tw2023_wallet_android.signature.JWT import com.ownd_project.tw2023_wallet_android.utils.SDJwtUtil import java.security.MessageDigest import java.util.Base64 @@ -33,8 +35,71 @@ data class JwtVpJsonPayloadOptions( var nonce: String ) +object SdJwtVcPresentation { + fun genKeyBindingJwtParts( + sdJwt: String, + selectedDisclosures: List, + aud: String, + nonce: String, + iat: Long? = null + ): Pair, Map> { + val header = mapOf("typ" to "kb+jwt", "alg" to "ES256") + + val parts = sdJwt.split('~') + val issuerSignedJwt = parts[0] + // It MUST be taken over the US-ASCII bytes preceding the KB-JWT in the Presentation + val sd = + issuerSignedJwt + "~" + selectedDisclosures.joinToString("~") { it.disclosure } + "~" + // The bytes of the digest MUST then be base64url-encoded. + val sdHash = sd.toByteArray(Charsets.US_ASCII).sha256ToBase64Url() + + val _iat = iat ?: (System.currentTimeMillis() / 1000) + val payload = mapOf( + "aud" to aud, + "iat" to _iat, + "_sd_hash" to sdHash, + "nonce" to nonce + ) + return Pair(header, payload) + } + + private fun ByteArray.sha256ToBase64Url(): String { + val sha = MessageDigest.getInstance("SHA-256").digest(this) + return Base64.getUrlEncoder().encodeToString(sha).trimEnd('=') + } + + fun createPresentation( + credential: SubmissionCredential, + selectedDisclosures: List, + authRequest: RequestObjectPayload, + keyBinding: KeyBinding + ): Triple> { + val sdJwt = credential.credential + val keyBindingJwt = keyBinding.generateJwt( + sdJwt, + selectedDisclosures, + authRequest.clientId!!, + authRequest.nonce!!, + ) + // 絞ったdisclosureでチルダ連結してsd-jwtを構成 + val parts = sdJwt.split('~') + val issuerSignedJwt = parts[0] + val vpToken = + issuerSignedJwt + "~" + selectedDisclosures.joinToString("~") { it.disclosure } + "~" + keyBindingJwt + + val dm = DescriptorMap( + id = credential.inputDescriptor.id, + format = credential.format, + path = "$" + ) + val disclosedClaims = + selectedDisclosures.map { DisclosedClaim(credential.id, credential.types, it.key!!) } + return Triple(vpToken, dm, disclosedClaims) + } +} + object JwtVpJsonPresentation { - fun genDescriptorMap( + private fun genDescriptorMap( inputDescriptorId: String, pathIndex: Int? = -1, pathNestedIndex: Int? = 0 @@ -71,38 +136,6 @@ object JwtVpJsonPresentation { ) } - fun genKeyBindingJwtParts( - sdJwt: String, - selectedDisclosures: List, - aud: String, - nonce: String, - iat: Long? = null - ): Pair, Map> { - val header = mapOf("typ" to "kb+jwt", "alg" to "ES256") - - val parts = sdJwt.split('~') - val issuerSignedJwt = parts[0] - // It MUST be taken over the US-ASCII bytes preceding the KB-JWT in the Presentation - val sd = - issuerSignedJwt + "~" + selectedDisclosures.joinToString("~") { it.disclosure } + "~" - // The bytes of the digest MUST then be base64url-encoded. - val sdHash = sd.toByteArray(Charsets.US_ASCII).sha256ToBase64Url() - - val _iat = iat ?: (System.currentTimeMillis() / 1000) - val payload = mapOf( - "aud" to aud, - "iat" to _iat, - "_sd_hash" to sdHash, - "nonce" to nonce - ) - return Pair(header, payload) - } - - private fun ByteArray.sha256ToBase64Url(): String { - val sha = MessageDigest.getInstance("SHA-256").digest(this) - return Base64.getUrlEncoder().encodeToString(sha).trimEnd('=') - } - fun genVpJwtPayload(vcJwt: String, payloadOptions: JwtVpJsonPayloadOptions): VpJwtPayload { val vpClaims = mapOf( "@context" to listOf("https://www.w3.org/2018/credentials/v1"), @@ -122,6 +155,49 @@ object JwtVpJsonPresentation { vp = vpClaims ) } + + fun createPresentation( + credential: SubmissionCredential, + authRequest: RequestObjectPayload, + jwtVpJsonGenerator: JwtVpJsonGenerator + ): Triple> { + val objectMapper = jacksonObjectMapper() + val (_, payload, _) = JWT.decodeJwt(jwt = credential.credential) + val disclosedClaims = payload.mapNotNull { (key, value) -> + if (key == "vc") { + val vcMap = objectMapper.readValue(value as String, Map::class.java) + vcMap.mapNotNull { (vcKey, vcValue) -> + if (vcKey == "credentialSubject") { + (vcValue as Map).mapNotNull { (subKey, subValue) -> + DisclosedClaim( + id = credential.id, + types = credential.types, + name = subKey as String + ) + } + } else { + null + } + }.flatten() + } else { + null + } + }.flatten() + val vpToken = jwtVpJsonGenerator.generateJwt( + credential.credential, + HeaderOptions(), + JwtVpJsonPayloadOptions( + aud = authRequest.clientId!!, + nonce = authRequest.nonce!! + ) + ) + + return Triple( + first = vpToken, + second = genDescriptorMap(credential.inputDescriptor.id), + third = disclosedClaims + ) + } } // https://openid.net/specs/openid-4-verifiable-presentations-1_0-20.html#name-presentation-response diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/shared/KeyBindingImpl.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/shared/KeyBindingImpl.kt index 781d33d..5fcde64 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/shared/KeyBindingImpl.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/shared/KeyBindingImpl.kt @@ -1,7 +1,7 @@ package com.ownd_project.tw2023_wallet_android.ui.shared -import com.ownd_project.tw2023_wallet_android.oid.JwtVpJsonPresentation import com.ownd_project.tw2023_wallet_android.oid.KeyBinding +import com.ownd_project.tw2023_wallet_android.oid.SdJwtVcPresentation import com.ownd_project.tw2023_wallet_android.signature.JWT import com.ownd_project.tw2023_wallet_android.utils.SDJwtUtil @@ -12,7 +12,7 @@ class KeyBindingImpl(val keyAlias: String): KeyBinding { aud: String, nonce: String ): String { - val (header, payload) = JwtVpJsonPresentation.genKeyBindingJwtParts( + val (header, payload) = SdJwtVcPresentation.genKeyBindingJwtParts( sdJwt, selectedDisclosures, aud, diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt index 792eb79..930789e 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt @@ -735,7 +735,7 @@ class KeyBinding4Test(private val keyPair: KeyPair) : KeyBinding { keyPair.public as ECPublicKey, keyPair.private as ECPrivateKey? ) - val (header, payload) = JwtVpJsonPresentation.genKeyBindingJwtParts( + val (header, payload) = SdJwtVcPresentation.genKeyBindingJwtParts( sdJwt, selectedDisclosures, aud,