diff --git a/eudi-wallet-oidc-android/build.gradle.kts b/eudi-wallet-oidc-android/build.gradle.kts index f377323..d71a180 100644 --- a/eudi-wallet-oidc-android/build.gradle.kts +++ b/eudi-wallet-oidc-android/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { } implementation("com.google.crypto.tink:tink-android:1.7.0") + implementation("co.nstant.in:cbor:0.9") } diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/AuthorizationDetails.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/AuthorizationDetails.kt index 9e35957..6c84aa6 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/AuthorizationDetails.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/AuthorizationDetails.kt @@ -9,6 +9,7 @@ data class AuthorizationDetails( @SerializedName("type") var type: String? = "openid_credential", @SerializedName("format") var format: String? = null, + @SerializedName("doctype") var doctype: String? = null, @SerializedName("types") var types: ArrayList? = arrayListOf(), @SerializedName("locations") var locations: ArrayList? = arrayListOf(), @SerializedName("credential_definition") var credentialDefinition: CredentialTypeDefinition? = CredentialTypeDefinition() diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialRequest.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialRequest.kt index e270728..c341bef 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialRequest.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialRequest.kt @@ -9,6 +9,7 @@ data class CredentialRequest( @SerializedName("credential_definition") var credentialDefinition: CredentialDefinition? = null, @SerializedName("vct") var vct: String? = null, @SerializedName("format") var format: String? = null, + @SerializedName("doctype") var doctype: String? = null, @SerializedName("proof") var proof: ProofV3? = null ) diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidator.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidator.kt index 52398a7..cb0b74d 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidator.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidator.kt @@ -15,7 +15,11 @@ class CredentialValidator:CredentialValidatorInterface { * Returns true if the JWT is valid; otherwise, throws IllegalArgumentException with appropriate messages. */ @Throws(IllegalArgumentException::class) - override suspend fun validateCredential(jwt: String?, jwksUri: String?): Boolean { + override suspend fun validateCredential(jwt: String?, + jwksUri: String?, + format: String?): Boolean { + if (format == "mso_mdoc") + return true try { // Check if the JWT has expired ExpiryValidator().isJwtExpired(jwt = jwt) @@ -33,4 +37,4 @@ class CredentialValidator:CredentialValidatorInterface { throw IllegalArgumentException("JWT signature invalid") } } -} +} \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidatorInterface.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidatorInterface.kt index e3965ec..665e093 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidatorInterface.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/credentialValidation/CredentialValidatorInterface.kt @@ -11,5 +11,7 @@ interface CredentialValidatorInterface { * Returns true if the JWT is valid; otherwise, throws IllegalArgumentException with appropriate messages. */ @Throws(IllegalArgumentException::class) - suspend fun validateCredential(jwt: String?,jwksUri:String?):Boolean + suspend fun validateCredential(jwt: String?, + jwksUri:String?, + format: String?):Boolean } \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/discovery/DiscoveryService.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/discovery/DiscoveryService.kt index 76a19e6..8ae8fba 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/discovery/DiscoveryService.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/discovery/DiscoveryService.kt @@ -18,11 +18,15 @@ class DiscoveryService : DiscoveryServiceInterface { * @return WrappedIssuerConfigResponse */ override suspend fun getIssuerConfig(credentialIssuerWellKnownURI: String?): WrappedIssuerConfigResponse { + var credentialIssuer = credentialIssuerWellKnownURI?.replace("/.well-known/openid-credential-issuer","") + credentialIssuer = removeTrailingSlash(credentialIssuer) + credentialIssuer = "$credentialIssuer/.well-known/openid-credential-issuer" + try { - UrlUtils.validateUri(credentialIssuerWellKnownURI) + UrlUtils.validateUri(credentialIssuer) val response = ApiManager.api.getService() - ?.fetchIssuerConfig("$credentialIssuerWellKnownURI") + ?.fetchIssuerConfig("$credentialIssuer") return if (response?.isSuccessful == true) { WrappedIssuerConfigResponse(issuerConfig = response.body(), errorResponse = null) } else { @@ -32,7 +36,13 @@ class DiscoveryService : DiscoveryServiceInterface { return WrappedIssuerConfigResponse(issuerConfig = null, errorResponse = ErrorResponse(error = null, errorDescription = "URI validation failed")) } } - + private fun removeTrailingSlash(input: String?): String? { + return if (input?.endsWith("/")==true) { + input?.dropLast(1) // Removes the last character + } else { + input + } + } /** * To fetch the authorization server configuration * @@ -40,12 +50,15 @@ class DiscoveryService : DiscoveryServiceInterface { * @return WrappedAuthConfigResponse */ override suspend fun getAuthConfig(authorisationServerWellKnownURI: String?): WrappedAuthConfigResponse { + var authorizationServer = authorisationServerWellKnownURI?.replace("/.well-known/openid-configuration","") + authorizationServer = removeTrailingSlash(authorizationServer) + authorizationServer = "$authorizationServer/.well-known/openid-configuration" try { - UrlUtils.validateUri(authorisationServerWellKnownURI) + UrlUtils.validateUri(authorizationServer) val response = ApiManager.api.getService() - ?.fetchAuthConfig("$authorisationServerWellKnownURI") + ?.fetchAuthConfig("$authorizationServer") return if (response?.isSuccessful == true) { WrappedAuthConfigResponse(authConfig = response.body(), errorResponse = null) } else { diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueService.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueService.kt index e0e224f..bdc89c0 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueService.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueService.kt @@ -74,80 +74,6 @@ class IssueService : IssueServiceInterface { } } - - /** - * To process the authorisation request The authorisation request is to - * grant access to the credential endpoint - * - * @param did - DID created for the issuance - * @param subJwk - for singing the requests - * @param credentialOffer - To build the authorisation request - * @param codeVerifier - to build the authorisation request - * @param authorisationEndPoint - to build the authorisation request - * @return String - short-lived authorisation code - */ - override suspend fun processAuthorisationRequest( - did: String?, - subJwk: ECKey?, - credentialOffer: CredentialOffer?, - codeVerifier: String, - authorisationEndPoint: String? - ): String? { - val responseType = "code" - val scope = "openid" - val state = UUID.randomUUID().toString() - val clientId = did - val authorisationDetails = buildAuthorizationRequest(credentialOffer) - val redirectUri = "http://localhost:8080" - val nonce = UUID.randomUUID().toString() - - val codeChallenge = CodeVerifierService().generateCodeChallenge(codeVerifier) - val codeChallengeMethod = "S256" - val clientMetadata = Gson().toJson( - ClientMetaDataas( - vpFormatsSupported = VpFormatsSupported( - jwtVp = Jwt(arrayListOf("ES256")), jwtVc = Jwt(arrayListOf("ES256")) - ), responseTypesSupported = arrayListOf( - "vp_token", "id_token" - ), authorizationEndpoint = redirectUri - ) - ) - - val response = ApiManager.api.getService()?.processAuthorisationRequest( - authorisationEndPoint ?: "", - mapOf( - "response_type" to responseType, - "scope" to scope, - "state" to state, - "client_id" to (clientId ?: ""), - "authorization_details" to authorisationDetails, - "redirect_uri" to redirectUri, - "nonce" to nonce, - "code_challenge" to (codeChallenge ?: ""), - "code_challenge_method" to codeChallengeMethod, - "client_metadata" to clientMetadata, - "issuer_state" to (credentialOffer?.grants?.authorizationCode?.issuerState ?: "") - ), - ) - - val location: String? = if (response?.code() == 302) { - response.headers()["Location"] - } else { - null - } - - return if (Uri.parse(location).getQueryParameter("code") != null) { - location - } else { - processAuthorisationRequestUsingIdToken( - did = did, - authorisationEndPoint = authorisationEndPoint, - location = location, - subJwk = subJwk - ) - } - } - /** * To process the authorisation request The authorisation request is to * grant access to the credential endpoint @@ -164,20 +90,25 @@ class IssueService : IssueServiceInterface { subJwk: JWK?, credentialOffer: CredentialOffer?, codeVerifier: String, - authorisationEndPoint: String? + authorisationEndPoint: String?, + format: String?, ): String? { val responseType = "code" - val scope = "openid" + val types = getTypesFromCredentialOffer(credentialOffer) + val scope = if (format == "mso_mdoc") { "${types.firstOrNull() ?: ""} openid" } else { "openid" } + val doctype = if (format == "mso_mdoc") "org.iso.18013.5.1.mDL" else null + // val doctype = if (format == "mso_mdoc") "${types.firstOrNull()}" else null + val state = UUID.randomUUID().toString() val clientId = did - val authorisationDetails = buildAuthorizationRequest(credentialOffer) + val authorisationDetails = buildAuthorizationRequest(credentialOffer, format, doctype) val redirectUri = "http://localhost:8080" val nonce = UUID.randomUUID().toString() val codeChallenge = CodeVerifierService().generateCodeChallenge(codeVerifier) val codeChallengeMethod = "S256" - val clientMetadata = Gson().toJson( + val clientMetadata = if (format == "mso_mdoc") "" else Gson().toJson( ClientMetaDataas( vpFormatsSupported = VpFormatsSupported( jwtVp = Jwt(arrayListOf("ES256")), jwtVc = Jwt(arrayListOf("ES256")) @@ -191,7 +122,7 @@ class IssueService : IssueServiceInterface { authorisationEndPoint ?: "", mapOf( "response_type" to responseType, - "scope" to scope, + "scope" to scope.trim(), "state" to state, "client_id" to (clientId ?: ""), "authorization_details" to authorisationDetails, @@ -216,8 +147,6 @@ class IssueService : IssueServiceInterface { null } - - return if(location != null && Uri.parse(location).getQueryParameter("error") != null) { location }else if (location != null && Uri.parse(location).getQueryParameter("code") != null @@ -285,7 +214,7 @@ class IssueService : IssueServiceInterface { } } - private fun buildAuthorizationRequest(credentialOffer: CredentialOffer?):String{ + private fun buildAuthorizationRequest(credentialOffer: CredentialOffer?, format: String?,doctype:String?):String{ val gson = Gson() var credentialDefinitionNeeded = false try { @@ -298,30 +227,43 @@ class IssueService : IssueServiceInterface { } catch (e: Exception) { credentialDefinitionNeeded = true } - if (credentialDefinitionNeeded) { + if (format == "mso_mdoc"){ return gson.toJson( arrayListOf( AuthorizationDetails( - format = "jwt_vc_json", - locations = arrayListOf(credentialOffer?.credentialIssuer ?: ""), - credentialDefinition = CredentialTypeDefinition( - type = getTypesFromCredentialOffer(credentialOffer) - ) + format = format , + doctype = doctype, + locations = arrayListOf(credentialOffer?.credentialIssuer ?: "") ) ) ) - }else{ - return gson.toJson( - arrayListOf( - AuthorizationDetails( - format = "jwt_vc", - types = getTypesFromCredentialOffer(credentialOffer), - locations = arrayListOf(credentialOffer?.credentialIssuer ?: "") + if (credentialDefinitionNeeded) { + return gson.toJson( + arrayListOf( + AuthorizationDetails( + format = "jwt_vc_json", + locations = arrayListOf(credentialOffer?.credentialIssuer ?: ""), + credentialDefinition = CredentialTypeDefinition( + type = getTypesFromCredentialOffer(credentialOffer) + ) + ) ) ) - ) + + }else{ + return gson.toJson( + arrayListOf( + AuthorizationDetails( + format = "jwt_vc", + types = getTypesFromCredentialOffer(credentialOffer), + locations = arrayListOf(credentialOffer?.credentialIssuer ?: "") + ) + ) + ) + } } + } /** @@ -387,99 +329,6 @@ class IssueService : IssueServiceInterface { return tokenResponse } - /** - * To process the credential, credentials can be issued in two ways, intime - * and deferred - * - * If its intime, then we will receive the credential as the response - * If its deferred, then we will get he acceptance token and use this acceptance token to call deferred - * - * @param did - * @param subJwk - * @param credentialIssuerUrl - * @param nonce - * @param credentialOffer - * @param credentialIssuerEndPoint - * @param accessToken - * @return credential response - */ - override suspend fun processCredentialRequest( - did: String?, - subJwk: ECKey?, - credentialIssuerUrl: String?, - nonce: String?, - credentialOffer: CredentialOffer?, - credentialIssuerEndPoint: String?, - accessToken: String?, - format: String - ): WrappedCredentialResponse? { - - // Add claims - val claimsSet = JWTClaimsSet - .Builder() - .issueTime(Date()) - .expirationTime(Date(Date().time + 86400)) - .issuer(did) - .audience(credentialIssuerUrl) - .claim("nonce", nonce).build() - - // Add header - val jwsHeader = JWSHeader - .Builder(JWSAlgorithm.ES256) - .type(JOSEObjectType("openid4vci-proof+jwt")) - .keyID("$did#${did?.replace("did:key:", "")}") - .jwk(subJwk?.toPublicJWK()) - .build() - - - // Sign with private EC key - val jwt = SignedJWT( - jwsHeader, claimsSet - ) - jwt.sign(ECDSASigner(subJwk)) - - // Construct credential request - val body = CredentialRequest( - types = getTypesFromCredentialOffer(credentialOffer), - format = format, - proof = ProofV3( - proofType = "jwt", - jwt = jwt.serialize() - ) - ) - // API call - val response = ApiManager.api.getService()?.getCredential( - credentialIssuerEndPoint ?: "", - "application/json", - "Bearer $accessToken", - body - ) - - val credentialResponse = when { - response?.isSuccessful == true -> { - WrappedCredentialResponse( - credentialResponse = response.body() - ) - } - - (response?.code() ?: 0) >= 400 -> { - try { - WrappedCredentialResponse( - errorResponse = processError(response?.errorBody()?.string()) - ) - } catch (e: Exception) { - null - } - } - - else -> { - null - } - } - - return credentialResponse - } - /** * To process the credential, credentials can be issued in two ways, * intime and deferred @@ -506,6 +355,7 @@ class IssueService : IssueServiceInterface { accessToken: String?, format: String ): WrappedCredentialResponse? { + val doctype = if (format == "mso_mdoc") "org.iso.18013.5.1.mDL" else null // Add claims val claimsSet = JWTClaimsSet @@ -540,6 +390,7 @@ class IssueService : IssueServiceInterface { credentialOffer = credentialOffer, issuerConfig = issuerConfig, format = format, + doctype = doctype , jwt = jwt.serialize() ) // API call @@ -579,7 +430,8 @@ class IssueService : IssueServiceInterface { credentialOffer: CredentialOffer?, issuerConfig: IssuerWellKnownConfiguration?, format: String?, - jwt: String + jwt: String, + doctype: String? ): CredentialRequest { val gson = Gson() @@ -594,54 +446,67 @@ class IssueService : IssueServiceInterface { } catch (e: Exception) { credentialDefinitionNeeded = true } - - if (credentialDefinitionNeeded) { - var types: ArrayList? = getTypesFromCredentialOffer(credentialOffer) - when (val data = getTypesFromIssuerConfig( - issuerConfig, - type = if (types?.isNotEmpty() == true) types.last() else "" - )) { - is ArrayList<*> -> { - return CredentialRequest( - credentialDefinition = CredentialDefinition(type = data as ArrayList), - format = format, - proof = ProofV3( - proofType = "jwt", - jwt = jwt - ) - ) - } - - is String -> { - return CredentialRequest( - vct = data as String, - format = format, - proof = ProofV3( - proofType = "jwt", - jwt = jwt - ) - ) - } - } - + if (format == "mso_mdoc"){ return CredentialRequest( - credentialDefinition = CredentialDefinition(type = types), format = format, + doctype = doctype, proof = ProofV3( proofType = "jwt", jwt = jwt ) ) - } else { - return CredentialRequest( - types = getTypesFromCredentialOffer(credentialOffer), - format = format, - proof = ProofV3( - proofType = "jwt", - jwt = jwt + } + else{ + if (credentialDefinitionNeeded) { + var types: ArrayList? = getTypesFromCredentialOffer(credentialOffer) + when (val data = getTypesFromIssuerConfig( + issuerConfig, + type = if (types?.isNotEmpty() == true) types.last() else "" + )) { + is ArrayList<*> -> { + return CredentialRequest( + credentialDefinition = CredentialDefinition(type = data as ArrayList), + format = format, + proof = ProofV3( + proofType = "jwt", + jwt = jwt + ) + ) + } + + is String -> { + return CredentialRequest( + vct = data as String, + format = format, + proof = ProofV3( + proofType = "jwt", + jwt = jwt + ) + ) + } + } + + return CredentialRequest( + credentialDefinition = CredentialDefinition(type = types), + format = format, + proof = ProofV3( + proofType = "jwt", + jwt = jwt + ) ) - ) + } else { + return CredentialRequest( + types = getTypesFromCredentialOffer(credentialOffer), + format = format, + proof = ProofV3( + proofType = "jwt", + jwt = jwt + ) + ) + } } + + } fun processError(err: String?): ErrorResponse? { diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueServiceInterface.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueServiceInterface.kt index 70b0355..0510a53 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueServiceInterface.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/issue/IssueServiceInterface.kt @@ -18,25 +18,6 @@ interface IssueServiceInterface { */ suspend fun resolveCredentialOffer(data: String?): CredentialOffer? - /** - * To process the authorisation request - * The authorisation request is to grant access to the credential endpoint - * @param did - DID created for the issuance - * @param subJwk - for singing the requests - * @param credentialOffer - To build the authorisation request - * @param codeVerifier - to build the authorisation request - * @param authorisationEndPoint - to build the authorisation request - * - * @return String - Uri with query parameter code with value short-lived authorisation code - */ - suspend fun processAuthorisationRequest( - did: String?, - subJwk: ECKey?, - credentialOffer: CredentialOffer?, - codeVerifier: String, - authorisationEndPoint: String? - ): String? - /** * To process the authorisation request * The authorisation request is to grant access to the credential endpoint @@ -53,7 +34,8 @@ interface IssueServiceInterface { subJwk: JWK?, credentialOffer: CredentialOffer?, codeVerifier: String, - authorisationEndPoint: String? + authorisationEndPoint: String?, + format: String? = "jwt_vc_json" ): String? /** @@ -80,35 +62,6 @@ interface IssueServiceInterface { userPin: String? ): WrappedTokenResponse? - /** - * To process the credential, credentials can be issued in two ways, - * intime and deferred - * - * If its intime, then we will receive the credential as the response - * If its deferred, then we will get he acceptance token and use this acceptance token to call deferred - * - * @param did - * @param subJwk - * @param credentialIssuerUrl - * @param nonce - * @param credentialOffer - * @param credentialIssuerEndPoint - * @param accessToken - * @param format - * - * @return credential response - */ - suspend fun processCredentialRequest( - did: String?, - subJwk: ECKey?, - credentialIssuerUrl: String?, - nonce: String?, - credentialOffer: CredentialOffer?, - credentialIssuerEndPoint: String?, - accessToken: String?, - format: String - ): WrappedCredentialResponse? - /** * To process the credential, credentials can be issued in two ways, * intime and deferred diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/utils/CborUtils.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/utils/CborUtils.kt new file mode 100644 index 0000000..9d00420 --- /dev/null +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/utils/CborUtils.kt @@ -0,0 +1,75 @@ +package com.ewc.eudi_wallet_oidc_android.services.utils +import android.util.Base64 +import android.util.Log +import co.nstant.`in`.cbor.CborDecoder +import co.nstant.`in`.cbor.model.DataItem +import co.nstant.`in`.cbor.model.MajorType +import org.json.JSONObject +import java.io.ByteArrayInputStream +import kotlin.io.encoding.ExperimentalEncodingApi +import co.nstant.`in`.cbor.model.Array as CborArray +import co.nstant.`in`.cbor.model.ByteString as CborByteString +import co.nstant.`in`.cbor.model.Map as CborMap +import co.nstant.`in`.cbor.model.UnicodeString as CborUnicodeString +class CborUtils { + companion object { + @OptIn(ExperimentalEncodingApi::class) + fun decodeCborCredential(cbor: String?): JSONObject? { + if (cbor.isNullOrBlank()) { + return null + } + val cborInBytes = kotlin.io.encoding.Base64.UrlSafe.decode(cbor ?: "") + return extractCborDataElements(cborInBytes) + } + private fun extractCborDataElements(cborBytes: ByteArray): JSONObject { + val cbors = CborDecoder(ByteArrayInputStream(cborBytes)).decode() + val nameSpaces = cbors[0]["nameSpaces"] + val jsonObject = JSONObject() + if (nameSpaces is CborMap) { + Log.d("TAG", "extractIssuerNamespacedElements: Map") + nameSpaces.let { map -> + // Get all keys from the nameSpaces map + val allKeys = map.keys.mapNotNull { + (it as? CborUnicodeString)?.string + } + for (key in allKeys) { + val elements = nameSpaces[key] as CborArray + val newJson = JSONObject() + for (item in elements.dataItems) { + val decoded = + CborDecoder(ByteArrayInputStream((item as CborByteString).bytes)).decode() + val identifier = decoded[0]["elementIdentifier"].toString() + val value = decoded[0]["elementValue"] + if (value.majorType == MajorType.BYTE_STRING) { + // Convert the ByteString into a readable format, e.g., hex string or Base64 + val byteValue = value as CborByteString + val base64String = + Base64.encodeToString(byteValue.bytes, Base64.NO_WRAP) + newJson.put(identifier, base64String) + } else { + if (identifier == "driving_privileges") { + Log.d("TAG", "extractIssuerNamespacedElements: ") + } + newJson.put(identifier, value.toString()) + } + } + jsonObject.put(key, newJson) + } + } + } else if (nameSpaces is CborArray) { + Log.d("TAG", "extractIssuerNamespacedElements: Array") + } + return jsonObject + } + } +} +operator fun DataItem.get(name: String): DataItem { + check(this.majorType == MajorType.MAP) + this as CborMap + return this.get(CborUnicodeString(name)) +} +operator fun DataItem.get(index: Int): DataItem { + check(this.majorType == MajorType.ARRAY) + this as CborArray + return this.dataItems[index] +} \ No newline at end of file