Skip to content

Commit

Permalink
Merge pull request #855 from walt-id/feat/support-for-multiple-versions
Browse files Browse the repository at this point in the history
Feat/support for multiple versions
  • Loading branch information
philpotisk authored Dec 19, 2024
2 parents e65361c + 15fbdbe commit 4c16337
Show file tree
Hide file tree
Showing 38 changed files with 1,345 additions and 378 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const getOfferUrl = async (
vpProfile?: string
) => {
const data = await fetch(
`${NEXT_PUBLIC_ISSUER}/.well-known/openid-credential-issuer`
`${NEXT_PUBLIC_ISSUER}/draft13/.well-known/openid-credential-issuer`
).then((data) => {
return data.json();
});
Expand Down
36 changes: 29 additions & 7 deletions waltid-applications/waltid-web-wallet/libs/composables/issuance.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useLazyAsyncData, createError, navigateTo } from "nuxt/app";
import { useCurrentWallet } from "./accountWallet.ts";
import { decodeRequest } from "./siop-requests.ts";
import { type Ref, ref, watch } from "vue";
import { groupBy } from "./groupings.ts";
import {createError, navigateTo, useLazyAsyncData} from "nuxt/app";
import {useCurrentWallet} from "./accountWallet.ts";
import {decodeRequest} from "./siop-requests.ts";
import {type Ref, ref, watch} from "vue";
import {groupBy} from "./groupings.ts";

export async function useIssuance(query: any) {
const currentWallet = useCurrentWallet()
Expand All @@ -19,6 +19,7 @@ export async function useIssuance(query: any) {
const response: {
credential_issuer: string;
credential_configuration_ids: string[];
credentials: string[];
} = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveCredentialOffer`, {
method: "POST",
body: request
Expand Down Expand Up @@ -47,12 +48,33 @@ export async function useIssuance(query: any) {
issuerHost = issuer;
}

const credential_issuer: { credential_configurations_supported: Array<{ types: Array<String>; }>; } = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveIssuerOpenIDMetadata?issuer=${issuer}`)
const credentialList = credentialOffer.credential_configuration_ids.map((id) => credential_issuer.credential_configurations_supported[id]);
const credential_issuer: {
credential_configurations_supported: Array<{ types: Array<String>; }>; // Draft13
credentials_supported?: Array<{ id: string; types: Array<String> }>; // Draft11
} = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveIssuerOpenIDMetadata?issuer=${issuer}`)


const credentialList = credentialOffer.credential_configuration_ids
// Draft13
? credentialOffer.credential_configuration_ids.map((id) => credential_issuer.credential_configurations_supported[id])

// Draft11
: credentialOffer.credentials.map((id) => {
return credential_issuer.credentials_supported?.find(
(credential_supported) => credential_supported.id === id
);
}).filter(Boolean);


let credentialTypes: String[] = [];
for (let credentialListElement of credentialList) {

if (typeof credentialListElement["types"] !== 'undefined') {
const typeList = credentialListElement["types"] as Array<String>;
const lastType = typeList[typeList.length - 1] as String;
credentialTypes.push(lastType);
}

if (typeof credentialListElement["credential_definition"] !== 'undefined') {
const typeList = credentialListElement["credential_definition"]["type"] as Array<String>;
const lastType = typeList[typeList.length - 1] as String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
Expand All @@ -22,9 +23,8 @@ val dataFunctions = mapOf<String, suspend (call: CredentialDataMergeUtils.Functi

"context" to { it.context[it.args!!]!! },

// Add this because clock.now returns millis and the timestamps for exp etc claims must be identical
"timestamp-ebsi" to { JsonPrimitive(Clock.System.now().toString())},
"timestamp-ebsi-in" to { JsonPrimitive((Clock.System.now() + Duration.parse(it.args!!)).toString().replaceRange(19..28, "")) },
"timestamp-ebsi" to { JsonPrimitive(Clock.System.now().toIso8681WithoutSubSecondPrecision())},
"timestamp-ebsi-in" to { JsonPrimitive((Clock.System.now() + Duration.parse(it.args!!)).toIso8681WithoutSubSecondPrecision()) },

"timestamp" to { JsonPrimitive(Clock.System.now().toString()) },

Expand All @@ -46,3 +46,6 @@ val dataFunctions = mapOf<String, suspend (call: CredentialDataMergeUtils.Functi
?: throw IllegalArgumentException("No such function in history or no history: ${it.args}")
}
)

private fun Instant.toIso8681WithoutSubSecondPrecision(): String =
toString().substringBefore(".") + "Z"
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import io.ktor.utils.io.core.*
import kotlinx.datetime.Clock
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

object OpenID4VC {
private val log = KotlinLogging.logger { }
Expand Down Expand Up @@ -129,44 +127,50 @@ object OpenID4VC {

else -> throw TokenError(tokenRequest, TokenErrorCode.unsupported_grant_type, "Grant type not supported")
}
return verifyAndParseToken(code, issuer, TokenTarget.TOKEN, tokenKey) ?: throw TokenError(

return verifyAndParseToken(
token = code,
issuer = issuer,
target = TokenTarget.TOKEN,
tokenKey = tokenKey
) ?: throw TokenError(
tokenRequest = tokenRequest,
errorCode = TokenErrorCode.invalid_grant,
message = "Authorization code could not be verified"
)
}

// Create an ID or VP Token request using JAR OAuth2.0 specification https://www.rfc-editor.org/rfc/rfc9101.html
@OptIn(ExperimentalUuidApi::class)
suspend fun processCodeFlowAuthorizationWithAuthorizationRequest(
authorizationRequest: AuthorizationRequest,
authServerState: String,
responseType: ResponseType,
providerMetadata: OpenIDProviderMetadata,
tokenKey: Key,
isJar: Boolean? = true,
presentationDefinition: PresentationDefinition? = null,
): AuthorizationCodeWithAuthorizationRequestResponse {

providerMetadata.castOrNull<OpenIDProviderMetadata.Draft11>()
?: providerMetadata.castOrNull<OpenIDProviderMetadata.Draft13>()
?: error("Unknown metadata type: $providerMetadata")

if (!authorizationRequest.responseType.contains(ResponseType.Code))
throw AuthorizationError(
authorizationRequest,
AuthorizationErrorCode.invalid_request,
authorizationRequest = authorizationRequest,
errorCode = AuthorizationErrorCode.invalid_request,
message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow."
)

// Bind authentication request with state
val authorizationRequestServerState = Uuid.random().toString()
val authorizationRequestServerNonce = Uuid.random().toString()
val authorizationRequestServerNonce = randomUUID()
val authorizationResponseServerMode = ResponseMode.direct_post

val clientId = providerMetadata.issuer!!
val redirectUri = providerMetadata.issuer + "/direct_post"
val scope = setOf("openid")

// Create a session with the state of the ID Token request since it is needed in the direct_post endpoint
//initializeAuthorization(authorizationRequest, 5.minutes, authorizationRequestServerState)

return AuthorizationCodeWithAuthorizationRequestResponse.success(
state = authorizationRequestServerState,
state = authServerState,
clientId = clientId,
redirectUri = redirectUri,
responseType = getResponseTypeString(responseType),
Expand All @@ -175,24 +179,25 @@ object OpenID4VC {
nonce = authorizationRequestServerNonce,
requestUri = null,
request = when (isJar) {
// Create a jwt as request object as defined in JAR OAuth2.0 specification
true -> signToken(
TokenTarget.TOKEN,
buildJsonObject {
target = TokenTarget.TOKEN,
payload = buildJsonObject {
put(JWTClaims.Payload.issuer, providerMetadata.issuer)
put(JWTClaims.Payload.audience, authorizationRequest.clientId)
put(JWTClaims.Payload.nonce, authorizationRequestServerNonce)
put("state", authorizationRequestServerState)
put("state", authServerState)
put("client_id", clientId)
put("redirect_uri", redirectUri)
put("response_type", getResponseTypeString(responseType))
put("response_mode", authorizationResponseServerMode.name)
put("scope", "openid")
when (responseType) {
ResponseType.VpToken -> put("presentation_definition", presentationDefinition!!.toJSON())
else -> null
else -> {}
}
}, tokenKey)
},
privKey = tokenKey
)

else -> null
},
Expand All @@ -210,12 +215,21 @@ object OpenID4VC {
AuthorizationErrorCode.invalid_request,
message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow."
)

providerMetadata.castOrNull<OpenIDProviderMetadata.Draft11>()
?: providerMetadata.castOrNull<OpenIDProviderMetadata.Draft13>()
?: error("Unknown metadata type: $providerMetadata")

val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata")
val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey)
return AuthorizationCodeResponse.success(code, mapOf("state" to listOf(authorizationRequest.state ?: randomUUID())))
}

suspend fun processImplicitFlowAuthorization(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): TokenResponse {
providerMetadata.castOrNull<OpenIDProviderMetadata.Draft11>()
?: providerMetadata.castOrNull<OpenIDProviderMetadata.Draft13>()
?: error("Unknown metadata type: $providerMetadata")

log.debug { "> processImplicitFlowAuthorization for $authorizationRequest" }
if (!authorizationRequest.responseType.contains(ResponseType.Token) && !authorizationRequest.responseType.contains(ResponseType.VpToken)
&& !authorizationRequest.responseType.contains(ResponseType.IdToken)
Expand All @@ -235,15 +249,27 @@ object OpenID4VC {
}

suspend fun processDirectPost(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): AuthorizationCodeResponse {
providerMetadata.castOrNull<OpenIDProviderMetadata.Draft11>()
?: providerMetadata.castOrNull<OpenIDProviderMetadata.Draft13>()
?: error("Unknown metadata type: $providerMetadata")

// Verify nonce - need to add Id token nonce session
// if (payload[JWTClaims.Payload.nonce] != session.)

// Generate code and proceed as regular authorization request
val mappedState = mapOf("state" to listOf(authorizationRequest.state!!))
val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata")
val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey)

return AuthorizationCodeResponse.success(code, mappedState)
val code = generateAuthorizationCodeFor(
sessionId = sessionId,
issuer = issuer,
tokenKey = tokenKey
)

return AuthorizationCodeResponse.success(
code = code,
customParameters = mappedState
)
}

const val PUSHED_AUTHORIZATION_REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:"
Expand Down
Loading

0 comments on commit 4c16337

Please sign in to comment.