Skip to content

Commit

Permalink
marshal txCode for OpenId4VCI pre-authorized flow
Browse files Browse the repository at this point in the history
  • Loading branch information
vkanellopoulos committed Jun 27, 2024
1 parent 0f6b949 commit 7a1887e
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
# TxCodeSpec

[androidJvm]\
constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC,
constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC,
length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?,
description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
# TxCodeSpec

[androidJvm]\
data class [TxCodeSpec](index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC,
val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val
data class [TxCodeSpec](index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC, val
length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val
description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)

Specification for a transaction code.

## Constructors

| | |
|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [TxCodeSpec](-tx-code-spec.md) | [androidJvm]<br>constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = InputMode.NUMERIC, length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null) |
| | |
|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [TxCodeSpec](-tx-code-spec.md) | [androidJvm]<br>constructor(inputMode: [Offer.TxCodeSpec.InputMode](-input-mode/index.md) = NUMERIC, length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null) |

## Types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ An offer of credentials to be issued.

## Types

| Name | Summary |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [OfferedDocument](-offered-document/index.md) | [androidJvm]<br>interface [OfferedDocument](-offered-document/index.md)<br>An item to be issued. |
| [TxCodeSpec](-tx-code-spec/index.md) | [androidJvm]<br>data class [TxCodeSpec](-tx-code-spec/index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-tx-code-spec/-input-mode/index.md) = InputMode.NUMERIC, val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)<br>Specification for a transaction code. |
| Name | Summary |
|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [OfferedDocument](-offered-document/index.md) | [androidJvm]<br>interface [OfferedDocument](-offered-document/index.md)<br>An item to be issued. |
| [TxCodeSpec](-tx-code-spec/index.md) | [androidJvm]<br>data class [TxCodeSpec](-tx-code-spec/index.md)(val inputMode: [Offer.TxCodeSpec.InputMode](-tx-code-spec/-input-mode/index.md) = NUMERIC, val length: [Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html)?, val description: [String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)? = null)<br>Specification for a transaction code. |

## Properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ internal class DefaultOpenId4VciManager(
) {
offer as DefaultOffer
val issuer = issuerCreator.createIssuer(offer)
var authorizedRequest = issuerAuthorization.authorize(issuer, txCode)

var authorizedRequest = issuerAuthorization.use { it.authorize(issuer, txCode) }
listener(IssueEvent.Started(offer.offeredDocuments.size))
val issuedDocumentIds = mutableListOf<DocumentId>()
val requestMap = offer.offeredDocuments.associateBy { offeredDocument ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
package eu.europa.ec.eudi.wallet.issue.openid4vci

import androidx.biometric.BiometricPrompt.CryptoObject
import eu.europa.ec.eudi.openid4vci.SubmittedRequest
import eu.europa.ec.eudi.openid4vci.SubmissionOutcome

/**
* Exception thrown when user authentication is required.
*/
internal class UserAuthRequiredException(
val cryptoObject: CryptoObject?,
val resume: suspend (Boolean) -> SubmittedRequest
val resume: suspend (Boolean) -> SubmissionOutcome,
) : Throwable()

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import eu.europa.ec.eudi.openid4vci.AuthorizationCode
import eu.europa.ec.eudi.openid4vci.AuthorizationRequestPrepared
import eu.europa.ec.eudi.openid4vci.AuthorizedRequest
import eu.europa.ec.eudi.openid4vci.Issuer
import eu.europa.ec.eudi.openid4vci.*
import eu.europa.ec.eudi.wallet.issue.openid4vci.OpenId4VciManager.Companion.TAG
import eu.europa.ec.eudi.wallet.logging.Logger
import kotlinx.coroutines.CancellableContinuation
Expand All @@ -49,12 +46,17 @@ internal class IssuerAuthorization(
* otherwise the browser will be opened for user authorization
* @param issuer The issuer to authorize.
* @param txCode The pre-authorization code.
* @throws IllegalArgumentException if the txCode is invalid.
*/
suspend fun authorize(issuer: Issuer, txCode: String?): AuthorizedRequest {
close() // close any previous suspensions
return with(issuer) {
when {
!txCode.isNullOrEmpty() -> authorizeWithPreAuthorizationCode(txCode)
isPreAuthorized() -> {
marshalTxCode(txCode)
authorizeWithPreAuthorizationCode(txCode)
}

else -> {
val prepareAuthorizationCodeRequest = prepareAuthorizationRequest().getOrThrow()
val authResponse = openBrowserForAuthorization(prepareAuthorizationCodeRequest).getOrThrow()
Expand Down Expand Up @@ -123,4 +125,32 @@ internal class IssuerAuthorization(
}

data class Response(val authorizationCode: String, val serverState: String)

companion object {
/**
* Checks if the issuer's credential offer is pre-authorized.
* @receiver The issuer to check.
*/
fun Issuer.isPreAuthorized(): Boolean = credentialOffer.grants?.preAuthorizedCode() != null

/**
* Ensures the txCode is valid for the given issuer.
* @param txCode The pre-authorization code.
* @throws IllegalArgumentException if the txCode is invalid.
* @receiver The issuer to authorize.
*/
fun Issuer.marshalTxCode(txCode: String?) {
when (val txCodeSpec = credentialOffer.grants?.preAuthorizedCode()?.txCode) {
// no txCode in credential offer grants, so no txCode is expected for pre-authorization
null -> if (txCode != null) throw IllegalArgumentException("txCode is not required")
// txCode is present in credential offer grants, so it is required for pre-authorization
else -> when {
txCode == null -> throw IllegalArgumentException("txCode is required")
txCode.length != txCodeSpec.length -> throw IllegalArgumentException("txCode length is invalid")
txCodeSpec.inputMode == TxCodeInputMode.NUMERIC && !txCode.all { it.isDigit() } ->
throw IllegalArgumentException("txCode is not numeric")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
*/
package eu.europa.ec.eudi.wallet.issue.openid4vci

import eu.europa.ec.eudi.openid4vci.CredentialConfiguration
import eu.europa.ec.eudi.openid4vci.CredentialConfigurationIdentifier
import eu.europa.ec.eudi.openid4vci.TxCode
import eu.europa.ec.eudi.openid4vci.TxCodeInputMode
import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec.InputMode.NUMERIC
import eu.europa.ec.eudi.wallet.issue.openid4vci.Offer.TxCodeSpec.InputMode.TEXT

/**
* An offer of credentials to be issued.
Expand Down Expand Up @@ -55,7 +53,7 @@ interface Offer {
* @property description a description of the transaction code
*/
data class TxCodeSpec(
val inputMode: InputMode = InputMode.NUMERIC,
val inputMode: InputMode = NUMERIC,
val length: Int?,
val description: String? = null,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,99 +12,4 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.europa.ec.eudi.wallet.issue.openid4vci

import com.nimbusds.jose.JWSAlgorithm
import eu.europa.ec.eudi.openid4vci.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.net.URI
import java.time.Instant
import java.util.*

class DeferredStateTest {

@Test
fun `test encode and decode methods`() {
val deferredCredential = IssuedCredential.Deferred(
transactionId = TransactionId("transactionId"),
)
val credentialIdentifiers = mapOf(
CredentialConfigurationIdentifier("credentialConfigurationId") to listOf(
CredentialIdentifier("credentialId")
)
)
val authorizedRequest = AuthorizedRequest.ProofRequired(
accessToken = AccessToken(accessToken = "accessToken", expiresInSec = 3600, useDPoP = false),
refreshToken = RefreshToken("refreshToken", expiresInSec = 3600),
cNonce = CNonce(value = "cNonce"),
credentialIdentifiers = credentialIdentifiers,
timestamp = Instant.now()
)

val credentialIssuerMetadata = CredentialIssuerMetadata(
credentialIssuerIdentifier = CredentialIssuerId("https://localhost:8080").getOrThrow(),
authorizationServers = listOf(HttpsUrl("https://localhost:8080").getOrThrow()),
credentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
batchCredentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
deferredCredentialEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
notificationEndpoint = CredentialIssuerEndpoint("https://localhost:8080").getOrThrow(),
credentialResponseEncryption = CredentialResponseEncryption.NotSupported,
credentialIdentifiersSupported = true,
credentialConfigurationsSupported = mapOf(
CredentialConfigurationIdentifier("credentialConfigurationId") to MsoMdocCredential(
credentialSigningAlgorithmsSupported = listOf("alg"),
isoCredentialSigningAlgorithmsSupported = listOf(CoseAlgorithm.ES256),
isoCredentialCurvesSupported = listOf(CoseCurve.P_256),
proofTypesSupported = ProofTypesSupported(
setOf(
ProofTypeMeta.Jwt(listOf(JWSAlgorithm.ES256)),
ProofTypeMeta.Cwt(listOf(CoseAlgorithm.ES256), listOf(CoseCurve.P_256))
)
),
display = listOf(
Display(
name = "name",
locale = Locale.US,
logo = Display.Logo(URI("http://localhost:8080"), "image/png"),
textColor = "#444444"
)
),
claims = mapOf(
"namespace" to mapOf(
"claim" to Claim(
true, "claim", listOf(
Claim.Display("name", Locale.US)
)
)
)
),
order = listOf("claim"),
docType = "docType",
isoPolicy = MsoMdocPolicy(true, 10),
scope = "scope",
cryptographicBindingMethodsSupported = listOf(CryptographicBindingMethod.JWK),
)
),
display = listOf(
CredentialIssuerMetadata.Display(name = "name", locale = "el_GR")
),
)
val state = DeferredState(
clientId = "clientId",
tokenEndpoint = URI("http://localhost:8080"),
credentialIssuerMetadata = credentialIssuerMetadata,
authorizedRequest = authorizedRequest,
deferredCredential = deferredCredential
)

val encoded = state.encode()

val decoded = DeferredState.decode(encoded)

assertEquals(state, decoded)

}
}
*/
Loading

0 comments on commit 7a1887e

Please sign in to comment.