From 016774755dcc259185fd19ef80d591416df4d34b Mon Sep 17 00:00:00 2001 From: Felix Moser Date: Mon, 12 Aug 2024 14:09:27 +0200 Subject: [PATCH 1/5] Add SdJwtDocumentDataStore with in memory storage to DocumentManagerSdJwt --- .../issue/openid4vci/DocumentManagerSdJwt.kt | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt index 322c2752..d8e118fb 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt @@ -1,30 +1,59 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci -class DocumentManagerSdJwt { +object DocumentManagerSdJwt { + private lateinit var dataStore: SdJwtDocumentDataStore - private val documents: Map = mapOf() //SHARED PREFS - - private val testDocument = - """eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiUDZiLUxnTk9fRUlpNm5ubmRCbW1wS1FIdDVvTlBpLVVsWldGRmU4VnFDMCJdfSwiX3NkIjpbIi1sOFBnZ2hLdEFmZUFwZXRDOEZGS0lfcGFlVkV4bmY4dFpJZzZCQ205TlkiLCJDSEhVb1JySFhIbXNvbHVEbEtqMWF1X3RmTEVKUmYzUUszVzFLZkVXYUlZIiwiQ1cwbVBFblFMMnhZYWxEbXBRLS11Vkg5bEM1cG1MU1JEeTdjblRBU0FfNCIsIkd5dEtxYzM0SHM2UjAtTEpMWVNYOUJVSGloZi1kbmtoYV9KM1NlQWN2M0EiLCJOZGZkeEJWY0Q4Smo5MHIyUUxFamhvMkpDTjRPWWRxeG1KcGs0S1hmVlp3IiwiZDJjNDdxZ3pGR1lDR194dFFYYVNEeEdueWpwZXFrRk16bV92MDVERjFOSSIsIm1zVW1QVEE4ZE1rRFRvam43cm5waFEzRnpjN3k4NkptT1NkX2NaWWdKQXMiXSwiYWRkcmVzcyI6eyJfc2QiOlsiQ2ZtTlY3WVNfMURod3NIUi1scWRScXAtT1NETFpVS2FBR3F3eHhMdzZTOCIsIkt0VjdoblFuNXdINVlIYXBfajhXX3BmMlJnV2x1LWZFSTd6VTNLOWo4NGsiLCJid19TVUtCWERnVDVYdE04Z1l3OFVvY05pV0JTNDN3T1lXazZGMjZQRlY0IiwiekRSTndDMkV0UUZoaWVpRmJtUEtiYy1ISU5nZnZ6SnpGSi1qUFdhOHdtMCJdfSwiaXNzdWluZ19jb3VudHJ5IjoiREUiLCJ2Y3QiOiJ1cm46ZXUuZXVyb3BhLmVjLmV1ZGk6cGlkOjEiLCJpc3N1aW5nX2F1dGhvcml0eSI6IkRFIiwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJodHRwczovL2RlbW8ucGlkLWlzc3Vlci5idW5kZXNkcnVja2VyZWkuZGUvYyIsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJUZVdfdEJIM01KM3M4RHA3NE1oMWtWdUNvWURMaGlIMW56dFcxVnVUcEpRIiwieSI6IkdtRFVXQXVibmQxbHlnUEJSX1ZvVUt0YkR6SEYtNFBma3daQjhwMnU4SUEifX0sImV4cCI6MTcyMzYxNjc1MSwiaWF0IjoxNzIyNDA3MTUxLCJhZ2VfZXF1YWxfb3Jfb3ZlciI6eyJfc2QiOlsiM1pqTUJkM1pRbThGYzFtQmwtZC1PLVFJaTA3YkpjY3lzNDlqaFlPNVJVYyIsIjY3YVN5dzJGenVaQjVJZXV6Q1V1VXNuVml5T3owUDBCckdQMzFhbGtCbjAiLCJDZkQ1MEstcHRwaXpGQnB2cE8yWFhyaVFfRzdWcGIxRDB3bGdjUHpUSlQ0IiwiSW1qU0I2Z2lhMDhZeXhRUFBfcXp5d1FFamcwU254R1ZxWlVfTl9FVFl2SSIsIlExZ1ZOSWtIYWU0ZGdmY2RoSUwwTEZIckdSX3dBZUpRT0ZwbTljbXBaREUiLCJYY290cHIzV3Q5U21jUjZDSzhtMlBPRE5zRXBDWnRRelZGT1N3UnJ5QXMwIl19fQ.xqQyzeKALOWVnmJBx7BjH8YBdwu-5H51f6dkUkXsp2BcwUDUvo-ni4NVo3cB9FKf-eoCU4e_jJIuYr5o-S003w~WyJHV3JoVGVzaDE4Y191aEgtR01ORFJ3IiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJaZGRMRzhBd0dDWTF3Sm9qWmkweEpBIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyJIWHVBd3JXaXVBT01hN0JfZ2ZVZlhnIiwiYmlydGhkYXRlIiwiMTk4NC0wMS0yNiJd~WyJFcVAtNnY5eVdZOGdKSmItMFVLMUh3IiwiYWdlX2JpcnRoX3llYXIiLDE5ODRd~WyJEMTdfUGxGdHlDVml0V3JvaTJ5bEtRIiwiYWdlX2luX3llYXJzIiw0MF0~WyJiMkp3ZjZhakN1eXoyWmxfUDd3bnZ3IiwiYmlydGhfZmFtaWx5X25hbWUiLCJHQUJMRVIiXQ~WyJBUzZNRjJrZFVBdmQ5S1p0Wnl3N1FnIiwibmF0aW9uYWxpdGllcyIsWyJERSJdXQ~WyJFWmVrOFMxMUlBNUZwVG1Iem1mTW5BIiwiMTIiLHRydWVd~WyJLcEdrWW85SzA2NThyZnVyZHJPLUJRIiwiMTQiLHRydWVd~WyJqQkRBVzdsWWRrUVNvRUV2c2hfMm1BIiwiMTYiLHRydWVd~WyItTUc3M3hwNUhnOHpBRFVaNU9lN1B3IiwiMTgiLHRydWVd~WyJiMGNwT0ZxT0lVeW53cDdma0ZoN3RRIiwiMjEiLHRydWVd~WyJZS3o1SUZPQk5mZHc4R2JhU3l1TlJ3IiwiNjUiLGZhbHNlXQ~WyJMNHY3ajc1N2poS1BPX2xtTmMxQ0dnIiwibG9jYWxpdHkiLCJCRVJMSU4iXQ~WyJIVEVzdmZpZEtBTXV2aFdFbW9DN25nIiwibG9jYWxpdHkiLCJLw5ZMTiJd~WyJfWDMtalZFMWdkWWlTNmY0RGhFU3V3IiwiY291bnRyeSIsIkRFIl0~WyI5b1huQTNBM01PWGZhbV9jdzZ5N1ZBIiwicG9zdGFsX2NvZGUiLCI1MTE0NyJd~WyJGNzNmMThYSnpWbzYtbG1tTzJoUnBnIiwic3RyZWV0X2FkZHJlc3MiLCJIRUlERVNUUkFTU0UgMTciXQ~""" - - fun getDocumentById(id: String): SdJwtDocument { - TODO() + fun getDocumentById(id: String): SdJwtDocument? { + return dataStore.get(id) } fun getAllDocuments(): List { - TODO() + return dataStore.getAll() } fun storeDocument(id: String, credential: String) { + dataStore.add(id, credential) + } +} + +data class SdJwtDocument( + val id: String, + val vct: String, + val docName: String, + val requiresUserAuth: Boolean, + val data: String, +) + +private class SdJwtDocumentDataStore { + private val documents = mutableMapOf() + fun add(id: String, credential: String) { + documents[id] = credential.toDocument(id) } - private fun makeDocument(id: String, credential: String): SdJwtDocument { - TODO() + fun get(id: String): SdJwtDocument? { + return documents[id] } - data class SdJwtDocument( - val id: String, - val data: String //Hier mehr + fun getAll(): List { + return documents.values.toList() + } + +} + +private fun String.toDocument(id: String): SdJwtDocument { + + // WIP parse values from this + val vct = "vct" + val docName = "docName" + val requiresUserAuth = false + val data = this + + return SdJwtDocument( + id = id, + vct = vct, + docName = docName, + requiresUserAuth = requiresUserAuth, + data = data, ) } \ No newline at end of file From 14adbe74d83d3b4f7dea71ba67a41f88db8db3fc Mon Sep 17 00:00:00 2001 From: Felix Moser Date: Mon, 12 Aug 2024 14:11:29 +0200 Subject: [PATCH 2/5] Setup EncryptedSharedPreferences for DocumentManagerSdJwt --- gradle/libs.versions.toml | 2 + wallet-core/build.gradle | 1 + .../eu/europa/ec/eudi/wallet/EudiWallet.kt | 1 + .../issue/openid4vci/DocumentManagerSdJwt.kt | 13 +++++- .../wallet/util/EncryptedSharedPrefsUtil.kt | 40 +++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75ed9db4..3729948c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,6 +27,7 @@ mockito-android = "4.0.0" mockito-inline = "4.0.0" mockk = "1.13.5" nimbus-sdk = "11.8" +securityCrypto = "1.1.0-alpha06" sonarqube = "4.4.1.3373" test-core = "1.4.0" test-rules = "1.4.0" @@ -62,6 +63,7 @@ mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-inline" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } nimbus-oauth2-oidc-sdk = { module = "com.nimbusds:oauth2-oidc-sdk", version.ref = "nimbus-sdk" } +security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" } test-core = { module = "androidx.test:core", version.ref = "test-core" } test-coreKtx = { module = "androidx.test:core-ktx", version.ref = "test-core" } test-rules = { module = "androidx.test:rules", version.ref = "test-rules" } diff --git a/wallet-core/build.gradle b/wallet-core/build.gradle index e6964e86..1aa017bc 100644 --- a/wallet-core/build.gradle +++ b/wallet-core/build.gradle @@ -160,6 +160,7 @@ dependencies { //sdjwt api libs.eudi.lib.jvm.sdjwt.kt + implementation libs.security.crypto testImplementation libs.junit testImplementation libs.junit.jupiter.params diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt index f43a29f5..7a9628a3 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt @@ -90,6 +90,7 @@ object EudiWallet : KeyGenerator by KeyGeneratorImpl { fun init(context: Context, config: EudiWalletConfig) { this.context = context.applicationContext this._config = config + DocumentManagerSdJwt.init(context) } /** diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt index d8e118fb..c5048edb 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt @@ -1,8 +1,15 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci +import android.content.Context +import eu.europa.ec.eudi.wallet.util.getEncryptedSharedPreferences + object DocumentManagerSdJwt { private lateinit var dataStore: SdJwtDocumentDataStore + fun init(context: Context) { + dataStore = SdJwtDocumentDataStore(context) + } + fun getDocumentById(id: String): SdJwtDocument? { return dataStore.get(id) } @@ -24,7 +31,11 @@ data class SdJwtDocument( val data: String, ) -private class SdJwtDocumentDataStore { +private class SdJwtDocumentDataStore( + context: Context, +) { + // WIP add persistence + private var sharedPreferences = getEncryptedSharedPreferences(context) private val documents = mutableMapOf() fun add(id: String, credential: String) { diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt new file mode 100644 index 00000000..0385f3a7 --- /dev/null +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt @@ -0,0 +1,40 @@ +package eu.europa.ec.eudi.wallet.util + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import javax.crypto.AEADBadTagException + +private const val PREF_FILE_NAME = "secure_prefs" + +@Throws(java.security.GeneralSecurityException::class, java.io.IOException::class) +fun getEncryptedSharedPreferences(context: Context): SharedPreferences { + val masterKey: MasterKey = MasterKey + .Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + return try { + createEncryptedSharedPreferences(context, masterKey) + } catch (e: AEADBadTagException) { + clearEncryptedSharedPreferences(context) + createEncryptedSharedPreferences(context, masterKey) + } +} + +@Throws(java.security.GeneralSecurityException::class, java.io.IOException::class) +private fun createEncryptedSharedPreferences( + context: Context, + masterKey: MasterKey, +) = EncryptedSharedPreferences.create( + context, + PREF_FILE_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM +) + +private fun clearEncryptedSharedPreferences(context: Context) { + context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE).edit().clear().apply() +} \ No newline at end of file From aadbbb04734e97c1b3f5bc0b8a4c2835aababb25 Mon Sep 17 00:00:00 2001 From: Felix Moser Date: Mon, 12 Aug 2024 15:02:54 +0200 Subject: [PATCH 3/5] Adjust SdJwtDocumentDataStore to use EncryptedSharedPreferences --- .../issue/openid4vci/DocumentManagerSdJwt.kt | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt index c5048edb..7c0a5128 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt @@ -10,17 +10,13 @@ object DocumentManagerSdJwt { dataStore = SdJwtDocumentDataStore(context) } - fun getDocumentById(id: String): SdJwtDocument? { - return dataStore.get(id) + fun storeDocument(id: String, credentials: String) { + dataStore.add(id, credentials) } - fun getAllDocuments(): List { - return dataStore.getAll() - } + fun getDocumentById(id: String) = dataStore.get(id) - fun storeDocument(id: String, credential: String) { - dataStore.add(id, credential) - } + fun getAllDocuments() = dataStore.getAll() } data class SdJwtDocument( @@ -34,22 +30,23 @@ data class SdJwtDocument( private class SdJwtDocumentDataStore( context: Context, ) { - // WIP add persistence private var sharedPreferences = getEncryptedSharedPreferences(context) - private val documents = mutableMapOf() - fun add(id: String, credential: String) { - documents[id] = credential.toDocument(id) + fun add(id: String, credentials: String) { + sharedPreferences.edit().putString(PREFIX_ID + id, credentials).apply() } - fun get(id: String): SdJwtDocument? { - return documents[id] - } + fun get(id: String) = sharedPreferences.getString(PREFIX_ID + id, null)?.toDocument(id) - fun getAll(): List { - return documents.values.toList() + fun getAll() = sharedPreferences.all.filter { + it.key.startsWith(PREFIX_ID) + }.mapNotNull { + (it.value as? String)?.toDocument(it.key) } + private companion object { + private const val PREFIX_ID = "id:" + } } private fun String.toDocument(id: String): SdJwtDocument { From 07bc27f51b9d7a4aff3c6f90f7ba43e4262695de Mon Sep 17 00:00:00 2001 From: Felix Moser Date: Mon, 12 Aug 2024 15:25:57 +0200 Subject: [PATCH 4/5] Add parsing SdJwtDocument --- .../eu/europa/ec/eudi/wallet/EudiWallet.kt | 2 +- .../issue/openid4vci/DocumentManagerSdJwt.kt | 31 ++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt index 7a9628a3..240c1ebb 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt @@ -90,7 +90,7 @@ object EudiWallet : KeyGenerator by KeyGeneratorImpl { fun init(context: Context, config: EudiWalletConfig) { this.context = context.applicationContext this._config = config - DocumentManagerSdJwt.init(context) + DocumentManagerSdJwt.init(context, config.userAuthenticationRequired) } /** diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt index 7c0a5128..4ada75a9 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt @@ -2,12 +2,15 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci import android.content.Context import eu.europa.ec.eudi.wallet.util.getEncryptedSharedPreferences +import org.json.JSONException +import org.json.JSONObject +import java.util.Base64 object DocumentManagerSdJwt { private lateinit var dataStore: SdJwtDocumentDataStore - fun init(context: Context) { - dataStore = SdJwtDocumentDataStore(context) + fun init(context: Context, requiresUserAuth: Boolean) { + dataStore = SdJwtDocumentDataStore(context, requiresUserAuth) } fun storeDocument(id: String, credentials: String) { @@ -29,6 +32,7 @@ data class SdJwtDocument( private class SdJwtDocumentDataStore( context: Context, + val requiresUserAuth: Boolean, ) { private var sharedPreferences = getEncryptedSharedPreferences(context) @@ -36,12 +40,12 @@ private class SdJwtDocumentDataStore( sharedPreferences.edit().putString(PREFIX_ID + id, credentials).apply() } - fun get(id: String) = sharedPreferences.getString(PREFIX_ID + id, null)?.toDocument(id) + fun get(id: String) = sharedPreferences.getString(PREFIX_ID + id, null)?.toDocument(id, requiresUserAuth) fun getAll() = sharedPreferences.all.filter { it.key.startsWith(PREFIX_ID) }.mapNotNull { - (it.value as? String)?.toDocument(it.key) + (it.value as? String)?.toDocument(it.key, requiresUserAuth) } private companion object { @@ -49,19 +53,24 @@ private class SdJwtDocumentDataStore( } } -private fun String.toDocument(id: String): SdJwtDocument { +private fun String.toDocument( + id: String, + requiresUserAuth: Boolean, +) = try { + val payloadString = split(".")[1] + val payloadJson = JSONObject(String(Base64.getUrlDecoder().decode(payloadString))) - // WIP parse values from this - val vct = "vct" - val docName = "docName" - val requiresUserAuth = false - val data = this + val vct = payloadJson.getString("vct") + val docName = "Personalausweis" + val data = payloadJson.toString() - return SdJwtDocument( + SdJwtDocument( id = id, vct = vct, docName = docName, requiresUserAuth = requiresUserAuth, data = data, ) +} catch (_: JSONException) { + null } \ No newline at end of file From 458b057751018f05571c21863ce9641d6ba9d76f Mon Sep 17 00:00:00 2001 From: Felix Moser Date: Mon, 12 Aug 2024 16:43:05 +0200 Subject: [PATCH 5/5] Extend EudiWallet.deleteDocumentById to also delete SdJwt documents --- .../eu/europa/ec/eudi/wallet/EudiWallet.kt | 4 ++- .../issue/openid4vci/DocumentManagerSdJwt.kt | 31 ++++++++++++++----- .../wallet/util/EncryptedSharedPrefsUtil.kt | 17 +++++----- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt index 240c1ebb..321bd5db 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt @@ -200,7 +200,9 @@ object EudiWallet : KeyGenerator by KeyGeneratorImpl { * @throws IllegalStateException if [EudiWallet] is not firstly initialized via the [init] method */ fun deleteDocumentById(documentId: DocumentId): DeleteDocumentResult = - documentManager.deleteDocumentById(documentId) + documentManager.deleteDocumentById(documentId).apply { + if (this is DeleteDocumentResult.Success) DocumentManagerSdJwt.deleteDocument(documentId) + } /** * Create an [UnsignedDocument] for the given [docType] diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt index 4ada75a9..31a6f8cb 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DocumentManagerSdJwt.kt @@ -1,6 +1,7 @@ package eu.europa.ec.eudi.wallet.issue.openid4vci import android.content.Context +import eu.europa.ec.eudi.wallet.document.DocumentId import eu.europa.ec.eudi.wallet.util.getEncryptedSharedPreferences import org.json.JSONException import org.json.JSONObject @@ -20,6 +21,16 @@ object DocumentManagerSdJwt { fun getDocumentById(id: String) = dataStore.get(id) fun getAllDocuments() = dataStore.getAll() + + fun deleteDocument(documentId: DocumentId) { + // quick but very dirty solution (we decided to only have one document at all times) + deleteAllDocuments() + } + + fun deleteAllDocuments() { + dataStore.deleteAll() + } + } data class SdJwtDocument( @@ -34,22 +45,28 @@ private class SdJwtDocumentDataStore( context: Context, val requiresUserAuth: Boolean, ) { - private var sharedPreferences = getEncryptedSharedPreferences(context) + private var sharedPreferences = getEncryptedSharedPreferences(context, PREF_FILE_NAME) fun add(id: String, credentials: String) { - sharedPreferences.edit().putString(PREFIX_ID + id, credentials).apply() + sharedPreferences.edit().putString(id, credentials).apply() } - fun get(id: String) = sharedPreferences.getString(PREFIX_ID + id, null)?.toDocument(id, requiresUserAuth) + fun get(id: String) = sharedPreferences.getString(id, null)?.toDocument(id, requiresUserAuth) - fun getAll() = sharedPreferences.all.filter { - it.key.startsWith(PREFIX_ID) - }.mapNotNull { + fun getAll() = sharedPreferences.all.mapNotNull { (it.value as? String)?.toDocument(it.key, requiresUserAuth) } + fun delete(id: String) { + sharedPreferences.edit().remove(id).apply() + } + + fun deleteAll() { + sharedPreferences.edit().clear().apply() + } + private companion object { - private const val PREFIX_ID = "id:" + private const val PREF_FILE_NAME = "document_manager_sdjwt_prefs" } } diff --git a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt index 0385f3a7..19d0625f 100644 --- a/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt +++ b/wallet-core/src/main/java/eu/europa/ec/eudi/wallet/util/EncryptedSharedPrefsUtil.kt @@ -6,20 +6,18 @@ import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import javax.crypto.AEADBadTagException -private const val PREF_FILE_NAME = "secure_prefs" - @Throws(java.security.GeneralSecurityException::class, java.io.IOException::class) -fun getEncryptedSharedPreferences(context: Context): SharedPreferences { +fun getEncryptedSharedPreferences(context: Context, name: String): SharedPreferences { val masterKey: MasterKey = MasterKey .Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() return try { - createEncryptedSharedPreferences(context, masterKey) + createEncryptedSharedPreferences(context, masterKey, name) } catch (e: AEADBadTagException) { - clearEncryptedSharedPreferences(context) - createEncryptedSharedPreferences(context, masterKey) + clearEncryptedSharedPreferences(context, name) + createEncryptedSharedPreferences(context, masterKey, name) } } @@ -27,14 +25,15 @@ fun getEncryptedSharedPreferences(context: Context): SharedPreferences { private fun createEncryptedSharedPreferences( context: Context, masterKey: MasterKey, + name: String, ) = EncryptedSharedPreferences.create( context, - PREF_FILE_NAME, + name, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) -private fun clearEncryptedSharedPreferences(context: Context) { - context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE).edit().clear().apply() +private fun clearEncryptedSharedPreferences(context: Context, name: String) { + context.getSharedPreferences(name, Context.MODE_PRIVATE).edit().clear().apply() } \ No newline at end of file