@@ -12,7 +12,6 @@ import at.asitplus.signum.indispensable.CryptoPublicKey
1212import at.asitplus.signum.indispensable.cosef.CoseSigned
1313import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper
1414import at.asitplus.signum.indispensable.cosef.io.coseCompliantSerializer
15- import at.asitplus.signum.indispensable.io.Base64Strict
1615import at.asitplus.signum.indispensable.io.Base64UrlStrict
1716import at.asitplus.signum.indispensable.josef.JsonWebKey
1817import at.asitplus.signum.indispensable.josef.JwsSigned
@@ -24,13 +23,16 @@ import at.asitplus.wallet.lib.data.DeprecatedBase64URLTransactionDataSerializer
2423import at.asitplus.wallet.lib.data.dif.PresentationSubmissionValidator
2524import at.asitplus.wallet.lib.data.vckJsonSerializer
2625import at.asitplus.wallet.lib.iso.*
26+ import at.asitplus.wallet.lib.iso.DeviceSignedItemList
27+ import at.asitplus.wallet.lib.iso.wrapInCborTag
2728import at.asitplus.wallet.lib.jws.JwsService
2829import at.asitplus.wallet.lib.oidvci.OAuth2Exception
2930import at.asitplus.wallet.lib.oidvci.OAuth2Exception.*
3031import io.github.aakira.napier.Napier
3132import io.matthewnelson.encoding.base16.Base16
3233import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
3334import kotlinx.datetime.Clock
35+ import kotlinx.serialization.Contextual
3436import kotlinx.serialization.PolymorphicSerializer
3537import kotlinx.serialization.builtins.ByteArraySerializer
3638import kotlinx.serialization.encodeToByteArray
@@ -64,7 +66,12 @@ internal class PresentationFactory(
6466 audience = audience,
6567 transactionData = transactionData,
6668 calcIsoDeviceSignature = { docType, mdocGenNonce ->
67- reuseMdocGeneratedNonce(mdocGenNonce, clientId, responseUrl, nonce, docType, responseWillBeEncrypted)
69+ calcDeviceSignature(mdocGenNonce, clientId, responseUrl, nonce, docType)
70+ },
71+ provideMdocGeneratedNonce = {
72+ if (clientId != null && responseUrl != null ) {
73+ if (responseWillBeEncrypted) Random .nextBytes(16 ).encodeToString(Base64UrlStrict ) else " "
74+ } else null
6875 }
6976 )
7077
@@ -78,26 +85,13 @@ internal class PresentationFactory(
7885 clientMetadata?.vpFormats?.let {
7986 when (presentation) {
8087 is PresentationResponseParameters .DCQLParameters -> presentation.verifyFormatSupport(it)
81-
82- is PresentationResponseParameters .PresentationExchangeParameters -> {
88+ is PresentationResponseParameters .PresentationExchangeParameters ->
8389 presentation.verifyFormatSupport(it)
84- }
8590 }
8691 }
8792 }
8893 }
8994
90- private suspend fun PresentationFactory.reuseMdocGeneratedNonce (
91- mdocGeneratedNonce : String? ,
92- clientId : String? ,
93- responseUrl : String? ,
94- nonce : String ,
95- docType : String ,
96- responseWillBeEncrypted : Boolean
97- ) = mdocGeneratedNonce?.let {
98- calcDeviceSignatureWithNonce(mdocGeneratedNonce, clientId!! , responseUrl!! , nonce, docType)
99- } ? : calcDeviceSignature(responseWillBeEncrypted, clientId, responseUrl, nonce, docType)
100-
10195 /* *
10296 * Parses all `transaction_data` fields from the request, with a JsonPath, because
10397 * ... for OpenID4VP Draft 23, that's encoded in the AuthnRequest
@@ -120,20 +114,38 @@ internal class PresentationFactory(
120114
121115 /* *
122116 * Performs calculation of the [at.asitplus.wallet.lib.iso.SessionTranscript] and [at.asitplus.wallet.lib.iso.DeviceAuthentication],
123- * acc. to ISO/IEC 18013-5:2021 and ISO/IEC 18013-7:2024, if required in [responseWillBeEncrypted] (i.e. it will be encrypted)
117+ * acc. to ISO/IEC 18013-5:2021 and ISO/IEC 18013-7:2024, with the [mdocGeneratedNonce] provided if set,
118+ * or a fallback mechanism used otherwise
124119 */
125120 @Throws(PresentationException ::class , CancellationException ::class )
126121 private suspend fun calcDeviceSignature (
127- responseWillBeEncrypted : Boolean ,
122+ mdocGeneratedNonce : String? ,
128123 clientId : String? ,
129124 responseUrl : String? ,
130125 nonce : String ,
131126 docType : String ,
132- ): Pair <CoseSigned <ByteArray >, String?> = if (clientId != null && responseUrl != null ) {
133- // if it's not encrypted, we have no way of transporting the mdocGeneratedNonce, so we'll use the empty string
134- val mdocGeneratedNonce = if (responseWillBeEncrypted)
135- Random .Default .nextBytes(16 ).encodeToString(Base64UrlStrict ) else " "
136- calcDeviceSignatureWithNonce(mdocGeneratedNonce, clientId, responseUrl, nonce, docType)
127+ ): CoseSigned <ByteArray > = if (mdocGeneratedNonce != null && clientId != null && responseUrl != null ) {
128+ run {
129+ val deviceAuthentication = DeviceAuthentication (
130+ type = " DeviceAuthentication" ,
131+ sessionTranscript = calcSessionTranscript(mdocGeneratedNonce, clientId, responseUrl, nonce),
132+ docType = docType,
133+ namespaces = ByteStringWrapper (DeviceNameSpaces (mapOf<String , @Contextual DeviceSignedItemList >()))
134+ )
135+ val deviceAuthenticationBytes = coseCompliantSerializer
136+ .encodeToByteArray(ByteStringWrapper (deviceAuthentication))
137+ .wrapInCborTag(24 )
138+ .also { Napier .d(" Device authentication signature input is ${it.encodeToString(Base16 ())} " ) }
139+
140+ coseService.createSignedCoseWithDetachedPayload(
141+ payload = deviceAuthenticationBytes,
142+ serializer = ByteArraySerializer (),
143+ addKeyId = false
144+ ).getOrElse {
145+ Napier .w(" Could not create DeviceAuth for presentation" , it)
146+ throw PresentationException (it)
147+ }
148+ }
137149 } else {
138150 coseService.createSignedCose(
139151 payload = nonce.encodeToByteArray(),
@@ -142,23 +154,16 @@ internal class PresentationFactory(
142154 ).getOrElse {
143155 Napier .w(" Could not create DeviceAuth for presentation" , it)
144156 throw PresentationException (it)
145- } to null
157+ }
146158 }
147159
148160
149- /* *
150- * Performs calculation of the [at.asitplus.wallet.lib.iso.SessionTranscript] and [at.asitplus.wallet.lib.iso.DeviceAuthentication],
151- * acc. to ISO/IEC 18013-5:2021 and ISO/IEC 18013-7:2024, with the [mdocGeneratedNonce] provided.
152- */
153- @Throws(PresentationException ::class , CancellationException ::class )
154- private suspend fun calcDeviceSignatureWithNonce (
161+ private fun calcSessionTranscript (
155162 mdocGeneratedNonce : String ,
156163 clientId : String ,
157164 responseUrl : String ,
158165 nonce : String ,
159- docType : String ,
160- ): Pair <CoseSigned <ByteArray >, String?> = run {
161- val deviceNameSpaceBytes = ByteStringWrapper (DeviceNameSpaces (mapOf ()))
166+ ): SessionTranscript {
162167 val clientIdToHash = ClientIdToHash (
163168 clientId = clientId,
164169 mdocGeneratedNonce = mdocGeneratedNonce
@@ -176,25 +181,7 @@ internal class PresentationFactory(
176181 nonce = nonce
177182 ),
178183 )
179- val deviceAuthentication = DeviceAuthentication (
180- type = " DeviceAuthentication" ,
181- sessionTranscript = sessionTranscript,
182- docType = docType,
183- namespaces = deviceNameSpaceBytes
184- )
185- val deviceAuthenticationBytes = coseCompliantSerializer
186- .encodeToByteArray(ByteStringWrapper (deviceAuthentication))
187- .wrapInCborTag(24 )
188- .also { Napier .d(" Device authentication signature input is ${it.encodeToString(Base16 ())} " ) }
189-
190- coseService.createSignedCoseWithDetachedPayload(
191- payload = deviceAuthenticationBytes,
192- serializer = ByteArraySerializer (),
193- addKeyId = false
194- ).getOrElse {
195- Napier .w(" Could not create DeviceAuth for presentation" , it)
196- throw PresentationException (it)
197- } to mdocGeneratedNonce
184+ return sessionTranscript
198185 }
199186
200187 suspend fun <T : RequestParameters > createSignedIdToken (
0 commit comments