• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / VCVerifier / 17800985195

17 Sep 2025 02:28PM UTC coverage: 44.67% (+0.5%) from 44.17%
17800985195

Pull #67

github

wistefan
doc
Pull Request #67: Token exchange

53 of 59 new or added lines in 1 file covered. (89.83%)

349 existing lines in 7 files now uncovered.

1517 of 3396 relevant lines covered (44.67%)

0.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.32
/openapi/api_api.go
1
/*
2
 * vcverifier
3
 *
4
 * Backend component to verify credentials
5
 *
6
 * API version: 0.0.1
7
 * Generated by: OpenAPI Generator (https://openapi-generator.tech)
8
 */
9

10
package openapi
11

12
import (
13
        "encoding/base64"
14
        "encoding/json"
15
        "errors"
16
        "fmt"
17
        "net/http"
18
        "slices"
19
        "strings"
20

21
        "github.com/fiware/VCVerifier/common"
22
        "github.com/fiware/VCVerifier/logging"
23
        "github.com/fiware/VCVerifier/verifier"
24
        "github.com/lestrrat-go/jwx/v3/jwa"
25
        "github.com/lestrrat-go/jwx/v3/jwt"
26
        vdr_jwk "github.com/trustbloc/did-go/method/jwk"
27
        vdr_key "github.com/trustbloc/did-go/method/key"
28
        vdr_web "github.com/trustbloc/did-go/method/web"
29
        "github.com/trustbloc/did-go/vdr/api"
30
        "github.com/trustbloc/vc-go/verifiable"
31

32
        "github.com/gin-gonic/gin"
33
)
34

35
var apiVerifier verifier.Verifier
36
var presentationParser verifier.PresentationParser
37
var sdJwtParser verifier.SdJwtParser
38
var keyResolver verifier.KeyResolver
39

40
var ErrorMessagNoGrantType = ErrorMessage{"no_grant_type_provided", "Token requests require a grant_type."}
41
var ErrorMessageUnsupportedGrantType = ErrorMessage{"unsupported_grant_type", "Provided grant_type is not supported by the implementation."}
42
var ErrorMessageNoCode = ErrorMessage{"no_code_provided", "Token requests require a code."}
43
var ErrorMessageNoRedircetUri = ErrorMessage{"no_redirect_uri_provided", "Token requests require a redirect_uri."}
44
var ErrorMessageNoResource = ErrorMessage{"no_resource_provided", "When using token-exchange, resource is required to provide the client_id."}
45
var ErrorMessageNoState = ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."}
46
var ErrorMessageNoScope = ErrorMessage{"no_scope_provided", "Authentication requires a scope provided as a parameter."}
47
var ErrorMessageNoNonce = ErrorMessage{"no_nonce_provided", "Authentication requires a nonce provided as a query parameter."}
48
var ErrorMessageNoToken = ErrorMessage{"no_token_provided", "Authentication requires a token provided as a form parameter."}
49
var ErrorMessageNoPresentationSubmission = ErrorMessage{"no_presentation_submission_provided", "Authentication requires a presentation submission provided as a form parameter."}
50
var ErrorMessageNoCallback = ErrorMessage{"NoCallbackProvided", "A callback address has to be provided as query-parameter."}
51
var ErrorMessageUnableToDecodeToken = ErrorMessage{"invalid_token", "Token could not be decoded."}
52
var ErrorMessageUnableToDecodeCredential = ErrorMessage{"invalid_token", "Could not read the credential(s) inside the token."}
53
var ErrorMessageUnableToDecodeHolder = ErrorMessage{"invalid_token", "Could not read the holder inside the token."}
54
var ErrorMessageNoSuchSession = ErrorMessage{"no_session", "Session with the requested id is not available."}
55
var ErrorMessageInvalidSdJwt = ErrorMessage{"invalid_sdjwt", "SdJwt does not contain all required fields."}
56
var ErrorMessageNoWebsocketConnection = ErrorMessage{"invalid_connection", "No Websocket connection available for the authenticated session."}
57
var ErrorMessageUnresolvableRequestObject = ErrorMessage{"unresolvable_request_object", "Was not able to get the request object from the client."}
58
var ErrorMessageInvalidAudience = ErrorMessage{"invalid_audience", "Audience of the request object was invalid."}
59
var ErrorMessageUnsupportedAssertionType = ErrorMessage{"unsupported_assertion_type", "Assertion type is not supported."}
60
var ErrorMessageInvalidClientAssertion = ErrorMessage{"invalid_client_assertion", "Provided client assertion is invalid."}
61
var ErrorMessageInvalidTokenRequest = ErrorMessage{"invalid_token_request", "Token request has no redirect_uri and no valid client assertion."}
62
var ErrorMessageInvalidSubjectTokenType = ErrorMessage{"invalid_subject_token_type", "Token exchange is only supported for token type urn:eu:oidf:vp_token."}
63
var ErrorMessageInvalidRequestedTokenType = ErrorMessage{"invalid_requested_token_type", "Token exchange is only supported for requesting tokens of type urn:ietf:params:oauth:token-type:access_token."}
64

65
func getApiVerifier() verifier.Verifier {
1✔
66
        if apiVerifier == nil {
1✔
UNCOV
67
                apiVerifier = verifier.GetVerifier()
×
UNCOV
68
        }
×
69
        return apiVerifier
1✔
70
}
71

72
func getPresentationParser() verifier.PresentationParser {
1✔
73
        if presentationParser == nil {
1✔
74
                presentationParser = verifier.GetPresentationParser()
×
75
        }
×
76
        return presentationParser
1✔
77
}
78

79
func getSdJwtParser() verifier.SdJwtParser {
1✔
80
        if sdJwtParser == nil {
1✔
81
                sdJwtParser = verifier.GetSdJwtParser()
×
82
        }
×
83
        return sdJwtParser
1✔
84
}
85

UNCOV
86
func getKeyResolver() verifier.KeyResolver {
×
UNCOV
87
        if keyResolver == nil {
×
88
                keyResolver = &verifier.VdrKeyResolver{Vdr: []api.VDR{vdr_key.New(), vdr_jwk.New(), vdr_web.New()}}
×
89
        }
×
UNCOV
90
        return keyResolver
×
91
}
92

93
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
94
func GetToken(c *gin.Context) {
1✔
95

1✔
96
        logging.Log().Debugf("%v", c.Request)
1✔
97
        grantType, grantTypeExists := c.GetPostForm("grant_type")
1✔
98
        if !grantTypeExists {
2✔
99
                logging.Log().Debug("No grant_type present in the request.")
1✔
100
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
1✔
101
                return
1✔
102
        }
1✔
103

104
        switch grantType {
1✔
105
        case common.TYPE_CODE:
1✔
106
                handleTokenTypeCode(c)
1✔
107
        case common.TYPE_VP_TOKEN:
1✔
108
                handleTokenTypeVPToken(c, c.GetHeader("client_id"))
1✔
109
        case common.TYPE_TOKEN_EXCHANGE:
1✔
110
                resource, resourceExists := c.GetPostForm("resource")
1✔
111
                if !resourceExists {
2✔
112
                        c.AbortWithStatusJSON(400, ErrorMessageNoResource)
1✔
113
                        return
1✔
114
                }
1✔
115
                handleTokenTypeTokenExchange(c, resource)
1✔
116
        default:
1✔
117
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
1✔
118
        }
119
}
120

121
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
122
func GetTokenForService(c *gin.Context) {
×
123

×
124
        logging.Log().Debugf("%v", c.Request)
×
125
        grantType, grantTypeExists := c.GetPostForm("grant_type")
×
126
        if !grantTypeExists {
×
127
                logging.Log().Debug("No grant_type present in the request.")
×
128
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
×
129
                return
×
130
        }
×
131

NEW
132
        switch grantType {
×
NEW
133
        case common.TYPE_CODE:
×
134
                handleTokenTypeCode(c)
×
NEW
135
        case common.TYPE_VP_TOKEN:
×
136
                handleTokenTypeVPToken(c, c.Param("service_id"))
×
NEW
137
        case common.TYPE_TOKEN_EXCHANGE:
×
NEW
138
                handleTokenTypeTokenExchange(c, c.Param("service_id"))
×
NEW
139
        default:
×
140
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
×
141
        }
142
}
143

144
func handleTokenTypeTokenExchange(c *gin.Context, clientId string) {
1✔
145
        subjectTokenType, subjectTokenTypeExists := c.GetPostForm("subject_token_type")
1✔
146
        if !subjectTokenTypeExists || subjectTokenType != common.TYPE_VP_TOKEN_SUBJECT {
2✔
147
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSubjectTokenType)
1✔
148
                return
1✔
149
        }
1✔
150
        requestedTokenType, requestedTokenTypeExists := c.GetPostForm("requested_token_type")
1✔
151
        if requestedTokenTypeExists && requestedTokenType != common.TYPE_ACCESS_TOKEN {
2✔
152
                c.AbortWithStatusJSON(400, ErrorMessageInvalidRequestedTokenType)
1✔
153
                return
1✔
154
        }
1✔
155

156
        scopes := getScopesFromRequest(c)
1✔
157
        if len(scopes) == 0 {
2✔
158
                return
1✔
159
        }
1✔
160

161
        audience, audienceExists := c.GetPostForm("audience")
1✔
162
        if !audienceExists {
2✔
163
                audience = clientId
1✔
164
        }
1✔
165

166
        subjectToken, subjectTokenExists := c.GetPostForm("subject_token")
1✔
167
        if !subjectTokenExists {
2✔
168
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
169
                return
1✔
170
        }
1✔
171

172
        logging.Log().Debugf("Got token %s", subjectToken)
1✔
173

1✔
174
        verifiyVPToken(c, subjectToken, clientId, scopes, audience)
1✔
175
}
176

177
func handleTokenTypeVPToken(c *gin.Context, clientId string) {
1✔
178

1✔
179
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
180
        if !vpTokenExists {
2✔
181
                logging.Log().Debug("No vp token present in the request.")
1✔
182
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
183
                return
1✔
184
        }
1✔
185

186
        logging.Log().Warnf("Got token %s", vpToken)
1✔
187

1✔
188
        scopes := getScopesFromRequest(c)
1✔
189
        if len(scopes) == 0 {
2✔
190
                return
1✔
191
        }
1✔
192

193
        verifiyVPToken(c, vpToken, clientId, scopes, clientId)
1✔
194
}
195

196
func verifiyVPToken(c *gin.Context, vpToken string, clientId string, scopes []string, audience string) {
1✔
197

1✔
198
        presentation, err := extractVpFromToken(c, vpToken)
1✔
199
        if err != nil {
1✔
200
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token. E: %v", err)
×
201
                return
×
202
        }
×
203

204
        // Subject is empty since multiple VCs with different subjects can be provided
205
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
206
        if err != nil {
1✔
207
                logging.Log().Error("Failure during generating M2M token: ", err)
×
208
                c.AbortWithStatusJSON(400, err)
×
209
                return
×
210
        }
×
211
        response := TokenResponse{TokenType: "Bearer", IssuedTokenType: common.TYPE_ACCESS_TOKEN, ExpiresIn: float32(expiration), AccessToken: signedToken, Scope: strings.Join(scopes, ",")}
1✔
212
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
213
        c.JSON(http.StatusOK, response)
1✔
214
}
215

216
func handleTokenTypeCode(c *gin.Context) {
1✔
217

1✔
218
        code, codeExists := c.GetPostForm("code")
1✔
219
        if !codeExists {
2✔
220
                logging.Log().Debug("No code present in the request.")
1✔
221
                c.AbortWithStatusJSON(400, ErrorMessageNoCode)
1✔
222
                return
1✔
223
        }
1✔
224

225
        assertionType, assertionTypeExists := c.GetPostForm("client_assertion_type")
1✔
226
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
227
        if redirectUriExists {
2✔
228
                jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri, false)
1✔
229
                if err != nil {
2✔
230
                        c.AbortWithStatusJSON(403, ErrorMessage{Summary: err.Error()})
1✔
231
                        return
1✔
232
                }
1✔
233
                c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
234
                return
1✔
235
        }
236
        if assertionTypeExists {
1✔
237
                handleWithClientAssertion(c, assertionType, code)
×
238
                return
×
UNCOV
239
        }
×
240
        c.AbortWithStatusJSON(400, ErrorMessageInvalidTokenRequest)
1✔
241
}
242

243
func getScopesFromRequest(c *gin.Context) (scopes []string) {
1✔
244

1✔
245
        scope, scopeExists := c.GetPostForm("scope")
1✔
246
        if !scopeExists {
2✔
247
                logging.Log().Debug("No scope present in the request.")
1✔
248
                c.AbortWithStatusJSON(400, ErrorMessageNoScope)
1✔
249
                return scopes
1✔
250
        }
1✔
251

252
        return strings.Split(scope, ",")
1✔
253

254
}
255

256
func handleWithClientAssertion(c *gin.Context, assertionType string, code string) {
×
257
        if assertionType != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
×
258
                logging.Log().Warnf("Assertion type %s is not supported.", assertionType)
×
259
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedAssertionType)
×
260
                return
×
261
        }
×
262

263
        clientAssertion, clientAssertionExists := c.GetPostForm("client_assertion")
×
264
        clientId, clientIdExists := c.GetPostForm("client_id")
×
265
        if !clientAssertionExists || !clientIdExists {
×
266
                logging.Log().Warnf("Client Id (%s) or assertion (%s) not provided.", clientId, clientAssertion)
×
267
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedAssertionType)
×
268
                return
×
269
        }
×
270

271
        kid, err := getKeyResolver().ExtractKIDFromJWT(clientAssertion)
×
272
        if err != nil {
×
273
                logging.Log().Warnf("Was not able to retrive kid from token %s. Err: %v.", clientAssertion, err)
×
274
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
275
                return
×
276
        }
×
277
        pubKey, err := getKeyResolver().ResolvePublicKeyFromDID(kid)
×
278
        if err != nil {
×
279
                logging.Log().Warnf("Was not able to retrive key from kid %s. Err: %v.", kid, err)
×
280
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
281
                return
×
282
        }
×
283

284
        alg, algExists := pubKey.Algorithm()
×
285
        if !algExists {
×
286
                // fallback to default
×
287
                alg = jwa.ES256()
×
288
        }
×
289

UNCOV
290
        parsed, err := jwt.Parse([]byte(clientAssertion), jwt.WithKey(alg, pubKey))
×
291
        if err != nil {
×
292
                logging.Log().Warnf("Was not able to parse and verify the token %s. Err: %v", clientAssertion, err)
×
293
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
294
                return
×
UNCOV
295
        }
×
296

297
        // Serialize token to JSON
UNCOV
298
        jsonBytes, err := json.Marshal(parsed)
×
UNCOV
299
        if err != nil {
×
UNCOV
300
                logging.Log().Warnf("Was not able to marshal the token %s. Err: %v", clientAssertion, err)
×
UNCOV
301
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
302
                return
×
UNCOV
303
        }
×
304

305
        // Unmarshal to your struct
UNCOV
306
        var clientAssertionObject ClientAssertion
×
UNCOV
307
        if err := json.Unmarshal(jsonBytes, &clientAssertionObject); err != nil {
×
308
                logging.Log().Warnf("Was not able to unmarshal the token: %s, Err: %v", string(jsonBytes), err)
×
309
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
UNCOV
310
                return
×
UNCOV
311
        }
×
312

313
        if clientAssertionObject.Sub != clientId || clientAssertionObject.Iss != clientId || !slices.Contains(clientAssertionObject.Aud, getFrontendVerifier().GetHost()) {
×
314
                logging.Log().Warnf("Invalid assertion: %s. Client Id: %s, Host: %s", logging.PrettyPrintObject(clientAssertionObject), clientId, getApiVerifier().GetHost())
×
315
                c.AbortWithStatusJSON(400, ErrorMessageInvalidClientAssertion)
×
316
                return
×
317
        }
×
318

UNCOV
319
        jwt, expiration, err := getApiVerifier().GetToken(code, "", true)
×
320
        if err != nil {
×
321
                c.AbortWithStatusJSON(403, ErrorMessage{Summary: err.Error()})
×
322
                return
×
UNCOV
323
        }
×
324
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
×
325
}
326

327
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
328
func StartSIOPSameDevice(c *gin.Context) {
1✔
329
        state, stateExists := c.GetQuery("state")
1✔
330
        if !stateExists {
2✔
331
                logging.Log().Debugf("No state was provided.")
1✔
332
                c.AbortWithStatusJSON(400, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
333
                return
1✔
334
        }
1✔
335
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
336
        if !redirectPathExists {
2✔
337
                redirectPath = "/"
1✔
338
        }
1✔
339

340
        protocol := "https"
1✔
341
        if c.Request.TLS == nil {
2✔
342
                protocol = "http"
1✔
343
        }
1✔
344

345
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
346
        logging.Log().Debugf("The client id %s", clientId)
1✔
347
        if !clientIdExists {
2✔
348
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
349
        }
1✔
350

351
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
352
        if !requestModeExists {
2✔
353
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
354
                requestMode = DEFAULT_REQUEST_MODE
1✔
355
        }
1✔
356

357
        redirect, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, requestMode)
1✔
358
        if err != nil {
2✔
359
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
360
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
361
                return
1✔
362
        }
1✔
363
        c.Redirect(302, redirect)
1✔
364
}
365

366
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
367
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
368

1✔
369
        var state string
1✔
370
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
371
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
372
        if !stateFormExists && !stateQueryExists {
2✔
373
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
374
                return
1✔
375
        }
1✔
376
        if stateFormExists {
2✔
377
                state = stateForm
1✔
378
        } else {
1✔
379
                // allow the state submitted through a query parameter for backwards-compatibility
×
380
                state = stateQuery
×
381
        }
×
382

383
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
384
        if !tokenExists {
2✔
385
                logging.Log().Info("No token was provided.")
1✔
386
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
387
                return
1✔
388
        }
1✔
389

390
        presentation, err := extractVpFromToken(c, vptoken)
1✔
391
        if err != nil {
2✔
392
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
1✔
393
                return
1✔
394
        }
1✔
395
        handleAuthenticationResponse(c, state, presentation)
1✔
396
}
397

398
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
399
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
400
        state, stateExists := c.GetQuery("state")
×
401
        if !stateExists {
×
402
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
×
403
                return
×
UNCOV
404
        }
×
405

406
        vpToken, tokenExists := c.GetQuery("vp_token")
×
407
        if !tokenExists {
×
408
                logging.Log().Info("No token was provided.")
×
409
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
×
UNCOV
410
                return
×
411
        }
×
412
        presentation, err := extractVpFromToken(c, vpToken)
×
413
        if err != nil {
×
414
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
415
                return
×
416
        }
×
UNCOV
417
        handleAuthenticationResponse(c, state, presentation)
×
418
}
419

420
// GetRequestByReference - Get the request object by reference
421
func GetRequestByReference(c *gin.Context) {
×
422
        sessionId := c.Param("id")
×
423

×
424
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
UNCOV
425
        if err != nil {
×
UNCOV
426
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
427
                c.AbortWithStatusJSON(404, ErrorMessageNoSuchSession)
×
428
                return
×
429
        }
×
430
        c.String(http.StatusOK, jwt)
×
431
}
432

433
func extractVpFromToken(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
434

1✔
435
        logging.Log().Debugf("The token %s.", vpToken)
1✔
436

1✔
437
        parsedPresentation, err = getPresentationFromQuery(c, vpToken)
1✔
438

1✔
439
        if err != nil {
1✔
440
                logging.Log().Debugf("Received a vpToken with a query, but was not able to extract the presentation. Token: %s", vpToken)
×
441
                return parsedPresentation, err
×
442
        }
×
443
        if parsedPresentation != nil {
1✔
444
                return parsedPresentation, err
×
445
        }
×
446
        return tokenToPresentation(c, vpToken)
1✔
447

448
}
449

450
func tokenToPresentation(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
451
        tokenBytes := decodeVpString(vpToken)
1✔
452

1✔
453
        isSdJWT, parsedPresentation, err := isSdJWT(c, vpToken)
1✔
454
        if isSdJWT && err != nil {
1✔
UNCOV
455
                return
×
UNCOV
456
        }
×
457
        if isSdJWT {
2✔
458
                logging.Log().Debugf("Received an sdJwt: %s", logging.PrettyPrintObject(parsedPresentation))
1✔
459
                return
1✔
460
        }
1✔
461

462
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
463

1✔
464
        if err != nil {
2✔
465
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
1✔
466
                c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
1✔
467
                return
1✔
468
        }
1✔
469
        return
1✔
470
}
471

472
func getPresentationFromQuery(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
473
        tokenBytes := decodeVpString(vpToken)
1✔
474

1✔
475
        var queryMap map[string]string
1✔
476
        //unmarshal
1✔
477
        err = json.Unmarshal(tokenBytes, &queryMap)
1✔
478
        if err != nil {
2✔
479
                logging.Log().Debug("VP Token does not contain query map. Checking the other options.", err)
1✔
480
                return nil, nil
1✔
481
        }
1✔
482

483
        for _, v := range queryMap {
×
484
                p, err := tokenToPresentation(c, v)
×
485
                if err != nil {
×
UNCOV
486
                        return nil, err
×
UNCOV
487
                }
×
488
                if parsedPresentation == nil {
×
UNCOV
489
                        parsedPresentation = p
×
UNCOV
490
                } else {
×
491
                        parsedPresentation.AddCredentials(p.Credentials()...)
×
492
                }
×
493
        }
UNCOV
494
        return parsedPresentation, err
×
495
}
496

497
// checks if the presented token contains a single sd-jwt credential. Will be repackage to a presentation for further validation
498
func isSdJWT(c *gin.Context, vpToken string) (isSdJwt bool, presentation *verifiable.Presentation, err error) {
1✔
499
        claims, err := getSdJwtParser().Parse(vpToken)
1✔
500
        if err != nil {
2✔
501
                logging.Log().Debugf("Was not a sdjwt. Err: %v", err)
1✔
502
                return false, presentation, err
1✔
503
        }
1✔
504
        issuer, i_ok := claims["iss"]
1✔
505
        vct, vct_ok := claims["vct"]
1✔
506
        if !i_ok || !vct_ok {
1✔
UNCOV
507
                logging.Log().Infof("Token does not contain issuer(%v) or vct(%v).", i_ok, vct_ok)
×
508
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
509
                return true, presentation, errors.New(ErrorMessageInvalidSdJwt.Summary)
×
510
        }
×
511
        customFields := verifiable.CustomFields{}
1✔
512
        for k, v := range claims {
2✔
513
                if k != "iss" && k != "vct" {
2✔
514
                        customFields[k] = v
1✔
515
                }
1✔
516
        }
517
        subject := verifiable.Subject{CustomFields: customFields}
1✔
518
        contents := verifiable.CredentialContents{Issuer: &verifiable.Issuer{ID: issuer.(string)}, Types: []string{vct.(string)}, Subject: []verifiable.Subject{subject}}
1✔
519
        credential, err := verifiable.CreateCredential(contents, verifiable.CustomFields{})
1✔
520
        if err != nil {
1✔
UNCOV
521
                logging.Log().Infof("Was not able to create credential from sdJwt. E: %v", err)
×
UNCOV
522
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
UNCOV
523
                return true, presentation, err
×
UNCOV
524
        }
×
525
        presentation, err = verifiable.NewPresentation()
1✔
526
        if err != nil {
1✔
UNCOV
527
                logging.Log().Infof("Was not able to create credpresentation from sdJwt. E: %v", err)
×
528
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
529
                return true, presentation, err
×
530
        }
×
531
        presentation.AddCredentials(credential)
1✔
532
        presentation.Holder = issuer.(string)
1✔
533
        return true, presentation, nil
1✔
534
}
535

536
// decodeVpString - In newer versions of OID4VP the token is not encoded as a whole but only its segments separately. This function covers the older and newer versions
537
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
538
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
539
        if err != nil {
2✔
540
                return []byte(vpToken)
1✔
541
        }
1✔
542
        return tokenBytes
1✔
543
}
544

545
func handleAuthenticationResponse(c *gin.Context, state string, presentation *verifiable.Presentation) {
1✔
546

1✔
547
        response, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
548
        if err != nil {
2✔
549
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
550
                c.AbortWithStatusJSON(400, ErrorMessage{Summary: err.Error()})
1✔
551
                return
1✔
552
        }
1✔
553
        if response != (verifier.Response{}) && response.FlowVersion == verifier.SAME_DEVICE {
2✔
554
                if response.Nonce != "" {
1✔
555
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s&nonce=%s", response.RedirectTarget, response.SessionId, response.Code, response.Nonce))
×
556
                } else {
1✔
557
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", response.RedirectTarget, response.SessionId, response.Code))
1✔
558
                }
1✔
559
                return
1✔
UNCOV
560
        } else if response != (verifier.Response{}) && response.FlowVersion == verifier.CROSS_DEVICE_V2 {
×
UNCOV
561
                sendRedirect(c, response.SessionId, response.Code, response.RedirectTarget)
×
UNCOV
562
        }
×
UNCOV
563
        logging.Log().Debugf("Successfully authenticated %s.", state)
×
UNCOV
564
        c.JSON(http.StatusOK, gin.H{})
×
565
}
566

567
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
UNCOV
568
func VerifierAPIJWKS(c *gin.Context) {
×
569
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
570
}
×
571

572
// VerifierAPIOpenID
573
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
574

×
UNCOV
575
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
UNCOV
576
        if err != nil {
×
UNCOV
577
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
UNCOV
578
                return
×
UNCOV
579
        }
×
UNCOV
580
        c.JSON(http.StatusOK, metadata)
×
581
}
582

583
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
584
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
585
        state, stateExists := c.GetQuery("state")
1✔
586
        if !stateExists {
2✔
587
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
588
                // early exit
1✔
589
                return
1✔
590
        }
1✔
591

592
        callback, callbackExists := c.GetQuery("client_callback")
1✔
593
        if !callbackExists {
2✔
594
                c.AbortWithStatusJSON(400, ErrorMessageNoCallback)
1✔
595
                // early exit
1✔
596
                return
1✔
597
        }
1✔
598
        protocol := "https"
1✔
599
        if c.Request.TLS == nil {
2✔
600
                protocol = "http"
1✔
601
        }
1✔
602
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
603
        if !clientIdExists {
2✔
604
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
605
        }
1✔
606

607
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
608
        if !requestModeExists {
2✔
609
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
610
                requestMode = DEFAULT_REQUEST_MODE
1✔
611
        }
1✔
612

613
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, "", requestMode)
1✔
614
        if err != nil {
2✔
615
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
616
                return
1✔
617
        }
1✔
618
        c.String(http.StatusOK, connectionString)
1✔
619
}
620

621
type ClientAssertion struct {
622
        Iss string   `json:"iss"`
623
        Aud []string `json:"aud"`
624
        Sub string   `json:"sub"`
625
        Exp int      `json:"exp"`
626
        Iat int      `json:"iat"`
627
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc