Skip to content

Implement DocumentManagerSdJwt #3

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions wallet-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, config.userAuthenticationRequired)
}

/**
Expand Down Expand Up @@ -199,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]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,93 @@
package eu.europa.ec.eudi.wallet.issue.openid4vci

class DocumentManagerSdJwt {
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
import java.util.Base64

private val documents: Map<String, String> = mapOf() //SHARED PREFS
object DocumentManagerSdJwt {
private lateinit var dataStore: SdJwtDocumentDataStore

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 init(context: Context, requiresUserAuth: Boolean) {
dataStore = SdJwtDocumentDataStore(context, requiresUserAuth)
}

fun storeDocument(id: String, credentials: String) {
dataStore.add(id, credentials)
}

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(
val id: String,
val vct: String,
val docName: String,
val requiresUserAuth: Boolean,
val data: String,
)

private class SdJwtDocumentDataStore(
context: Context,
val requiresUserAuth: Boolean,
) {
private var sharedPreferences = getEncryptedSharedPreferences(context, PREF_FILE_NAME)

fun getDocumentById(id: String): SdJwtDocument {
TODO()
fun add(id: String, credentials: String) {
sharedPreferences.edit().putString(id, credentials).apply()
}

fun getAllDocuments(): List<SdJwtDocument> {
TODO()
fun get(id: String) = sharedPreferences.getString(id, null)?.toDocument(id, requiresUserAuth)

fun getAll() = sharedPreferences.all.mapNotNull {
(it.value as? String)?.toDocument(it.key, requiresUserAuth)
}

fun storeDocument(id: String, credential: String) {
fun delete(id: String) {
sharedPreferences.edit().remove(id).apply()
}

fun deleteAll() {
sharedPreferences.edit().clear().apply()
}

private fun makeDocument(id: String, credential: String): SdJwtDocument {
TODO()
private companion object {
private const val PREF_FILE_NAME = "document_manager_sdjwt_prefs"
}
}

private fun String.toDocument(
id: String,
requiresUserAuth: Boolean,
) = try {
val payloadString = split(".")[1]
val payloadJson = JSONObject(String(Base64.getUrlDecoder().decode(payloadString)))

val vct = payloadJson.getString("vct")
val docName = "Personalausweis"
val data = payloadJson.toString()

data class SdJwtDocument(
val id: String,
val data: String //Hier mehr
SdJwtDocument(
id = id,
vct = vct,
docName = docName,
requiresUserAuth = requiresUserAuth,
data = data,
)
} catch (_: JSONException) {
null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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

@Throws(java.security.GeneralSecurityException::class, java.io.IOException::class)
fun getEncryptedSharedPreferences(context: Context, name: String): SharedPreferences {
val masterKey: MasterKey = MasterKey
.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

return try {
createEncryptedSharedPreferences(context, masterKey, name)
} catch (e: AEADBadTagException) {
clearEncryptedSharedPreferences(context, name)
createEncryptedSharedPreferences(context, masterKey, name)
}
}

@Throws(java.security.GeneralSecurityException::class, java.io.IOException::class)
private fun createEncryptedSharedPreferences(
context: Context,
masterKey: MasterKey,
name: String,
) = EncryptedSharedPreferences.create(
context,
name,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

private fun clearEncryptedSharedPreferences(context: Context, name: String) {
context.getSharedPreferences(name, Context.MODE_PRIVATE).edit().clear().apply()
}
Loading