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

FIWARE / VCVerifier / 22714353581

05 Mar 2026 10:38AM UTC coverage: 50.171% (+6.1%) from 44.081%
22714353581

Pull #81

github

wistefan
Step 12: Remove all trustbloc dependencies (#12)

## Summary
- Run `go mod tidy` to remove `trustbloc/did-go`, `trustbloc/vc-go`, and `trustbloc/kms-go` from go.mod/go.sum
- 145 lines removed from go.sum
- Zero trustbloc references remain in any `.go`, `go.mod`, or `go.sum` files

## Test plan
- [x] `go build ./...` succeeds
- [x] `go test ./...` — all 8 packages pass
- [x] `grep -r trustbloc` returns nothing

Co-authored-by: Stefan Wiedemann <wistefan@googlemail.com>
Reviewed-on: http://localhost:3000/wistefan/verifier/pulls/12
Reviewed-by: wistefan <wistefan@googlemail.com>
Co-authored-by: claude <claude@gitea.com>
Co-committed-by: claude <claude@gitea.com>
Pull Request #81: Remove trustbloc libraries

860 of 1397 new or added lines in 23 files covered. (61.56%)

90 existing lines in 3 files now uncovered.

2490 of 4963 relevant lines covered (50.17%)

0.57 hits per line

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

51.12
/verifier/presentation_parser.go
1
package verifier
2

3
import (
4
        "context"
5
        "encoding/base64"
6
        "encoding/json"
7
        "errors"
8
        "net/http"
9
        "strings"
10
        "time"
11

12
        "github.com/fiware/VCVerifier/common"
13
        configModel "github.com/fiware/VCVerifier/config"
14
        "github.com/fiware/VCVerifier/did"
15
        "github.com/fiware/VCVerifier/jades"
16
        "github.com/fiware/VCVerifier/logging"
17
        "github.com/hellofresh/health-go/v5"
18
        "github.com/lestrrat-go/jwx/v3/jwk"
19
)
20

21
var ErrorNoValidationEndpoint = errors.New("no_validation_endpoint_configured")
22
var ErrorNoValidationHost = errors.New("no_validation_host_configured")
23
var ErrorInvalidSdJwt = errors.New("credential_is_not_sd_jwt")
24
var ErrorPresentationNoCredentials = errors.New("presentation_not_contains_credentials")
25
var ErrorInvalidProof = errors.New("invalid_vp_proof")
26
var ErrorVCNotArray = errors.New("verifiable_credential_not_array")
27
var ErrorInvalidJWTFormat = errors.New("invalid_jwt_format")
28
var ErrorCnfKeyMismatch = errors.New("cnf_key_does_not_match_vp_signer")
29

30
// allow singleton access to the parser
31
var presentationParser PresentationParser
32

33
// allow singleton access to the parser
34
var sdJwtParser SdJwtParser
35

36
// parser interface
37
type PresentationParser interface {
38
        ParsePresentation(tokenBytes []byte) (*common.Presentation, error)
39
}
40

41
type SdJwtParser interface {
42
        Parse(tokenString string) (map[string]interface{}, error)
43
        ParseWithSdJwt(tokenBytes []byte) (presentation *common.Presentation, err error)
44
}
45

46
type ConfigurablePresentationParser struct {
47
        ProofChecker *JWTProofChecker
48
}
49

50
type ConfigurableSdJwtParser struct {
51
        ProofChecker *JWTProofChecker
52
}
53

54
/**
55
* Global singelton access to the parser
56
**/
57
func GetSdJwtParser() SdJwtParser {
×
58
        if sdJwtParser == nil {
×
59
                logging.Log().Error("SdJwtParser is not initialized.")
×
60
        }
×
61
        return sdJwtParser
×
62
}
63

64
/**
65
* Global singelton access to the parser
66
**/
67
func GetPresentationParser() PresentationParser {
×
68
        if presentationParser == nil {
×
69
                logging.Log().Error("PresentationParser is not initialized.")
×
70
        }
×
71
        return presentationParser
×
72
}
73

74
// init the presentation parser depending on the config, either with or without did:elsi support
75
func InitPresentationParser(config *configModel.Configuration, healthCheck *health.Health) error {
×
76
        elsiConfig := &config.Elsi
×
77
        err := validateConfig(elsiConfig)
×
78
        if err != nil {
×
79
                logging.Log().Warnf("No valid elsi configuration provided. Error: %v", err)
×
80
                return err
×
81
        }
×
82

NEW
83
        registry := did.NewRegistry(did.WithVDR(did.NewWebVDR()), did.WithVDR(did.NewKeyVDR()), did.WithVDR(did.NewJWKVDR()))
×
NEW
84

×
NEW
85
        var jAdESValidator jades.JAdESValidator
×
86
        if elsiConfig.Enabled {
×
NEW
87
                externalValidator := &jades.ExternalJAdESValidator{
×
88
                        HttpClient:        &http.Client{},
×
89
                        ValidationAddress: buildAddress(elsiConfig.ValidationEndpoint.Host, elsiConfig.ValidationEndpoint.ValidationPath),
×
NEW
90
                        HealthAddress:     buildAddress(elsiConfig.ValidationEndpoint.Host, elsiConfig.ValidationEndpoint.HealthPath),
×
91
                }
×
NEW
92
                jAdESValidator = externalValidator
×
93

×
94
                healthCheck.Register(health.Config{
×
95
                        Name:      "JAdES-Validator",
×
96
                        Timeout:   time.Second * 5,
×
97
                        SkipOnErr: false,
×
98
                        Check: func(ctx context.Context) error {
×
NEW
99
                                return externalValidator.IsReady()
×
100
                        },
×
101
                })
102
        }
103

NEW
104
        checker := NewJWTProofChecker(registry, jAdESValidator)
×
NEW
105
        presentationParser = &ConfigurablePresentationParser{ProofChecker: checker}
×
NEW
106
        sdJwtParser = &ConfigurableSdJwtParser{
×
NEW
107
                ProofChecker: checker,
×
108
        }
×
109

×
110
        return nil
×
111
}
112

113
func validateConfig(elsiConfig *configModel.Elsi) error {
1✔
114
        if !elsiConfig.Enabled {
2✔
115
                return nil
1✔
116
        }
1✔
117
        if elsiConfig.ValidationEndpoint == nil {
2✔
118
                return ErrorNoValidationEndpoint
1✔
119
        }
1✔
120
        if elsiConfig.ValidationEndpoint.Host == "" {
2✔
121
                return ErrorNoValidationHost
1✔
122
        }
1✔
123
        return nil
1✔
124
}
125

126
func buildAddress(host, path string) string {
1✔
127
        return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
1✔
128
}
1✔
129

130
// ParsePresentation parses a VP from JWT or JSON-LD format.
131
func (cpp *ConfigurablePresentationParser) ParsePresentation(tokenBytes []byte) (*common.Presentation, error) {
1✔
132
        trimmed := strings.TrimSpace(string(tokenBytes))
1✔
133
        if len(trimmed) > 0 && trimmed[0] == '{' {
2✔
134
                return parseJSONLDPresentation([]byte(trimmed))
1✔
135
        }
1✔
136
        return cpp.parseJWTPresentation(tokenBytes)
1✔
137
}
138

139
// parseJWTPresentation parses a JWT-encoded VP, verifies the VP signature, and parses embedded VCs.
140
// If a VC contains a cnf (confirmation) claim, it is verified against the VP signer's key (RFC 7800).
141
func (cpp *ConfigurablePresentationParser) parseJWTPresentation(tokenBytes []byte) (*common.Presentation, error) {
1✔
142
        var payload []byte
1✔
143
        var holderKey jwk.Key
1✔
144
        var err error
1✔
145
        if cpp.ProofChecker != nil {
2✔
146
                payload, holderKey, err = cpp.ProofChecker.VerifyJWTAndReturnKey(tokenBytes)
1✔
147
        } else {
1✔
NEW
148
                payload, err = extractJWTPayload(tokenBytes)
×
NEW
149
        }
×
150
        if err != nil {
2✔
151
                return nil, err
1✔
152
        }
1✔
153

NEW
154
        var claims map[string]interface{}
×
NEW
155
        if err := json.Unmarshal(payload, &claims); err != nil {
×
NEW
156
                return nil, err
×
NEW
157
        }
×
158

NEW
159
        vpClaim, ok := claims[common.JWTClaimVP].(map[string]interface{})
×
NEW
160
        if !ok {
×
NEW
161
                return nil, ErrorPresentationNoCredentials
×
NEW
162
        }
×
163

NEW
164
        pres, _ := common.NewPresentation()
×
NEW
165
        if holderKey != nil {
×
NEW
166
                pres.SetHolderKey(holderKey)
×
NEW
167
        }
×
168

169
        // Holder from iss claim (standard JWT VP mapping)
NEW
170
        if iss, ok := claims[common.JWTClaimIss].(string); ok {
×
NEW
171
                pres.Holder = iss
×
NEW
172
        }
×
173

NEW
174
        vcsRaw, ok := vpClaim[common.VPKeyVerifiableCredential]
×
NEW
175
        if !ok {
×
NEW
176
                return pres, nil
×
NEW
177
        }
×
178

NEW
179
        vcList, ok := vcsRaw.([]interface{})
×
NEW
180
        if !ok {
×
NEW
181
                return nil, ErrorVCNotArray
×
NEW
182
        }
×
183

NEW
184
        for _, vc := range vcList {
×
NEW
185
                switch v := vc.(type) {
×
NEW
186
                case string:
×
NEW
187
                        cred, err := cpp.parseJWTCredential([]byte(v))
×
NEW
188
                        if err != nil {
×
NEW
189
                                return nil, err
×
NEW
190
                        }
×
191
                        // Verify cryptographic holder binding (cnf) if present
NEW
192
                        if holderKey != nil {
×
NEW
193
                                if err := verifyCnfBinding(cred, holderKey); err != nil {
×
NEW
194
                                        return nil, err
×
NEW
195
                                }
×
196
                        }
NEW
197
                        pres.AddCredentials(cred)
×
NEW
198
                case map[string]interface{}:
×
NEW
199
                        cred, err := parseJSONLDCredential(v)
×
NEW
200
                        if err != nil {
×
NEW
201
                                return nil, err
×
NEW
202
                        }
×
NEW
203
                        pres.AddCredentials(cred)
×
204
                }
205
        }
206

NEW
207
        return pres, nil
×
208
}
209

210
// parseJWTCredential parses and verifies a JWT-encoded VC.
NEW
211
func (cpp *ConfigurablePresentationParser) parseJWTCredential(tokenBytes []byte) (*common.Credential, error) {
×
NEW
212
        var payload []byte
×
NEW
213
        var err error
×
NEW
214
        if cpp.ProofChecker != nil {
×
NEW
215
                payload, err = cpp.ProofChecker.VerifyJWT(tokenBytes)
×
NEW
216
        } else {
×
NEW
217
                payload, err = extractJWTPayload(tokenBytes)
×
NEW
218
        }
×
NEW
219
        if err != nil {
×
NEW
220
                return nil, err
×
NEW
221
        }
×
222

NEW
223
        var claims map[string]interface{}
×
NEW
224
        if err := json.Unmarshal(payload, &claims); err != nil {
×
NEW
225
                return nil, err
×
NEW
226
        }
×
227

NEW
228
        return jwtClaimsToCredential(claims)
×
229
}
230

231
// jwtClaimsToCredential maps JWT VC claims to a common.Credential.
232
// Extracts standard JWT claims (iss, jti, nbf, iat, exp), VC-specific claims
233
// (type, @context, credentialSubject, credentialStatus), and the cnf claim
234
// for cryptographic holder binding verification.
235
func jwtClaimsToCredential(claims map[string]interface{}) (*common.Credential, error) {
1✔
236
        contents := common.CredentialContents{}
1✔
237

1✔
238
        if iss, ok := claims[common.JWTClaimIss].(string); ok {
2✔
239
                contents.Issuer = &common.Issuer{ID: iss}
1✔
240
        }
1✔
241
        if jti, ok := claims[common.JWTClaimJti].(string); ok {
2✔
242
                contents.ID = jti
1✔
243
        }
1✔
244

245
        customFields := common.CustomFields{}
1✔
246

1✔
247
        vcClaim, _ := claims[common.JWTClaimVC].(map[string]interface{})
1✔
248
        if vcClaim != nil {
2✔
249
                if types, ok := vcClaim[common.JSONLDKeyType].([]interface{}); ok {
2✔
250
                        for _, t := range types {
2✔
251
                                if s, ok := t.(string); ok {
2✔
252
                                        contents.Types = append(contents.Types, s)
1✔
253
                                }
1✔
254
                        }
255
                }
256
                if ctxs, ok := vcClaim[common.JSONLDKeyContext].([]interface{}); ok {
2✔
257
                        for _, c := range ctxs {
2✔
258
                                if s, ok := c.(string); ok {
2✔
259
                                        contents.Context = append(contents.Context, s)
1✔
260
                                }
1✔
261
                        }
262
                }
263
                if subject, ok := vcClaim[common.VCKeyCredentialSubject].(map[string]interface{}); ok {
2✔
264
                        s := common.Subject{CustomFields: common.CustomFields{}}
1✔
265
                        if id, ok := subject[common.JSONLDKeyID].(string); ok {
2✔
266
                                s.ID = id
1✔
267
                        }
1✔
268
                        for k, v := range subject {
2✔
269
                                if k != common.JSONLDKeyID {
2✔
270
                                        s.CustomFields[k] = v
1✔
271
                                }
1✔
272
                        }
273
                        contents.Subject = []common.Subject{s}
1✔
274
                }
275

276
                // Extract credentialStatus for revocation checking (W3C VC Data Model 2.0 §7.1).
277
                if status, ok := vcClaim[common.VCKeyCredentialStatus].(map[string]interface{}); ok {
1✔
NEW
278
                        contents.Status = &common.TypedID{
×
NEW
279
                                ID:   stringFromMap(status, common.JSONLDKeyID),
×
NEW
280
                                Type: stringFromMap(status, common.JSONLDKeyType),
×
NEW
281
                        }
×
NEW
282
                }
×
283
        }
284

285
        if nbf, ok := claims[common.JWTClaimNbf].(float64); ok {
2✔
286
                t := time.Unix(int64(nbf), 0)
1✔
287
                contents.ValidFrom = &t
1✔
288
        } else if iat, ok := claims[common.JWTClaimIat].(float64); ok {
1✔
NEW
289
                t := time.Unix(int64(iat), 0)
×
NEW
290
                contents.ValidFrom = &t
×
NEW
291
        }
×
292
        if exp, ok := claims[common.JWTClaimExp].(float64); ok {
2✔
293
                t := time.Unix(int64(exp), 0)
1✔
294
                contents.ValidUntil = &t
1✔
295
        }
1✔
296

297
        // Preserve cnf (confirmation) claim for cryptographic holder binding (RFC 7800).
298
        if cnf, ok := claims[common.JWTClaimCnf]; ok {
1✔
NEW
299
                customFields[common.JWTClaimCnf] = cnf
×
NEW
300
        }
×
301

302
        cred, err := common.CreateCredential(contents, customFields)
1✔
303
        if err != nil {
1✔
NEW
304
                return nil, err
×
NEW
305
        }
×
306

307
        if vcClaim != nil {
2✔
308
                cred.SetRawJSON(vcClaim)
1✔
309
        }
1✔
310

311
        return cred, nil
1✔
312
}
313

314
// stringFromMap safely extracts a string value from a map.
NEW
315
func stringFromMap(m map[string]interface{}, key string) string {
×
NEW
316
        if v, ok := m[key].(string); ok {
×
NEW
317
                return v
×
NEW
318
        }
×
NEW
319
        return ""
×
320
}
321

322
// parseJSONLDPresentation parses a JSON-LD VP (no proof verification).
323
func parseJSONLDPresentation(data []byte) (*common.Presentation, error) {
1✔
324
        var vpMap map[string]interface{}
1✔
325
        if err := json.Unmarshal(data, &vpMap); err != nil {
1✔
NEW
326
                return nil, err
×
NEW
327
        }
×
328

329
        pres, _ := common.NewPresentation()
1✔
330
        if holder, ok := vpMap[common.VPKeyHolder].(string); ok {
2✔
331
                pres.Holder = holder
1✔
332
        }
1✔
333

334
        vcsRaw, ok := vpMap[common.VPKeyVerifiableCredential]
1✔
335
        if !ok {
1✔
NEW
336
                return pres, nil
×
NEW
337
        }
×
338

339
        vcList, ok := vcsRaw.([]interface{})
1✔
340
        if !ok {
1✔
NEW
341
                return pres, nil
×
NEW
342
        }
×
343

344
        for _, vc := range vcList {
2✔
345
                switch v := vc.(type) {
1✔
NEW
346
                case string:
×
NEW
347
                        logging.Log().Warn("JWT VC embedded in JSON-LD VP — parsing without signature verification")
×
NEW
348
                        cred, err := parseUnsignedJWTCredential(v)
×
NEW
349
                        if err != nil {
×
NEW
350
                                return nil, err
×
NEW
351
                        }
×
NEW
352
                        pres.AddCredentials(cred)
×
353
                case map[string]interface{}:
1✔
354
                        cred, err := parseJSONLDCredential(v)
1✔
355
                        if err != nil {
1✔
NEW
356
                                return nil, err
×
NEW
357
                        }
×
358
                        pres.AddCredentials(cred)
1✔
359
                }
360
        }
361

362
        return pres, nil
1✔
363
}
364

365
// extractJWTPayload decodes the payload from a JWT without signature verification.
NEW
366
func extractJWTPayload(token []byte) ([]byte, error) {
×
NEW
367
        parts := strings.SplitN(string(token), ".", 3)
×
NEW
368
        if len(parts) < 2 {
×
NEW
369
                return nil, ErrorInvalidJWTFormat
×
NEW
370
        }
×
NEW
371
        return base64.RawURLEncoding.DecodeString(parts[1])
×
372
}
373

374
// parseUnsignedJWTCredential extracts claims from a JWT VC without signature verification.
NEW
375
func parseUnsignedJWTCredential(tokenString string) (*common.Credential, error) {
×
NEW
376
        parts := strings.SplitN(tokenString, ".", 3)
×
NEW
377
        if len(parts) < 2 {
×
NEW
378
                return nil, ErrorInvalidJWTFormat
×
NEW
379
        }
×
NEW
380
        payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
×
NEW
381
        if err != nil {
×
NEW
382
                return nil, err
×
NEW
383
        }
×
NEW
384
        var claims map[string]interface{}
×
NEW
385
        if err := json.Unmarshal(payloadBytes, &claims); err != nil {
×
NEW
386
                return nil, err
×
NEW
387
        }
×
NEW
388
        return jwtClaimsToCredential(claims)
×
389
}
390

391
// parseJSONLDCredential parses a JSON-LD VC from a map.
392
func parseJSONLDCredential(vcMap map[string]interface{}) (*common.Credential, error) {
1✔
393
        contents := common.CredentialContents{}
1✔
394

1✔
395
        if id, ok := vcMap[common.JSONLDKeyID].(string); ok {
1✔
NEW
396
                contents.ID = id
×
NEW
397
        }
×
398
        if types, ok := vcMap[common.JSONLDKeyType].([]interface{}); ok {
2✔
399
                for _, t := range types {
2✔
400
                        if s, ok := t.(string); ok {
2✔
401
                                contents.Types = append(contents.Types, s)
1✔
402
                        }
1✔
403
                }
404
        }
405
        if ctxs, ok := vcMap[common.JSONLDKeyContext].([]interface{}); ok {
2✔
406
                for _, c := range ctxs {
2✔
407
                        if s, ok := c.(string); ok {
2✔
408
                                contents.Context = append(contents.Context, s)
1✔
409
                        }
1✔
410
                }
411
        }
412

413
        switch issuer := vcMap[common.VCKeyIssuer].(type) {
1✔
414
        case string:
1✔
415
                contents.Issuer = &common.Issuer{ID: issuer}
1✔
NEW
416
        case map[string]interface{}:
×
NEW
417
                if id, ok := issuer[common.JSONLDKeyID].(string); ok {
×
NEW
418
                        contents.Issuer = &common.Issuer{ID: id}
×
NEW
419
                }
×
420
        }
421

422
        if subject, ok := vcMap[common.VCKeyCredentialSubject].(map[string]interface{}); ok {
2✔
423
                s := common.Subject{CustomFields: common.CustomFields{}}
1✔
424
                if id, ok := subject[common.JSONLDKeyID].(string); ok {
2✔
425
                        s.ID = id
1✔
426
                }
1✔
427
                for k, v := range subject {
2✔
428
                        if k != common.JSONLDKeyID {
2✔
429
                                s.CustomFields[k] = v
1✔
430
                        }
1✔
431
                }
432
                contents.Subject = []common.Subject{s}
1✔
433
        }
434

435
        // Extract credentialStatus for revocation checking.
436
        if status, ok := vcMap[common.VCKeyCredentialStatus].(map[string]interface{}); ok {
1✔
NEW
437
                contents.Status = &common.TypedID{
×
NEW
438
                        ID:   stringFromMap(status, common.JSONLDKeyID),
×
NEW
439
                        Type: stringFromMap(status, common.JSONLDKeyType),
×
NEW
440
                }
×
NEW
441
        }
×
442

443
        cred, err := common.CreateCredential(contents, common.CustomFields{})
1✔
444
        if err != nil {
1✔
NEW
445
                return nil, err
×
NEW
446
        }
×
447
        cred.SetRawJSON(vcMap)
1✔
448
        return cred, nil
1✔
449
}
450

451
func (sjp *ConfigurableSdJwtParser) Parse(tokenString string) (map[string]interface{}, error) {
1✔
452
        var verifyFunc func([]byte) ([]byte, error)
1✔
453
        if sjp.ProofChecker != nil {
2✔
454
                verifyFunc = sjp.ProofChecker.VerifyJWT
1✔
455
        }
1✔
456
        return common.ParseSDJWT(tokenString, verifyFunc)
1✔
457
}
458

459
func (sjp *ConfigurableSdJwtParser) ClaimsToCredential(claims map[string]interface{}) (credential *common.Credential, err error) {
1✔
460

1✔
461
        issuer, i_ok := claims[common.JWTClaimIss]
1✔
462
        vct, vct_ok := claims[common.JWTClaimVct]
1✔
463
        if !i_ok || !vct_ok {
2✔
464
                logging.Log().Warnf("Token does not contain issuer(%v) or vct(%v).", i_ok, vct_ok)
1✔
465
                return credential, ErrorInvalidSdJwt
1✔
466
        }
1✔
467
        customFields := common.CustomFields{}
1✔
468
        for k, v := range claims {
2✔
469
                if k != common.JWTClaimIss && k != common.JWTClaimVct {
2✔
470
                        customFields[k] = v
1✔
471
                }
1✔
472
        }
473
        subject := common.Subject{CustomFields: customFields}
1✔
474
        contents := common.CredentialContents{Issuer: &common.Issuer{ID: issuer.(string)}, Types: []string{vct.(string)}, Subject: []common.Subject{subject}}
1✔
475
        return common.CreateCredential(contents, common.CustomFields{})
1✔
476
}
477

478
func (sjp *ConfigurableSdJwtParser) ParseWithSdJwt(tokenBytes []byte) (presentation *common.Presentation, err error) {
1✔
479
        logging.Log().Debug("Parse with SD-Jwt")
1✔
480

1✔
481
        tokenString := string(tokenBytes)
1✔
482
        payloadString := strings.Split(tokenString, ".")[1]
1✔
483
        payloadBytes, _ := base64.RawURLEncoding.DecodeString(payloadString)
1✔
484

1✔
485
        var vpMap map[string]interface{}
1✔
486
        if err := json.Unmarshal(payloadBytes, &vpMap); err != nil {
2✔
487
                logging.Log().Warnf("Failed to unmarshal VP payload: %v", err)
1✔
488
                return nil, err
1✔
489
        }
1✔
490

491
        vp, ok := vpMap[common.JWTClaimVP].(map[string]interface{})
1✔
492
        if !ok {
2✔
493
                logging.Log().Warn("VP token does not contain vp claim")
1✔
494
                return presentation, ErrorPresentationNoCredentials
1✔
495
        }
1✔
496

497
        vcs, ok := vp[common.VPKeyVerifiableCredential]
1✔
498
        if !ok {
2✔
499
                logging.Log().Warn("VP does not contain verifiableCredential")
1✔
500
                return presentation, ErrorPresentationNoCredentials
1✔
501
        }
1✔
502

503
        presentation, err = common.NewPresentation()
1✔
504
        if err != nil {
1✔
505
                return nil, err
×
506
        }
×
507

508
        presentation.Holder = vp[common.VPKeyHolder].(string)
1✔
509

1✔
510
        // due to dcql, we only need to take care of presentations containing credentials of the same type.
1✔
511
        for _, vc := range vcs.([]interface{}) {
2✔
512
                logging.Log().Debugf("The vc %s", vc.(string))
1✔
513
                parsed, err := sjp.Parse(vc.(string))
1✔
514
                if err != nil {
2✔
515
                        logging.Log().Warnf("Failed to parse SD-JWT VC: %v", err)
1✔
516
                        return nil, err
1✔
517
                }
1✔
518
                credential, err := sjp.ClaimsToCredential(parsed)
×
519
                if err != nil {
×
NEW
520
                        logging.Log().Warnf("Failed to create credential from SD-JWT claims: %v", err)
×
521
                        return nil, err
×
522
                }
×
523
                presentation.AddCredentials(credential)
×
524
        }
525

526
        // Verify VP JWT signature and capture holder key
NEW
527
        if sjp.ProofChecker != nil {
×
NEW
528
                _, holderKey, err := sjp.ProofChecker.VerifyJWTAndReturnKey(tokenBytes)
×
NEW
529
                if err != nil {
×
NEW
530
                        logging.Log().Warnf("VP JWT signature verification failed: %v", err)
×
NEW
531
                        return nil, ErrorInvalidProof
×
NEW
532
                }
×
NEW
533
                if holderKey != nil {
×
NEW
534
                        presentation.SetHolderKey(holderKey)
×
NEW
535
                }
×
536
        }
537

538
        return presentation, nil
×
539
}
540

541
// verifyCnfBinding checks the cnf (confirmation) claim in a credential against the VP signer's key.
542
// Per RFC 7800, if the credential contains a cnf.jwk, the key must match the VP signer's public key.
543
// If no cnf claim is present, the check is skipped (no error).
544
func verifyCnfBinding(cred *common.Credential, holderKey jwk.Key) error {
1✔
545
        cnfRaw, ok := cred.CustomFields()[common.JWTClaimCnf]
1✔
546
        if !ok {
2✔
547
                return nil
1✔
548
        }
1✔
549

550
        cnfMap, ok := cnfRaw.(map[string]interface{})
1✔
551
        if !ok {
1✔
NEW
552
                return nil
×
NEW
553
        }
×
554

555
        jwkRaw, ok := cnfMap[common.CnfKeyJWK]
1✔
556
        if !ok {
1✔
NEW
557
                return nil
×
NEW
558
        }
×
559

560
        jwkMap, ok := jwkRaw.(map[string]interface{})
1✔
561
        if !ok {
1✔
NEW
562
                return nil
×
NEW
563
        }
×
564

565
        cnfKeyBytes, err := json.Marshal(jwkMap)
1✔
566
        if err != nil {
1✔
NEW
567
                return ErrorCnfKeyMismatch
×
NEW
568
        }
×
569

570
        cnfKey, err := jwk.ParseKey(cnfKeyBytes)
1✔
571
        if err != nil {
1✔
NEW
572
                logging.Log().Warnf("Failed to parse cnf.jwk: %v", err)
×
NEW
573
                return ErrorCnfKeyMismatch
×
NEW
574
        }
×
575

576
        // Compare using JWK thumbprints (RFC 7638)
577
        if !jwk.Equal(cnfKey, holderKey) {
2✔
578
                logging.Log().Warn("CNF key does not match VP signer key")
1✔
579
                return ErrorCnfKeyMismatch
1✔
580
        }
1✔
581

582
        return nil
1✔
583
}
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