Skip to content
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

Fix wording for OpenID4VP #193

Closed
wants to merge 9 commits into from
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ Release 5.3.0:
- In `Verifier` and `VerifierAgent` add methods `verifyPresentationVcJwt()`, `verifyPresentationSdJwt()` and `verifyPresentationIsoMdoc()` to directly verify typed objects
- For verification of credentials and presentations add `ValidationError` cases to sealed classes
- In `OidcSiopVerifier` replace `stateToNonceStore` and `stateToResponseTypeStore` with `stateToAuthnRequestStore`
- OpenID4VP in general:
- Deprecate `OidcSiopVerifier`, use `at.asitplus.wallet.lib.openid.OpenId4VpVerifier` instead
- Move classes `ClientIdScheme`, `RequestOptions`, `AuthResponseResult` out of `OpenId4VpVerifier`
- Change type of `RequestOptionsCredential.requestedAttributes` from `List` to `Set`
- Change type of `RequestOptionsCredential.requestedOptionalAttributes` from `List` to `Set`
- Deprecate `OidcSiopWallet`, use `at.asitplus.wallet.lib.openid.OpenId4VpHolder` instead
- Move `RequestObjectJwsVerifier` from `at.asitplus.wallet.lib.oidc` to `at.asitplus.wallet.lib.openid`
- Move `RemoteResourceRetrieverFunction` from `at.asitplus.wallet.lib.oidc` to `at.asitplus.wallet.lib`
- Move `AuthorizationResponsePreparationState` from `at.asitplus.wallet.lib.oidc.helpers` to `at.asitplus.wallet.lib.openid`
- General cleanup:
- Remove `SchemaIndex`

Release 5.2.2:
- Remote qualified electronic signatures:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import at.asitplus.wallet.lib.agent.HolderAgent
import at.asitplus.wallet.lib.cbor.DefaultCoseService
import at.asitplus.wallet.lib.data.vckJsonSerializer
import at.asitplus.wallet.lib.jws.DefaultJwsService
import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult
import at.asitplus.wallet.lib.oidc.OidcSiopWallet
import at.asitplus.wallet.lib.oidc.helpers.AuthorizationResponsePreparationState
import at.asitplus.wallet.lib.openid.AuthorizationResponsePreparationState
import at.asitplus.wallet.lib.openid.AuthenticationResponseResult
import at.asitplus.wallet.lib.openid.OpenId4VpHolder
import io.github.aakira.napier.Napier
import io.ktor.client.*
import io.ktor.client.call.*
Expand Down Expand Up @@ -62,7 +62,7 @@ class OpenId4VpWallet(
}
httpClientConfig?.let { apply(it) }
}
val oidcSiopWallet = OidcSiopWallet(
val openId4VpHolder = OpenId4VpHolder(
holder = holderAgent,
agentPublicKey = cryptoService.keyMaterial.publicKey,
jwsService = DefaultJwsService(cryptoService),
Expand All @@ -76,20 +76,20 @@ class OpenId4VpWallet(
)

suspend fun parseAuthenticationRequestParameters(input: String): KmmResult<RequestParametersFrom<AuthenticationRequestParameters>> =
oidcSiopWallet.parseAuthenticationRequestParameters(input)
openId4VpHolder.parseAuthenticationRequestParameters(input)

suspend fun startAuthorizationResponsePreparation(
request: RequestParametersFrom<AuthenticationRequestParameters>,
): KmmResult<AuthorizationResponsePreparationState> =
oidcSiopWallet.startAuthorizationResponsePreparation(request)
openId4VpHolder.startAuthorizationResponsePreparation(request)

suspend fun startAuthorizationResponsePreparation(
input: String,
): KmmResult<AuthorizationResponsePreparationState> =
oidcSiopWallet.startAuthorizationResponsePreparation(input)
openId4VpHolder.startAuthorizationResponsePreparation(input)

/**
* Calls [oidcSiopWallet] to create the authentication response.
* Calls [openId4VpHolder] to create the authentication response.
* In case the result shall be POSTed to the verifier, we call [client] to do that,
* and optionally [openUrlExternally] with the `redirect_uri` of that POST.
* In case the result shall be sent as a redirect to the verifier, we call [openUrlExternally].
Expand All @@ -98,7 +98,7 @@ class OpenId4VpWallet(
request: RequestParametersFrom<AuthenticationRequestParameters>,
): KmmResult<Unit> = catching {
Napier.i("startPresentation: $request")
oidcSiopWallet.createAuthnResponse(request).getOrThrow().let {
openId4VpHolder.createAuthnResponse(request).getOrThrow().let {
when (it) {
is AuthenticationResponseResult.Post -> postResponse(it)
is AuthenticationResponseResult.Redirect -> redirectResponse(it)
Expand All @@ -107,7 +107,7 @@ class OpenId4VpWallet(
}

/**
* Calls [oidcSiopWallet] to finalize the authentication response.
* Calls [openId4VpHolder] to finalize the authentication response.
* In case the result shall be POSTed to the verifier, we call [client] to do that,
* and optionally [openUrlExternally] with the `redirect_uri` of that POST.
* In case the result shall be sent as a redirect to the verifier, we call [openUrlExternally].
Expand All @@ -118,7 +118,7 @@ class OpenId4VpWallet(
inputDescriptorSubmission: Map<String, CredentialSubmission>,
): KmmResult<Unit> = catching {
Napier.i("startPresentation: $request")
oidcSiopWallet.finalizeAuthorizationResponse(
openId4VpHolder.finalizeAuthorizationResponse(
request = request,
preparationState = preparationState,
inputDescriptorSubmissions = inputDescriptorSubmission
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package at.asitplus.wallet.lib.ktor.openid

import at.asitplus.openid.AuthenticationResponseParameters
import at.asitplus.openid.OpenIdConstants.ResponseMode
import at.asitplus.wallet.eupid.EuPidScheme
import at.asitplus.wallet.lib.agent.*
Expand All @@ -9,11 +8,9 @@ import at.asitplus.wallet.lib.data.ConstantIndex.CredentialRepresentation.ISO_MD
import at.asitplus.wallet.lib.data.ConstantIndex.CredentialRepresentation.SD_JWT
import at.asitplus.wallet.lib.data.SelectiveDisclosureItem
import at.asitplus.wallet.lib.iso.IssuerSignedItem
import at.asitplus.wallet.lib.oidc.OidcSiopVerifier
import at.asitplus.wallet.lib.oidc.OidcSiopVerifier.AuthnResponseResult.SuccessIso
import at.asitplus.wallet.lib.oidc.OidcSiopVerifier.AuthnResponseResult.SuccessSdJwt
import at.asitplus.wallet.lib.oidvci.decodeFromPostBody
import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery
import at.asitplus.wallet.lib.openid.*
import at.asitplus.wallet.lib.openid.AuthnResponseResult.SuccessIso
import at.asitplus.wallet.lib.openid.AuthnResponseResult.SuccessSdJwt
import com.benasher44.uuid.uuid4
import io.github.aakira.napier.Napier
import io.kotest.core.spec.style.FunSpec
Expand Down Expand Up @@ -106,12 +103,12 @@ class OpenId4VpWalletTest : FunSpec() {
responseMode: ResponseMode,
clientId: String,
): Pair<OpenId4VpWallet, String> {
val requestOptions = OidcSiopVerifier.RequestOptions(
val requestOptions = RequestOptions(
credentials = setOf(
OidcSiopVerifier.RequestOptionsCredential(
RequestOptionsCredential(
credentialScheme = scheme,
representation = representation,
requestedAttributes = attributes.keys.toList()
requestedAttributes = attributes.keys
)
),
responseMode = responseMode,
Expand Down Expand Up @@ -169,13 +166,13 @@ class OpenId4VpWalletTest : FunSpec() {
private fun Map.Entry<String, String>.toIssuerSignedItem(): IssuerSignedItem =
IssuerSignedItem(0U, Random.nextBytes(16), key, value)

private fun OidcSiopVerifier.AuthnResponseResult.verifyReceivedAttributes(expectedAttributes: Map<String, String>) {
private fun AuthnResponseResult.verifyReceivedAttributes(expectedAttributes: Map<String, String>) {
if (this.containsAllAttributes(expectedAttributes)) {
countdownLatch.unlock()
}
}

private fun OidcSiopVerifier.AuthnResponseResult.containsAllAttributes(expectedAttributes: Map<String, String>): Boolean =
private fun AuthnResponseResult.containsAllAttributes(expectedAttributes: Map<String, String>): Boolean =
when (this) {
is SuccessSdJwt -> this.containsAllAttributes(expectedAttributes)
is SuccessIso -> this.containsAllAttributes(expectedAttributes)
Expand Down Expand Up @@ -215,12 +212,12 @@ class OpenId4VpWalletTest : FunSpec() {
*/
private suspend fun setupRelyingPartyService(
clientId: String,
requestOptions: OidcSiopVerifier.RequestOptions,
validate: (OidcSiopVerifier.AuthnResponseResult) -> Unit,
requestOptions: RequestOptions,
validate: (AuthnResponseResult) -> Unit,
): Pair<HttpClientEngine, String> {
val requestEndpointPath = "/request/${uuid4()}"
val verifier = OidcSiopVerifier(
clientIdScheme = OidcSiopVerifier.ClientIdScheme.PreRegistered(clientId),
val verifier = OpenId4VpVerifier(
clientIdScheme = ClientIdScheme.PreRegistered(clientId),
)
val responseEndpointPath = "/response"
val (url, jar) = verifier.createAuthnRequestUrlWithRequestObjectByReference(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package at.asitplus.wallet.lib

/**
* Implementations need to fetch the url passed in, and return either the body, if there is one,
* or the HTTP header `Location`, i.e. if the server sends the request object as a redirect.
*/
typealias RemoteResourceRetrieverFunction = suspend (String) -> String?
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import at.asitplus.openid.*
import at.asitplus.openid.OpenIdConstants.Errors
import at.asitplus.signum.indispensable.io.Base64UrlStrict
import at.asitplus.wallet.lib.iso.sha256
import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult
import at.asitplus.wallet.lib.openid.AuthenticationResponseResult
import at.asitplus.wallet.lib.oidvci.*
import io.github.aakira.napier.Napier
import io.ktor.http.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package at.asitplus.wallet.lib.oidc
import at.asitplus.wallet.lib.data.vckJsonSerializer
import kotlinx.serialization.json.Json

@Deprecated("Use vckJsonSerializer",
ReplaceWith("vckJsonSerializer", "at.asitplus.wallet.lib.data"))
val jsonSerializer by lazy {
Json {
prettyPrint = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import at.asitplus.wallet.lib.data.ConstantIndex.supportsVcJwt
import at.asitplus.wallet.lib.iso.*
import at.asitplus.wallet.lib.jws.*
import at.asitplus.wallet.lib.oidvci.*
import at.asitplus.wallet.lib.openid.ResponseParser
import com.benasher44.uuid.uuid4
import io.github.aakira.napier.Napier
import io.ktor.http.*
Expand All @@ -51,6 +52,10 @@ import kotlin.time.toDuration
*
* This class creates the Authentication Request, [verifier] verifies the response. See [OidcSiopWallet] for the holder.
*/
@Deprecated(
message = "Replace with OpenId4VpVerifier",
ReplaceWith("OpenId4VpVerifier", "at.asitplus.wallet.lib.openid")
)
class OidcSiopVerifier(
private val clientIdScheme: ClientIdScheme,
private val keyMaterial: KeyMaterial = EphemeralKeyWithoutCert(),
Expand All @@ -61,19 +66,17 @@ class OidcSiopVerifier(
timeLeewaySeconds: Long = 300L,
private val clock: Clock = Clock.System,
private val nonceService: NonceService = DefaultNonceService(),
/**
* Used to store the nonce, associated to the state, to first send [AuthenticationRequestParameters.nonce],
* and then verify the challenge in the submitted verifiable presentation in
* [AuthenticationResponseParameters.vpToken].
*/
private val stateToNonceStore: MapStore<String, String> = DefaultMapStore(),
/** Used to store issued authn requests, to verify the authn response to it */
private val stateToAuthnRequestStore: MapStore<String, AuthenticationRequestParameters> = DefaultMapStore(),
) {

private val responseParser = ResponseParser(jwsService, verifierJwsService)
private val timeLeeway = timeLeewaySeconds.toDuration(DurationUnit.SECONDS)

@Deprecated(
message = "Replace with external class",
ReplaceWith("ClientIdScheme", "at.asitplus.wallet.lib.openid")
)
sealed class ClientIdScheme(
val scheme: OpenIdConstants.ClientIdScheme,
open val clientId: String,
Expand Down Expand Up @@ -212,6 +215,10 @@ class OidcSiopVerifier(
addX5c = false
)

@Deprecated(
message = "Replace with external class",
ReplaceWith("RequestOptions", "at.asitplus.wallet.lib.openid")
)
data class RequestOptions(
/**
* Requested credentials, should be at least one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@ import at.asitplus.signum.indispensable.CryptoPublicKey
import at.asitplus.signum.indispensable.io.Base64UrlStrict
import at.asitplus.signum.indispensable.josef.JsonWebKey
import at.asitplus.signum.indispensable.josef.JsonWebKeySet
import at.asitplus.signum.indispensable.josef.JwsSigned
import at.asitplus.signum.indispensable.josef.toJsonWebKey
import at.asitplus.wallet.lib.RemoteResourceRetrieverFunction
import at.asitplus.wallet.lib.agent.*
import at.asitplus.wallet.lib.cbor.CoseService
import at.asitplus.wallet.lib.cbor.DefaultCoseService
import at.asitplus.wallet.lib.jws.DefaultJwsService
import at.asitplus.wallet.lib.jws.JwsService
import at.asitplus.wallet.lib.oidc.helper.AuthenticationResponseFactory
import at.asitplus.wallet.lib.oidc.helper.AuthorizationRequestValidator
import at.asitplus.wallet.lib.oidc.helper.PresentationFactory
import at.asitplus.wallet.lib.oidc.helper.RequestParser
import at.asitplus.wallet.lib.oidc.helpers.AuthorizationResponsePreparationState
import at.asitplus.wallet.lib.oidvci.OAuth2Exception
import at.asitplus.wallet.lib.openid.*
import io.github.aakira.napier.Napier
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.datetime.Clock
Expand All @@ -40,8 +36,12 @@ import kotlinx.serialization.json.buildJsonArray
* Implements [OIDC for VP](https://openid.net/specs/openid-connect-4-verifiable-presentations-1_0.html) (2023-04-21)
* as well as [SIOP V2](https://openid.net/specs/openid-connect-self-issued-v2-1_0.html) (2023-01-01).
*
* The [holder] creates the Authentication Response, see [OidcSiopVerifier] for the verifier.
* The [holder] creates the Authentication Response, see [at.asitplus.wallet.lib.openid.OpenId4VpVerifier] for the verifier.
*/
@Deprecated(
message = "Replace with OpenId4VpHolder",
ReplaceWith("OpenId4VpHolder", "at.asitplus.wallet.lib.openid")
)
class OidcSiopWallet(
private val holder: Holder,
private val agentPublicKey: CryptoPublicKey,
Expand All @@ -61,14 +61,14 @@ class OidcSiopWallet(
* which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]).
*/
private val requestObjectJwsVerifier: RequestObjectJwsVerifier,
) {
/**
* Used to resolve [RequestParameters] by reference and also matches them to the correct [RequestParametersFrom]
*/
private val requestParser: RequestParser = RequestParser(
remoteResourceRetriever = remoteResourceRetriever,
requestObjectJwsVerifier = requestObjectJwsVerifier,
),
) {
)
constructor(
keyMaterial: KeyMaterial = EphemeralKeyWithoutCert(),
holder: Holder = HolderAgent(keyMaterial),
Expand All @@ -88,13 +88,6 @@ class OidcSiopWallet(
* which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]).
*/
requestObjectJwsVerifier: RequestObjectJwsVerifier = RequestObjectJwsVerifier { _ -> true },
/**
* Used to resolve [RequestParameters] by reference and also matches them to the correct [RequestParametersFrom]
*/
requestParser: RequestParser = RequestParser(
remoteResourceRetriever = remoteResourceRetriever,
requestObjectJwsVerifier = requestObjectJwsVerifier,
),
) : this(
holder = holder,
agentPublicKey = keyMaterial.publicKey,
Expand All @@ -104,7 +97,6 @@ class OidcSiopWallet(
clientId = clientId,
remoteResourceRetriever = remoteResourceRetriever,
requestObjectJwsVerifier = requestObjectJwsVerifier,
requestParser = requestParser,
)

val metadata: OAuth2AuthorizationServerMetadata by lazy {
Expand All @@ -124,8 +116,8 @@ class OidcSiopWallet(

/**
* Pass in the URL sent by the Verifier (containing the [AuthenticationRequestParameters] as query parameters),
* to create [AuthenticationResponseResult] that can be sent back to the Verifier, see
* [AuthenticationResponseResult].
* to create [at.asitplus.wallet.lib.openid.AuthenticationResponseResult] that can be sent back to the Verifier, see
* [at.asitplus.wallet.lib.openid.AuthenticationResponseResult].
*/
suspend fun createAuthnResponse(input: String): KmmResult<AuthenticationResponseResult> =
catching {
Expand Down Expand Up @@ -296,19 +288,6 @@ class OidcSiopWallet(
}
}

/**
* Implementations need to fetch the url passed in, and return either the body, if there is one,
* or the HTTP header `Location`, i.e. if the server sends the request object as a redirect.
*/
typealias RemoteResourceRetrieverFunction = suspend (String) -> String?

/**
* Implementations need to verify the passed [JwsSigned] and return its result
*/
fun interface RequestObjectJwsVerifier {
operator fun invoke(jws: JwsSigned<RequestParameters>): Boolean
}

private fun Collection<JsonWebKey>?.combine(certKey: JsonWebKey?): Collection<JsonWebKey> {
return certKey?.let { (this ?: listOf()) + certKey } ?: this ?: listOf()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package at.asitplus.wallet.lib.oidc

import at.asitplus.openid.RequestParameters
import at.asitplus.signum.indispensable.josef.JwsSigned

/**
* Implementations need to verify the passed [at.asitplus.signum.indispensable.josef.JwsSigned] and return its result
*/
fun interface RequestObjectJwsVerifier {
operator fun invoke(jws: JwsSigned<RequestParameters>): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package at.asitplus.wallet.lib.oidvci
import at.asitplus.wallet.lib.data.vckJsonSerializer
import kotlinx.serialization.json.Json

@Deprecated("Use vckJsonSerializer",
ReplaceWith("vckJsonSerializer", "at.asitplus.wallet.lib.data"))
val jsonSerializer by lazy {
Json {
prettyPrint = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import at.asitplus.wallet.lib.jws.JwsContentTypeConstants
import at.asitplus.wallet.lib.jws.JwsService
import at.asitplus.wallet.lib.oauth2.OAuth2Client
import at.asitplus.wallet.lib.oidc.OidcSiopVerifier.AuthnResponseResult
import at.asitplus.wallet.lib.oidc.RemoteResourceRetrieverFunction
import at.asitplus.wallet.lib.RemoteResourceRetrieverFunction
import com.benasher44.uuid.uuid4
import io.github.aakira.napier.Napier
import io.ktor.http.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package at.asitplus.wallet.lib.oidc
package at.asitplus.wallet.lib.openid

import at.asitplus.openid.AuthenticationResponseParameters
import at.asitplus.openid.RelyingPartyMetadata
Expand All @@ -18,4 +18,4 @@ data class AuthenticationResponse(
* [at.asitplus.signum.indispensable.josef.JweHeader.agreementPartyUInfo] when encrypting the response.
*/
val mdocGeneratedNonce: String? = null,
)
)
Loading
Loading