Skip to content

Commit

Permalink
Merge pull request #46 from FIWARE/support-holder-verification
Browse files Browse the repository at this point in the history
verify the holder if configured
  • Loading branch information
wistefan authored Jan 15, 2025
2 parents cb364db + e02292a commit 580780f
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 21 deletions.
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

0 comments on commit 580780f

Please sign in to comment.