Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verify the holder if configured #46

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion config/configClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

const SERVICES_PATH = "service"


const SERVICE_DEFAULT_SCOPE = ""

var ErrorCcsNoResponse = errors.New("no_response_from_ccs")
Expand Down Expand Up @@ -53,6 +52,15 @@ type Credential struct {
TrustedParticipantsLists []string `json:"trustedParticipantsLists,omitempty" mapstructure:"trustedParticipantsLists,omitempty"`
// A list of (EBSI Trusted Issuers Registry compatible) endpoints to retrieve the trusted issuers from. The attributes need to be formated to comply with the verifiers requirements.
TrustedIssuersLists []string `json:"trustedIssuersLists,omitempty" mapstructure:"trustedIssuersLists,omitempty"`
// Configuration of Holder Verfification
HolderVerification HolderVerification `json:"holderVerification" mapstructure:"holderVerification"`
}

type HolderVerification struct {
// should holder verification be enabled
Enabled bool `json:"enabled" mapstructure:"enabled"`
// the claim containing the holder
Claim string `json:"claim" mapstructure:"claim"`
}

func (cs ConfiguredService) GetRequiredCredentialTypes(scope string) []string {
Expand Down
7 changes: 4 additions & 3 deletions config/configClient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ func Test_getServices(t *testing.T) {
}
assert.NotEmpty(t, services)
expectedData := []ConfiguredService{
ConfiguredService{
{
Id: "service_all",
DefaultOidcScope: "did_write",
ServiceScopes: map[string][]Credential{
"did_write": []Credential{
Credential{
"did_write": {
{
Type: "VerifiableCredential",
TrustedParticipantsLists: []string{"https://tir-pdc.gaia-x.fiware.dev"},
TrustedIssuersLists: []string{"https://til-pdc.gaia-x.fiware.dev"},
HolderVerification: HolderVerification{Enabled: false, Claim: "subject"},
},
},
},
Expand Down
6 changes: 5 additions & 1 deletion config/data/ccs_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
],
"trustedIssuersLists": [
"https://til-pdc.gaia-x.fiware.dev"
]
],
"holderVerification": {
"enabled": false,
"claim": "subject"
}
}
]
}
Expand Down
16 changes: 16 additions & 0 deletions verifier/credentialsConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type CredentialsConfig interface {
GetTrustedIssuersLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []string, err error)
// The credential types that are required for the given service and scope
RequiredCredentialTypes(serviceIdentifier string, scope string) (credentialTypes []string, err error)
// Get holder verification
GetHolderVerification(serviceIdentifier string, scope string, credentialType string) (isEnabled bool, holderClaim string, err error)
}

type ServiceBackedCredentialsConfig struct {
Expand Down Expand Up @@ -159,3 +161,17 @@ func (cc ServiceBackedCredentialsConfig) GetTrustedIssuersLists(serviceIdentifie
logging.Log().Debugf("No trusted issuers for %s - %s", serviceIdentifier, credentialType)
return []string{}, nil
}

func (cc ServiceBackedCredentialsConfig) GetHolderVerification(serviceIdentifier string, scope string, credentialType string) (isEnabled bool, holderClaim string, err error) {
logging.Log().Debugf("Get holder verification for %s - %s - %s.", serviceIdentifier, scope, credentialType)
cacheEntry, hit := common.GlobalCache.ServiceCache.Get(serviceIdentifier)
if hit {
credential, ok := cacheEntry.(config.ConfiguredService).GetCredential(scope, credentialType)
if ok {
logging.Log().Debugf("Found holder verification %v:%s for %s - %s", credential.HolderVerification.Enabled, credential.HolderVerification.Claim, serviceIdentifier, credentialType)
return credential.HolderVerification.Enabled, credential.HolderVerification.Claim, nil
}
}
logging.Log().Debugf("No holder verification for %s - %s", serviceIdentifier, credentialType)
return false, "", nil
}
34 changes: 34 additions & 0 deletions verifier/holder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package verifier

import (
"strings"

"github.com/fiware/VCVerifier/logging"
"github.com/trustbloc/vc-go/verifiable"
)

type HolderValidationService struct{}

func (hvs *HolderValidationService) ValidateVC(verifiableCredential *verifiable.Credential, validationContext ValidationContext) (result bool, err error) {
logging.Log().Debugf("Validate holder for %s", logging.PrettyPrintObject(verifiableCredential))
defer func() {
if recErr := recover(); recErr != nil {
logging.Log().Warnf("Was not able to convert context. Err: %v", recErr)
err = ErrorCannotConverContext
}
}()
holderContext := validationContext.(HolderValidationContext)

path := strings.Split(holderContext.claim, ".")
pathLength := len(path)

credentialJson := verifiableCredential.ToRawJSON()
currentClaim := credentialJson["credentialSubject"].(map[string]interface{})
for i, p := range path {
if i == pathLength-1 {
return currentClaim[p].(string) == holderContext.holder, err
}
currentClaim = currentClaim[p].(verifiable.JSONObject)
}
return false, err
}
63 changes: 63 additions & 0 deletions verifier/holder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package verifier

import (
"testing"

"github.com/fiware/VCVerifier/logging"
"github.com/trustbloc/vc-go/verifiable"
)

func TestValidateVC(t *testing.T) {

type test struct {
testName string
credentialToVerifiy verifiable.Credential
validationContext ValidationContext
expectedResult bool
}
tests := []test{
{testName: "If the holder is correct, the vc should be allowed.", credentialToVerifiy: getCredentialWithHolder("subject", "holder"), validationContext: HolderValidationContext{claim: "subject", holder: "holder"}, expectedResult: true},
{testName: "If the holder is correct inside the sub element, the vc should be allowed.", credentialToVerifiy: getCredentialWithHolderInSubelement("holder"), validationContext: HolderValidationContext{claim: "sub.holder", holder: "holder"}, expectedResult: true},
{testName: "If the holder is not correct, the vc should be rejected.", credentialToVerifiy: getCredentialWithHolder("subject", "holder"), validationContext: HolderValidationContext{claim: "subject", holder: "someOneElse"}, expectedResult: false},
{testName: "If the holder is not correct inside the sub element, the vc should be rejected.", credentialToVerifiy: getCredentialWithHolderInSubelement("holder"), validationContext: HolderValidationContext{claim: "sub.holder", holder: "someOneElse"}, expectedResult: false},
}
for _, tc := range tests {
t.Run(tc.testName, func(t *testing.T) {

logging.Log().Info("TestValidateVC +++++++++++++++++ Running test: ", tc.testName)

holderValidationService := HolderValidationService{}

result, _ := holderValidationService.ValidateVC(&tc.credentialToVerifiy, tc.validationContext)
if result != tc.expectedResult {
t.Errorf("%s - Expected result %v but was %v.", tc.testName, tc.expectedResult, result)
return
}
})
}
}

func getCredentialWithHolder(holderClaim, holder string) verifiable.Credential {
vc, _ := verifiable.CreateCredential(verifiable.CredentialContents{
Issuer: &verifiable.Issuer{ID: "did:test:issuer"},
Types: []string{"VerifiableCredential"},
Subject: []verifiable.Subject{
{
CustomFields: map[string]interface{}{holderClaim: holder},
},
}}, verifiable.CustomFields{})
return *vc
}

func getCredentialWithHolderInSubelement(holder string) verifiable.Credential {

vc, _ := verifiable.CreateCredential(verifiable.CredentialContents{
Issuer: &verifiable.Issuer{ID: "did:test:issuer"},
Types: []string{"VerifiableCredential"},
Subject: []verifiable.Subject{
{
CustomFields: map[string]interface{}{"sub": map[string]interface{}{"holder": holder}},
},
}}, verifiable.CustomFields{})
return *vc
}
47 changes: 47 additions & 0 deletions verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ func (trvc TrustRegistriesValidationContext) GetRequiredCredentialTypes() []stri
return removeDuplicate(requiredTypes)
}

type HolderValidationContext struct {
claim string
holder string
}

func (hvc HolderValidationContext) GetClaim() string {
return hvc.claim
}

func (hvc HolderValidationContext) GetHolder() string {
return hvc.holder
}

func removeDuplicate[T string | int](sliceList []T) []T {
allKeys := make(map[T]bool)
list := []T{}
Expand Down Expand Up @@ -367,6 +380,7 @@ func (v *CredentialVerifier) GenerateToken(clientId, subject, audience string, s
// collect all submitted credential types
credentialsByType := map[string][]*verifiable.Credential{}
credentialTypes := []string{}
holder := verifiablePresentation.Holder
for _, vc := range verifiablePresentation.Credentials() {
for _, credentialType := range vc.Contents().Types {
if _, ok := credentialsByType[credentialType]; !ok {
Expand All @@ -392,6 +406,23 @@ func (v *CredentialVerifier) GenerateToken(clientId, subject, audience string, s
}
}
for _, credential := range credentialsNeededForScope {
holderValidationContexts, err := v.getHolderValidationContext(clientId, scope, credentialTypes, holder)
if err != nil {
logging.Log().Warnf("Was not able to create the holder validation context. Credential will be rejected. Err: %v", err)
return 0, "", ErrorVerficationContextSetup
}
holderValidationService := HolderValidationService{}
for _, holderValidationContext := range holderValidationContexts {
result, err := holderValidationService.ValidateVC(credential, holderValidationContext)
if err != nil {
logging.Log().Warnf("Failed to verify credential %s. Err: %v", logging.PrettyPrintObject(credential), err)
return 0, "", err
}
if !result {
logging.Log().Infof("VC %s is not valid.", logging.PrettyPrintObject(credential))
return 0, "", ErrorInvalidVC
}
}
for _, verificationService := range v.validationServices {
result, err := verificationService.ValidateVC(credential, verificationContext)
if err != nil {
Expand Down Expand Up @@ -523,6 +554,22 @@ func (v *CredentialVerifier) AuthenticationResponse(state string, verifiablePres
}
}

func (v *CredentialVerifier) getHolderValidationContext(clientId string, scope string, credentialTypes []string, holder string) (validationContext []HolderValidationContext, err error) {
validationContexts := []HolderValidationContext{}
for _, credentialType := range credentialTypes {
isEnabled, claim, err := v.credentialsConfig.GetHolderVerification(clientId, scope, credentialType)
if err != nil {
logging.Log().Warnf("Was not able to get valid holder verification config for client %s, scope %s and type %s. Err: %v", clientId, scope, credentialType, err)
return validationContext, err
}
if !isEnabled {
continue
}
validationContexts = append(validationContext, HolderValidationContext{claim: claim, holder: holder})
}
return validationContexts, err
}

func (v *CredentialVerifier) getTrustRegistriesValidationContext(clientId string, credentialTypes []string) (verificationContext TrustRegistriesValidationContext, err error) {
trustedIssuersLists := map[string][]string{}
trustedParticipantsRegistries := map[string][]string{}
Expand Down
Loading
Loading