From cbbdea0a1f21ba08ce4386b45b815c2fd4780b68 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Fri, 8 Dec 2023 17:12:25 +0100 Subject: [PATCH] Discovery: require aud claim to be service ID --- discovery/module.go | 14 ++++++++++++++ discovery/module_test.go | 18 +++++++++++++++--- discovery/test.go | 8 ++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/discovery/module.go b/discovery/module.go index ce7fd89642..f9c5286349 100644 --- a/discovery/module.go +++ b/discovery/module.go @@ -122,6 +122,10 @@ func (m *Module) Add(serviceID string, presentation vc.VerifiablePresentation) e if presentation.ID == nil { return errors.New("presentation does not have an ID") } + // Make sure the presentation is intended for this service + if err := validateAudience(definition, presentation.JWT().Audience()); err != nil { + return err + } expiration := presentation.JWT().Expiration() if expiration.IsZero() { return errors.New("presentation does not have an expiration") @@ -206,6 +210,16 @@ func (m *Module) validateRetraction(serviceID string, presentation vc.Verifiable return nil } +// validateAudience checks if the given audience of the presentation matches the service ID. +func validateAudience(service ServiceDefinition, audience []string) error { + for _, audienceID := range audience { + if audienceID == service.ID { + return nil + } + } + return errors.New("aud claim is missing or invalid") +} + func (m *Module) Get(serviceID string, startAt Timestamp) ([]vc.VerifiablePresentation, *Timestamp, error) { if _, exists := m.services[serviceID]; !exists { return nil, nil, ErrServiceNotFound diff --git a/discovery/module_test.go b/discovery/module_test.go index 4c6d96b570..dde222e5fe 100644 --- a/discovery/module_test.go +++ b/discovery/module_test.go @@ -20,6 +20,7 @@ package discovery import ( "errors" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/storage" @@ -82,6 +83,7 @@ func Test_Module_Add(t *testing.T) { t.Run("no expiration", func(t *testing.T) { m, _ := setupModule(t, storageEngine) err := m.Add(testServiceID, createPresentationCustom(aliceDID, func(claims map[string]interface{}, _ *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} delete(claims, "exp") })) assert.EqualError(t, err, "presentation does not have an expiration") @@ -90,6 +92,7 @@ func Test_Module_Add(t *testing.T) { m, _ := setupModule(t, storageEngine) vpWithoutID := createPresentationCustom(aliceDID, func(claims map[string]interface{}, _ *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} delete(claims, "jti") }, vcAlice) err := m.Add(testServiceID, vpWithoutID) @@ -122,9 +125,12 @@ func Test_Module_Add(t *testing.T) { m, _ := setupModule(t, storageEngine) vcAlice := createCredential(authorityDID, aliceDID, nil, func(claims map[string]interface{}) { + claims[jwt.AudienceKey] = []string{testServiceID} claims["exp"] = time.Now().Add(time.Hour) }) - vpAlice := createPresentation(aliceDID, vcAlice) + vpAlice := createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} + }, vcAlice) err := m.Add(testServiceID, vpAlice) assert.EqualError(t, err, "presentation is valid longer than the credential(s) it contains") }) @@ -132,7 +138,9 @@ func Test_Module_Add(t *testing.T) { m, _ := setupModule(t, storageEngine) // Presentation Definition only allows did:example DIDs - otherVP := createPresentation(unsupportedDID, createCredential(unsupportedDID, unsupportedDID, nil, nil)) + otherVP := createPresentationCustom(unsupportedDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} + }, createCredential(unsupportedDID, unsupportedDID, nil, nil)) err := m.Add(testServiceID, otherVP) require.ErrorContains(t, err, "presentation does not fulfill Presentation ServiceDefinition") @@ -144,6 +152,7 @@ func Test_Module_Add(t *testing.T) { vpAliceRetract := createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { vp.Type = append(vp.Type, retractionPresentationType) claims["retract_jti"] = vpAlice.ID.String() + claims[jwt.AudienceKey] = []string{testServiceID} }) t.Run("ok", func(t *testing.T) { m, presentationVerifier := setupModule(t, storageEngine) @@ -163,14 +172,16 @@ func Test_Module_Add(t *testing.T) { m, _ := setupModule(t, storageEngine) vp := createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { vp.Type = append(vp.Type, retractionPresentationType) + claims[jwt.AudienceKey] = []string{testServiceID} }, vcAlice) err := m.Add(testServiceID, vp) assert.EqualError(t, err, "retraction presentation must not contain credentials") }) t.Run("missing 'retract_jti' claim", func(t *testing.T) { m, _ := setupModule(t, storageEngine) - vp := createPresentationCustom(aliceDID, func(_ map[string]interface{}, vp *vc.VerifiablePresentation) { + vp := createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { vp.Type = append(vp.Type, retractionPresentationType) + claims[jwt.AudienceKey] = []string{testServiceID} }) err := m.Add(testServiceID, vp) assert.EqualError(t, err, "retraction presentation does not contain 'retract_jti' claim") @@ -180,6 +191,7 @@ func Test_Module_Add(t *testing.T) { vp := createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { vp.Type = append(vp.Type, retractionPresentationType) claims["retract_jti"] = 10 + claims[jwt.AudienceKey] = []string{testServiceID} }) err := m.Add(testServiceID, vp) assert.EqualError(t, err, "retraction presentation 'retract_jti' claim is not a string") diff --git a/discovery/test.go b/discovery/test.go index ead7807120..15b30f0f56 100644 --- a/discovery/test.go +++ b/discovery/test.go @@ -115,14 +115,18 @@ func init() { "familyName": "Jones", }, }, nil) - vpAlice = createPresentation(aliceDID, vcAlice) + vpAlice = createPresentationCustom(aliceDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} + }, vcAlice) vcBob = createCredential(authorityDID, bobDID, map[string]interface{}{ "person": map[string]interface{}{ "givenName": "Bob", "familyName": "Jomper", }, }, nil) - vpBob = createPresentation(bobDID, vcBob) + vpBob = createPresentationCustom(bobDID, func(claims map[string]interface{}, vp *vc.VerifiablePresentation) { + claims[jwt.AudienceKey] = []string{testServiceID} + }, vcBob) } func createCredential(issuerDID did.DID, subjectDID did.DID, credentialSubject map[string]interface{}, claimVisitor func(map[string]interface{})) vc.VerifiableCredential {