diff --git a/pkg/restapi/v1/oidc4ci/controller_e2e_flows_test.go b/pkg/restapi/v1/oidc4ci/controller_e2e_flows_test.go index ebae7f6d5..23e01073a 100644 --- a/pkg/restapi/v1/oidc4ci/controller_e2e_flows_test.go +++ b/pkg/restapi/v1/oidc4ci/controller_e2e_flows_test.go @@ -497,13 +497,16 @@ func registerThirdPartyOIDCAuthorizeEndpoint(t *testing.T, e *echo.Echo) { e.GET("/third-party/oidc/authorize", func(c echo.Context) error { req := c.Request() - // TODO: Validate authorize request + queryData := req.URL.Query() + assert.EqualValues(t, "test-client", queryData.Get("client_id")) + assert.EqualValues(t, "code", queryData.Get("response_type")) + assert.EqualValues(t, "openid profile", queryData.Get("scope")) q := &url.Values{} q.Set("code", "foo") - q.Set("state", req.URL.Query().Get("state")) + q.Set("state", queryData.Get("state")) - redirectURI := req.URL.Query().Get("redirect_uri") + "?" + q.Encode() + redirectURI := queryData.Get("redirect_uri") + "?" + q.Encode() return c.Redirect(http.StatusSeeOther, redirectURI) }) diff --git a/pkg/service/verifycredential/verifycredential_service_test.go b/pkg/service/verifycredential/verifycredential_service_test.go index a62793850..1489e3ba4 100644 --- a/pkg/service/verifycredential/verifycredential_service_test.go +++ b/pkg/service/verifycredential/verifycredential_service_test.go @@ -105,7 +105,7 @@ func TestService_VerifyCredential(t *testing.T) { } for _, ktTestCase := range tests { t.Run(ktTestCase.name, func(t *testing.T) { - tests := []struct { + signatureTests := []struct { name string sr verifiable.SignatureRepresentation }{ @@ -118,9 +118,9 @@ func TestService_VerifyCredential(t *testing.T) { sr: verifiable.SignatureProofValue, }, } - for _, sigRepresentationTextCase := range tests { + for _, sigRepresentationTextCase := range signatureTests { t.Run(sigRepresentationTextCase.name, func(t *testing.T) { - tests := []struct { + representationTests := []struct { name string sf vcs.Format }{ @@ -137,9 +137,9 @@ func TestService_VerifyCredential(t *testing.T) { sf: vcs.Cwt, }, } - for _, signatureFormatTestCase := range tests { + for _, signatureFormatTestCase := range representationTests { t.Run(signatureFormatTestCase.name, func(t *testing.T) { - tests := []struct { + formatTests := []struct { name string vcFile []byte isSDJWT bool @@ -162,10 +162,9 @@ func TestService_VerifyCredential(t *testing.T) { vcFile: []byte(sampleVCJsonLD), }, } - for _, vcFileTestCase := range tests { + for _, vcFileTestCase := range formatTests { t.Run(vcFileTestCase.name, func(t *testing.T) { - // Assert - vc, vdr := testutil.SignedVC( + signedVC, vdr := testutil.SignedVC( t, vcFileTestCase.vcFile, ktTestCase.kt, sigRepresentationTextCase.sr, signatureFormatTestCase.sf, loader, @@ -177,7 +176,6 @@ func TestService_VerifyCredential(t *testing.T) { }, } - // Verify op := New(&Config{ VCStatusProcessorGetter: mockStatusProcessorGetter.GetMockStatusProcessor, StatusListVCResolver: mockStatusListVCGetter, @@ -185,7 +183,7 @@ func TestService_VerifyCredential(t *testing.T) { DocumentLoader: loader, }) - res, err := op.VerifyCredential(context.Background(), vc, &Options{ + res, err := op.VerifyCredential(context.Background(), signedVC, &Options{ Challenge: crypto.Challenge, Domain: crypto.Domain, }, testProfile) @@ -200,99 +198,99 @@ func TestService_VerifyCredential(t *testing.T) { } }) } + }) - t.Run("Failed", func(t *testing.T) { - // Assert - mockVDRRegistry := &vdrmock.VDRegistry{} - loader := testutil.DocumentLoader(t) - - vc, err := verifiable.ParseCredential( - []byte(sampleVCJsonLD), - verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(loader)) - require.NoError(t, err) + t.Run("Failed", func(t *testing.T) { + // Assert + mockVDRRegistry := &vdrmock.VDRegistry{} + loader := testutil.DocumentLoader(t) - t.Run("Proof", func(t *testing.T) { - mockStatusListVCGetter := NewMockStatusListVCResolver(gomock.NewController(t)) - mockStatusListVCGetter.EXPECT().Resolve( - context.Background(), gomock.Any()).AnyTimes().Return( - createVC(t, verifiable.CredentialContents{ - Subject: []verifiable.Subject{{ - ID: "", - CustomFields: map[string]interface{}{ - "statusListIndex": "1", - "statusPurpose": "2", - "encodedList": "H4sIAAAAAAAA_2IABAAA__-N7wLSAQAAAA", - }, - }}, - Issuer: &verifiable.Issuer{ - ID: "did:trustblock:abc", + vc, err := verifiable.ParseCredential( + []byte(sampleVCJsonLD), + verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + t.Run("Proof", func(t *testing.T) { + mockStatusListVCGetter := NewMockStatusListVCResolver(gomock.NewController(t)) + mockStatusListVCGetter.EXPECT().Resolve( + context.Background(), gomock.Any()).AnyTimes().Return( + createVC(t, verifiable.CredentialContents{ + Subject: []verifiable.Subject{{ + ID: "", + CustomFields: map[string]interface{}{ + "statusListIndex": "1", + "statusPurpose": "2", + "encodedList": "H4sIAAAAAAAA_2IABAAA__-N7wLSAQAAAA", }, - }), nil) - - mockStatusProcessorGetter := &status.MockStatusProcessorGetter{ - StatusProcessor: &status.MockVCStatusProcessor{ - StatusListIndex: 1, + }}, + Issuer: &verifiable.Issuer{ + ID: "did:trustblock:abc", }, - } + }), nil) - service := New(&Config{ - VCStatusProcessorGetter: mockStatusProcessorGetter.GetMockStatusProcessor, - StatusListVCResolver: mockStatusListVCGetter, - VDR: mockVDRRegistry, - DocumentLoader: loader, - }) + mockStatusProcessorGetter := &status.MockStatusProcessorGetter{ + StatusProcessor: &status.MockVCStatusProcessor{ + StatusListIndex: 1, + }, + } - var res []CredentialsVerificationCheckResult + service := New(&Config{ + VCStatusProcessorGetter: mockStatusProcessorGetter.GetMockStatusProcessor, + StatusListVCResolver: mockStatusListVCGetter, + VDR: mockVDRRegistry, + DocumentLoader: loader, + }) - res, err = service.VerifyCredential(context.Background(), vc, &Options{ - Challenge: crypto.Challenge, - Domain: crypto.Domain, - }, testProfile) + var res []CredentialsVerificationCheckResult - require.NoError(t, err) - require.Len(t, res, 1) - }) + res, err = service.VerifyCredential(context.Background(), vc, &Options{ + Challenge: crypto.Challenge, + Domain: crypto.Domain, + }, testProfile) - t.Run("Proof and Status", func(t *testing.T) { - require.NoError(t, err) - failedStatusListGetter := NewMockStatusListVCResolver(gomock.NewController(t)) - failedStatusListGetter.EXPECT().Resolve( - context.Background(), gomock.Any()).AnyTimes().Return( - createVC(t, verifiable.CredentialContents{ - Subject: []verifiable.Subject{{ - ID: "", - CustomFields: map[string]interface{}{ - "statusListIndex": "1", - "statusPurpose": "2", - "encodedList": "H4sIAAAAAAAA_2ICBAAA__-hjgw8AQAAAA", - }, - }}, - Issuer: &verifiable.Issuer{ - ID: "did:trustblock:abc", - }, - }), nil) + require.NoError(t, err) + require.Len(t, res, 1) + }) - mockStatusProcessorGetter := &status.MockStatusProcessorGetter{ - StatusProcessor: &status.MockVCStatusProcessor{ - ValidateErr: errors.New("some error"), + t.Run("Proof and Status", func(t *testing.T) { + require.NoError(t, err) + failedStatusListGetter := NewMockStatusListVCResolver(gomock.NewController(t)) + failedStatusListGetter.EXPECT().Resolve( + context.Background(), gomock.Any()).AnyTimes().Return( + createVC(t, verifiable.CredentialContents{ + Subject: []verifiable.Subject{{ + ID: "", + CustomFields: map[string]interface{}{ + "statusListIndex": "1", + "statusPurpose": "2", + "encodedList": "H4sIAAAAAAAA_2ICBAAA__-hjgw8AQAAAA", + }, + }}, + Issuer: &verifiable.Issuer{ + ID: "did:trustblock:abc", }, - } + }), nil) + + mockStatusProcessorGetter := &status.MockStatusProcessorGetter{ + StatusProcessor: &status.MockVCStatusProcessor{ + ValidateErr: errors.New("some error"), + }, + } - service := New(&Config{ - VCStatusProcessorGetter: mockStatusProcessorGetter.GetMockStatusProcessor, - StatusListVCResolver: failedStatusListGetter, - VDR: mockVDRRegistry, - DocumentLoader: loader, - }) - res, err := service.VerifyCredential(context.Background(), vc, &Options{ - Challenge: crypto.Challenge, - Domain: crypto.Domain, - }, testProfile) - - require.NoError(t, err) - require.Len(t, res, 2) + service := New(&Config{ + VCStatusProcessorGetter: mockStatusProcessorGetter.GetMockStatusProcessor, + StatusListVCResolver: failedStatusListGetter, + VDR: mockVDRRegistry, + DocumentLoader: loader, }) + res, err := service.VerifyCredential(context.Background(), vc, &Options{ + Challenge: crypto.Challenge, + Domain: crypto.Domain, + }, testProfile) + + require.NoError(t, err) + require.Len(t, res, 2) }) }) } diff --git a/test/bdd/loginconsent/go.mod b/test/bdd/loginconsent/go.mod index 7f74aad5c..d14b47532 100644 --- a/test/bdd/loginconsent/go.mod +++ b/test/bdd/loginconsent/go.mod @@ -11,12 +11,14 @@ require ( github.com/gorilla/mux v1.8.0 github.com/ory/hydra-client-go v1.11.8 github.com/stretchr/testify v1.8.0 + gopkg.in/square/go-jose.v2 v2.6.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.1.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/test/bdd/loginconsent/go.sum b/test/bdd/loginconsent/go.sum index dc320a67e..dbf552371 100644 --- a/test/bdd/loginconsent/go.sum +++ b/test/bdd/loginconsent/go.sum @@ -139,6 +139,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -374,6 +375,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/test/bdd/loginconsent/server.go b/test/bdd/loginconsent/server.go index fc3818e0b..256555e3d 100644 --- a/test/bdd/loginconsent/server.go +++ b/test/bdd/loginconsent/server.go @@ -11,14 +11,17 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "fmt" "log" "net/http" "net/url" + "strings" "time" "github.com/google/uuid" "github.com/gorilla/mux" client "github.com/ory/hydra-client-go" + "gopkg.in/square/go-jose.v2" ) const ( @@ -346,9 +349,61 @@ func (s *server) completeConsent(w http.ResponseWriter, r *http.Request, request log.Printf("user authorized; redirected to: %s", redirectURL) } +func (s *server) parseJWTToken( + token string, +) (map[string]interface{}, error) { + parsedToken, err := jose.ParseSigned(token) + if err != nil { + return nil, fmt.Errorf("failed to parse idToken JWT: %v", err) + } + + var payload []byte + payload = parsedToken.UnsafePayloadWithoutVerification() + var claims map[string]interface{} + if err = json.Unmarshal(payload, &claims); err != nil { + return nil, fmt.Errorf("failed to unmarshal idToken payload: %v", err) + } + + return claims, nil +} + func (s *server) claimDataHandler(w http.ResponseWriter, r *http.Request) { log.Printf("handling request: %s", r.URL.String()) + authData := r.Header.Get("Authorization") + + if authData != "" { + log.Printf("got header auth: %v", authData) + authData = strings.ReplaceAll(authData, "Bearer ", "") + + parsedToken, err := s.parseJWTToken(authData) + if err != nil { + log.Printf("failed to parse token: %s", err.Error()) + w.WriteHeader(http.StatusBadRequest) + return + } + + if fmt.Sprint(parsedToken["token_use"]) != "access" { + log.Printf("invalid token use: %v", parsedToken["token_use"]) + w.WriteHeader(http.StatusBadRequest) + return + } + + if fmt.Sprint(parsedToken["iss"]) != "https://cognito-idp.{region}.amazonaws.com/local_5a9GzRvB" { + log.Printf("invalid issuer: %v", parsedToken["iss"]) + w.WriteHeader(http.StatusBadRequest) + return + } + + if fmt.Sprint(parsedToken["client_id"]) == "" { + log.Printf("missing client_id") + w.WriteHeader(http.StatusBadRequest) + return + } + + log.Print("token checked") + } + // TODO: Perform token introspection // Check if the claims are included in the query.