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

FIWARE / VCVerifier / 17828697867

18 Sep 2025 12:30PM UTC coverage: 44.82% (+0.7%) from 44.17%
17828697867

Pull #67

github

wistefan
add support for authorization path
Pull Request #67: Token exchange

109 of 156 new or added lines in 4 files covered. (69.87%)

337 existing lines in 7 files now uncovered.

1540 of 3436 relevant lines covered (44.82%)

0.51 hits per line

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

62.23
/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(http.StatusBadRequest, ErrorMessageNoResource)
1✔
113
                        return
1✔
114
                }
1✔
115
                handleTokenTypeTokenExchange(c, resource)
1✔
116
        default:
1✔
117
                c.AbortWithStatusJSON(http.StatusBadRequest, 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.")
×
NEW
128
                c.AbortWithStatusJSON(http.StatusBadRequest, 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:
×
NEW
140
                c.AbortWithStatusJSON(http.StatusBadRequest, 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(http.StatusBadRequest, 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(http.StatusBadRequest, 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(http.StatusBadRequest, 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(http.StatusBadRequest, 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)
×
NEW
208
                c.AbortWithStatusJSON(http.StatusBadRequest, 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(http.StatusBadRequest, 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(http.StatusForbidden, 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(http.StatusBadRequest, 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(http.StatusBadRequest, 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)
×
NEW
259
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
267
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
274
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
280
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
293
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
UNCOV
301
                c.AbortWithStatusJSON(http.StatusBadRequest, 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)
×
NEW
309
                c.AbortWithStatusJSON(http.StatusBadRequest, 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())
×
NEW
315
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
316
                return
×
317
        }
×
318

UNCOV
319
        jwt, expiration, err := getApiVerifier().GetToken(code, "", true)
×
320
        if err != nil {
×
NEW
321
                c.AbortWithStatusJSON(http.StatusForbidden, 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(http.StatusBadRequest, 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
        requestProtocol := verifier.REDIRECT_PROTOCOL
1✔
337
        if !redirectPathExists {
2✔
338
                requestProtocol = verifier.OPENID4VP_PROTOCOL
1✔
339
        }
1✔
340

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

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

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

358
        scope, scopeExists := c.GetQuery("scope")
1✔
359
        if !scopeExists {
2✔
360
                logging.Log().Infof("Start a login flow with default scope.")
1✔
361
                scope = ""
1✔
362
        }
1✔
363

364
        authenticationRequest, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, requestMode, scope, requestProtocol)
1✔
365
        if err != nil {
2✔
366
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
367
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
368
                return
1✔
369
        }
1✔
370
        if requestProtocol == verifier.OPENID4VP_PROTOCOL {
2✔
371
                c.String(http.StatusOK, authenticationRequest)
1✔
372
        } else {
2✔
373
                c.Redirect(http.StatusFound, authenticationRequest)
1✔
374

1✔
375
        }
1✔
376
}
377

378
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
379
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
380

1✔
381
        var state string
1✔
382
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
383
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
384
        if !stateFormExists && !stateQueryExists {
2✔
385
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
386
                return
1✔
387
        }
1✔
388
        if stateFormExists {
2✔
389
                state = stateForm
1✔
390
        } else {
1✔
UNCOV
391
                // allow the state submitted through a query parameter for backwards-compatibility
×
392
                state = stateQuery
×
393
        }
×
394

395
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
396
        if !tokenExists {
2✔
397
                logging.Log().Info("No token was provided.")
1✔
398
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
399
                return
1✔
400
        }
1✔
401

402
        presentation, err := extractVpFromToken(c, vptoken)
1✔
403
        if err != nil {
2✔
404
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
1✔
405
                return
1✔
406
        }
1✔
407
        handleAuthenticationResponse(c, state, presentation)
1✔
408
}
409

410
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
411
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
412
        state, stateExists := c.GetQuery("state")
×
413
        if !stateExists {
×
NEW
414
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
×
415
                return
×
416
        }
×
417

UNCOV
418
        vpToken, tokenExists := c.GetQuery("vp_token")
×
419
        if !tokenExists {
×
420
                logging.Log().Info("No token was provided.")
×
NEW
421
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
×
422
                return
×
423
        }
×
424
        presentation, err := extractVpFromToken(c, vpToken)
×
UNCOV
425
        if err != nil {
×
UNCOV
426
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
427
                return
×
428
        }
×
429
        handleAuthenticationResponse(c, state, presentation)
×
430
}
431

432
// GetRequestByReference - Get the request object by reference
UNCOV
433
func GetRequestByReference(c *gin.Context) {
×
434
        sessionId := c.Param("id")
×
435

×
436
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
437
        if err != nil {
×
438
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
NEW
UNCOV
439
                c.AbortWithStatusJSON(http.StatusNotFound, ErrorMessageNoSuchSession)
×
440
                return
×
441
        }
×
442
        c.String(http.StatusOK, jwt)
×
443
}
444

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

1✔
447
        logging.Log().Debugf("The token %s.", vpToken)
1✔
448

1✔
449
        parsedPresentation, err = getPresentationFromQuery(c, vpToken)
1✔
450

1✔
451
        if err != nil {
1✔
UNCOV
452
                logging.Log().Debugf("Received a vpToken with a query, but was not able to extract the presentation. Token: %s", vpToken)
×
453
                return parsedPresentation, err
×
UNCOV
454
        }
×
455
        if parsedPresentation != nil {
1✔
UNCOV
456
                return parsedPresentation, err
×
457
        }
×
458
        return tokenToPresentation(c, vpToken)
1✔
459

460
}
461

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

1✔
465
        isSdJWT, parsedPresentation, err := isSdJWT(c, vpToken)
1✔
466
        if isSdJWT && err != nil {
1✔
UNCOV
467
                return
×
UNCOV
468
        }
×
469
        if isSdJWT {
2✔
470
                logging.Log().Debugf("Received an sdJwt: %s", logging.PrettyPrintObject(parsedPresentation))
1✔
471
                return
1✔
472
        }
1✔
473

474
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
475

1✔
476
        if err != nil {
2✔
477
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
1✔
478
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnableToDecodeToken)
1✔
479
                return
1✔
480
        }
1✔
481
        return
1✔
482
}
483

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

1✔
487
        var queryMap map[string]string
1✔
488
        //unmarshal
1✔
489
        err = json.Unmarshal(tokenBytes, &queryMap)
1✔
490
        if err != nil {
2✔
491
                logging.Log().Debug("VP Token does not contain query map. Checking the other options.", err)
1✔
492
                return nil, nil
1✔
493
        }
1✔
494

UNCOV
495
        for _, v := range queryMap {
×
UNCOV
496
                p, err := tokenToPresentation(c, v)
×
UNCOV
497
                if err != nil {
×
UNCOV
498
                        return nil, err
×
UNCOV
499
                }
×
UNCOV
500
                if parsedPresentation == nil {
×
UNCOV
501
                        parsedPresentation = p
×
502
                } else {
×
UNCOV
503
                        parsedPresentation.AddCredentials(p.Credentials()...)
×
UNCOV
504
                }
×
505
        }
UNCOV
506
        return parsedPresentation, err
×
507
}
508

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

548
// 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
549
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
550
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
551
        if err != nil {
2✔
552
                return []byte(vpToken)
1✔
553
        }
1✔
554
        return tokenBytes
1✔
555
}
556

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

1✔
559
        response, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
560
        if err != nil {
2✔
561
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
562
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessage{Summary: err.Error()})
1✔
563
                return
1✔
564
        }
1✔
565
        if response != (verifier.Response{}) && response.FlowVersion == verifier.SAME_DEVICE {
2✔
566
                if response.Nonce != "" {
1✔
UNCOV
567
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s&nonce=%s", response.RedirectTarget, response.SessionId, response.Code, response.Nonce))
×
568
                } else {
1✔
569
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", response.RedirectTarget, response.SessionId, response.Code))
1✔
570
                }
1✔
571
                return
1✔
UNCOV
572
        } else if response != (verifier.Response{}) && response.FlowVersion == verifier.CROSS_DEVICE_V2 {
×
573
                sendRedirect(c, response.SessionId, response.Code, response.RedirectTarget)
×
574
        }
×
UNCOV
575
        logging.Log().Debugf("Successfully authenticated %s.", state)
×
UNCOV
576
        c.JSON(http.StatusOK, gin.H{})
×
577
}
578

579
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
UNCOV
580
func VerifierAPIJWKS(c *gin.Context) {
×
UNCOV
581
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
UNCOV
582
}
×
583

584
// VerifierAPIOpenID
585
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
UNCOV
586

×
UNCOV
587
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
UNCOV
588
        if err != nil {
×
NEW
UNCOV
589
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
UNCOV
590
                return
×
591
        }
×
592
        c.JSON(http.StatusOK, metadata)
×
593
}
594

595
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
596
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
597
        state, stateExists := c.GetQuery("state")
1✔
598
        if !stateExists {
2✔
599
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
600
                // early exit
1✔
601
                return
1✔
602
        }
1✔
603

604
        callback, callbackExists := c.GetQuery("client_callback")
1✔
605
        if !callbackExists {
2✔
606
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoCallback)
1✔
607
                // early exit
1✔
608
                return
1✔
609
        }
1✔
610
        protocol := "https"
1✔
611
        if c.Request.TLS == nil {
2✔
612
                protocol = "http"
1✔
613
        }
1✔
614
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
615
        if !clientIdExists {
2✔
616
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
617
        }
1✔
618

619
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
620
        if !requestModeExists {
2✔
621
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
622
                requestMode = DEFAULT_REQUEST_MODE
1✔
623
        }
1✔
624

625
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, "", requestMode)
1✔
626
        if err != nil {
2✔
627
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
628
                return
1✔
629
        }
1✔
630
        c.String(http.StatusOK, connectionString)
1✔
631
}
632

633
type ClientAssertion struct {
634
        Iss string   `json:"iss"`
635
        Aud []string `json:"aud"`
636
        Sub string   `json:"sub"`
637
        Exp int      `json:"exp"`
638
        Iat int      `json:"iat"`
639
}
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