Skip to content

Commit

Permalink
Organize Presentation.kt
Browse files Browse the repository at this point in the history
  • Loading branch information
ryosuke-wakaba committed Jul 2, 2024
1 parent c244d3f commit f7b4677
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 -> {
Expand Down Expand Up @@ -365,76 +351,31 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption(
credential: SubmissionCredential,
authRequest: RequestObjectPayload,
presentationDefinition: PresentationDefinition
): Triple<String, DescriptorMap, List<DisclosedClaim>> {
): Pair<String, Triple<String, DescriptorMap, List<DisclosedClaim>>> {
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<String, DescriptorMap, List<DisclosedClaim>> {
): Pair<String, Triple<String, DescriptorMap, List<DisclosedClaim>>> {
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<String, Any>).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 )
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,8 +35,71 @@ data class JwtVpJsonPayloadOptions(
var nonce: String
)

object SdJwtVcPresentation {
fun genKeyBindingJwtParts(
sdJwt: String,
selectedDisclosures: List<SDJwtUtil.Disclosure>,
aud: String,
nonce: String,
iat: Long? = null
): Pair<Map<String, Any>, Map<String, Any>> {
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<SDJwtUtil.Disclosure>,
authRequest: RequestObjectPayload,
keyBinding: KeyBinding
): Triple<String, DescriptorMap, List<DisclosedClaim>> {
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
Expand Down Expand Up @@ -71,38 +136,6 @@ object JwtVpJsonPresentation {
)
}

fun genKeyBindingJwtParts(
sdJwt: String,
selectedDisclosures: List<SDJwtUtil.Disclosure>,
aud: String,
nonce: String,
iat: Long? = null
): Pair<Map<String, Any>, Map<String, Any>> {
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"),
Expand All @@ -122,6 +155,49 @@ object JwtVpJsonPresentation {
vp = vpClaims
)
}

fun createPresentation(
credential: SubmissionCredential,
authRequest: RequestObjectPayload,
jwtVpJsonGenerator: JwtVpJsonGenerator
): Triple<String, DescriptorMap, List<DisclosedClaim>> {
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<String, Any>).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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit f7b4677

Please sign in to comment.