Skip to content

Commit

Permalink
Fix: Wallet unit attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
lijogeorgep authored and josmilan committed Jan 10, 2025
1 parent 1d8e94a commit b043f64
Show file tree
Hide file tree
Showing 17 changed files with 1,042 additions and 140 deletions.
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {

defaultConfig {
applicationId = "com.ewc.eudiwalletoidcandroid"
minSdk = 26
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"
Expand Down Expand Up @@ -78,4 +78,5 @@ dependencies {
strictly("2.0.9")
}
}
implementation ("com.google.android.play:integrity:1.4.0")
}
139 changes: 123 additions & 16 deletions app/src/main/java/com/ewc/eudiwalletoidcandroid/MainActivity.kt

Large diffs are not rendered by default.

164 changes: 86 additions & 78 deletions app/src/main/java/com/ewc/eudiwalletoidcandroid/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.ewc.eudiwalletoidcandroid

import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ewc.eudi_wallet_oidc_android.CryptographicAlgorithms
import com.ewc.eudi_wallet_oidc_android.models.AuthorisationServerWellKnownConfiguration
import com.ewc.eudi_wallet_oidc_android.models.CredentialOffer
import com.ewc.eudi_wallet_oidc_android.models.IssuerWellKnownConfiguration
import com.ewc.eudi_wallet_oidc_android.models.PresentationRequest
import com.ewc.eudi_wallet_oidc_android.models.TokenResponse
import com.ewc.eudi_wallet_oidc_android.models.WrappedPresentationRequest
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.did.DIDService
Expand Down Expand Up @@ -60,11 +58,11 @@ class MainViewModel : ViewModel() {
offerCredential = IssueService().resolveCredentialOffer(url)

val wrappedResponse = DiscoveryService().getIssuerConfig("${offerCredential?.credentialIssuer}/.well-known/openid-credential-issuer")
if (wrappedResponse.issuerConfig != null) {
if (wrappedResponse?.issuerConfig != null) {
// Handle successful response
issuerConfig = wrappedResponse.issuerConfig
} else {
displayErrorMessage(context, wrappedResponse.errorResponse?.errorDescription)
displayErrorMessage(context, wrappedResponse?.errorResponse?.errorDescription)
return@launch
}
val wrappedAuthResponse = DiscoveryService().getAuthConfig(
Expand Down Expand Up @@ -92,43 +90,43 @@ class MainViewModel : ViewModel() {

if (offerCredential?.grants?.preAuthorizationCode?.preAuthorizedCode != null) {
// pre authorized code flow
if (offerCredential?.grants?.preAuthorizationCode?.userPinRequired == true) {
if (offerCredential?.grants?.preAuthorizationCode?.transactionCode != null) {
// pre authorized code flow with pin required
isPreAuthorised.value = true
isLoading.value = false
} else {
// pre authorized code flow with no pin required
tokenResponse = IssueService().processTokenRequest(
did = did,
tokenEndPoint = authConfig?.tokenEndpoint,
code = offerCredential?.grants?.preAuthorizationCode?.preAuthorizedCode,
codeVerifier = codeVerifier,
isPreAuthorisedCodeFlow = true,
userPin = null
)
// tokenResponse = IssueService().processTokenRequest(
// did = did,
// tokenEndPoint = authConfig?.tokenEndpoint,
// code = offerCredential?.grants?.preAuthorizationCode?.preAuthorizedCode,
// codeVerifier = codeVerifier,
// isPreAuthorisedCodeFlow = true,
// userPin = null
// )
getCredential()
}
} else {
// Process Authorisation request
val authResponse = IssueService().processAuthorisationRequest(
did,
subJwk,
offerCredential,
codeVerifier,
authConfig?.authorizationEndpoint
)
// val authResponse = IssueService().processAuthorisationRequest(
// did,
// subJwk,
// offerCredential,
// codeVerifier,
// authConfig?.authorizationEndpoint
// )

val code = Uri.parse(authResponse).getQueryParameter("code")
// val code = Uri.parse(authResponse).getQueryParameter("code")

//process token request
tokenResponse = IssueService().processTokenRequest(
did = did,
tokenEndPoint = authConfig?.tokenEndpoint,
code = code,
codeVerifier = codeVerifier,
isPreAuthorisedCodeFlow = false,
userPin = null
)
// tokenResponse = IssueService().processTokenRequest(
// did = did,
// tokenEndPoint = authConfig?.tokenEndpoint,
// code = code,
// codeVerifier = codeVerifier,
// isPreAuthorisedCodeFlow = false,
// userPin = null
// )
getCredential()
}

Expand All @@ -142,7 +140,8 @@ class MainViewModel : ViewModel() {
}

private suspend fun getCredential() {

val subJwk = DIDService().createJWK()
val did = DIDService().createDID(subJwk)
val credential = IssueService().processCredentialRequest(
did,
subJwk,
Expand Down Expand Up @@ -211,14 +210,14 @@ class MainViewModel : ViewModel() {
fun verifyPin(pin: String?) {
isLoading.value = true
CoroutineScope(Dispatchers.Main).launch {
tokenResponse = IssueService().processTokenRequest(
did = did,
tokenEndPoint = authConfig?.tokenEndpoint,
code = offerCredential?.grants?.preAuthorizationCode?.preAuthorizedCode,
codeVerifier = codeVerifier,
isPreAuthorisedCodeFlow = true,
userPin = pin
)
// tokenResponse = IssueService().processTokenRequest(
// did = did,
// tokenEndPoint = authConfig?.tokenEndpoint,
// code = offerCredential?.grants?.preAuthorizationCode?.preAuthorizedCode,
// codeVerifier = codeVerifier,
// isPreAuthorisedCodeFlow = true,
// userPin = pin
// )
getCredential()
}
}
Expand All @@ -228,7 +227,7 @@ class MainViewModel : ViewModel() {
val presentationRequest =
VerificationService().processAuthorisationRequest(url)

val subJwk = DIDService().createJWK()
val subJwk = DIDService().createJWK(cryptographicAlgorithm = CryptographicAlgorithms.ES256)
val did = DIDService().createDID(subJwk)

withContext(Dispatchers.Main) {
Expand All @@ -251,39 +250,46 @@ class MainViewModel : ViewModel() {

if (presentationRequest != null) {

val presentationDefinition =
VerificationService().processPresentationDefinition(presentationRequest.presentationDefinition)

val allCredentials = listOf(credentialJwt.value)

val filteredCredentials = takeFirstElementInEachList(
VerificationService().filterCredentials(
allCredentials,
presentationDefinition
),
presentationDefinition.format?.containsKey("sd_jwt") == true,
presentationRequest,
subJwk
)

if (filteredCredentials.isNotEmpty()) {
val code = VerificationService().sendVPToken(
did = did,
subJwk = subJwk,
presentationRequest = presentationRequest,
credentialList = filteredCredentials
)
// val presentationDefinition =
// VerificationService().processPresentationDefinition(presentationRequest.presentationDefinition)
// val updatedJsonString = "{\"format\":{},\"id\":\"6978f6b6-97be-4918-af02-4625ea49cf20\",\"input_descriptors\":[{\"constraints\":{\"fields\":[{\"filter\":{\"contains\":{\"const\":\"NationalIdCard\"},\"type\":\"string\"},\"path\":[\"\$.vct\"]},{\"path\":[\"\$.name\"]},{\"path\":[\"\$.address\"]},{\"path\":[\"\$.phone.number\"]}],\"limit_disclosure\":\"required\"},\"format\":{\"vc+sd-jwt\":{\"alg\":[\"ES256\"]},\"vp+sd-jwt\":{\"alg\":[\"ES256\"]}},\"id\":\"f945fe99-1185-43ec-8f56-c9684114c9e4\"}]}"
// val presentationDefinition = Gson().fromJson(
// updatedJsonString,
// com.ewc.eudi_wallet_oidc_android.models.PresentationDefinition::class.java
// )

// val allCredentials = listOf(credentialJwt.value)
// val allCredentials = listOf("eyJhbGciOiJFUzI1NiIsImtpZCI6Ii1hZzAxSmNJTjBYOGhNWjV6UE8tVG13N1BMUnRuSWpIZW5MSVRRTnlZUzgiLCJ0eXAiOiJKV1QifQ.eyJfc2QiOlsiRkhpVDYwVW9mZy15UVJ0QjZnSlZNV0d4Nllvb1JVNWNlQXFFUV9YS1VmTSIsInZMem1XalVCSklodjRqdUJvTEQxcU83LVVKelQ3X0ZtMXZWTnRnOXlCcFEiXSwiYWRkcmVzcyI6eyJfc2QiOlsiTFdFb2xJVk4tN2lzSnJjcEF2ZTdzSTVJS0RIYk1JbGtYTTl1UGpIQ2pwVSIsInktVjlrNjd5Mnc2WDJrN0JTWjk2ekZoQVRjUFBibXZDUkhRcU9FbVN1dmciLCJpcENHUUZtQm9UeE1JakE2T1hQeXE1Zlg0ZVg0RTZFQ2ZRZ3d5QVpCVHRVIiwiaUljdkwxalJMbTMzMmgxZ3hYTVlsMkZNTjlHSUZ3M0l1ZENDSG1fYlZ3SSJdfSwiZW1wbG95bWVudCI6eyJkZXNpZ25hdGlvbiI6InRlc3RlciIsInR5cGVPZkVtcGxveW1lbnQiOiJmdWxsIHRpbWUgZW1wbG95ZWUifSwiZXhwIjoxNzMwODc2MzgzLCJpYXQiOjE3MzA3ODk5ODMsImlzcyI6Imh0dHBzOi8vc3RhZ2luZy1vaWQ0dmMuaWdyYW50LmlvL29yZ2FuaXNhdGlvbi8zMGUzMjE5OS02YWIzLTQ1NDMtOTllMC04OWQzYTRkYjU2YmUvc2VydmljZSIsImp0aSI6InVybjpkaWQ6ZWEzYzA1MTgtYzNmMy00OGI0LThjYWYtZWUxN2FlMDdiYmNmIiwibmJmIjoxNzMwNzg5OTgzLCJzdWIiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnJEcnAzUGZWSFJGVUcyOTU5dGZWN3RQeTNqUkdyNXpRaExyam1na2c2N0M2QVozamJ2UWNqMnJlSkZicGhKTGhObUFEYkJCV3IxQ21lSEw1cWZNQ0UzVEJQRmQxUjlZQXZVUGV5VVNQdVJ3RVJOM0YyRnEzTjlzZEhYNlJNeUZmd3oiLCJ2Y3QiOiJOYXRpb25hbElkQ2FyZCJ9.WM9wcchULnOYUrAdpWyl75ua8MC1sA5vqnjGjy85-w7qWYD5bDhhxXM-sGRaCWgQvfapVaTvdlcqH28wChUxGQ~WyI5Y2I3YjdiZmRjNzJiNDczMDhiNzAxNDYxZWEzYTIxOTVhNTIyZjVlMDgwYzQ4NWJkMDIzNGE0MDRmNDBiNjgyIiwiZmllbGQxIiwidGVzdCBmaWVsZDEiXQ~WyJhOGNlYmE1MWE0ZTYxMjhhMjU1OGUzODlkZjRiNWU3MGYxZjcxZGUyZDhhY2U3M2VhZmRhYzQ5ZmZjYWQwNjA5IiwiZmllbGQyIiwidGVzdCBmaWVsZDIiXQ~WyIyMDBlZTExZTUwN2U0OWMxMjgyMDQwNDU1ZjE3MmE3ZGFmNzczNDMwMTQ0MmI3ODdmOGUxNGI4ODMxZWE2NjQ1IiwicGluQ29kZSIsIjY4MDU1NSJd~WyIyMjE1MGNiMGE3NWI5YzA3M2Y2NGFiYTAxYjg4NjQyMzEwYzkwZGFiODhkYjc0NTRhYzJkODJlYTFmN2M0MTNhIiwic3RhdGUiLCJrZXJhbGEiXQ~WyIzOTdkMTVkMGE5NzM1MzY4MTYwZjQ2N2MzOTQ2NDlhMzBmYjNkNzU1ZDQ0ZGFlOWIxZjYwOGFiYzZmZDEzZjUyIiwibmFtZSIsIkxpam8iXQ~WyI1M2Q4NmY1ODQyMGM2MDFmNTYyZWQzNDdlYzU3YzQ4NzM1Nzg1NTliZmQ1MTI4NTJhMzRjOWQ2ZWEzMGYzYjk4IiwicGhvbmUiLHsiY291bnRyeUNvZGUiOiI5MSIsIm51bWJlciI6Ijk3NDU4MDEwNTYifV0")
// val filteredCredentials = takeFirstElementInEachList(
// VerificationService().filterCredentials(
// allCredentials,
// presentationDefinition
// ),
// presentationDefinition.format?.containsKey("sd_jwt") == true,
// presentationRequest,
// subJwk
// )

// if (filteredCredentials.isNotEmpty()) {
val code = presentationRequest.presentationRequest?.let {
VerificationService().processAndSendAuthorisationResponse(
did = did,
subJwk = subJwk,
presentationRequest = it,
credentialList = listOf("eyJhbGciOiJFUzI1NiIsImtpZCI6Ii1hZzAxSmNJTjBYOGhNWjV6UE8tVG13N1BMUnRuSWpIZW5MSVRRTnlZUzgiLCJ0eXAiOiJKV1QifQ.eyJfc2QiOlsiakZYSGx4U3lfdDFiU0hpRk44Q3U2d1RUNDN3aXBsSzdDTjhueUc5SmF4VSIsImtLb3NxMk1KVWZlZG9HckhaXzlJa1dacUc0SVpvQTREekkyVkdtRjVKRTgiXSwiZXhwIjoxNzM1Nzk4NDAxLCJpYXQiOjE3MzMyMDY0MDEsImlzcyI6Imh0dHBzOi8vc3RhZ2luZy1vaWQ0dmMuaWdyYW50LmlvL29yZ2FuaXNhdGlvbi8zMGUzMjE5OS02YWIzLTQ1NDMtOTllMC04OWQzYTRkYjU2YmUvc2VydmljZSIsImp0aSI6InVybjpkaWQ6YzMxNDYwNmUtNjMyNy00NTJiLTgzOGQtMzllYzg1OTRlOTYxIiwibmJmIjoxNzMzMjA2NDAxLCJzdGF0dXMiOnsic3RhdHVzX2xpc3QiOnsiaWR4IjoxNTA2LCJ1cmkiOiJodHRwczovL3N0YWdpbmctb2lkNHZjLmlncmFudC5pby9vcmdhbmlzYXRpb24vMzBlMzIxOTktNmFiMy00NTQzLTk5ZTAtODlkM2E0ZGI1NmJlL3NlcnZpY2UvcmV2b2NhdGlvbi1zdGF0dXNsaXN0cy8yYmRiNmI4ZS0yZGU3LTQ0ZDYtYjc2NC1mZmZkYmQwNDQ2ZDIifX0sInN1YiI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUtidEEzRkFOWDZ5SHduSkxicWlXNUsxaXhkWEQ0cktCcFJQdG4za29OZm9zZmZWZm1OV3F4akhIVFI5OGhGNVVVd0pXTTNwaUV3eUdYeEVLOUZrWDdocmlqSG5YWE1heFM2R1k2RXdVd1JwNnA2TVZObTlKNGtvb21MaU1ZSll6ODFwRyIsInZjdCI6IkxlZ2FsUGVyc29uYWxJZGVudGlmaWNhdGlvbkRhdGEifQ.nSee2ZHkEaeZGvFVVGm_7Mtj5cnGXYrI1UamtHdvBeC5a5k4PP3FAo1igXCRB3CSzJJ7poMUBo5IXG7lIwAbfQ~WyJiZDhkYjNmMmI1MjUxOGJhMzQ0ZGUxZDNiZjc5MWQyYWM1MGMyODc3Y2M4OGVjMjBiNDU2YTY3YTExZWY4N2ExIiwiaWRlbnRpZmllciIsImRmZCJd~WyIwMDZkMGFmYzJhOGRlYjRhM2RiYjIxNTVlNzdiY2QzMjE0ZGY0ZmE4NDYyZmQ3YzhmZDUxNDA3Y2MxODU4YWNlIiwibGVnYWxOYW1lIiwiZGZkZiJd")
)
}

withContext(Dispatchers.Main) {
displayText.value =
"${displayText.value} ${if (code != null) "Verification success" else "Verification failed"}\n\n"
}
} else {
withContext(Dispatchers.Main) {
displayText.value =
"${displayText.value}No valid credentials\n\n"
}
withContext(Dispatchers.Main) {
displayText.value =
"${displayText.value} ${if (code != null) "Verification success" else "Verification failed"}\n\n"
}
// } else {
// withContext(Dispatchers.Main) {
// displayText.value =
// "${displayText.value}No valid credentials\n\n"
// }
// }
}
}
}
Expand All @@ -292,19 +298,21 @@ class MainViewModel : ViewModel() {
private fun takeFirstElementInEachList(
filterCredentials: List<List<String>>,
isSdJwt: Boolean,
presentationRequest: PresentationRequest,
presentationRequest: WrappedPresentationRequest?,
subJwk: ECKey
): List<String> {
val response: MutableList<String> = mutableListOf()
filterCredentials.forEach {
if (it.isNotEmpty())
if (isSdJwt)
response.add(
SDJWTService().createSDJWTR(
it.first(),
presentationRequest,
subJwk
) ?: ""
WrappedPresentationRequest(). presentationRequest?.let { it1 ->
SDJWTService().createSDJWTR(
it.first(),
it1,
subJwk
)
} ?: ""
)
else {
response.add(it.first())
Expand All @@ -313,4 +321,4 @@ class MainViewModel : ViewModel() {
}
return response
}
}
}
3 changes: 2 additions & 1 deletion eudi-wallet-oidc-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {
compileSdk = 34

defaultConfig {
minSdk = 26
minSdk = 28

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
Expand Down Expand Up @@ -65,6 +65,7 @@ dependencies {

implementation("com.google.crypto.tink:tink-android:1.7.0")
implementation("co.nstant.in:cbor:0.9")
implementation ("com.google.android.play:integrity:1.4.0")
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ewc.eudi_wallet_oidc_android

import com.google.gson.annotations.SerializedName

data class CredentialOfferResponse(
@SerializedName("credentialOffer") var credentialOffer: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ewc.eudi_wallet_oidc_android

import com.google.gson.annotations.SerializedName

data class NonceResponse(
@SerializedName("nonce") var nonce: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ewc.eudi_wallet_oidc_android


import com.google.gson.annotations.SerializedName

data class WalletAttestationResult(
@SerializedName("credentialOffer") var credentialOffer: String? = null,
@SerializedName("clientAssertion") var clientAssertion: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ data class AuthorisationServerWellKnownConfiguration(
@SerializedName("subject_syntax_types_supported") var subjectSyntaxTypesSupported: ArrayList<String> = arrayListOf(),
@SerializedName("subject_syntax_types_discriminations") var subjectSyntaxTypesDiscriminations: ArrayList<String> = arrayListOf(),
@SerializedName("subject_trust_frameworks_supported") var subjectTrustFrameworksSupported: ArrayList<String> = arrayListOf(),
@SerializedName("id_token_types_supported") var idTokenTypesSupported: ArrayList<String> = arrayListOf(),
@SerializedName("id_token_signing_alg_values_supported") var idTokenTypesSupported: ArrayList<String> = arrayListOf(),
@SerializedName("require_pushed_authorization_requests") var requirePushedAuthorizationRequests: Boolean = false,
@SerializedName("pushed_authorization_request_endpoint") var pushedAuthorizationRequestEndpoint: String? = null,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ewc.eudi_wallet_oidc_android.models

import com.google.gson.annotations.SerializedName

data class ClientAssertion(
@SerializedName("client_assertion") var clientAssertion: String? = null,
@SerializedName("client_assertion_type") var clientAssertionType: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.nimbusds.jose.jwk.Curve
import com.nimbusds.jose.jwk.ECKey
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.OctetKeyPair
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator
import com.nimbusds.jose.util.Base64URL
import com.nimbusds.jose.util.JSONObjectUtils
Expand Down Expand Up @@ -178,6 +179,7 @@ class DIDService : DIDServiceInterface {
}



/**
* Create ED25519 JWK
*
Expand Down Expand Up @@ -255,7 +257,7 @@ class DIDService : DIDServiceInterface {
*
* @return ECPrivateKey
*/
private fun convertToECPrivateKey(privateKey: PrivateKey): ECPrivateKey? {
fun convertToECPrivateKey(privateKey: PrivateKey): ECPrivateKey? {
return if (privateKey is ECPrivateKey) {
// If the PrivateKey is already an ECPrivateKey, simply cast and return it
privateKey
Expand Down Expand Up @@ -284,7 +286,7 @@ class DIDService : DIDServiceInterface {
*
* @return ECPublicKey
*/
private fun convertToECPublicKey(publicKey: PublicKey): ECPublicKey? {
fun convertToECPublicKey(publicKey: PublicKey): ECPublicKey? {
return if (publicKey is ECPublicKey) {
// If the PublicKey is already an ECPublicKey, simply cast and return it
publicKey
Expand Down
Loading

0 comments on commit b043f64

Please sign in to comment.