From 54c1ce0d59dad5082190b38bed6cfd675fb45018 Mon Sep 17 00:00:00 2001 From: milan Date: Tue, 26 Mar 2024 09:10:17 +0530 Subject: [PATCH] Fix #3: SDJWT Issuance and Verification --- eudi-wallet-oidc-android/build.gradle.kts | 5 + .../models/CredentialResponse.kt | 13 +- .../models/PresentationDefinition.kt | 2 +- .../models/PresentationRequest.kt | 1 + .../models/TokenResponse.kt | 5 + .../services/UrlUtils.kt | 28 +++ .../services/did/DIDService.kt | 51 +++- .../services/did/DIDServiceInterface.kt | 6 + .../services/discovery/DiscoveryService.kt | 45 ++-- .../services/issue/IssueService.kt | 221 +++++++++------- .../services/issue/IssueServiceInterface.kt | 12 +- .../services/network/ApiManager.kt | 33 +-- .../services/sdjwt/SDJWTService.kt | 237 ++++++++++++++++++ .../services/sdjwt/SDJWTServiceInterface.kt | 26 ++ .../verification/VerificationService.kt | 135 +++++++++- .../VerificationServiceInterface.kt | 23 +- 16 files changed, 674 insertions(+), 169 deletions(-) create mode 100644 eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/UrlUtils.kt create mode 100644 eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTService.kt create mode 100644 eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTServiceInterface.kt diff --git a/eudi-wallet-oidc-android/build.gradle.kts b/eudi-wallet-oidc-android/build.gradle.kts index 078d63e..3cdbe4c 100644 --- a/eudi-wallet-oidc-android/build.gradle.kts +++ b/eudi-wallet-oidc-android/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.11.0") testImplementation("junit:junit:4.13.2") + testImplementation("org.json:json:20220924") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") @@ -53,4 +54,8 @@ dependencies { implementation("com.squareup.okhttp3:logging-interceptor:4.3.1") // Coroutine adapter for Retrofit implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2") + + implementation("com.github.decentralised-dataexchange:presentation-exchange-sdk-android:2024.3.1") + + implementation("com.google.crypto.tink:tink-android:1.7.0") } \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialResponse.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialResponse.kt index 40d2bf8..6e8eef5 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialResponse.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/CredentialResponse.kt @@ -2,15 +2,24 @@ package com.ewc.eudi_wallet_oidc_android.models import com.google.gson.annotations.SerializedName +data class ErrorResponse( + @SerializedName("error") var error: Int? = null, + @SerializedName("error_description") var errorDescription: String? = null +) + data class CredentialResponse( @SerializedName("format") var format: String? = null, @SerializedName("credential") var credential: String? = null, @SerializedName("acceptance_token") var acceptanceToken: String? = null, - @SerializedName("error") var error: Int? = null, - @SerializedName("error_description") var errorDescription: String? = null, @SerializedName("isDeferred") var isDeferred: Boolean? = null, @SerializedName("isPinRequired") var isPinRequired: Boolean? = null, @SerializedName("issuerConfig") var issuerConfig: IssuerWellKnownConfiguration? = null, @SerializedName("authorizationConfig") var authorizationConfig: AuthorisationServerWellKnownConfiguration? = null, @SerializedName("credentialOffer") var credentialOffer: CredentialOffer? = null ) + + +data class WrappedCredentialResponse( + var credentialResponse: CredentialResponse? = null, + var errorResponse: ErrorResponse? = null, +) diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationDefinition.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationDefinition.kt index 90f9e4e..68ecd15 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationDefinition.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationDefinition.kt @@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName data class PresentationDefinition( @SerializedName("id") var id: String? = null, - @SerializedName("format") var format: VpFormatsSupported? = VpFormatsSupported(), + @SerializedName("format") var format: Map? = mapOf(), @SerializedName("input_descriptors") var inputDescriptors: ArrayList? = arrayListOf() ) \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationRequest.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationRequest.kt index 54ee2ef..a6221f4 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationRequest.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/PresentationRequest.kt @@ -11,5 +11,6 @@ data class PresentationRequest( @SerializedName("scope") var scope: String? = null, @SerializedName("nonce") var nonce: String? = null, @SerializedName("request_uri") var requestUri: String? = null, + @SerializedName("response_uri") var responseUri: String? = null, @SerializedName("presentation_definition") var presentationDefinition: Any? = null ) diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/TokenResponse.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/TokenResponse.kt index 8cb9e48..643ec40 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/TokenResponse.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/models/TokenResponse.kt @@ -12,4 +12,9 @@ data class TokenResponse( @SerializedName("c_nonce_expires_in") var cNonceExpiresIn: Int? = null, @SerializedName("error") var error: String? = null, @SerializedName("error_description") var errorDescription: String? = null +) + +data class WrappedTokenResponse( + var tokenResponse: TokenResponse? = null, + var errorResponse: ErrorResponse? = null, ) \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/UrlUtils.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/UrlUtils.kt new file mode 100644 index 0000000..4160f3b --- /dev/null +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/UrlUtils.kt @@ -0,0 +1,28 @@ +package com.ewc.eudi_wallet_oidc_android.services + +import java.net.URL + +class UriValidationFailed(s: String) : Exception() + +object UrlUtils { + + /** + * Validate uri + * + * @param uri + */ + fun validateUri(uri: String?) { + if (uri.isNullOrBlank() || !UrlUtils.isValidUrl(uri?:"")) { + throw UriValidationFailed("URI validation failed") + } + } + + fun isValidUrl(url: String): Boolean { + return try { + URL(url) + true + } catch (e: Exception) { + false + } + } +} \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDService.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDService.kt index 71198a6..066261a 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDService.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDService.kt @@ -1,21 +1,26 @@ package com.ewc.eudi_wallet_oidc_android.services.did +import com.mediaparkpk.base58android.Base58 import com.nimbusds.jose.jwk.Curve import com.nimbusds.jose.jwk.ECKey +import com.nimbusds.jose.jwk.OctetKeyPair +import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator +import com.nimbusds.jose.util.Base64URL import java.nio.charset.StandardCharsets import java.security.KeyFactory import java.security.KeyPair import java.security.KeyPairGenerator import java.security.PrivateKey import java.security.PublicKey +import java.security.SecureRandom import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.spec.ECPublicKeySpec import java.security.spec.PKCS8EncodedKeySpec -import com.mediaparkpk.base58android.Base58 -import java.security.SecureRandom +import java.util.UUID + -class DIDService : DIDServiceInterface{ +class DIDService : DIDServiceInterface { /** * Generate a did:key:jcs-pub decentralised identifier. @@ -25,7 +30,6 @@ class DIDService : DIDServiceInterface{ override fun createDID(jwk: ECKey): String { val publicKey = jwk.toPublicJWK() - // Remove whitespaces from JSON string val compactJson = "{\"crv\":\"P-256\",\"kty\":\"EC\",\"x\":\"${publicKey?.x}\",\"y\":\"${publicKey?.y}\"}" @@ -39,9 +43,7 @@ class DIDService : DIDServiceInterface{ val multiBaseEncoded = multiBaseEncode(multiCodecBytes!!) // Prefix the string with "did:key" - val didKeyString = "did:key:z$multiBaseEncoded" - - return didKeyString + return "did:key:z$multiBaseEncoded" } /** @@ -70,8 +72,40 @@ class DIDService : DIDServiceInterface{ return ecKey } + /** + * Generate JWK of curve Ed25519 + * + * @return JWK + */ + override fun createED25519JWK(): OctetKeyPair? { + val jwk = OctetKeyPairGenerator(Curve.Ed25519) + .keyID(UUID.randomUUID().toString()) + .generate() + + return jwk + } + + /** + * Generate DID for the ED25519 + * @param privateKeyX - X value of the ED25519 jwk + * + * @return DID + */ + override fun createDidED25519(privateKeyX: Base64URL): String { + val startArray = byteArrayOf(0xed.toByte(), 0x01) + val newArray = startArray + Base64URL(privateKeyX.toString()).decode() + // 3. base58 encode the prefixed public key bytes. + var encoded = Base58.encode(newArray) + // 4. prefix the output with ā€˜zā€™ + encoded = "did:key:z$encoded" + return encoded + } + /** * Convert the PrivateKey to ECPrivateKey + * @param privateKey + * + * @return ECPrivateKey */ private fun convertToECPrivateKey(privateKey: PrivateKey): ECPrivateKey? { return if (privateKey is ECPrivateKey) { @@ -98,6 +132,9 @@ class DIDService : DIDServiceInterface{ /** * Convert the PublicKey to ECPublicKey + * @param publicKey + * + * @return ECPublicKey */ private fun convertToECPublicKey(publicKey: PublicKey): ECPublicKey? { return if (publicKey is ECPublicKey) { diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDServiceInterface.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDServiceInterface.kt index 20a8e9d..b40dc01 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDServiceInterface.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/did/DIDServiceInterface.kt @@ -1,6 +1,8 @@ package com.ewc.eudi_wallet_oidc_android.services.did import com.nimbusds.jose.jwk.ECKey +import com.nimbusds.jose.jwk.OctetKeyPair +import com.nimbusds.jose.util.Base64URL interface DIDServiceInterface { @@ -19,4 +21,8 @@ interface DIDServiceInterface { * @return JWK */ fun createJWK(seed: String? = null): ECKey + + fun createED25519JWK(): OctetKeyPair? + + fun createDidED25519(privateKeyX: Base64URL): String } \ 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 234b381..5eed5fc 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 @@ -2,45 +2,54 @@ package com.ewc.eudi_wallet_oidc_android.services.discovery import com.ewc.eudi_wallet_oidc_android.models.AuthorisationServerWellKnownConfiguration import com.ewc.eudi_wallet_oidc_android.models.IssuerWellKnownConfiguration +import com.ewc.eudi_wallet_oidc_android.services.UriValidationFailed +import com.ewc.eudi_wallet_oidc_android.services.UrlUtils import com.ewc.eudi_wallet_oidc_android.services.network.ApiManager class DiscoveryService : DiscoveryServiceInterface { /** * To fetch the Issue configuration + * * @param credentialIssuerWellKnownURI - * * @return IssuerWellKnownConfiguration */ override suspend fun getIssuerConfig(credentialIssuerWellKnownURI: String?): IssuerWellKnownConfiguration? { - if (credentialIssuerWellKnownURI.isNullOrBlank()) + try { + UrlUtils.validateUri(credentialIssuerWellKnownURI) + val response = + ApiManager.api.getService() + ?.fetchIssuerConfig("$credentialIssuerWellKnownURI") + return if (response?.isSuccessful == true) { + response.body() + } else { + null + } + } catch (exc: UriValidationFailed) { return null - - val response = - ApiManager.api.getService()?.fetchIssuerConfig("$credentialIssuerWellKnownURI/.well-known/openid-credential-issuer") - return if (response?.isSuccessful == true) { - response.body() - } else { - null } } /** * To fetch the authorisation server configuration - * @param authorisationServerWellKnownURI * + * @param authorisationServerWellKnownURI * @return AuthorisationServerWellKnownConfiguration */ override suspend fun getAuthConfig(authorisationServerWellKnownURI: String?): AuthorisationServerWellKnownConfiguration? { - if (authorisationServerWellKnownURI.isNullOrBlank()) - return null + try { + UrlUtils.validateUri(authorisationServerWellKnownURI) - val response = - ApiManager.api.getService()?.fetchAuthConfig("$authorisationServerWellKnownURI/.well-known/openid-configuration") - return if (response?.isSuccessful == true) { - response.body() - } else { - null + val response = + ApiManager.api.getService() + ?.fetchAuthConfig("$authorisationServerWellKnownURI") + return if (response?.isSuccessful == true) { + response.body() + } else { + null + } + } catch (exc: UriValidationFailed) { + return null } } } \ No newline at end of file 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 abbe779..62e0dfd 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 @@ -5,11 +5,13 @@ import com.ewc.eudi_wallet_oidc_android.models.AuthorizationDetails import com.ewc.eudi_wallet_oidc_android.models.ClientMetaData import com.ewc.eudi_wallet_oidc_android.models.CredentialOffer import com.ewc.eudi_wallet_oidc_android.models.CredentialRequest -import com.ewc.eudi_wallet_oidc_android.models.CredentialResponse +import com.ewc.eudi_wallet_oidc_android.models.ErrorResponse import com.ewc.eudi_wallet_oidc_android.models.Jwt import com.ewc.eudi_wallet_oidc_android.models.ProofV3 import com.ewc.eudi_wallet_oidc_android.models.TokenResponse import com.ewc.eudi_wallet_oidc_android.models.VpFormatsSupported +import com.ewc.eudi_wallet_oidc_android.models.WrappedCredentialResponse +import com.ewc.eudi_wallet_oidc_android.models.WrappedTokenResponse import com.ewc.eudi_wallet_oidc_android.services.codeVerifier.CodeVerifierService import com.ewc.eudi_wallet_oidc_android.services.network.ApiManager import com.google.gson.Gson @@ -29,8 +31,10 @@ class IssueService : IssueServiceInterface { /** * To process the credential offer request - * @param data - will accept the full data which is scanned from the QR code or deep link - * The data can contain credential offer or credential offer uri + * + * @param data - will accept the full data which is scanned from the QR + * code or deep link The data can contain credential offer or + * credential offer uri * @return Credential Offer */ override suspend fun resolveCredentialOffer(data: String?): CredentialOffer? { @@ -56,14 +60,14 @@ class IssueService : IssueServiceInterface { /** - * To process the authorisation request - * The authorisation request is to grant access to the credential endpoint + * 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( @@ -182,14 +186,15 @@ class IssueService : IssueServiceInterface { * * @param did * @param tokenEndPoint - * @param code - If the credential offer is pre authorised, then use the pre authorised code from the credential offer - * else use the code from the previous function - processAuthorisationRequest - * @param codeVerifier - use the same code verifier used for processAuthorisationRequest - * @param isPreAuthorisedCodeFlow - boolean value to notify its a pre authorised request - * if pre-authorized_code is present - * @param userPin - optional value, if the user_pin_required is true - * PIN will be provided by the user - * + * @param code - If the credential offer is pre authorised, then use the + * pre authorised code from the credential offer else use the code from + * the previous function - processAuthorisationRequest + * @param codeVerifier - use the same code verifier used for + * processAuthorisationRequest + * @param isPreAuthorisedCodeFlow - boolean value to notify its a pre + * authorised request if pre-authorized_code is present + * @param userPin - optional value, if the user_pin_required is true PIN + * will be provided by the user * @return Token response */ override suspend fun processTokenRequest( @@ -199,13 +204,13 @@ class IssueService : IssueServiceInterface { codeVerifier: String?, isPreAuthorisedCodeFlow: Boolean?, userPin: String? - ): TokenResponse? { + ): WrappedTokenResponse? { val response = ApiManager.api.getService()?.getAccessTokenFromCode( tokenEndPoint ?: "", if (isPreAuthorisedCodeFlow == true) mapOf( "grant_type" to "urn:ietf:params:oauth:grant-type:pre-authorized_code", "pre-authorized_code" to (code ?: ""), - "user_pin" to (userPin?:"") + "user_pin" to (userPin ?: "") ) else mapOf( "grant_type" to "authorization_code", @@ -215,41 +220,33 @@ class IssueService : IssueServiceInterface { ), ) - return if (response?.isSuccessful == true) { - response.body() - } else if ((response?.code() ?: 400) >= 400) { - try { - val jObjError = JSONObject(response?.errorBody()!!.string()) - if (jObjError.has("error_description")) { - TokenResponse( - error = jObjError.getString("error"), - errorDescription = jObjError.getString("error_description") - ) - } else if (jObjError.has("errors")) { - val errorList = JSONArray(jObjError.getString("errors")) - TokenResponse( - error = "Error", - errorDescription = errorList.getJSONObject(0).getString("message") - ) - } else if (jObjError.has("error")) { - TokenResponse( - error = jObjError.getString("error"), - errorDescription = jObjError.getString("error") + val tokenResponse = when { + response?.isSuccessful == true -> { + WrappedTokenResponse( + tokenResponse = response.body() + ) + } + + (response?.code() ?: 0) >= 400 -> { + try { + WrappedTokenResponse( + errorResponse = processError(response?.errorBody()?.string()) ) - } else { + } catch (e: Exception) { null } - } catch (e: Exception) { + } + + else -> { null } - } else { - null } + return tokenResponse } /** - * To process the credential, credentials can be issued in two ways, - * intime and deferred + * 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 @@ -261,7 +258,6 @@ class IssueService : IssueServiceInterface { * @param credentialOffer * @param credentialIssuerEndPoint * @param accessToken - * * @return credential response */ override suspend fun processCredentialRequest( @@ -272,23 +268,33 @@ class IssueService : IssueServiceInterface { credentialOffer: CredentialOffer?, credentialIssuerEndPoint: String?, accessToken: String? - ): CredentialResponse? { - val claimsSet = - JWTClaimsSet.Builder().issueTime(Date()).expirationTime(Date(Date().time + 86400)) - .issuer(did).audience(credentialIssuerUrl).claim("nonce", nonce).build() + ): WrappedCredentialResponse? { - // Create JWT for ES256K alg - val jwsHeader = - JWSHeader.Builder(JWSAlgorithm.ES256).type(JOSEObjectType("openid4vci-proof+jwt")) - .keyID("$did#${did?.replace("did:key:", "")}").jwk(subJwk?.toPublicJWK()).build() + // 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 ) - - // Sign with private EC key jwt.sign(ECDSASigner(subJwk)) + // Construct credential request val body = CredentialRequest( types = credentialOffer?.credentials?.get(0)?.types, format = credentialOffer?.credentials?.get(0)?.format, @@ -297,6 +303,7 @@ class IssueService : IssueServiceInterface { jwt = jwt.serialize() ) ) + // API call val response = ApiManager.api.getService()?.getCredential( credentialIssuerEndPoint ?: "", "application/json", @@ -304,67 +311,103 @@ class IssueService : IssueServiceInterface { body ) - return if (response?.isSuccessful == true) { - response.body() - } else if ((response?.code() ?: 0) >= 400) { - try { - val jObjError = JSONObject(response?.errorBody()!!.string()) - if (response.errorBody()!!.string() - .contains( - "Invalid Proof JWT: iss doesn't match the expected client_id", - true - ) - ) { - CredentialResponse(error = 1) - } else if (jObjError.has("error_description")) { - CredentialResponse( - error = -1, - errorDescription = jObjError.getString("error_description") - ) - } else if (jObjError.has("errors")) { - val errorList = JSONArray(jObjError.getString("errors")) - CredentialResponse( - error = -1, - errorDescription = errorList.getJSONObject(0).getString("message") - ) - } else if (jObjError.has("error")) { - CredentialResponse( - error = -1, - errorDescription = jObjError.getString("error") + val credentialResponse = when { + response?.isSuccessful == true -> { + WrappedCredentialResponse( + credentialResponse = response.body() + ) + } + + (response?.code() ?: 0) >= 400 -> { + try { + WrappedCredentialResponse( + errorResponse = processError(response?.errorBody()?.string()) ) - } else { + } catch (e: Exception) { null } - } catch (e: Exception) { + } + + else -> { null } + } + + return credentialResponse + } + + + fun processError(err: String?): ErrorResponse? { + // Known possibilities for error: + // 1. "Validation is failed" + // 2. {"error_description": "Validation is failed", } + // 3. {"errors": [{ "message": "Validation is failed" }]} + // 4. {"error": "Validation is failed"} + val jsonObject = try { + err?.let { JSONObject(it) } + } catch (e: Exception) { null - } else { - null } + val errorResponse = when { + err?.contains( + "Invalid Proof JWT: iss doesn't match the expected client_id", + true + ) == true -> { + ErrorResponse(error = 1, errorDescription = "DID is invalid") + } + + jsonObject?.has("error_description") == true -> { + ErrorResponse( + error = -1, + errorDescription = jsonObject.getString("error_description") + ) + } + + jsonObject?.has("errors") == true -> { + val errorList = JSONArray(jsonObject.getString("errors")) + ErrorResponse( + error = -1, + errorDescription = errorList.getJSONObject(0).getString("message") + ) + } + + jsonObject?.has("error") == true -> { + ErrorResponse( + error = -1, + errorDescription = jsonObject.getString("error") + ) + } + + else -> { + null + } + } + return errorResponse + } /** * For issuance of the deferred credential. - * @param acceptanceToken - token which we got from credential request - * @param deferredCredentialEndPoint - end point to call the deferred credential * + * @param acceptanceToken - token which we got from credential request + * @param deferredCredentialEndPoint - end point to call the deferred + * credential * @return Credential response */ override suspend fun processDeferredCredentialRequest( acceptanceToken: String?, deferredCredentialEndPoint: String? - ): CredentialResponse? { + ): WrappedCredentialResponse? { val response = ApiManager.api.getService()?.getDifferedCredential( deferredCredentialEndPoint ?: "", "Bearer $acceptanceToken", - CredentialRequest() //empty object + CredentialRequest() // empty object ) return if (response?.isSuccessful == true && response.body()?.credential != null ) { - response.body() + WrappedCredentialResponse(credentialResponse = response.body()) } else { null } 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 9fe6b04..5d650bd 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 @@ -1,8 +1,8 @@ package com.ewc.eudi_wallet_oidc_android.services.issue import com.ewc.eudi_wallet_oidc_android.models.CredentialOffer -import com.ewc.eudi_wallet_oidc_android.models.CredentialResponse -import com.ewc.eudi_wallet_oidc_android.models.TokenResponse +import com.ewc.eudi_wallet_oidc_android.models.WrappedCredentialResponse +import com.ewc.eudi_wallet_oidc_android.models.WrappedTokenResponse import com.nimbusds.jose.jwk.ECKey interface IssueServiceInterface { @@ -25,7 +25,7 @@ interface IssueServiceInterface { * @param codeVerifier - to build the authorisation request * @param authorisationEndPoint - to build the authorisation request * - * @return String - short-lived authorisation code + * @return String - Uri with query parameter code with value short-lived authorisation code */ suspend fun processAuthorisationRequest( did: String?, @@ -57,7 +57,7 @@ interface IssueServiceInterface { codeVerifier: String?, isPreAuthorisedCodeFlow: Boolean?, userPin: String? - ): TokenResponse? + ): WrappedTokenResponse? /** * To process the credential, credentials can be issued in two ways, @@ -84,7 +84,7 @@ interface IssueServiceInterface { credentialOffer: CredentialOffer?, credentialIssuerEndPoint: String?, accessToken: String? - ): CredentialResponse? + ): WrappedCredentialResponse? /** * For issuance of the deferred credential. @@ -96,5 +96,5 @@ interface IssueServiceInterface { suspend fun processDeferredCredentialRequest( acceptanceToken: String?, deferredCredentialEndPoint: String? - ): CredentialResponse? + ): WrappedCredentialResponse? } \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/network/ApiManager.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/network/ApiManager.kt index c2da157..2651628 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/network/ApiManager.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/network/ApiManager.kt @@ -32,7 +32,7 @@ object ApiManager { get() { if (apiManager == null) { apiManager = ApiManager - httpClient = getUnsafeOkHttpClient() + httpClient = OkHttpClient.Builder() httpClient?.followRedirects(false) val httpLoggingInterceptor = HttpLoggingInterceptor() httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY @@ -51,35 +51,4 @@ object ApiManager { } return apiManager!! } - - private fun getUnsafeOkHttpClient(): OkHttpClient.Builder = - try { - // Create a trust manager that does not validate certificate chains - val trustAllCerts: Array = arrayOf( - object : X509TrustManager { - @Throws(CertificateException::class) - override fun checkClientTrusted(chain: Array?, - authType: String?) = Unit - - @Throws(CertificateException::class) - override fun checkServerTrusted(chain: Array?, - authType: String?) = Unit - - override fun getAcceptedIssuers(): Array = arrayOf() - } - ) - // Install the all-trusting trust manager - val sslContext: SSLContext = SSLContext.getInstance("SSL") - sslContext.init(null, trustAllCerts, SecureRandom()) - // Create an ssl socket factory with our all-trusting manager - val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory - val builder = OkHttpClient.Builder() - builder.sslSocketFactory(sslSocketFactory, - trustAllCerts[0] as X509TrustManager - ) - builder.hostnameVerifier { _, _ -> true } - builder - } catch (e: Exception) { - throw RuntimeException(e) - } } \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTService.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTService.kt new file mode 100644 index 0000000..6ac6b00 --- /dev/null +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTService.kt @@ -0,0 +1,237 @@ +package com.ewc.eudi_wallet_oidc_android.services.sdjwt + +import android.util.Base64 +import android.util.Log +import com.ewc.eudi_wallet_oidc_android.models.PresentationDefinition +import com.ewc.eudi_wallet_oidc_android.models.PresentationRequest +import com.ewc.eudi_wallet_oidc_android.services.verification.VerificationService +import com.github.decentraliseddataexchange.presentationexchangesdk.models.MatchedCredential +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.nimbusds.jose.JOSEObjectType +import com.nimbusds.jose.JWSAlgorithm +import com.nimbusds.jose.JWSHeader +import com.nimbusds.jose.crypto.ECDSASigner +import com.nimbusds.jose.jwk.ECKey +import com.nimbusds.jwt.JWTClaimsSet +import com.nimbusds.jwt.SignedJWT +import org.json.JSONArray +import java.security.MessageDigest +import java.util.Date +import java.util.UUID + +class SDJWTService : SDJWTServiceInterface { + + /** + * Calculates the SHA-256 hash of the input string and returns it in base64url encoding. + * + * @param inputString The input string to be hashed. + * @return The SHA-256 hash of the input string in base64url encoding, or null if the input is null. + */ + override fun calculateSHA256Hash(inputString: String?): String? { + if (inputString == null) + return null + + // Step 1: Convert the text to bytes using UTF-8 encoding + val decodedBytes = inputString.toByteArray(Charsets.UTF_8) + + // Step 2: Compute the SHA-256 hash + val sha256Digest = MessageDigest.getInstance("SHA-256").digest(decodedBytes) + + // Step 3: Encode the hash using base64url encoding + val base64urlEncodedHash = Base64.encodeToString( + sha256Digest, + Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING + ) + return base64urlEncodedHash + } + + /** + * Creates a SD-JWT-R using the provided credential, presentation request, + * and private key. + * + * @param credential The credential string containing the disclosures. + * @param presentationRequest The presentation request containing the presentation definition. + * @param subJwk The private key used for signing. + * @return The SD-JWT-R string. + * @throws IllegalArgumentException if an error occurs during processing or signing. + */ + override fun createSDJWTR( + credential: String?, + presentationRequest: PresentationRequest, + subJwk: ECKey + ): String? { + try { + val processedCredentialWithRequiredDisclosures = + processDisclosuresWithPresentationDefinition( + credential, + VerificationService().processPresentationDefinition(presentationRequest.presentationDefinition) + ) + val iat = Date() + + val claimsSet = JWTClaimsSet.Builder() + .audience(presentationRequest.clientId) + .issueTime(iat) + .claim("nonce", UUID.randomUUID().toString()) + .claim( + "sd_hash", + SDJWTService().calculateSHA256Hash(processedCredentialWithRequiredDisclosures) + ) + .build() + + // Create JWT for ES256K alg + val jwsHeader = JWSHeader.Builder(JWSAlgorithm.ES256) + .type(JOSEObjectType("kb_jwt")) + .build() + + val jwt = SignedJWT( + jwsHeader, + claimsSet + ) + + // Sign with private EC key + jwt.sign(ECDSASigner(subJwk)) + + return jwt.serialize() + } catch (e: Exception) { + throw IllegalArgumentException("Error creating SD-JWT-R", e) + } + } + + /** + * Processes disclosures based on the provided credential and presentation definition. + * + * @param credential The credential string containing the disclosures. + * @param presentationDefinition The presentation definition specifying the requested parameters. + * @return The processed JWT containing only the disclosures matching the requested parameters. + * @throws IllegalArgumentException if the processing fails due to invalid inputs or other errors. + */ + override fun processDisclosuresWithPresentationDefinition( + credential: String?, + presentationDefinition: PresentationDefinition + ): String? { + try { + // Split the credential into disclosures and the issued JWT + + val disclosures = getDisclosuresFromSDJWT(credential) + var issuedJwt = getIssuerJwtFromSDJWT(credential) + + // Extract requested parameters from the presentation definition + val requestedParams: MutableList = mutableListOf() + presentationDefinition.inputDescriptors?.get(0)?.constraints?.fields?.forEach { + it.path?.get(0)?.split(".")?.lastOrNull()?.let { paramName -> + requestedParams.add(paramName) + } + } + + // Filter disclosures based on requested parameters + disclosures?.forEach { disclosure -> + val list = + JSONArray(Base64.decode(disclosure, Base64.URL_SAFE).toString(charset("UTF-8"))) + if (list.length() >= 2 && requestedParams.contains(list.optString(1))) { + issuedJwt = "$issuedJwt~$disclosure" + } + } + + return issuedJwt ?: "" + } catch (e: Exception) { + throw IllegalArgumentException( + "Error processing disclosures with presentation definition", + e + ) + } + } + + override fun updateIssuerJwtWithDisclosures(credential: String?): String? { + val split = credential?.split(".") + + val jsonString = Base64.decode( + split?.get(1) ?: "", + Base64.URL_SAFE + ).toString(charset("UTF-8")) + + val jsonObject = Gson().fromJson(jsonString, JsonObject::class.java) + + val hashList: MutableList = mutableListOf() + val disclosures = getDisclosuresFromSDJWT(credential) + disclosures?.forEach { encodedString -> + try { + val hash = calculateSHA256Hash(encodedString) + if (hash != null) { + hashList.add(hash) + } + } catch (e: IllegalArgumentException) { + } + } + + addDisclosuresToCredential( + jsonObject, + disclosures ?: listOf(), + hashList + ) + return Gson().toJson(jsonObject) + } + + private fun addDisclosuresToCredential( + jsonElement: JsonElement, + disclosures: List, + hashList: MutableList + ) { + if (jsonElement.isJsonObject) { + val jsonObject = jsonElement.asJsonObject + if (jsonObject.has("_sd")) { + val sdList = jsonObject.getAsJsonArray("_sd") + + hashList.forEachIndexed { index, hash -> + val sdKey = sdList[index].asString + if (hash == sdKey) { + try { + val disclosure = Base64.decode( + disclosures[index], + Base64.URL_SAFE + ).toString(charset("UTF-8")) + // Extract key-value pair from the encodedString + val (decodedKey, decodedValue) = extractKeyValue(disclosure) + // Add key-value pair to jsonObject + jsonObject.addProperty(decodedKey, decodedValue) + } catch (e: IllegalArgumentException) { + // Handle invalid base64-encoded strings + } + } + } + } + jsonObject.entrySet().forEach { (_, value) -> + addDisclosuresToCredential(value, disclosures, hashList) + } + } else if (jsonElement.isJsonArray) { + jsonElement.asJsonArray.forEach { arrayElement -> + addDisclosuresToCredential(arrayElement, disclosures, hashList) + } + } + } + + private fun extractKeyValue(decodedString: String): Pair { + val jsonArray = JsonParser.parseString(decodedString).asJsonArray + val key = jsonArray[1].asString + val value = jsonArray[2].asString + return Pair(key, value) + } + + private fun getDisclosuresFromSDJWT(credential: String?): List? { + val split = credential?.split("~") + return if ((split?.size ?: 0) > 1) + split?.subList(1, split.size) + else + listOf() + } + + private fun getIssuerJwtFromSDJWT(credential: String?): String? { + val split = credential?.split("~") + return if ((split?.size ?: 0) > 0) + split?.first() + else + null + } +} \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTServiceInterface.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTServiceInterface.kt new file mode 100644 index 0000000..eb56ba5 --- /dev/null +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/sdjwt/SDJWTServiceInterface.kt @@ -0,0 +1,26 @@ +package com.ewc.eudi_wallet_oidc_android.services.sdjwt + +import com.ewc.eudi_wallet_oidc_android.models.PresentationDefinition +import com.ewc.eudi_wallet_oidc_android.models.PresentationRequest +import com.github.decentraliseddataexchange.presentationexchangesdk.models.MatchedCredential +import com.nimbusds.jose.jwk.ECKey + +interface SDJWTServiceInterface { + + fun calculateSHA256Hash(inputString: String?): String? + + fun createSDJWTR( + credential: String?, + presentationRequest: PresentationRequest, + subJwk: ECKey + ): String? + + fun processDisclosuresWithPresentationDefinition( + credential: String?, + presentationDefinition: PresentationDefinition + ): String? + + fun updateIssuerJwtWithDisclosures( + credential: String? + ): String? +} \ No newline at end of file diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationService.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationService.kt index dab4b8f..46a5de1 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationService.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationService.kt @@ -8,17 +8,26 @@ import com.ewc.eudi_wallet_oidc_android.models.PresentationDefinition import com.ewc.eudi_wallet_oidc_android.models.PresentationRequest import com.ewc.eudi_wallet_oidc_android.models.PresentationSubmission import com.ewc.eudi_wallet_oidc_android.services.network.ApiManager +import com.ewc.eudi_wallet_oidc_android.services.sdjwt.SDJWTService +import com.github.decentraliseddataexchange.presentationexchangesdk.PresentationExchange +import com.github.decentraliseddataexchange.presentationexchangesdk.models.MatchedCredential import com.google.gson.Gson +import com.google.gson.internal.LinkedTreeMap import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jose.jwk.ECKey +import com.nimbusds.jose.shaded.json.parser.ParseException +import com.nimbusds.jwt.JWT import com.nimbusds.jwt.JWTClaimsSet +import com.nimbusds.jwt.JWTParser import com.nimbusds.jwt.SignedJWT +import org.json.JSONObject import java.util.Date import java.util.UUID + class VerificationService : VerificationServiceInterface { /** @@ -44,6 +53,7 @@ class VerificationService : VerificationServiceInterface { val responseType = Uri.parse(data).getQueryParameter("response_type") val scope = Uri.parse(data).getQueryParameter("scope") val requestUri = Uri.parse(data).getQueryParameter("request_uri") + val responseUri = Uri.parse(data).getQueryParameter("response_uri") val responseMode = Uri.parse(data).getQueryParameter("response_mode") if (presentationDefinition != null) { @@ -56,12 +66,14 @@ class VerificationService : VerificationServiceInterface { responseMode = responseMode, responseType = responseType, scope = scope, - requestUri = requestUri + requestUri = requestUri, + responseUri = responseUri ) } else if (data.startsWith("openid4vp") && !requestUri.isNullOrBlank() ) { - val response = ApiManager.api.getService()?.getPresentationDefinitionFromRequestUri(requestUri) + val response = + ApiManager.api.getService()?.getPresentationDefinitionFromRequestUri(requestUri) if (response?.isSuccessful == true) { val split = response.body().toString().split(".")[1] @@ -79,11 +91,32 @@ class VerificationService : VerificationServiceInterface { } else { return null } + } else if (isValidJWT(data)) { + val split = data.split(".") + var payload: String? = null + if (split.size == 3) { + payload = split[1] + return Gson().fromJson(payload, PresentationRequest::class.java) + } else { + return null + } } else { return null } } + private fun isValidJWT(token: String): Boolean { + try { + // Parse the JWT token + val parsedJWT: JWT = JWTParser.parse(token) + return parsedJWT.jwtClaimsSet != null + } catch (e: ParseException) { + println("JWT parsing failed: ${e.message}") + return false + } + } + + /** * Authorisation response is sent by constructing the vp_token and presentation_submission values. */ @@ -132,7 +165,7 @@ class VerificationService : VerificationServiceInterface { jwt.sign(ECDSASigner(subJwk)) val response = ApiManager.api.getService()?.sendVPToken( - presentationRequest.redirectUri ?: "", + presentationRequest.responseUri?:presentationRequest.redirectUri ?: "", mapOf( "vp_token" to jwt.serialize(), "presentation_submission" to Gson().toJson( @@ -151,21 +184,99 @@ class VerificationService : VerificationServiceInterface { } } + /** + * Returns all the list of credentials matching for all input descriptors + */ + override suspend fun filterCredentials( + credentialList: List, + presentationDefinition: PresentationDefinition + ): List> { + //list of credentials matched for all input descriptors + val response: MutableList> = mutableListOf() + + var processedCredentials: List = mutableListOf() + for (cred in credentialList) { + val split = cred?.split(".") + + + val jsonString = if ((cred?.split("~")?.size ?: 0) > 0) + SDJWTService().updateIssuerJwtWithDisclosures(cred) + else + Base64.decode( + split?.get(1) ?: "", + Base64.URL_SAFE + ).toString(charset("UTF-8")) + + val json = JSONObject(jsonString?:"{}") + + // todo known item, we are considering the path from only vc + processedCredentials = + processedCredentials + listOf( + if (json.has("vc")) json.getJSONObject("vc").toString() + else json.toString() + ) + } + + val pex = PresentationExchange() + + presentationDefinition.inputDescriptors?.forEach { inputDescriptors -> + val filteredCredentialList: MutableList = mutableListOf() + val inputDescriptor = Gson().toJson(inputDescriptors) + + val matches: List = + pex.matchCredentials(inputDescriptor, processedCredentials) + + for (match in matches) { + filteredCredentialList.add(credentialList[match.index] ?: "") + } + + response.add(filteredCredentialList) + } + + return response + } + + /** + * Processes the provided presentation definition and converts it into a PresentationDefinition object. + * + * @param presentationDefinition The presentation definition to be processed, can be of type PresentationDefinition, + * LinkedTreeMap<*, *> representing JSON structure, or a JSON string. + * @return The processed PresentationDefinition object. + * @throws IllegalArgumentException if the presentation definition cannot be processed. + */ + override fun processPresentationDefinition(presentationDefinition: Any?): PresentationDefinition { + try { + return when (presentationDefinition) { + is PresentationDefinition -> presentationDefinition + is LinkedTreeMap<*, *> -> { + val jsonString = Gson().toJson(presentationDefinition) + Gson().fromJson(jsonString, PresentationDefinition::class.java) + } + + is String -> Gson().fromJson( + presentationDefinition, + PresentationDefinition::class.java + ) + + else -> throw IllegalArgumentException("Invalid presentation definition format") + } + } catch (e: Exception) { + throw IllegalArgumentException("Error processing presentation definition", e) + } + } + + /** + * To generate the presentation submission from the presentation Request + */ private fun createPresentationSubmission( presentationRequest: PresentationRequest ): PresentationSubmission? { val id = UUID.randomUUID().toString() val descriptorMap: ArrayList = ArrayList() - var presentationDefinition: PresentationDefinition? = null - if (presentationRequest.presentationDefinition is PresentationDefinition) - presentationDefinition = - presentationRequest.presentationDefinition as PresentationDefinition - else - presentationDefinition = Gson().fromJson( - presentationRequest.presentationDefinition as String, - PresentationDefinition::class.java - ) + var presentationDefinition: PresentationDefinition? = + processPresentationDefinition(presentationRequest.presentationDefinition) + presentationDefinition?.inputDescriptors?.forEachIndexed { index, inputDescriptors -> val descriptor = DescriptorMap( id = inputDescriptors.id, diff --git a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationServiceInterface.kt b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationServiceInterface.kt index 95c8fa8..4ebce70 100644 --- a/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationServiceInterface.kt +++ b/eudi-wallet-oidc-android/src/main/java/com/ewc/eudi_wallet_oidc_android/services/verification/VerificationServiceInterface.kt @@ -1,6 +1,11 @@ package com.ewc.eudi_wallet_oidc_android.services.verification +import com.ewc.eudi_wallet_oidc_android.models.PresentationDefinition import com.ewc.eudi_wallet_oidc_android.models.PresentationRequest +import com.github.decentraliseddataexchange.presentationexchangesdk.PresentationExchange +import com.github.decentraliseddataexchange.presentationexchangesdk.models.MatchedCredential +import com.google.gson.Gson +import com.google.gson.internal.LinkedTreeMap import com.nimbusds.jose.jwk.ECKey interface VerificationServiceInterface { @@ -30,6 +35,20 @@ interface VerificationServiceInterface { did: String?, subJwk: ECKey?, presentationRequest: PresentationRequest, - credentialList:List - ):String? + credentialList: List + ): String? + + /** + * To filter the credential using the input descriptors + * @param credentialList - list of all issued credentials + * @param presentationDefinition + * + * @return list of credentials filtered using the presentation definition + */ + suspend fun filterCredentials( + credentialList: List, + presentationDefinition: PresentationDefinition + ): List> + + fun processPresentationDefinition(presentationDefinition: Any?): PresentationDefinition? } \ No newline at end of file