Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[INJIMOB-2799]: add support for sharing wallet metadata to the verifier #43

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions kotlin/openID4VP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ Description: Implementation of OpenID for Verifiable Presentations - draft 21 sp
* request_uri
* request_uri_method

- The request uri can return either a jwt token/encoded if it is a jwt the signature is verified as mentioned in the specification.
- The client id and client id scheme from the authorization request and the client id and client id scheme received from the response of the request uri should be same.
- The request uri can return either a jwt token/encoded if it is a jwt the signature is verified as mentioned in the specification.
- When the request_uri_method value is 'post', the wallet can share its metadata with the verifier by making a post call and passing the wallet_metadata in the body.
- Wallet Metadata can contain the following information:
* presentation_definition_uri_supported (Optional)
* vp_formats_supported
* client_id_schemes_supported (Optional)
* request_object_signing_alg_values_supported (Optional)
* authorization_encryption_alg_values_supported (Optional)
* authorization_encryption_enc_values_supported (Optional)

- The client id and client id scheme from the authorization request and the client id and client id scheme received from the response of the request uri should be same.
- VC format supported is Ldp Vc as of now.

**Note** : The pre-registered client id scheme validation can be toggled on/off based on the optional boolean which you can pass to the authenticateVerifier methods shouldValidateClient parameter. This is false by default.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ class OpenID4VP(private val traceabilityId: String) {
fun authenticateVerifier(
encodedAuthorizationRequest: String,
trustedVerifiers: List<Verifier>,
shouldValidateClient: Boolean = false
shouldValidateClient: Boolean = false,
walletMetadata: String? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we alos add Readme update for the new feature introduced?

): AuthorizationRequest {
try {
Logger.setTraceabilityId(traceabilityId)
authorizationRequest = AuthorizationRequest.validateAndGetAuthorizationRequest(
encodedAuthorizationRequest, ::setResponseUri, trustedVerifiers, shouldValidateClient
encodedAuthorizationRequest, ::setResponseUri, trustedVerifiers, shouldValidateClient, walletMetadata
)
return this.authorizationRequest
} catch (exception: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import io.mosip.openID4VP.common.determineHttpMethod
import io.mosip.openID4VP.common.extractDataJsonFromJwt
import io.mosip.openID4VP.common.isJWT
import io.mosip.openID4VP.dto.Verifier
import io.mosip.openID4VP.dto.WalletMetadata
import io.mosip.openID4VP.networkManager.HTTP_METHOD
import io.mosip.openID4VP.networkManager.NetworkManagerClient.Companion.sendHTTPRequest
import kotlinx.serialization.json.Json
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -55,13 +58,14 @@ data class AuthorizationRequest(
encodedAuthorizationRequest: String,
setResponseUri: (String) -> Unit,
trustedVerifiers: List<Verifier>,
shouldValidateClient: Boolean
shouldValidateClient: Boolean,
walletMetadata: String?
): AuthorizationRequest {
try {
val queryStart = encodedAuthorizationRequest.indexOf('?') + 1
val encodedString = encodedAuthorizationRequest.substring(queryStart)
val decodedQueryString = Decoder.decodeBase64ToString(encodedString)
val authorizationRequestParams = parseAuthorizationRequest(decodedQueryString)
val authorizationRequestParams = parseAuthorizationRequest(decodedQueryString, walletMetadata)
validateVerifier(trustedVerifiers, authorizationRequestParams, shouldValidateClient)
validateAuthorizationRequestParams(authorizationRequestParams, setResponseUri)
return createAuthorizationRequest(authorizationRequestParams)
Expand All @@ -71,7 +75,8 @@ data class AuthorizationRequest(
}

private fun parseAuthorizationRequest(
queryString: String
queryString: String,
walletMetadata: String?
): MutableMap<String, Any> {
try {
val encodedQuery = URLEncoder.encode(queryString, StandardCharsets.UTF_8.toString())
Expand All @@ -84,18 +89,19 @@ data class AuthorizationRequest(
className = className
)
val params = extractQueryParams(query)
return fetchAuthorizationRequestMap(params)
return fetchAuthorizationRequestMap(params, walletMetadata)
} catch (exception: Exception) {
Logger.error(logTag, exception)
throw exception
}
}

private fun fetchAuthorizationRequestMap(
params: MutableMap<String, Any>
params: MutableMap<String, Any>,
walletMetadata: String?
): MutableMap<String, Any> {
var authorizationRequestMap = getValue(params,"request_uri")?.let { requestUri ->
fetchAuthRequestObjectByReference(params, requestUri)
fetchAuthRequestObjectByReference(params, requestUri, walletMetadata)
} ?: params

authorizationRequestMap = parseAndValidateClientMetadataInAuthorizationRequest(authorizationRequestMap)
Expand All @@ -106,13 +112,34 @@ data class AuthorizationRequest(
private fun fetchAuthRequestObjectByReference(
params: MutableMap<String, Any>,
requestUri: String,
walletMetadata: String?
): MutableMap<String, Any> {
try {
val requestUriMethod = getValue(params, "request_uri_method") ?: "get"
validateRootFieldInvalidScenario("request_uri", requestUri)
validateRootFieldInvalidScenario("request_uri_method", requestUriMethod)
val httpMethod = determineHttpMethod(requestUriMethod)
val response = sendHTTPRequest(requestUri, httpMethod)
var headers : Map<String, String>? = null
var body : Map<String, String>? = null

if (httpMethod == HTTP_METHOD.POST) {
walletMetadata?.let {
Json.decodeFromString<WalletMetadata>(walletMetadata)

body = mapOf(
"wallet_metadata" to URLEncoder.encode(
walletMetadata,
StandardCharsets.UTF_8.toString()
)
)
headers = mapOf(
"content-type" to "application/x-www-form-urlencoded",
"accept" to "application/oauth-authz-req+jwt"
)
}
}

val response = sendHTTPRequest(requestUri, httpMethod, body, headers)
val authorizationRequestObject = extractAuthorizationRequestData(response, params)
return authorizationRequestObject
} catch (exception: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package io.mosip.openID4VP.authorizationRequest.proofJwt.handlerFactory

import io.mosip.openID4VP.authorizationRequest.ClientIdScheme
import io.mosip.openID4VP.authorizationRequest.proofJwt.didHandler.DidHandler

enum class ClientIdScheme(val rawValue: String) {
DID("did")
}

object TypeHandlerFactory {
fun getHandler(clientIdScheme: String): JwtProofTypeHandler? {
return when (clientIdScheme) {
ClientIdScheme.DID.rawValue -> DidHandler()
ClientIdScheme.DID.value -> DidHandler()
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

package io.mosip.openID4VP.dto

import Generated
import io.mosip.openID4VP.authorizationRequest.ClientIdScheme
import io.mosip.openID4VP.authorizationRequest.Validatable
import io.mosip.openID4VP.common.FieldDeserializer
import io.mosip.openID4VP.common.Logger
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.jsonObject

private val className = WalletMetadata::class.simpleName!!

object WalletMetadataSerializer : KSerializer<WalletMetadata> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClientMetadata") {
element<Boolean>("presentation_definition_uri_supported", isOptional = true)
element<Map<String,VPFormatSupported>>("vp_formats_supported", isOptional = false)
element<List<String>>("client_id_schemes_supported", isOptional = true)
element<List<String>>("request_object_signing_alg_values_supported", isOptional = true)
element<List<String>>("authorization_encryption_alg_values_supported", isOptional = true)
element<List<String>>("authorization_encryption_enc_values_supported", isOptional = true)
}

override fun deserialize(decoder: Decoder): WalletMetadata {
val jsonDecoder = try {
decoder as JsonDecoder
} catch (e: ClassCastException) {
throw Logger.handleException(
exceptionType = "DeserializationFailure",
fieldPath = listOf("wallet_metadata"),
message = e.message!!,
className = className
)
}
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
val deserializer = FieldDeserializer(
jsonObject = jsonObject,
className = className,
parentField = "wallet_metadata"
)

val presentationDefinitionURISupported: Boolean? =
deserializer.deserializeField(
key = "presentation_definition_uri_supported",
fieldType = "Boolean"
)
val vpFormatsSupported: Map<String, VPFormatSupported> =
deserializer.deserializeField(key = "vp_formats_supported", fieldType = "Map", isMandatory = true)
?: throw Logger.handleException(
exceptionType = "InvalidInput",
fieldPath = listOf("wallet_metadata", "vp_formats_supported"),
className = className,
fieldType = "map",
)
val clientIdSchemesSupported: List<String>? =
deserializer.deserializeField(key = "client_id_schemes_supported", fieldType = "List")
val requestObjectSigningAlgValuesSupported: List<String>? =
deserializer.deserializeField(key = "request_object_signing_alg_values_supported", fieldType = "List")
val authorizationEncryptionAlgValuesSupported: List<String>? =
deserializer.deserializeField(key = "authorization_encryption_alg_values_supported", fieldType = "List")
val authorizationEncryptionEncValuesSupported: List<String>? =
deserializer.deserializeField(key = "authorization_encryption_enc_values_supported", fieldType = "List")

return WalletMetadata(
presentationDefinitionURISupported = presentationDefinitionURISupported,
vpFormatsSupported = vpFormatsSupported,
clientIdSchemesSupported = clientIdSchemesSupported,
requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported,
authorizationEncryptionAlgValuesSupported = authorizationEncryptionAlgValuesSupported,
authorizationEncryptionEncValuesSupported = authorizationEncryptionEncValuesSupported
)
}

@Generated
override fun serialize(encoder: Encoder, value: WalletMetadata) {
val builtInEncoder = encoder.beginStructure(descriptor)

value.presentationDefinitionURISupported?.let {
builtInEncoder.encodeBooleanElement(
descriptor,
0,
it
)
}
value.clientIdSchemesSupported?.let {
builtInEncoder.encodeSerializableElement(
descriptor,
2,
ListSerializer(String.serializer()),
it
)
}
value.requestObjectSigningAlgValuesSupported?.let {
builtInEncoder.encodeSerializableElement(
descriptor,
3,
ListSerializer(String.serializer()),
it
)
}
value.authorizationEncryptionAlgValuesSupported?.let {
builtInEncoder.encodeSerializableElement(
descriptor,
4,
ListSerializer(String.serializer()),
it
)
}
value.authorizationEncryptionEncValuesSupported?.let {
builtInEncoder.encodeSerializableElement(
descriptor,
4,
ListSerializer(String.serializer()),
it
)
}
builtInEncoder.endStructure(descriptor)
}
}

@Serializable(with = WalletMetadataSerializer::class)
class WalletMetadata(

@SerialName("presentation_definition_uri_supported") val presentationDefinitionURISupported: Boolean? = true,
@SerialName("vp_formats_supported") val vpFormatsSupported: Map<String, VPFormatSupported>,
@SerialName("client_id_schemes_supported")val clientIdSchemesSupported: List<String>? = listOf(ClientIdScheme.PRE_REGISTERED.value),
@SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: List<String>? = null,
@SerialName("authorization_encryption_alg_values_supported") val authorizationEncryptionAlgValuesSupported: List<String>? = null,
@SerialName("authorization_encryption_enc_values_supported") val authorizationEncryptionEncValuesSupported: List<String>? = null
) : Validatable {
override fun validate() {
}
}
@Serializable
data class VPFormatSupported(
@SerialName("alg_values_supported") val algValuesSupported: List<String>?
)


Loading