Skip to content

Commit b2774ed

Browse files
committed
Add sdJwt presentation
1 parent de32a81 commit b2774ed

File tree

16 files changed

+908
-311
lines changed

16 files changed

+908
-311
lines changed

gradle/libs.versions.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ eudi-document-manager = "0.4.1"
1212
eudi-iso18013-data-transfer = "0.2.0"
1313
eudi-lib-jvm-openid4vci-kt = "dpopNonce-SNAPSHOT"
1414
eudi-lib-jvm-siop-openid4vp-kt = "0.4.2"
15+
eudiLibJvmSdjwtKt = "0.5.1"
1516
gradle-plugin = "7.4.0"
1617
identity-credential = "20231002"
1718
identity-credential-android = "20231002"
@@ -26,6 +27,7 @@ mockito-android = "4.0.0"
2627
mockito-inline = "4.0.0"
2728
mockk = "1.13.5"
2829
nimbus-sdk = "11.8"
30+
securityCrypto = "1.1.0-alpha06"
2931
sonarqube = "4.4.1.3373"
3032
test-core = "1.4.0"
3133
test-rules = "1.4.0"
@@ -48,6 +50,7 @@ espresso-intents = { module = "androidx.test.espresso:espresso-intents", version
4850
eudi-document-manager = { module = "eu.europa.ec.eudi:eudi-lib-android-wallet-document-manager", version.ref = "eudi-document-manager" }
4951
eudi-iso18013-data-transfer = { module = "eu.europa.ec.eudi:eudi-lib-android-iso18013-data-transfer", version.ref = "eudi-iso18013-data-transfer" }
5052
eudi-lib-jvm-openid4vci-kt = { module = "com.github.TICESoftware:eudi-lib-jvm-openid4vci-kt", version.ref = "eudi-lib-jvm-openid4vci-kt" }
53+
eudi-lib-jvm-sdjwt-kt = { module = "eu.europa.ec.eudi:eudi-lib-jvm-sdjwt-kt", version.ref = "eudiLibJvmSdjwtKt" }
5154
eudi-lib-jvm-siop-openid4vp-kt = { module = "eu.europa.ec.eudi:eudi-lib-jvm-siop-openid4vp-kt", version.ref = "eudi-lib-jvm-siop-openid4vp-kt" }
5255
identity-credential = { module = "com.android.identity:identity-credential", version.ref = "identity-credential" }
5356
json = { module = "org.json:json", version.ref = "json" }
@@ -60,6 +63,7 @@ mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito
6063
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-inline" }
6164
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
6265
nimbus-oauth2-oidc-sdk = { module = "com.nimbusds:oauth2-oidc-sdk", version.ref = "nimbus-sdk" }
66+
security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
6367
test-core = { module = "androidx.test:core", version.ref = "test-core" }
6468
test-coreKtx = { module = "androidx.test:core-ktx", version.ref = "test-core" }
6569
test-rules = { module = "androidx.test:rules", version.ref = "test-rules" }

wallet-core/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ dependencies {
158158
api libs.upokecenter.cbor
159159
api libs.cose.java
160160

161+
//sdjwt
162+
api libs.eudi.lib.jvm.sdjwt.kt
163+
implementation libs.security.crypto
164+
161165
testImplementation libs.junit
162166
testImplementation libs.junit.jupiter.params
163167
testImplementation libs.json

wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ import eu.europa.ec.eudi.wallet.document.sample.SampleDocumentManager
3535
import eu.europa.ec.eudi.wallet.internal.getCertificate
3636
import eu.europa.ec.eudi.wallet.internal.mainExecutor
3737
import eu.europa.ec.eudi.wallet.issue.openid4vci.*
38+
import eu.europa.ec.eudi.wallet.keystore.KeyGenerator
39+
import eu.europa.ec.eudi.wallet.keystore.KeyGeneratorImpl
3840
import eu.europa.ec.eudi.wallet.transfer.openid4vp.OpenId4VpCBORResponse
39-
import eu.europa.ec.eudi.wallet.transfer.openid4vp.OpenId4VpCBORResponseGeneratorImpl
4041
import eu.europa.ec.eudi.wallet.transfer.openid4vp.OpenId4vpManager
42+
import eu.europa.ec.eudi.wallet.transfer.openid4vp.responseGenerator.OpenId4VpResponseGeneratorDelegator
4143
import eu.europa.ec.eudi.wallet.util.DefaultNfcEngagementService
4244
import java.security.cert.X509Certificate
4345
import java.util.concurrent.Executor
@@ -68,7 +70,7 @@ import java.util.concurrent.Executor
6870
*
6971
*/
7072
@SuppressLint("StaticFieldLeak")
71-
object EudiWallet {
73+
object EudiWallet : KeyGenerator by KeyGeneratorImpl {
7274

7375
@Volatile
7476
private lateinit var context: Context
@@ -88,6 +90,7 @@ object EudiWallet {
8890
fun init(context: Context, config: EudiWalletConfig) {
8991
this.context = context.applicationContext
9092
this._config = config
93+
DocumentManagerSdJwt.init(context, config.userAuthenticationRequired)
9194
}
9295

9396
/**
@@ -197,7 +200,9 @@ object EudiWallet {
197200
* @throws IllegalStateException if [EudiWallet] is not firstly initialized via the [init] method
198201
*/
199202
fun deleteDocumentById(documentId: DocumentId): DeleteDocumentResult =
200-
documentManager.deleteDocumentById(documentId)
203+
documentManager.deleteDocumentById(documentId).apply {
204+
if (this is DeleteDocumentResult.Success) DocumentManagerSdJwt.deleteDocument(documentId)
205+
}
201206

202207
/**
203208
* Create an [UnsignedDocument] for the given [docType]
@@ -224,7 +229,10 @@ object EudiWallet {
224229
* @return [StoreDocumentResult]
225230
* @throws IllegalStateException if [EudiWallet] is not firstly initialized via the [init] method
226231
*/
227-
fun storeIssuedDocument(unsignedDocument: UnsignedDocument, data: ByteArray): StoreDocumentResult =
232+
fun storeIssuedDocument(
233+
unsignedDocument: UnsignedDocument,
234+
data: ByteArray
235+
): StoreDocumentResult =
228236
documentManager.storeIssuedDocument(unsignedDocument, data)
229237

230238
private var openId4VciManager: OpenId4VciManager? = null
@@ -280,7 +288,15 @@ object EudiWallet {
280288
config(config)
281289
logger = this@EudiWallet.logger
282290
ktorHttpClientFactory = _config.ktorHttpClientFactory
283-
}.also { it.issueDocumentByDocType(docType, txCode, executor, authorizationHandler, onEvent) }
291+
}.also {
292+
it.issueDocumentByDocType(
293+
docType,
294+
txCode,
295+
executor,
296+
authorizationHandler,
297+
onEvent
298+
)
299+
}
284300
} ?: run {
285301
(executor ?: context.mainExecutor()).execute {
286302
onEvent(IssueEvent.failure(IllegalStateException("OpenId4Vci config is not set in configuration")))
@@ -316,7 +332,15 @@ object EudiWallet {
316332
config(config)
317333
logger = this@EudiWallet.logger
318334
ktorHttpClientFactory = _config.ktorHttpClientFactory
319-
}.also { it.issueDocumentByOffer(offer, txCode, executor, authorizationHandler, onEvent) }
335+
}.also {
336+
it.issueDocumentByOffer(
337+
offer,
338+
txCode,
339+
executor,
340+
authorizationHandler,
341+
onEvent
342+
)
343+
}
320344
} ?: run {
321345
(executor ?: context.mainExecutor()).execute {
322346
onEvent(IssueEvent.failure(IllegalStateException("OpenId4Vci config is not set in configuration")))
@@ -351,7 +375,15 @@ object EudiWallet {
351375
config(config)
352376
logger = this@EudiWallet.logger
353377
ktorHttpClientFactory = _config.ktorHttpClientFactory
354-
}.also { it.issueDocumentByOfferUri(offerUri, txCode, executor, authorizationHandler, onEvent) }
378+
}.also {
379+
it.issueDocumentByOfferUri(
380+
offerUri,
381+
txCode,
382+
executor,
383+
authorizationHandler,
384+
onEvent
385+
)
386+
}
355387
} ?: run {
356388
(executor ?: context.mainExecutor()).execute {
357389
onEvent(IssueEvent.failure(IllegalStateException("OpenId4Vci config is not set in configuration")))
@@ -383,7 +415,12 @@ object EudiWallet {
383415
ktorHttpClientFactory = _config.ktorHttpClientFactory
384416
}.also {
385417
when (val document = documentManager.getDocumentById(documentId)) {
386-
is DeferredDocument -> it.issueDeferredDocument(document, executor, onResult)
418+
is DeferredDocument -> it.issueDeferredDocument(
419+
document,
420+
executor,
421+
onResult
422+
)
423+
387424
else -> (executor ?: context.mainExecutor()).execute {
388425
onResult(
389426
DeferredIssueResult.DocumentFailed(
@@ -474,8 +511,16 @@ object EudiWallet {
474511
* @return [EudiWallet]
475512
*/
476513
fun setTrustedReaderCertificates(trustedReaderCertificates: List<X509Certificate>) = apply {
477-
deviceResponseGenerator.setReaderTrustStore(ReaderTrustStore.getDefault(trustedReaderCertificates))
478-
openId4VpCBORResponseGenerator.setReaderTrustStore(ReaderTrustStore.getDefault(trustedReaderCertificates))
514+
deviceResponseGenerator.setReaderTrustStore(
515+
ReaderTrustStore.getDefault(
516+
trustedReaderCertificates
517+
)
518+
)
519+
openId4VpCBORResponseGenerator.setReaderTrustStore(
520+
ReaderTrustStore.getDefault(
521+
trustedReaderCertificates
522+
)
523+
)
479524
}
480525

481526
/**
@@ -630,9 +675,10 @@ object EudiWallet {
630675
// create response
631676
val responseResult = when (transferMode) {
632677
TransferMode.OPENID4VP ->
633-
openId4vpManager?.responseGenerator?.createResponse(disclosedDocuments) ?: ResponseResult.Failure(
634-
Throwable("Openid4vpManager has not been initialized properly")
635-
)
678+
openId4vpManager?.responseGenerator?.createResponse(disclosedDocuments)
679+
?: ResponseResult.Failure(
680+
Throwable("Openid4vpManager has not been initialized properly")
681+
)
636682

637683
TransferMode.ISO_18013_5, TransferMode.REST_API ->
638684
transferManager.responseGenerator.createResponse(disclosedDocuments)
@@ -645,7 +691,12 @@ object EudiWallet {
645691
is ResponseResult.Success -> {
646692
when (transferMode) {
647693
TransferMode.OPENID4VP ->
648-
openId4vpManager?.sendResponse((responseResult.response as OpenId4VpCBORResponse).deviceResponseBytes)
694+
openId4vpManager?.sendResponse(
695+
when(val result = responseResult.response){
696+
is OpenId4VpCBORResponse -> result.deviceResponseBytes
697+
is DeviceResponse -> result.deviceResponseBytes
698+
else -> throw Exception()
699+
})
649700

650701
TransferMode.ISO_18013_5, TransferMode.REST_API ->
651702
transferManager.sendResponse((responseResult.response as DeviceResponse).deviceResponseBytes)
@@ -695,9 +746,23 @@ object EudiWallet {
695746

696747
private val transferManagerDocumentsResolver: DocumentsResolver
697748
get() = DocumentsResolver { req ->
698-
documentManager.getDocuments(Document.State.ISSUED)
749+
750+
DocumentManagerSdJwt
751+
.getAllDocuments()
752+
// .filter { doc -> doc.vct == req.docType }
753+
.map { doc ->
754+
RequestDocument(
755+
documentId = doc.id,
756+
docType = doc.vct,
757+
docName = doc.docName,
758+
userAuthentication = doc.requiresUserAuth,
759+
docRequest = req
760+
)
761+
}.takeIf { it.isNotEmpty() }?.let { return@DocumentsResolver it }
762+
763+
return@DocumentsResolver documentManager.getDocuments(Document.State.ISSUED)
699764
.filterIsInstance<IssuedDocument>()
700-
.filter { doc -> doc.docType == req.docType }
765+
// .filter { doc -> doc.docType == req.docType }
701766
.map { doc ->
702767
RequestDocument(
703768
documentId = doc.id,
@@ -721,9 +786,9 @@ object EudiWallet {
721786
}
722787
}
723788

724-
private val openId4VpCBORResponseGenerator: OpenId4VpCBORResponseGeneratorImpl by lazy {
789+
private val openId4VpCBORResponseGenerator: OpenId4VpResponseGeneratorDelegator by lazy {
725790
requireInit {
726-
OpenId4VpCBORResponseGeneratorImpl.Builder(context)
791+
OpenId4VpResponseGeneratorDelegator.Builder(context)
727792
.apply {
728793
_config.trustedReaderCertificates?.let {
729794
readerTrustStore = ReaderTrustStore.getDefault(it)

wallet-core/src/main/java/eu/europa/ec/eudi/wallet/issue/openid4vci/DefaultOffer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import eu.europa.ec.eudi.openid4vci.*
3030
*/
3131
internal data class DefaultOffer(
3232
@JvmSynthetic val credentialOffer: CredentialOffer,
33-
@JvmSynthetic val credentialConfigurationFilter: CredentialConfigurationFilter = CredentialConfigurationFilter.MsoMdocFormatFilter,
33+
@JvmSynthetic val credentialConfigurationFilter: CredentialConfigurationFilter = CredentialConfigurationFilter.SdJwtOrMsoMdocFormatFilter,
3434
) : Offer {
3535

3636
private val issuerMetadata: CredentialIssuerMetadata
@@ -41,8 +41,8 @@ internal data class DefaultOffer(
4141

4242
override val offeredDocuments: List<Offer.OfferedDocument>
4343
get() = issuerMetadata.credentialConfigurationsSupported
44-
.filterKeys { it in credentialOffer.credentialConfigurationIdentifiers }
45-
.filterValues { credentialConfigurationFilter(it) }
44+
//.filterKeys { it in credentialOffer.credentialConfigurationIdentifiers }
45+
//.filterValues { credentialConfigurationFilter(it) }
4646
.map { (id, conf) -> DefaultOfferedDocument(id, conf) }
4747

4848
override val txCodeSpec: Offer.TxCodeSpec?
@@ -92,7 +92,7 @@ internal val CredentialConfiguration.name: String
9292
internal val CredentialConfiguration.docType: String
9393
@JvmSynthetic get() = when (this) {
9494
is MsoMdocCredential -> docType
95-
is SdJwtVcCredential -> "eu.europa.ec.eudi.pid_jwt_vc_json"
95+
is SdJwtVcCredential -> "eu.europa.ec.eudi.pid.1"
9696
else -> "unknown"
9797
}
9898

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package eu.europa.ec.eudi.wallet.issue.openid4vci
2+
3+
import android.content.Context
4+
import eu.europa.ec.eudi.wallet.document.DocumentId
5+
import eu.europa.ec.eudi.wallet.util.getEncryptedSharedPreferences
6+
import org.json.JSONException
7+
import org.json.JSONObject
8+
import java.util.Base64
9+
10+
object DocumentManagerSdJwt {
11+
private lateinit var dataStore: SdJwtDocumentDataStore
12+
13+
fun init(context: Context, requiresUserAuth: Boolean) {
14+
dataStore = SdJwtDocumentDataStore(context, requiresUserAuth)
15+
}
16+
17+
fun storeDocument(id: String, credentials: String) {
18+
dataStore.add(id, credentials)
19+
}
20+
21+
fun getDocumentById(id: String) = dataStore.get(id)
22+
23+
fun getAllDocuments() = dataStore.getAll()
24+
25+
fun deleteDocument(documentId: DocumentId) {
26+
// quick but very dirty solution (we decided to only have one document at all times)
27+
deleteAllDocuments()
28+
}
29+
30+
fun deleteAllDocuments() {
31+
dataStore.deleteAll()
32+
}
33+
34+
}
35+
36+
data class SdJwtDocument(
37+
val id: String,
38+
val vct: String,
39+
val docName: String,
40+
val requiresUserAuth: Boolean,
41+
val data: String,
42+
)
43+
44+
private class SdJwtDocumentDataStore(
45+
context: Context,
46+
val requiresUserAuth: Boolean,
47+
) {
48+
private var sharedPreferences = getEncryptedSharedPreferences(context, PREF_FILE_NAME)
49+
50+
fun add(id: String, credentials: String) {
51+
sharedPreferences.edit().putString(id, credentials).apply()
52+
}
53+
54+
fun get(id: String) = sharedPreferences.getString(id, null)?.toDocument(id, requiresUserAuth)
55+
56+
fun getAll() = sharedPreferences.all.mapNotNull {
57+
(it.value as? String)?.toDocument(it.key, requiresUserAuth)
58+
}
59+
60+
fun delete(id: String) {
61+
sharedPreferences.edit().remove(id).apply()
62+
}
63+
64+
fun deleteAll() {
65+
sharedPreferences.edit().clear().apply()
66+
}
67+
68+
private companion object {
69+
private const val PREF_FILE_NAME = "document_manager_sdjwt_prefs"
70+
}
71+
}
72+
73+
private fun String.toDocument(
74+
id: String,
75+
requiresUserAuth: Boolean,
76+
) = try {
77+
val payloadString = split(".")[1]
78+
val payloadJson = JSONObject(String(Base64.getUrlDecoder().decode(payloadString)))
79+
80+
val vct = payloadJson.getString("vct")
81+
val docName = "Personalausweis"
82+
val data = payloadJson.toString()
83+
84+
SdJwtDocument(
85+
id = id,
86+
vct = vct,
87+
docName = docName,
88+
requiresUserAuth = requiresUserAuth,
89+
data = this,
90+
)
91+
} catch (_: JSONException) {
92+
null
93+
}

0 commit comments

Comments
 (0)