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

support verification with gaia-x registry #49

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 6 additions & 0 deletions GAIA_X.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Check trust anchor

allow to configure for a credential the registry to be used

-> check linked certificate chain at <registry>/api/trustAnchor/chain/file

9 changes: 8 additions & 1 deletion config/configClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,20 @@ type Credential struct {
// Type of the credential
Type string `json:"type" mapstructure:"type"`
// A list of (EBSI Trusted Issuers Registry compatible) endpoints to retrieve the trusted participants from.
TrustedParticipantsLists []string `json:"trustedParticipantsLists,omitempty" mapstructure:"trustedParticipantsLists,omitempty"`
TrustedParticipantsLists []TrustedParticipantsList `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 TrustedParticipantsList struct {
// Type of praticipants list to be used - either gaia-x or ebsi
Type string `json:"type" mapstructure:"type"`
// url of the list
Url string `json:"url" mapstructure:"url"`
}

type HolderVerification struct {
// should holder verification be enabled
Enabled bool `json:"enabled" mapstructure:"enabled"`
Expand Down
2 changes: 1 addition & 1 deletion config/configClient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Test_getServices(t *testing.T) {
"did_write": {
{
Type: "VerifiableCredential",
TrustedParticipantsLists: []string{"https://tir-pdc.gaia-x.fiware.dev"},
TrustedParticipantsLists: []TrustedParticipantsList{{Type: "ebsi", Url: "https://tir-pdc.gaia-x.fiware.dev"}},
TrustedIssuersLists: []string{"https://til-pdc.gaia-x.fiware.dev"},
HolderVerification: HolderVerification{Enabled: false, Claim: "subject"},
},
Expand Down
5 changes: 4 additions & 1 deletion config/data/ccs_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
{
"type": "VerifiableCredential",
"trustedParticipantsLists": [
"https://tir-pdc.gaia-x.fiware.dev"
{
"type": "ebsi",
"url": "https://tir-pdc.gaia-x.fiware.dev"
}
],
"trustedIssuersLists": [
"https://til-pdc.gaia-x.fiware.dev"
Expand Down
3 changes: 2 additions & 1 deletion config/data/config_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ configRepo:
someScope:
- type: VerifiableCredential
trustedParticipantsLists:
- https://tir-pdc.gaia-x.fiware.dev
- type: ebsi
url: https://tir-pdc.gaia-x.fiware.dev
trustedIssuersLists:
- https://til-pdc.gaia-x.fiware.dev

2 changes: 1 addition & 1 deletion config/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func Test_ReadConfig(t *testing.T) {
"someScope": {
{
Type: "VerifiableCredential",
TrustedParticipantsLists: []string{"https://tir-pdc.gaia-x.fiware.dev"},
TrustedParticipantsLists: []TrustedParticipantsList{{Type: "ebsi", Url: "https://tir-pdc.gaia-x.fiware.dev"}},
TrustedIssuersLists: []string{"https://til-pdc.gaia-x.fiware.dev"},
},
},
Expand Down
107 changes: 107 additions & 0 deletions gaiax/gaiaXClient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package gaiax

import (
"bytes"
"encoding/json"
"errors"
"net/http"
"strings"

"github.com/fiware/VCVerifier/common"
"github.com/fiware/VCVerifier/logging"
"github.com/trustbloc/did-go/doc/did"
"github.com/trustbloc/did-go/vdr"
)

const GAIAX_REGISTRY_TRUSTANCHOR_FILE = "/v2/api/trustAnchor/chain/file"

var ErrorUnresolvableDid = errors.New("unresolvable_did")

/**
* A client to retrieve infromation from EBSI-compatible TrustedIssuerRegistry APIs.
*/
type GaiaXHttpClient struct {
client common.HttpClient
// TODO: check if needed
didCache common.Cache
didRegistry *vdr.Registry
}

func (ghc GaiaXHttpClient) IsTrustedParticipant(registryEndpoint string, did string) (trusted bool) {

// 1. get jwk from did
didDocument, err := ghc.resolveIssuer(did)

if err != nil {
return false
}

// 2. verify at the registry
for _, verficationMethod := range didDocument.DIDDocument.VerificationMethod {
if verficationMethod.ID == did {
return ghc.verifiyIssuer(registryEndpoint, verficationMethod)
}
}

return false
}

func (ghc GaiaXHttpClient) verifiyIssuer(registryEndpoint string, verificationMethod did.VerificationMethod) (trusted bool) {
jwk := verificationMethod.JSONWebKey()

if jwk.CertificatesURL != nil {
return ghc.verifyFileChain(registryEndpoint, jwk.CertificatesURL.String())
}
// gaia-x did-json need to provide an x5u, thus x5c checks should not be required.
return false
}

func (ghc GaiaXHttpClient) verifyFileChain(registryEndpoint string, x5u string) (trusted bool) {
requestBody := FileChainRequest{Uri: x5u}

encodedRequest, err := json.Marshal(requestBody)
if err != nil {
logging.Log().Warnf("Was not able to build a valid certificate check bode. E: %v", err)
return false
}

request, err := http.NewRequest("POST", buildURL(registryEndpoint, GAIAX_REGISTRY_TRUSTANCHOR_FILE), bytes.NewBuffer(encodedRequest))

response, err := ghc.client.Do(request)
if err != nil {
logging.Log().Infof("Was not able to check cert chain %s at %s. E: %v", x5u, registryEndpoint, err)
return false
}
defer response.Body.Close()
if response.StatusCode != 200 {
logging.Log().Infof("x5u %s was not verified to be a trust anchor at %s.", x5u, registryEndpoint)
return false
}
// according to the doc, all 200s are valid chains, thus no need to parse the body
return true
}

func (ghc GaiaXHttpClient) resolveIssuer(did string) (didDocument *did.DocResolution, err error) {
didDocument, err = ghc.didRegistry.Resolve(did)
if err != nil {
logging.Log().Warnf("Was not able to resolve the issuer %s.", did)
return nil, ErrorUnresolvableDid
}
return didDocument, err
}

func buildURL(host, path string) string {
return strings.TrimSuffix(host, "/") + "/" + strings.TrimPrefix(path, "/")
}

type FileChainRequest struct {
Uri string `json:"uri"`
}

type CertChainRequest struct {
Certs string `json:"certs"`
}

type VerificationResult struct {
Result bool `json:"result"`
}
4 changes: 4 additions & 0 deletions gaiax/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func InitGaiaXRegistryVerificationService(url string) RegistryClient {
return &GaiaXRegistryClient{url}
}

func (rc *GaiaXRegistryClient) CheckTrustAnchor(trustAnchorAddress string) (bool, error) {
return true, nil
}

// TODO Could propably cache the response very generously as new issuers are not added often
func (rc *GaiaXRegistryClient) GetComplianceIssuers() ([]string, error) {
response, err := http.Get(rc.endpoint)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,4 @@ require (
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
)
2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -820,4 +820,4 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
13 changes: 5 additions & 8 deletions tir/tirClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var ErrorTirNoResponse = errors.New("no_response_from_tir")
var ErrorTirEmptyResponse = errors.New("empty_response_from_tir")

type TirClient interface {
IsTrustedParticipant(tirEndpoints []string, did string) (trusted bool)
IsTrustedParticipant(tirEndpoints string, did string) (trusted bool)
GetTrustedIssuer(tirEndpoints []string, did string) (exists bool, trustedIssuer TrustedIssuer, err error)
}

Expand Down Expand Up @@ -104,13 +104,10 @@ func NewTirHttpClient(tokenProvider TokenProvider, m2mConfig config.M2M, verifie
return TirHttpClient{client: httpGetClient, tirCache: tirCache, tilCache: tilCache}, err
}

func (tc TirHttpClient) IsTrustedParticipant(tirEndpoints []string, did string) (trusted bool) {
for _, tirEndpoint := range tirEndpoints {
logging.Log().Debugf("Check if a participant %s is trusted through %s.", did, tirEndpoint)
if tc.issuerExists(tirEndpoint, did) {
logging.Log().Debugf("Issuer %s is a trusted participant via %s.", did, tirEndpoint)
return true
}
func (tc TirHttpClient) IsTrustedParticipant(tirEndpoint string, did string) (trusted bool) {
if tc.issuerExists(tirEndpoint, did) {
logging.Log().Debugf("Issuer %s is a trusted participant via %s.", did, tirEndpoint)
return true
}
return false
}
Expand Down
18 changes: 6 additions & 12 deletions tir/tirClient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,27 @@ func TestIsTrustedParticipant(t *testing.T) {
type test struct {
testName string
testIssuer string
testEndpoints []string
testEndpoint string
mockResponses map[string]*http.Response
mockErrors map[string]error
expectedResult bool
}
tests := []test{
{testName: "The issuer should have been returned.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-tir.org"},
{testName: "The issuer should have been returned.", testIssuer: "did:web:test.org", testEndpoint: "https://my-tir.org",
mockResponses: map[string]*http.Response{"https://my-tir.org/v4/issuers/did:web:test.org": getIssuerResponse("did:web:test.org")}, expectedResult: true},
{testName: "The issuer should be returned, if its found at one of the endpoints", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-other-tir.org", "https://my-tir.org"},
mockResponses: map[string]*http.Response{"https://my-other-tir.org/v4/issuers/did:web:test.org": getNotFoundResponse(), "https://my-tir.org/v4/issuers/did:web:test.org": getIssuerResponse("did:web:test.org")}, expectedResult: true},
{testName: "The issuer should not be returned, if its nowhere found.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-other-tir.org", "https://my-tir.org"},
{testName: "The issuer should not be returned, if its nowhere found.", testIssuer: "did:web:test.org", testEndpoint: "https://my-other-tir.org",
mockResponses: map[string]*http.Response{"https://my-other-tir.org/v4/issuers/did:web:test.org": getNotFoundResponse(), "https://my-tir.org/v4/issuers/did:web:test.org": getNotFoundResponse()}, expectedResult: false},
{testName: "The issuer should be returned, even if an error is thrown at one endpoint.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-other-tir.org", "https://my-tir.org"},
mockResponses: map[string]*http.Response{"https://my-tir.org/v4/issuers/did:web:test.org": getIssuerResponse("did:web:test.org")}, mockErrors: map[string]error{"https://my-other-tir.org/v4/issuers/did:web:test.org": errors.New("something_bad")}, expectedResult: true},
{testName: "The issuer should be returned, even if something unparsable is returned at one endpoint.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-other-tir.org", "https://my-tir.org"},
mockResponses: map[string]*http.Response{"https://my-other-tir.org/v4/issuers/did:web:test.org": getUnparsableResponse(), "https://my-tir.org/v4/issuers/did:web:test.org": getIssuerResponse("did:web:test.org")}, expectedResult: true},
{testName: "The issuer not should be returned, if an error is thrown at the endpoint.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-erronous-tir.org"},
{testName: "The issuer not should be returned, if an error is thrown at the endpoint.", testIssuer: "did:web:test.org", testEndpoint: "https://my-erronous-tir.org",
mockErrors: map[string]error{"https://https://my-erronous-tir.org/v4/issuers/did:web:test.org": errors.New("something_bad")}, expectedResult: false},
{testName: "The issuer not should be returned, if the response cannot be parsed.", testIssuer: "did:web:test.org", testEndpoints: []string{"https://my-erronous-tir.org"},
{testName: "The issuer not should be returned, if the response cannot be parsed.", testIssuer: "did:web:test.org", testEndpoint: "https://my-erronous-tir.org",
mockResponses: map[string]*http.Response{"https://https://my-erronous-tir.org/v4/issuers/did:web:test.org": getUnparsableResponse()}, expectedResult: false},
}

for _, tc := range tests {
common.ResetGlobalCache()
t.Run(tc.testName, func(t *testing.T) {
tirClient := TirHttpClient{client: mockClient{responses: tc.mockResponses, errors: tc.mockErrors}, tilCache: mockCache{}, tirCache: mockCache{}}
isTrusted := tirClient.IsTrustedParticipant(tc.testEndpoints, tc.testIssuer)
isTrusted := tirClient.IsTrustedParticipant(tc.testEndpoint, tc.testIssuer)

if tc.expectedResult != isTrusted {
t.Errorf("%s - Expected the issuer to be trusted %v but was %v.", tc.testName, tc.expectedResult, isTrusted)
Expand Down
6 changes: 3 additions & 3 deletions verifier/credentialsConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type CredentialsConfig interface {
// should return the list of credentialtypes to be requested via the scope parameter
GetScope(serviceIdentifier string) (credentialTypes []string, err error)
// get (EBSI TrustedIssuersRegistry compliant) endpoints for the given service/credential combination, to check its issued by a trusted participant.
GetTrustedParticipantLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []string, err error)
GetTrustedParticipantLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []config.TrustedParticipantsList, err error)
// get (EBSI TrustedIssuersRegistry compliant) endpoints for the given service/credential combination, to check that credentials are issued by trusted issuers
// and that the issuer has permission to issue such claims.
GetTrustedIssuersLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []string, err error)
Expand Down Expand Up @@ -134,7 +134,7 @@ func (cc ServiceBackedCredentialsConfig) GetScope(serviceIdentifier string) (cre
return []string{}, nil
}

func (cc ServiceBackedCredentialsConfig) GetTrustedParticipantLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []string, err error) {
func (cc ServiceBackedCredentialsConfig) GetTrustedParticipantLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []config.TrustedParticipantsList, err error) {
logging.Log().Debugf("Get participants list for %s - %s - %s.", serviceIdentifier, scope, credentialType)
cacheEntry, hit := common.GlobalCache.ServiceCache.Get(serviceIdentifier)
if hit {
Expand All @@ -145,7 +145,7 @@ func (cc ServiceBackedCredentialsConfig) GetTrustedParticipantLists(serviceIdent
}
}
logging.Log().Debugf("No trusted participants for %s - %s", serviceIdentifier, credentialType)
return []string{}, nil
return []config.TrustedParticipantsList{}, nil
}

func (cc ServiceBackedCredentialsConfig) GetTrustedIssuersLists(serviceIdentifier string, scope string, credentialType string) (trustedIssuersRegistryUrl []string, err error) {
Expand Down
2 changes: 1 addition & 1 deletion verifier/jwt_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (jwtVMR JWTVerfificationMethodResolver) ResolveVerificationMethod(verificat
registry := vdr.New(vdr.WithVDR(web.New()), vdr.WithVDR(key.New()), vdr.WithVDR(jwk.New()))
didDocument, err := registry.Resolve(expectedProofIssuer)
if err != nil {
logging.Log().Warnf("Was not able to resolve the issuer %s.", expectedProofIssuer)
logging.Log().Warnf("Was not able to resolve the issuer %s. E: %v", expectedProofIssuer, err)
return nil, ErrorUnresolvableDid
}
for _, vm := range didDocument.DIDDocument.VerificationMethod {
Expand Down
9 changes: 5 additions & 4 deletions verifier/trustedissuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"testing"

"github.com/fiware/VCVerifier/config"
"github.com/fiware/VCVerifier/logging"
tir "github.com/fiware/VCVerifier/tir"
"github.com/trustbloc/vc-go/verifiable"
Expand Down Expand Up @@ -115,19 +116,19 @@ func getTrustedIssuer(attributes []tir.IssuerAttribute) tir.TrustedIssuer {
}

func getVerificationContext() ValidationContext {
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]string{"VerifiableCredential": {"http://my-trust-registry.org"}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"http://my-til.org"}}}
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]config.TrustedParticipantsList{"VerifiableCredential": {{Type: "ebsi", Url: "http://my-trust-registry.org"}}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"http://my-til.org"}}}
}

func getWildcardVerificationContext() ValidationContext {
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]string{"VerifiableCredential": {"http://my-trust-registry.org"}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*"}}}
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]config.TrustedParticipantsList{"VerifiableCredential": {{Type: "ebsi", Url: "http://my-trust-registry.org"}}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*"}}}
}

func getInvalidMixedVerificationContext() ValidationContext {
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]string{"VerifiableCredential": {"http://my-trust-registry.org"}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*", "http://my-til.org"}}}
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]config.TrustedParticipantsList{"VerifiableCredential": {{Type: "ebsi", Url: "http://my-trust-registry.org"}}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*", "http://my-til.org"}}}
}

func getWildcardAndNormalVerificationContext() ValidationContext {
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]string{"VerifiableCredential": {"http://my-trust-registry.org"}, "SecondType": {"http://my-trust-registry.org"}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*"}, "SecondType": {"http://my-til.org"}}}
return TrustRegistriesValidationContext{trustedParticipantsRegistries: map[string][]config.TrustedParticipantsList{"VerifiableCredential": {{Type: "ebsi", Url: "http://my-trust-registry.org"}}, "SecondType": {{Type: "ebsi", Url: "http://my-trust-registry.org"}}}, trustedIssuersLists: map[string][]string{"VerifiableCredential": {"*"}, "SecondType": {"http://my-til.org"}}}
}

func getMultiTypeCredential(types []string, claimName string, value interface{}) verifiable.Credential {
Expand Down
22 changes: 14 additions & 8 deletions verifier/trustedparticipant.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,20 @@ func (tpvs *TrustedParticipantValidationService) ValidateVC(verifiableCredential
return true, err
}
// FIXME Can we assume that if we have a VC with multiple types, its enough to check for only one type?
return tpvs.tirClient.IsTrustedParticipant(getFirstElementOfMap(trustContext.GetTrustedParticipantLists()), verifiableCredential.Contents().Issuer.ID), err
}

func getFirstElementOfMap(slices map[string][]string) []string {
logging.Log().Infof("Participants are: %v", slices)
for _, value := range slices {
logging.Log().Infof("First Value is %v", value)
return value
for _, listEntries := range trustContext.GetTrustedParticipantLists() {
for _, participantList := range listEntries {
if participantList.Type == "ebsi" {
result = tpvs.tirClient.IsTrustedParticipant(participantList.Url, verifiableCredential.Contents().Issuer.ID)
}
if participantList.Type == "gaia-X" {
// gaia-x client
}
if result {
return result, err
}
}
}
return []string{}

return false, err
}
Loading
Loading