diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56b267587..a4e1f2750 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,6 +75,7 @@ jobs: echo '127.0.0.1 cognito-mock.trustbloc.local' | sudo tee -a /etc/hosts echo '127.0.0.1 mock-login-consent.example.com' | sudo tee -a /etc/hosts echo '127.0.0.1 cognito-auth.local' | sudo tee -a /etc/hosts + echo '127.0.0.1 mock-trustregistry.trustbloc.local' | sudo tee -a /etc/hosts make bdd-test env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 7fa465063..878a74453 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,15 @@ mock-login-consent-docker: --build-arg GO_PROXY=$(GOPROXY) \ --build-arg GO_IMAGE=$(GO_IMAGE) test/bdd/loginconsent +.PHONY: mock-trustregistry-docker +mock-trustregistry-docker: + @echo "Building mock Trust Registry server" + @docker build -f ./images/mocks/trustregistry/Dockerfile --no-cache -t vcs/mock-trustregistry:latest \ + --build-arg GO_VER=$(GO_VER) \ + --build-arg ALPINE_VER=$(GO_ALPINE_VER) \ + --build-arg GO_PROXY=$(GOPROXY) \ + --build-arg GO_IMAGE=$(GO_IMAGE) test/bdd/trustregistry + .PHONY: sample-cognito-auth sample-cognito-auth: @echo "Building sample cognito auth server" @@ -142,7 +151,7 @@ sample-cognito-auth-docker: .PHONY: bdd-test -bdd-test: clean vc-rest-docker sample-cognito-auth-docker sample-webhook-docker mock-login-consent-docker generate-test-keys build-krakend-plugin +bdd-test: clean vc-rest-docker sample-cognito-auth-docker sample-webhook-docker mock-login-consent-docker mock-trustregistry-docker generate-test-keys build-krakend-plugin @cd test/bdd && GOPROXY=$(GOPROXY) go test -count=1 -v -cover . -p 1 -timeout=10m -race .PHONY: unit-test diff --git a/component/wallet-cli/cmd/oidc4vp_cmd.go b/component/wallet-cli/cmd/oidc4vp_cmd.go index d00271cc4..ee7d025e3 100644 --- a/component/wallet-cli/cmd/oidc4vp_cmd.go +++ b/component/wallet-cli/cmd/oidc4vp_cmd.go @@ -34,6 +34,7 @@ type oidc4vpCommandFlags struct { enableLinkedDomainVerification bool enableTracing bool disableDomainMatching bool + trustRegistryURL string } // NewOIDC4VPCommand returns a new command for running OIDC4VP flow. @@ -142,6 +143,7 @@ func NewOIDC4VPCommand() *cobra.Command { opts := []oidc4vp.Opt{ oidc4vp.WithRequestURI(requestURI), + oidc4vp.WithTrustRegistryURL(flags.trustRegistryURL), } if flags.walletDIDIndex != -1 { @@ -182,6 +184,7 @@ func createFlags(cmd *cobra.Command, flags *oidc4vpCommandFlags) { cmd.Flags().BoolVar(&flags.enableLinkedDomainVerification, "enable-linked-domain-verification", false, "enables linked domain verification") cmd.Flags().BoolVar(&flags.disableDomainMatching, "disable-domain-matching", false, "disables domain matching for issuer and verifier when presenting credentials (only for did:web)") cmd.Flags().IntVar(&flags.walletDIDIndex, "wallet-did-index", -1, "index of wallet did, if not set the most recently created DID is used") + cmd.Flags().StringVar(&flags.trustRegistryURL, "trust-registry-url", "", "Trust Registry URL. If supplied, Wallet will run Verifier verification against Trust Registry") cmd.Flags().BoolVar(&flags.enableTracing, "enable-tracing", false, "enables http tracing") } diff --git a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go index e8650e649..cf281986e 100644 --- a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go +++ b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go @@ -39,6 +39,7 @@ import ( vcskms "github.com/trustbloc/vcs/pkg/kms" kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" "github.com/trustbloc/vcs/pkg/observability/metrics/noop" + "github.com/trustbloc/vcs/pkg/service/trustregistry" ) const ( @@ -61,6 +62,7 @@ type Flow struct { requestURI string enableLinkedDomainVerification bool disableDomainMatching bool + trustRegistryURL string } type provider interface { @@ -124,6 +126,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) { requestURI: o.requestURI, enableLinkedDomainVerification: o.enableLinkedDomainVerification, disableDomainMatching: o.disableDomainMatching, + trustRegistryURL: o.trustRegistryURL, }, nil } @@ -144,6 +147,19 @@ func (f *Flow) Run(ctx context.Context) error { return fmt.Errorf("query wallet: %w", err) } + if trustRegistryURL := f.trustRegistryURL; trustRegistryURL != "" { + slog.Info("Run Trust Registry Verifier validation", "url", trustRegistryURL) + + trustRegistry := trustregistry.New(&trustregistry.Config{ + TrustRegistryURL: trustRegistryURL, + HTTPClient: f.httpClient, + }) + + if err = trustRegistry.ValidateVerifier(requestObject.ClientID, credentials); err != nil { + return fmt.Errorf("trust registry verifier validation: %w", err) + } + } + if !f.disableDomainMatching { for i := len(credentials) - 1; i >= 0; i-- { credential := credentials[i] @@ -655,6 +671,7 @@ type options struct { requestURI string enableLinkedDomainVerification bool disableDomainMatching bool + trustRegistryURL string } type Opt func(opts *options) @@ -683,6 +700,12 @@ func WithDomainMatchingDisabled() Opt { } } +func WithTrustRegistryURL(url string) Opt { + return func(opts *options) { + opts.trustRegistryURL = url + } +} + // ExtractCustomScopeClaims returns Claims associated with custom scope. func ExtractCustomScopeClaims(requestObjectScope string) (map[string]Claims, error) { chunks := strings.Split(requestObjectScope, "+") diff --git a/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go b/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go index 4469162b3..4025179a1 100644 --- a/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go +++ b/component/wallet-cli/pkg/walletrunner/vcprovider/provider.go @@ -27,6 +27,7 @@ type Config struct { UniResolverURL string ContextProviderURL string OidcProviderURL string + TrustRegistryURL string IssueVCURL string DidDomain string DidServiceAuthToken string diff --git a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go b/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go index 98ca58701..c5b24071a 100644 --- a/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go +++ b/component/wallet-cli/pkg/walletrunner/wallet_runner_oidc4vp.go @@ -42,6 +42,7 @@ import ( vcskms "github.com/trustbloc/vcs/pkg/kms" "github.com/trustbloc/vcs/pkg/kms/signer" "github.com/trustbloc/vcs/pkg/observability/metrics/noop" + "github.com/trustbloc/vcs/pkg/service/trustregistry" ) type RPConfigOverride func(rpc *RPConfig) @@ -121,6 +122,22 @@ func (s *Service) RunOIDC4VPFlow(ctx context.Context, authorizationRequest strin s.wallet.Close() } + // Run Trust Registry verification only if TrustRegistryURL supplied + if trustRegistryURL := s.vcProviderConf.TrustRegistryURL; trustRegistryURL != "" { + log.Println("Run Trust Registry Verifier validation") + + trustRegistry := trustregistry.New(&trustregistry.Config{ + TrustRegistryURL: trustRegistryURL, + HTTPClient: s.httpClient, + }) + + credentials := s.vpFlowExecutor.requestPresentation[0].Credentials() + + if err = trustRegistry.ValidateVerifier(s.vpFlowExecutor.requestObject.ClientID, credentials); err != nil { + return fmt.Errorf("trust registry verifier validation: %w", err) + } + } + var createAuthorizedResponseHooks []RPConfigOverride if hooks != nil { createAuthorizedResponseHooks = hooks.CreateAuthorizedResponse diff --git a/docs/build.md b/docs/build.md index 83aaddf7f..ff737d9c3 100644 --- a/docs/build.md +++ b/docs/build.md @@ -43,3 +43,4 @@ To run BDD tests (`make bdd-test`) you need to modify your hosts file (`/etc/hos 127.0.0.1 cognito-mock.trustbloc.local 127.0.0.1 mock-login-consent.example.com 127.0.0.1 cognito-auth.local + 127.0.0.1 mock-trustregistry.trustbloc.local diff --git a/images/mocks/trustregistry/Dockerfile b/images/mocks/trustregistry/Dockerfile new file mode 100644 index 000000000..3891059f0 --- /dev/null +++ b/images/mocks/trustregistry/Dockerfile @@ -0,0 +1,29 @@ +# +# Copyright Avast Software. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ARG GO_VER +ARG GO_IMAGE +ARG ALPINE_VER + +FROM ${GO_IMAGE}:${GO_VER}-alpine${ALPINE_VER} as builder + +RUN apk update && apk add git && apk add ca-certificates +RUN adduser -D -g '' appuser +COPY . $GOPATH/src/github.com/trustbloc/vcs/test/bdd/trustregistry/ +WORKDIR $GOPATH/src/github.com/trustbloc/vcs/test/bdd/trustregistry/ +ARG GO_PROXY +RUN GOPROXY=${GO_PROXY} CGO_ENABLED=0 go build -o /usr/bin/mock-trustregistry + +FROM scratch + +LABEL org.opencontainers.image.source https://github.com/trustbloc/vcs/test/bdd/trustregistry + +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /usr/bin/mock-trustregistry /usr/bin/mock-trustregistry +USER appuser + +ENTRYPOINT ["/usr/bin/mock-trustregistry"] diff --git a/pkg/service/trustregistry/models.go b/pkg/service/trustregistry/models.go new file mode 100644 index 000000000..2ef4d0df0 --- /dev/null +++ b/pkg/service/trustregistry/models.go @@ -0,0 +1,30 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package trustregistry + +type VerifierValidationConfig struct { + VerifierDID string `json:"verifier_did"` + Metadata []*CredentialMetadata `json:"metadata"` +} + +type PresentationValidationConfig struct { + PolicyID string `json:"policy_id"` + AttestationVC interface{} `json:"attestation_vc"` + Metadata []*CredentialMetadata `json:"metadata"` +} + +type CredentialMetadata struct { + CredentialID string `json:"credential_id"` + Types []string `json:"types"` + Issuer string `json:"issuer"` + Issued string `json:"issued"` + Expired string `json:"expired"` +} + +type Response struct { + Allowed bool `json:"allowed"` +} diff --git a/pkg/service/trustregistry/service.go b/pkg/service/trustregistry/service.go new file mode 100644 index 000000000..2990fcd04 --- /dev/null +++ b/pkg/service/trustregistry/service.go @@ -0,0 +1,158 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package trustregistry + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/samber/lo" + + "github.com/trustbloc/logutil-go/pkg/log" + "github.com/trustbloc/vc-go/verifiable" +) + +var ( + ErrInteractionRestricted = errors.New("interaction restricted") + logger = log.New("trustregistry") +) + +const ( + walletAttestationVCType = "WalletAttestationCredential" +) + +type Service struct { + url string + httpClient *http.Client +} + +type Config struct { + TrustRegistryURL string + HTTPClient *http.Client +} + +func New(conf *Config) *Service { + return &Service{ + url: conf.TrustRegistryURL, + httpClient: conf.HTTPClient, + } +} + +func (s *Service) ValidateVerifier(verifierDID string, presentationCredentials []*verifiable.Credential) error { + logger.Debug("ValidateVerifier begin") + verifierValidationConfig := &VerifierValidationConfig{ + VerifierDID: verifierDID, + Metadata: make([]*CredentialMetadata, len(presentationCredentials)), + } + + for i, credential := range presentationCredentials { + content := credential.Contents() + + verifierValidationConfig.Metadata[i] = s.getCredentialMetadata(content) + } + + req, err := json.Marshal(verifierValidationConfig) + if err != nil { + return fmt.Errorf("encode verifier config: %w", err) + } + + responseDecoded, err := s.doRequest(req) + if err != nil { + return err + } + + if !responseDecoded.Allowed { + return ErrInteractionRestricted + } + + logger.Debug("ValidateVerifier succeed") + + return nil +} + +func (s *Service) ValidatePresentation(policyID string, presentationCredentials []*verifiable.Credential) error { + logger.Debug("ValidatePresentation begin") + + presentationValidationConfig := &PresentationValidationConfig{ + PolicyID: policyID, + Metadata: make([]*CredentialMetadata, len(presentationCredentials)), + } + + for i, credential := range presentationCredentials { + content := credential.Contents() + + if lo.Contains(content.Types, walletAttestationVCType) { + attestationVC, err := credential.ToUniversalForm() + if err == nil { + presentationValidationConfig.AttestationVC = attestationVC + } + } + + presentationValidationConfig.Metadata[i] = s.getCredentialMetadata(content) + } + + req, err := json.Marshal(presentationValidationConfig) + if err != nil { + return fmt.Errorf("encode presentation config: %w", err) + } + + responseDecoded, err := s.doRequest(req) + if err != nil { + return err + } + + if !responseDecoded.Allowed { + return ErrInteractionRestricted + } + + logger.Debug("ValidatePresentation succeed") + + return nil +} + +func (s *Service) doRequest(req []byte) (*Response, error) { + resp, err := s.httpClient.Post(s.url, "application/json", bytes.NewReader(req)) //nolint:noctx + if err != nil { + return nil, fmt.Errorf("send request: %w", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var responseDecoded *Response + err = json.NewDecoder(resp.Body).Decode(&responseDecoded) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + return responseDecoded, nil +} + +func (s *Service) getCredentialMetadata(content verifiable.CredentialContents) *CredentialMetadata { + var iss, exp string + if content.Issued != nil { + iss = content.Issued.FormatToString() + } + + if content.Expired != nil { + exp = content.Expired.FormatToString() + } + + return &CredentialMetadata{ + CredentialID: content.ID, + Types: content.Types, + Issuer: content.Issuer.ID, + Issued: iss, + Expired: exp, + } +} diff --git a/pkg/service/trustregistry/service_test.go b/pkg/service/trustregistry/service_test.go new file mode 100644 index 000000000..2be4f6a66 --- /dev/null +++ b/pkg/service/trustregistry/service_test.go @@ -0,0 +1,327 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package trustregistry + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + + util "github.com/trustbloc/did-go/doc/util/time" + "github.com/trustbloc/vc-go/verifiable" +) + +func TestService_ValidateVerifier(t *testing.T) { + now := time.Now() + handler := echo.New() + + srv := httptest.NewServer(handler) + defer srv.Close() + + type fields struct { + url string + } + type args struct { + verifierDID string + getPresentationCredentials func(t *testing.T, now time.Time) []*verifiable.Credential + } + tests := []struct { + name string + addTestCaseHandler func(t *testing.T, e *echo.Echo) + fields fields + args args + wantErr bool + errContains string + }{ + { + name: "Success", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase1", func(c echo.Context) error { + var got *VerifierValidationConfig + assert.NoError(t, c.Bind(&got)) + + expected := &VerifierValidationConfig{ + VerifierDID: "did:oin:abc", + Metadata: getDefaultMetadata(t, now), + } + + assert.Equal(t, expected, got) + + return c.JSON(http.StatusOK, map[string]bool{"allowed": true}) + }) + }, + fields: fields{ + url: srv.URL + "/testcase1", + }, + args: args{ + verifierDID: "did:oin:abc", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: false, + }, + { + name: "httpClient.Post error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) {}, + fields: fields{ + url: "abcd", + }, + args: args{ + verifierDID: "did:oin:abc", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "send request:", + }, + { + name: "Unexpected status code error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase3", func(c echo.Context) error { + return c.NoContent(http.StatusForbidden) + }) + }, + fields: fields{ + url: srv.URL + "/testcase3", + }, + args: args{ + verifierDID: "did:oin:abc", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "unexpected status code: 403", + }, + { + name: "Read Response error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase4", func(c echo.Context) error { + return c.NoContent(http.StatusOK) + }) + }, + fields: fields{ + url: srv.URL + "/testcase4", + }, + args: args{ + verifierDID: "did:oin:abc", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "read response:", + }, + { + name: "Interaction restricted error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase5", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]bool{"allowed": false}) + }) + }, + fields: fields{ + url: srv.URL + "/testcase5", + }, + args: args{ + verifierDID: "did:oin:abc", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "interaction restricted", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.addTestCaseHandler(t, handler) + + s := &Service{ + url: tt.fields.url, + httpClient: http.DefaultClient, + } + + err := s.ValidateVerifier(tt.args.verifierDID, tt.args.getPresentationCredentials(t, now)) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateVerifier() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantErr { + assert.ErrorContains(t, err, tt.errContains) + } + }) + } +} + +func TestService_ValidatePresentation(t *testing.T) { + now := time.Now() + handler := echo.New() + + srv := httptest.NewServer(handler) + defer srv.Close() + + type fields struct { + url string + } + type args struct { + policyID string + getPresentationCredentials func(t *testing.T, now time.Time) []*verifiable.Credential + } + tests := []struct { + name string + addTestCaseHandler func(t *testing.T, e *echo.Echo) + fields fields + args args + wantErr bool + errContains string + }{ + { + name: "Success", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase1", func(c echo.Context) error { + var got *PresentationValidationConfig + assert.NoError(t, c.Bind(&got)) + + attestationVCUniversalForm, err := getAttestationCredential(t, now).ToUniversalForm() + assert.NoError(t, err) + + expected := &PresentationValidationConfig{ + PolicyID: "policy1", + AttestationVC: attestationVCUniversalForm, + Metadata: getDefaultMetadata(t, now), + } + + assert.Equal(t, expected, got) + + return c.JSON(http.StatusOK, map[string]bool{"allowed": true}) + }) + }, + fields: fields{ + url: srv.URL + "/testcase1", + }, + args: args{ + policyID: "policy1", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: false, + }, + { + name: "httpClient.Post error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) {}, + fields: fields{ + url: "abcd", + }, + args: args{ + policyID: "policy1", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "send request:", + }, + { + name: "Interaction restricted error", + addTestCaseHandler: func(t *testing.T, e *echo.Echo) { + e.Add(http.MethodPost, "/testcase2", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]bool{"allowed": false}) + }) + }, + fields: fields{ + url: srv.URL + "/testcase2", + }, + args: args{ + policyID: "policy1", + getPresentationCredentials: getDefaultCredentials, + }, + wantErr: true, + errContains: "interaction restricted", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.addTestCaseHandler(t, handler) + + s := New(&Config{ + TrustRegistryURL: tt.fields.url, + HTTPClient: http.DefaultClient, + }) + + err := s.ValidatePresentation(tt.args.policyID, tt.args.getPresentationCredentials(t, now)) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateVerifier() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantErr { + assert.ErrorContains(t, err, tt.errContains) + } + }) + } +} + +func getRequestedCredential(t *testing.T, now time.Time) *verifiable.Credential { + t.Helper() + + vcContent := verifiable.CredentialContents{ + ID: "credentialID1", + Types: []string{ + "VerifiableCredential", + }, + Issuer: &verifiable.Issuer{ID: "issuerID1"}, + Issued: util.NewTime(now), + Expired: util.NewTime(now.Add(time.Hour)), + } + + credential, err := verifiable.CreateCredential(vcContent, nil) + assert.NoError(t, err) + + return credential +} + +func getAttestationCredential(t *testing.T, now time.Time) *verifiable.Credential { + t.Helper() + + attestationVCContent := verifiable.CredentialContents{ + ID: "credentialID2", + Types: []string{ + walletAttestationVCType, + }, + Issuer: &verifiable.Issuer{ID: "issuerID2"}, + Issued: util.NewTime(now), + Expired: util.NewTime(now.Add(time.Hour)), + } + + credential, err := verifiable.CreateCredential(attestationVCContent, nil) + assert.NoError(t, err) + + return credential +} + +func getDefaultCredentials(t *testing.T, now time.Time) []*verifiable.Credential { + t.Helper() + + return []*verifiable.Credential{getRequestedCredential(t, now), getAttestationCredential(t, now)} +} + +func getDefaultMetadata(t *testing.T, now time.Time) []*CredentialMetadata { + t.Helper() + + return []*CredentialMetadata{ + { + CredentialID: "credentialID1", + Types: []string{ + "VerifiableCredential", + }, + Issuer: "issuerID1", + Issued: now.Format(time.RFC3339Nano), + Expired: now.Add(time.Hour).Format(time.RFC3339Nano), + }, + { + CredentialID: "credentialID2", + Types: []string{ + walletAttestationVCType, + }, + Issuer: "issuerID2", + Issued: now.Format(time.RFC3339Nano), + Expired: now.Add(time.Hour).Format(time.RFC3339Nano), + }, + } +} diff --git a/pkg/service/verifypresentation/verifypresentation_service.go b/pkg/service/verifypresentation/verifypresentation_service.go index 39e3ff57a..de95c18c2 100644 --- a/pkg/service/verifypresentation/verifypresentation_service.go +++ b/pkg/service/verifypresentation/verifypresentation_service.go @@ -35,16 +35,22 @@ type vcVerifier interface { ValidateLinkedDomain(ctx context.Context, signingDID string) error } +type trustRegistry interface { + ValidatePresentation(policyID string, presentationCredentials []*verifiable.Credential) error +} + type Config struct { VDR vdrapi.Registry DocumentLoader ld.DocumentLoader VcVerifier vcVerifier + TrustRegistry trustRegistry } type Service struct { vdr vdrapi.Registry documentLoader ld.DocumentLoader vcVerifier vcVerifier + trustRegistry trustRegistry } func New(config *Config) *Service { @@ -52,6 +58,7 @@ func New(config *Config) *Service { vdr: config.VDR, documentLoader: config.DocumentLoader, vcVerifier: config.VcVerifier, + trustRegistry: config.TrustRegistry, } } diff --git a/scripts/generate_test_keys.sh b/scripts/generate_test_keys.sh index fb711f00d..ba83caa06 100755 --- a/scripts/generate_test_keys.sh +++ b/scripts/generate_test_keys.sh @@ -25,6 +25,7 @@ DNS.4 = file-server.trustbloc.local DNS.5 = oidc-provider.example.com DNS.6 = mock-login-consent.example.com DNS.7 = api-gateway.trustbloc.local +DNS.8 = mock-trustregistry.trustbloc.local " >> "$tmp" #create CA diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 54ae53383..1fd3d341c 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -240,3 +240,18 @@ Feature: OIDC4VC REST API | issuerProfile | credentialType | clientRegistrationMethod | credentialTemplate | verifierProfile | presentationDefinitionID | fields | # SDJWT issuer, JWT verifier, no limit disclosure in PD query. | bank_issuer/v1.0 | UniversityDegreeCredential | dynamic | universityDegreeTemplateID | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | + +# @oidc4vc_rest_auth_flow_attestation_vc +# Scenario: OIDC credential issuance and verification Auth flow with Attestation VC +# When User inits wallet with Trust Registry verifier validation enabled +# And New verifiable credentials is created from table: +# | IssuerProfile | UserName | Password | Credential | DIDIndex | +# | i_myprofile_ud_es256k_jwt/v1.0 | profile-user-issuer-1 | profile-user-issuer-1-pwd | attestation_vc.jwt | 0 | +# | i_myprofile_ud_es256k_jwt/v1.0 | profile-user-issuer-1 | profile-user-issuer-1-pwd | university_degree.json | 0 | +# And User saves issued credentials +# And Profile "v_myprofile_jwt_no_strict/v1.0" verifier has been authorized with username "profile-user-verifier-1" and password "profile-user-verifier-1-pwd" +# +# Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_no_strict/v1.0" profile with presentation definition ID "y6s13jos-attestation-vc-single-field" and fields "attestation_vc_type,degree_type_id" +# And Verifier with profile "v_myprofile_jwt_no_strict/v1.0" retrieves interactions claims +# Then we wait 2 seconds +# And Verifier with profile "v_myprofile_jwt_no_strict/v1.0" requests deleted interactions claims diff --git a/test/bdd/fixtures/docker-compose.yml b/test/bdd/fixtures/docker-compose.yml index fe0a07047..2ba1a8b68 100644 --- a/test/bdd/fixtures/docker-compose.yml +++ b/test/bdd/fixtures/docker-compose.yml @@ -224,6 +224,20 @@ services: networks: - bdd_net + mock-trustregistry.trustbloc.local: # mock for Trust registry + container_name: mock-trustregistry.trustbloc.local + image: vcs/mock-trustregistry:latest + environment: + - LISTEN_ADDR=:8098 + - TLS_CERT_PATH=/etc/tls/ec-pubCert.pem + - TLS_KEY_PATH=/etc/tls/ec-key.pem + ports: + - "8098:8098" + volumes: + - ./keys/tls:/etc/tls + networks: + - bdd_net + oidc4vp-store.example.com: # oidc4vp request object public store container_name: oidc4vp-store.example.com image: nginx:latest diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index 7c5037110..d157a8804 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -1820,6 +1820,97 @@ }, "createDID": true }, + { + "verifier": { + "id": "v_myprofile_jwt_no_strict", + "version": "v1.0", + "name": "v_myprofile_jwt_no_strict", + "organizationID": "00000000-0000-0000-0000-000000000001", + "url": "https://test-verifier.com", + "active": true, + "webHook": "http://vcs.webhook.example.com:8180", + "checks": { + "credential": { + "format": [ + "jwt" + ], + "issuerTrustList": { + "i_myprofile_ud_es256k_jwt": {} + }, + "proof": true, + "status": true + }, + "presentation": { + "format": [ + "jwt" + ], + "vcSubject": true, + "proof": true + } + }, + "oidcConfig": { + "roSigningAlgorithm": "EcdsaSecp256k1Signature2019", + "keyType": "ECDSASecp256k1DER", + "didMethod": "ion" + }, + "presentationDefinitions": [ + { + "id": "y6s13jos-attestation-vc-single-field", + "input_descriptors": [ + { + "id": "attestation_vc_type", + "name": "type", + "purpose": "Wallet Attestation VC required", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" + } + ], + "constraints": { + "fields": [ + { + "path": [ + "$.type" + ], + "id": "attestation_vc_type", + "purpose": "Wallet Attestation VC required", + "filter": { + "type": "array", + "contains": { + "type": "string", + "pattern": "WalletAttestationCredential" + } + } + } + ] + } + }, + { + "id": "degree_type_id", + "name": "degree", + "purpose": "We can only hire with bachelor degree.", + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.degree.type" + ], + "id": "degree_type_id", + "purpose": "We can only hire with bachelor degree.", + "filter": { + "type": "string", + "const": "BachelorDegree" + } + } + ] + } + } + ] + } + ] + }, + "createDID": true + }, { "verifier": { "id": "v_myprofile_multivp_jwt", @@ -1931,7 +2022,7 @@ "jwt" ], "issuerTrustList": { - "bank_issuer" : {}, + "bank_issuer": {}, "bank_issuer_sdjwt_v5": { "credentialTypes": [ "CrudeProductCredential" diff --git a/test/bdd/go.mod b/test/bdd/go.mod index 1eeb52483..f7db2d325 100644 --- a/test/bdd/go.mod +++ b/test/bdd/go.mod @@ -19,6 +19,7 @@ require ( github.com/ory/fosite v0.44.0 github.com/rdumont/assistdog v0.0.0-20201106100018-168b06230d14 github.com/samber/lo v1.38.1 + github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.4 github.com/trustbloc/cmdutil-go v0.0.0-20221125151303-09d42adcc811 github.com/trustbloc/did-go v1.0.2-0.20231117120416-ed019bda587f @@ -61,6 +62,7 @@ require ( github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect github.com/cucumber/messages-go/v16 v16.0.1 // indirect github.com/dave/jennifer v1.6.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/deepmap/oapi-codegen v1.11.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect @@ -123,6 +125,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4ci.go b/test/bdd/pkg/v1/oidc4vc/oidc4ci.go index bfb10d947..2d0a62dc5 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4ci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4ci.go @@ -27,6 +27,7 @@ import ( "golang.org/x/oauth2" "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/walletrunner/vcprovider" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" ) @@ -41,6 +42,7 @@ const ( oidcProviderURL = "http://cognito-auth.local:8094/cognito" loginPageURL = "https://localhost:8099/login" claimDataURL = "https://mock-login-consent.example.com:8099/claim-data" + trustRegistryURL = "https://mock-trustregistry.trustbloc.local:8098/policies/evaluate" ) func (s *Steps) authorizeIssuerProfileUser(profileVersionedID, username, password string) error { @@ -730,6 +732,51 @@ func (s *Steps) checkSignatureHolder(vc *verifiable.Credential) error { return nil } +func (s *Steps) initWalletWithTrustRegistryURL() error { + walletRunner, err := walletrunner.New(vcprovider.ProviderVCS, + func(c *vcprovider.Config) { + c.DidKeyType = "ECDSAP384DER" + c.DidMethod = "ion" + c.KeepWalletOpen = true + c.TrustRegistryURL = trustRegistryURL + }) + if err != nil { + return fmt.Errorf("unable create wallet runner: %w", err) + } + + s.walletRunner = walletRunner + + err = walletRunner.CreateWallet() + if err != nil { + return fmt.Errorf("walletRunner.CreateWallet: %w", err) + } + + s.bddContext.CredentialSubject = append(s.bddContext.CredentialSubject, s.walletRunner.GetConfig().WalletParams.DidID...) + return nil +} + +func (s *Steps) saveCredentials() error { + for _, cred := range s.bddContext.CreatedCredentialsSet { + err := s.walletRunner.SaveCredentialInWallet(cred) + if err != nil { + return fmt.Errorf("wallet add credential failed: %w", err) + } + } + + return nil +} + +func (s *Steps) saveCredentialsInWallet() error { + for _, cred := range s.bddContext.CreatedCredentialsSet { + err := s.walletRunner.SaveCredentialInWallet(cred) + if err != nil { + return fmt.Errorf("wallet add credential failed: %w", err) + } + } + + return nil +} + func (s *Steps) initiateCredentialIssuanceWithError(errorContains string) error { _, err := s.initiateCredentialIssuance(s.getInitiateIssuanceRequest()) diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index 59eb11916..1f14a924d 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -116,6 +116,8 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^Profile "([^"]*)" issuer has been authorized with username "([^"]*)" and password "([^"]*)"$`, s.authorizeIssuerProfileUser) sc.Step(`^Profile "([^"]*)" verifier has been authorized with username "([^"]*)" and password "([^"]*)"$`, s.authorizeVerifierProfileUser) sc.Step(`^User holds credential "([^"]*)" with templateID "([^"]*)"$`, s.credentialTypeTemplateID) + sc.Step(`^User inits wallet with Trust Registry verifier validation enabled`, s.initWalletWithTrustRegistryURL) + sc.Step(`^User saves issued credentials`, s.saveCredentials) sc.Step(`^credential is issued$`, s.checkIssuedCredential) sc.Step(`^issued credential history is updated`, s.checkIssuedCredentialHistoryStep) diff --git a/test/bdd/pkg/v1/oidc4vp/oidc4vp.go b/test/bdd/pkg/v1/oidc4vp/oidc4vp.go index 66f2f9d0e..d9dfc696b 100644 --- a/test/bdd/pkg/v1/oidc4vp/oidc4vp.go +++ b/test/bdd/pkg/v1/oidc4vp/oidc4vp.go @@ -77,7 +77,7 @@ func (e *Steps) fetchRequestObjectAndDecodeClaims() error { return err } - _, err = e.waitForEvent("verifier.oidc-interaction-qr-scanned.v1") + _, err = e.waitForEvent("verifier.oidc-interaction-initiated.v1") if err != nil { return err } diff --git a/test/bdd/testdata/attestation_vc.jwt b/test/bdd/testdata/attestation_vc.jwt new file mode 100644 index 000000000..f6d776a97 --- /dev/null +++ b/test/bdd/testdata/attestation_vc.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDppb246RWlDSkhLS3h6ekU2WmpLOWpBRkRoRk1tWE5RZVFwWDZGUFVweDBjdjBtZTZ4UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklqQmxNamcwT1dObExURTBNMlV0TkdGa01TMWlZalF6TFRVeFl6QTVPR1EyTldVNVl5SXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKUUxUSTFOaUlzSW10cFpDSTZJakJsTWpnME9XTmxMVEUwTTJVdE5HRmtNUzFpWWpRekxUVXhZekE1T0dRMk5XVTVZeUlzSW10MGVTSTZJa1ZESWl3aWVDSTZJa2hQTW5ObWN6QnhaR05ZZEVkbmJWSnpiREJSWDJKNFQzcHNaMFpIU2tKUWNEUlFkM05JV1RKdFoyTWlMQ0o1SWpvaVdVeFVRMUpTZVhreFJrRjVSV2RFY2pCRWVsZDFjekZFYkY5UFgyWk5iR1paU25keVRYQk1TMWxSUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSXNJbUZ6YzJWeWRHbHZiazFsZEdodlpDSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZWMTlYU3dpZFhCa1lYUmxRMjl0YldsMGJXVnVkQ0k2SWtWcFF6QklTSEIyTjB0VmJVbHlkVUZaTmtKbU1XUXROV3BXU1UxT1JIcG1lbEpUWkUwMFYwaDZiRWQxWDFFaWZTd2ljM1ZtWm1sNFJHRjBZU0k2ZXlKa1pXeDBZVWhoYzJnaU9pSkZhVVJsUkhGVlUyOVZRMnAxWkZVNE9URmtZM0JoVDJaWU1VeHZhRXRVVFVwZldqUlVUbXR6VUdGWmVHUlJJaXdpY21WamIzWmxjbmxEYjIxdGFYUnRaVzUwSWpvaVJXbEVNWGszUVd0TmJIWnJNSGg1UWpoYVRrcEpRbk53TmpWR1luRjNibEpvWkZsM1pHcEVjMHhuU214NFFTSjlMQ0owZVhCbElqb2lZM0psWVhSbEluMCMwZTI4NDljZS0xNDNlLTRhZDEtYmI0My01MWMwOThkNjVlOWMifQ.eyJpYXQiOjE3MDAyMTkyMjEsImlzcyI6ImRpZDppb246RWlDSkhLS3h6ekU2WmpLOWpBRkRoRk1tWE5RZVFwWDZGUFVweDBjdjBtZTZ4UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklqQmxNamcwT1dObExURTBNMlV0TkdGa01TMWlZalF6TFRVeFl6QTVPR1EyTldVNVl5SXNJbkIxWW14cFkwdGxlVXAzYXlJNmV5SmpjbllpT2lKUUxUSTFOaUlzSW10cFpDSTZJakJsTWpnME9XTmxMVEUwTTJVdE5HRmtNUzFpWWpRekxUVXhZekE1T0dRMk5XVTVZeUlzSW10MGVTSTZJa1ZESWl3aWVDSTZJa2hQTW5ObWN6QnhaR05ZZEVkbmJWSnpiREJSWDJKNFQzcHNaMFpIU2tKUWNEUlFkM05JV1RKdFoyTWlMQ0o1SWpvaVdVeFVRMUpTZVhreFJrRjVSV2RFY2pCRWVsZDFjekZFYkY5UFgyWk5iR1paU25keVRYQk1TMWxSUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSXNJbUZ6YzJWeWRHbHZiazFsZEdodlpDSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZWMTlYU3dpZFhCa1lYUmxRMjl0YldsMGJXVnVkQ0k2SWtWcFF6QklTSEIyTjB0VmJVbHlkVUZaTmtKbU1XUXROV3BXU1UxT1JIcG1lbEpUWkUwMFYwaDZiRWQxWDFFaWZTd2ljM1ZtWm1sNFJHRjBZU0k2ZXlKa1pXeDBZVWhoYzJnaU9pSkZhVVJsUkhGVlUyOVZRMnAxWkZVNE9URmtZM0JoVDJaWU1VeHZhRXRVVFVwZldqUlVUbXR6VUdGWmVHUlJJaXdpY21WamIzWmxjbmxEYjIxdGFYUnRaVzUwSWpvaVJXbEVNWGszUVd0TmJIWnJNSGg1UWpoYVRrcEpRbk53TmpWR1luRjNibEpvWkZsM1pHcEVjMHhuU214NFFTSjlMQ0owZVhCbElqb2lZM0psWVhSbEluMCIsImp0aSI6InVybjp1dWlkOmYxMmEwMDYxLTY1NzctNDk5MC05ZDc4LTA3NjM5MDg3YzE5NCIsIm5iZiI6MTcwMDIxOTIyMSwic3ViIjoiZGlkOlx1MDAzY3dhbGxldF9kaWRcdTAwM2UiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJhc3N1cmFuY2VfbGV2ZWwiOiJsb3ciLCJjb21wbGlhbmNlIjp7IlRCRCI6IlRCRCJ9LCJpZCI6ImRpZDpcdTAwM2N3YWxsZXRfZGlkXHUwMDNlIiwia2V5X3R5cGUiOiJcdTAwM2NUQkRcdTAwM2UiLCJ1c2VyX2F1dGhlbnRpY2F0aW9uIjoiXHUwMDNjVEJEXHUwMDNlIiwid2FsbGV0X2F1dGhlbnRpY2F0aW9uIjp7ImR0c19hdXRoX3N0cmluZzEiOiJzb21lX3ZhbHVlIiwiZHRzX2F1dGhfc3RyaW5nMiI6InNvbWVfdmFsdWUyIn0sIndhbGxldF9tZXRhZGF0YSI6eyJ3YWxsZXRfbmFtZSI6IkF3ZXNvbWUgV2FsbGV0Iiwid2FsbGV0X3ZlcnNpb24iOiIyLjAuMCJ9fSwiaWQiOiJ1cm46dXVpZDpmMTJhMDA2MS02NTc3LTQ5OTAtOWQ3OC0wNzYzOTA4N2MxOTQiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTExLTE3VDExOjA3OjAxLjgxMjc4OTAwNFoiLCJpc3N1ZXIiOiJkaWQ6aW9uOkVpQ0pIS0t4enpFNlpqSzlqQUZEaEZNbVhOUWVRcFg2RlBVcHgwY3YwbWU2eFE6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJakJsTWpnME9XTmxMVEUwTTJVdE5HRmtNUzFpWWpRekxUVXhZekE1T0dRMk5XVTVZeUlzSW5CMVlteHBZMHRsZVVwM2F5STZleUpqY25ZaU9pSlFMVEkxTmlJc0ltdHBaQ0k2SWpCbE1qZzBPV05sTFRFME0yVXROR0ZrTVMxaVlqUXpMVFV4WXpBNU9HUTJOV1U1WXlJc0ltdDBlU0k2SWtWRElpd2llQ0k2SWtoUE1uTm1jekJ4WkdOWWRFZG5iVkp6YkRCUlgySjRUM3BzWjBaSFNrSlFjRFJRZDNOSVdUSnRaMk1pTENKNUlqb2lXVXhVUTFKU2VYa3hSa0Y1UldkRWNqQkVlbGQxY3pGRWJGOVBYMlpOYkdaWlNuZHlUWEJNUzFsUlJTSjlMQ0p3ZFhKd2IzTmxjeUk2V3lKaGRYUm9aVzUwYVdOaGRHbHZiaUlzSW1GemMyVnlkR2x2YmsxbGRHaHZaQ0pkTENKMGVYQmxJam9pU25OdmJsZGxZa3RsZVRJd01qQWlmVjE5WFN3aWRYQmtZWFJsUTI5dGJXbDBiV1Z1ZENJNklrVnBRekJJU0hCMk4wdFZiVWx5ZFVGWk5rSm1NV1F0TldwV1NVMU9SSHBtZWxKVFpFMDBWMGg2YkVkMVgxRWlmU3dpYzNWbVptbDRSR0YwWVNJNmV5SmtaV3gwWVVoaGMyZ2lPaUpGYVVSbFJIRlZVMjlWUTJwMVpGVTRPVEZrWTNCaFQyWllNVXh2YUV0VVRVcGZXalJVVG10elVHRlplR1JSSWl3aWNtVmpiM1psY25sRGIyMXRhWFJ0Wlc1MElqb2lSV2xFTVhrM1FXdE5iSFpyTUhoNVFqaGFUa3BKUW5Od05qVkdZbkYzYmxKb1pGbDNaR3BFYzB4blNteDRRU0o5TENKMGVYQmxJam9pWTNKbFlYUmxJbjAiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiV2FsbGV0QXR0ZXN0YXRpb25DcmVkZW50aWFsIl19fQ. \ No newline at end of file diff --git a/test/bdd/trustregistry/go.mod b/test/bdd/trustregistry/go.mod new file mode 100644 index 000000000..3a74e2d65 --- /dev/null +++ b/test/bdd/trustregistry/go.mod @@ -0,0 +1,9 @@ +// Copyright Avast Software. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +module github.com/trustbloc/vcs/test/bdd/trustregistry + +go 1.21 + +require github.com/gorilla/mux v1.8.0 diff --git a/test/bdd/trustregistry/go.sum b/test/bdd/trustregistry/go.sum new file mode 100644 index 000000000..535028803 --- /dev/null +++ b/test/bdd/trustregistry/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/test/bdd/trustregistry/main.go b/test/bdd/trustregistry/main.go new file mode 100644 index 000000000..5409a72d7 --- /dev/null +++ b/test/bdd/trustregistry/main.go @@ -0,0 +1,44 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "log" + "net/http" + "os" +) + +func main() { + serveCertPath := os.Getenv("TLS_CERT_PATH") + if serveCertPath == "" { + log.Fatalf("TLS_CERT_PATH is required") + + return + } + + serveKeyPath := os.Getenv("TLS_KEY_PATH") + if serveKeyPath == "" { + log.Fatalf("TLS_KEY_PATH is required") + + return + } + + listenAddr := os.Getenv("LISTEN_ADDR") + if listenAddr == "" { + log.Fatalf("LISTEN_ADDR is required") + + return + } + + log.Printf("Listening on %s", listenAddr) + + log.Fatal(http.ListenAndServeTLS( + listenAddr, + serveCertPath, serveKeyPath, + newServer(), + )) +} diff --git a/test/bdd/trustregistry/server.go b/test/bdd/trustregistry/server.go new file mode 100644 index 000000000..19488e908 --- /dev/null +++ b/test/bdd/trustregistry/server.go @@ -0,0 +1,59 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "encoding/json" + "github.com/gorilla/mux" + "log" + "net/http" +) + +type server struct { + router *mux.Router +} + +func newServer() *server { + router := mux.NewRouter() + + srv := &server{ + router: router, + } + + router.HandleFunc("/policies/evaluate", srv.evaluatePolicies).Methods(http.MethodPost) + + return srv +} + +func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.router.ServeHTTP(w, r) +} + +func (s *server) evaluatePolicies(w http.ResponseWriter, r *http.Request) { + var request map[string]interface{} + + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + log.Printf("failed to decode evaluate request: %s", err.Error()) + + return + } + + log.Printf("handling request: %s with payload %v", r.URL.String(), request) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + claims := map[string]interface{}{ + "allowed": true, + } + + err = json.NewEncoder(w).Encode(claims) + if err != nil { + log.Printf("failed to write response: %s", err.Error()) + } +}