From cd8689f7a53c2c04c322840f03dd0ae86f3fff94 Mon Sep 17 00:00:00 2001 From: ThibaultHerard Date: Fri, 16 Dec 2022 16:22:16 +0000 Subject: [PATCH] feat(saml): vulnerabilities check + update saml tests Signed-off-by: ThibaultHerard Co-authored-by: sebferrer --- go.mod | 13 +- go.sum | 16 +- selfservice/strategy/saml/config_test.go | 6 +- selfservice/strategy/saml/handler.go | 21 +- selfservice/strategy/saml/handler_test.go | 22 +- selfservice/strategy/saml/metadata_test.go | 3 - selfservice/strategy/saml/provider_saml.go | 2 +- .../saml/saml_vulnerabilities_check.md | 52 + selfservice/strategy/saml/strategy.go | 11 +- .../strategy/saml/strategy_helper_test.go | 16 +- .../TestSPCanHandleOneloginResponse_response | 1 - .../strategy/saml/testdata/authn_request.url | 1 + .../strategy/saml/testdata/evilcert.crt | 32 + .../strategy/saml/testdata/evilkey.key | 52 + .../saml/testdata/expected_metadata.xml | 7 +- .../saml/testdata/{cert.pem => idp_cert.pem} | 2 +- .../saml/testdata/{key.pem => idp_key.pem} | 2 +- .../strategy/saml/testdata/idp_metadata.xml | 31 + .../strategy/saml/testdata/myservice.cert | 19 - .../strategy/saml/testdata/myservice.key | 28 - .../strategy/saml/testdata/saml.jsonnet | 1 + .../strategy/saml/testdata/saml_response.xml | 2 +- .../strategy/saml/testdata/samlkratos.crt | 21 - .../strategy/saml/testdata/sp2_cert.pem | 32 + .../strategy/saml/testdata/sp2_key.pem | 52 + .../strategy/saml/testdata/sp_cert.pem | 13 + selfservice/strategy/saml/testdata/sp_key.pem | 15 + selfservice/strategy/saml/testdata/token.json | 46 + .../saml/vulnerabilities_helper_test.go | 330 ++++ .../strategy/saml/vulnerabilities_test.go | 1336 +++++++++++++++++ 30 files changed, 2053 insertions(+), 132 deletions(-) create mode 100644 selfservice/strategy/saml/saml_vulnerabilities_check.md delete mode 100644 selfservice/strategy/saml/testdata/TestSPCanHandleOneloginResponse_response create mode 100644 selfservice/strategy/saml/testdata/authn_request.url create mode 100644 selfservice/strategy/saml/testdata/evilcert.crt create mode 100644 selfservice/strategy/saml/testdata/evilkey.key rename selfservice/strategy/saml/testdata/{cert.pem => idp_cert.pem} (96%) rename selfservice/strategy/saml/testdata/{key.pem => idp_key.pem} (96%) create mode 100644 selfservice/strategy/saml/testdata/idp_metadata.xml delete mode 100755 selfservice/strategy/saml/testdata/myservice.cert delete mode 100755 selfservice/strategy/saml/testdata/myservice.key delete mode 100755 selfservice/strategy/saml/testdata/samlkratos.crt create mode 100644 selfservice/strategy/saml/testdata/sp2_cert.pem create mode 100644 selfservice/strategy/saml/testdata/sp2_key.pem create mode 100644 selfservice/strategy/saml/testdata/sp_cert.pem create mode 100644 selfservice/strategy/saml/testdata/sp_key.pem create mode 100644 selfservice/strategy/saml/testdata/token.json create mode 100644 selfservice/strategy/saml/vulnerabilities_helper_test.go create mode 100644 selfservice/strategy/saml/vulnerabilities_test.go diff --git a/go.mod b/go.mod index ee007868efd2..c5ad631c680a 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible github.com/cortesi/modd v0.0.0-20210323234521-b35eddab86cc - github.com/crewjam/saml v0.4.6 + github.com/crewjam/saml v0.4.10 github.com/davecgh/go-spew v1.1.1 github.com/davidrjonas/semver-cli v0.0.0-20190116233701-ee19a9a0dda6 github.com/dgraph-io/ristretto v0.1.1 @@ -44,9 +44,9 @@ require ( github.com/go-swagger/go-swagger v0.30.3 github.com/gobuffalo/fizz v1.14.4 github.com/gobuffalo/httptest v1.5.2 - github.com/gobuffalo/pop/v6 v6.1.2-0.20230124165254-ec9229dbf7d7 - github.com/gofrs/uuid v4.3.1+incompatible - github.com/golang-jwt/jwt/v4 v4.1.0 + github.com/gobuffalo/pop/v6 v6.0.8 + github.com/gofrs/uuid v4.3.0+incompatible + github.com/golang-jwt/jwt/v4 v4.4.2 github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2 github.com/golang/mock v1.6.0 github.com/google/go-github/v27 v27.0.1 @@ -59,6 +59,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/imdario/mergo v0.3.13 github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf + github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65 github.com/jarcoal/httpmock v1.0.5 github.com/jteeuwen/go-bindata v3.0.7+incompatible github.com/julienschmidt/httprouter v1.3.0 @@ -102,6 +103,7 @@ require ( golang.org/x/oauth2 v0.4.0 golang.org/x/sync v0.1.0 golang.org/x/tools v0.2.0 + google.golang.org/grpc v1.50.1 gotest.tools v2.2.0+incompatible ) @@ -326,8 +328,7 @@ require ( golang.org/x/time v0.1.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect - google.golang.org/grpc v1.52.0 // indirect + google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 7b31d9b93377..d20267a40297 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,8 @@ github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= -github.com/crewjam/saml v0.4.6 h1:XCUFPkQSJLvzyl4cW9OvpWUbRf0gE7VUpU8ZnilbeM4= -github.com/crewjam/saml v0.4.6/go.mod h1:ZBOXnNPFzB3CgOkRm7Nd6IVdkG+l/wF+0ZXLqD96t1A= +github.com/crewjam/saml v0.4.10 h1:Rjs6x4s/aQFXiaPjw3uhB4VdxRqoxHXOJrrj4BsMn9o= +github.com/crewjam/saml v0.4.10/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= @@ -291,7 +291,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidrjonas/semver-cli v0.0.0-20190116233701-ee19a9a0dda6 h1:VzPvKOw28XJ77PYwOq5gAqvFB4gk6gst0HxxiW8kfZQ= github.com/davidrjonas/semver-cli v0.0.0-20190116233701-ee19a9a0dda6/go.mod h1:+6FzxsSbK4oEuvdN06Jco8zKB2mQqIB6UduZdd0Zesk= -github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -532,8 +531,9 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2 h1:xisWqjiKEff2B0KfFYGpCqc3M3zdTz+OHQHRc09FeYk= github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -785,6 +785,8 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= +github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65 h1:T25FL3WEzgmKB0m6XCJNZ65nw09/QIp3T1yXr487D+A= +github.com/instana/testify v1.6.2-0.20200721153833-94b1851f4d65/go.mod h1:nYhEREG/B7HUY7P+LKOrqy53TpIqmJ9JyUShcaEKtGw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -1117,8 +1119,8 @@ github.com/ory/nosurf v1.2.7/go.mod h1:d4L3ZBa7Amv55bqxCBtCs63wSlyaiCkWVl4vKf3OU github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2 h1:zm6sDvHy/U9XrGpixwHiuAwpp0Ock6khSVHkrv6lQQU= github.com/ory/sessions v1.2.2-0.20220110165800-b09c17334dc2/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= -github.com/ory/x v0.0.534 h1:hc49pmcOuHdJ6rbHVGtJJ4/LU88dzDCtEQKfgeo/ecU= -github.com/ory/x v0.0.534/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0= +github.com/ory/x v0.0.519 h1:T8/LbbQQqm+3P7bfI838T7eECv6+laXlvIyCp0QB+R8= +github.com/ory/x v0.0.519/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -1435,7 +1437,6 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= @@ -1578,6 +1579,7 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/selfservice/strategy/saml/config_test.go b/selfservice/strategy/saml/config_test.go index a6d90c8e5d19..65b394949506 100644 --- a/selfservice/strategy/saml/config_test.go +++ b/selfservice/strategy/saml/config_test.go @@ -225,7 +225,7 @@ func TestAttributesMapWithAnExtraField(t *testing.T) { idpInformation := make(map[string]string) idpInformation["idp_sso_url"] = "https://samltest.id/idp/profile/SAML2/Redirect/SSO" idpInformation["idp_entity_id"] = "https://samltest.id/saml/idp" - idpInformation["idp_certificate_path"] = "file://testdata/samlkratos.crt" + idpInformation["idp_certificate_path"] = "file://testdata/idp_cert.pem" idpInformation["idp_logout_url"] = "https://samltest.id/idp/profile/SAML2/Redirect/SSO" // Initiates the service provider @@ -235,8 +235,8 @@ func TestAttributesMapWithAnExtraField(t *testing.T) { saml.Configuration{ ID: "samlProvider", Label: "samlProviderLabel", - PublicCertPath: "file://testdata/myservice.cert", - PrivateKeyPath: "file://testdata/myservice.key", + PublicCertPath: "file://testdata/sp_cert.pem", + PrivateKeyPath: "file://testdata/sp_key.pem", Mapper: "file://testdata/saml.jsonnet", AttributesMap: attributesMap, IDPInformation: idpInformation, diff --git a/selfservice/strategy/saml/handler.go b/selfservice/strategy/saml/handler.go index 6ffac7ae70fc..eacb5e742c3f 100644 --- a/selfservice/strategy/saml/handler.go +++ b/selfservice/strategy/saml/handler.go @@ -93,18 +93,7 @@ func (h *Handler) serveMetadata(w http.ResponseWriter, r *http.Request, ps httpr w.Write(buf) } -// swagger:route GET /self-service/methods/saml/auth v0alpha2 initializeSelfServiceSamlFlowForBrowsers -// -// Initialize Authentication Flow for SAML (Either the login or the register) -// -// If you already have a session, it will redirect you to the main page. -// -// Schemes: http, https -// -// Responses: -// 200: selfServiceRegistrationFlow -// 400: jsonError -// 500: jsonError +// TODO Swagger func (h *Handler) loginWithIdp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Middleware is a singleton so we have to verify that it exists config := h.d.Config() @@ -153,9 +142,9 @@ func (h *Handler) instantiateMiddleware(ctx context.Context, config config.Confi } // Key pair to encrypt and sign SAML requests - keyPair, err := tls.LoadX509KeyPair(strings.Replace(providerConfig.PublicCertPath, "file://", "", 1), strings.Replace(providerConfig.PrivateKeyPath, "file://", "", 1)) + keyPair, err := tls.LoadX509KeyPair(strings.Replace(providerConfig.PublicCertPath, "file://", "", 1), strings.Replace(providerConfig.PrivateKeyPath, "file://", "", 1)) // TODO : Fetcher if err != nil { - return herodot.ErrNotFound.WithTrace(err) + return herodot.ErrNotFound.WithTrace(err) // TODO : Replace with File not found error } keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) if err != nil { @@ -313,7 +302,7 @@ func (h *Handler) instantiateMiddleware(ctx context.Context, config config.Confi // Return the singleton MiddleWare func GetMiddleware(pid string) (*samlsp.Middleware, error) { if samlMiddlewares[pid] == nil { - return nil, errors.Errorf("An error occurred while retrieving the middeware, it is null") + return nil, errors.Errorf("An error occurred while retrieving the middeware, it is null") // TODO : Improve error message } return samlMiddlewares[pid], nil } @@ -321,7 +310,7 @@ func GetMiddleware(pid string) (*samlsp.Middleware, error) { func MustParseCertificate(pemStr []byte) (*x509.Certificate, error) { b, _ := pem.Decode(pemStr) if b == nil { - return nil, errors.Errorf("Cannot find the next PEM formatted block") + return nil, errors.Errorf("Cannot find the next PEM formatted block while parsing the certificate") } cert, err := x509.ParseCertificate(b.Bytes) if err != nil { diff --git a/selfservice/strategy/saml/handler_test.go b/selfservice/strategy/saml/handler_test.go index c0d15540da57..cd8b40626438 100644 --- a/selfservice/strategy/saml/handler_test.go +++ b/selfservice/strategy/saml/handler_test.go @@ -38,7 +38,7 @@ func TestInitMiddleWareWithoutMetadata(t *testing.T) { middleWare, _, _, err := InitTestMiddlewareWithoutMetadata(t, "https://samltest.id/idp/profile/SAML2/Redirect/SSO", "https://samltest.id/saml/idp", - "file://testdata/samlkratos.crt", + "file://testdata/idp_cert.pem", "https://samltest.id/idp/profile/SAML2/Redirect/SSO") require.NoError(t, err) @@ -74,7 +74,7 @@ func TestMustParseCertificate(t *testing.T) { saml.DestroyMiddlewareIfExists("samlProvider") - certificateBuffer, err := fetcher.NewFetcher().Fetch("file://testdata/samlkratos.crt") + certificateBuffer, err := fetcher.NewFetcher().Fetch("file://testdata/sp_cert.pem") require.NoError(t, err) certificate, err := ioutil.ReadAll(certificateBuffer) @@ -83,13 +83,13 @@ func TestMustParseCertificate(t *testing.T) { cert, err := saml.MustParseCertificate(certificate) require.NoError(t, err) - assert.Check(t, cert.Issuer.Country[0] == "AU") - assert.Check(t, cert.Issuer.Organization[0] == "Internet Widgits Pty Ltd") - assert.Check(t, cert.Issuer.Province[0] == "Some-State") - assert.Check(t, cert.Subject.Country[0] == "AU") - assert.Check(t, cert.Subject.Organization[0] == "Internet Widgits Pty Ltd") - assert.Check(t, cert.Subject.Province[0] == "Some-State") - assert.Check(t, cert.NotBefore.String() == "2022-02-21 11:08:20 +0000 UTC") - assert.Check(t, cert.NotAfter.String() == "2023-02-21 11:08:20 +0000 UTC") - assert.Check(t, cert.SerialNumber.String() == "485646075402096403898806020771481121115125312047") + assert.Check(t, cert.Issuer.Country[0] == "US") + assert.Check(t, cert.Issuer.Organization[0] == "foo") + assert.Check(t, cert.Issuer.Province[0] == "GA") + assert.Check(t, cert.Subject.Country[0] == "US") + assert.Check(t, cert.Subject.Organization[0] == "foo") + assert.Check(t, cert.Subject.Province[0] == "GA") + assert.Check(t, cert.NotBefore.String() == "2013-10-02 00:08:51 +0000 UTC") + assert.Check(t, cert.NotAfter.String() == "2014-10-02 00:08:51 +0000 UTC") + assert.Check(t, cert.SerialNumber.String() == "14253244695696570161") } diff --git a/selfservice/strategy/saml/metadata_test.go b/selfservice/strategy/saml/metadata_test.go index 8a5bce69c115..a422e3a93433 100644 --- a/selfservice/strategy/saml/metadata_test.go +++ b/selfservice/strategy/saml/metadata_test.go @@ -2,7 +2,6 @@ package saml_test import ( "encoding/xml" - "fmt" "io" "io/ioutil" "net/http" @@ -75,8 +74,6 @@ func TestXmlMetadataExist(t *testing.T) { assert.NilError(t, err) res, err := NewTestClient(t, nil).Get(ts.URL + "/self-service/methods/saml/metadata/samlProvider") assert.NilError(t, err) - body, _ := ioutil.ReadAll(res.Body) - fmt.Println(body) assert.Check(t, is.Equal(http.StatusOK, res.StatusCode)) assert.Check(t, is.Equal("text/xml", res.Header.Get("Content-Type"))) } diff --git a/selfservice/strategy/saml/provider_saml.go b/selfservice/strategy/saml/provider_saml.go index 23047bf7d30c..e0917dfd44ea 100644 --- a/selfservice/strategy/saml/provider_saml.go +++ b/selfservice/strategy/saml/provider_saml.go @@ -26,7 +26,7 @@ func NewProviderSAML( } } -// Translate attributes from saml asseryion into kratos claims +// Translate attributes from saml assertion into kratos claims func (d *ProviderSAML) Claims(ctx context.Context, config *config.Config, attributeSAML samlsp.Attributes, pid string) (*Claims, error) { var c ConfigurationCollection diff --git a/selfservice/strategy/saml/saml_vulnerabilities_check.md b/selfservice/strategy/saml/saml_vulnerabilities_check.md new file mode 100644 index 000000000000..f9424d007b33 --- /dev/null +++ b/selfservice/strategy/saml/saml_vulnerabilities_check.md @@ -0,0 +1,52 @@ +# **SAML Crewjam over Kratos vulnerabilities checks** + +|Status|Summary|Description|Comment| +| :- | :- | :- | :- | +|**OK**|**Check that it's not possible to modify the signed SAML Response**||| +|OK|- By adding an attribute||| +|OK|- By adding an element||| +|OK|- By modifying the indent||| +|**OK**|**Check that it's not possible to modify the signed SAML Assertion**|**If the SAML Response isn't signed**|| +|OK|- By adding an attribute||| +|OK|- By adding an element||| +|**OK**|**Check that it's not possible to remove the signature**||| +|OK|- Not possible to remove SAML Response signature value|A signature must contain a signature value|| +|OK|- Possible to remove SAML Response signature if the SAML Assertion is signed|Either the SAML Response or the SAML Assertion must be signed|| +|OK|- Not possible to remove SAML Assertion signature value|A signature must contain a signature value|| +|OK|- Not possible to remove SAML Assertion signature|If the SAML Response is still signed, any SAML Assertion modification is an unauthorized SAML Response modification|| +|OK|- Not possible to remove both SAML Response signature and SAML Assertion signature|Either the SAML Response or the SAML Assertion must be signed|| +|**OK**|**Prevent from Signature Wrapping Attacks (XSW)**||| +|OK|- XSW1 response wrap 1|XSW #1 manipulates SAML Responses. It does this by making a copy of the SAML Response and Assertion, then inserting the original Signature into the XML as a child element of the copied Response. The assumption being that the XML parser finds and uses the copied Response at the top of the document after signature validation instead of the original signed Response.|| +|OK|- XSW2 response wrap 2|Similar to XSW #1, XSW #2 manipulates SAML Responses. XSW #1 and XSW #2 are the only two that deal with Responses. The key difference between #1 and #2 is that the type of Signature used is a detached signature where XSW #1 used an enveloping signature. The location of the malicious Response remains the same.|| +|OK|- XSW3 assertion wrap 1|XSW #3 is the first example of an XSW that wraps the Assertion element. SAML Raider inserts the copied Assertion as the first child of the root Response element. The original Assertion is a sibling of the copied Assertion.|| +|OK|- XSW4 assertion wrap 2|XSW #4 is similar to #3, except in this case the original Assertion becomes a child of the copied Assertion.|| +|OK|- XSW5 assertion wrap 3|XSW #5 is the first instance of Assertion wrapping we see where the Signature and the original Assertion aren’t in one of the three standard configurations (enveloped/enveloping/detached). In this case, the copied Assertion envelopes the Signature.|| +|OK|- XSW6 assertion wrap 4|XSW #6 inserts its copied Assertion into the same location as #’s 4 and 5. The interesting piece here is that the copied Assertion envelopes the Signature, which in turn envelopes the original Assertion.|| +|OK|- XSW7 assertion wrap 5|XSW #7 inserts an Extensions element and adds the copied Assertion as a child. Extensions is a valid XML element with a less restrictive schema definition. OpenSAML used schema validation to correctly compare the ID used during signature validation to the ID of the processed Assertion. The authors found in cases where copied Assertions with the same ID of the original Assertion were children of an element with a less restrictive schema definition, they were able to bypass this particular countermeasure.|| +|OK|- XSW8 assertion wrap 6|XSW #8 uses another less restrictive XML element to perform a variation of the attack pattern used in XSW #7. This time around the original Assertion is the child of the less restrictive element instead of the copied Assertion.|| +|**OK**|**Analyse the application behaviour when adding XML comments**|**In the beginning, middle, and end of an attribute (such as username)**|| +|OK|- The XML comments aren't removed||| +|OK|- The XML comments don't allow the user to authenticate with another identity||| +|**OK**|**Prevent from signing the SAML Response with own certificate**|**Depending on the case: assertion, message, or both**|| +|OK|- Prevent from signing the SAML assertion with own certificate||| +|OK|- Prevent from signing the SAML response with own certificate||| +|OK|- Prevent from signing both the response and the assertion||| +|**OK**|**Prevent from XXE and XSLT attacks**||| +|**OK**|**Check if there are any known vulnerabilities for the SAML library or software in use**||| +|**OK**|**Check if it is possible to send the same SAML Response twice (Replay Attack)**||| +|**OK**|**Check if the SP uses the same attribute as IdP to identify the user**||**There is a mapping**| +|**N/A**|**Check if IdP allows anonymous registration**||| +|**N/A**|**Verify Single Log Out (if required)**||| +|**OK**|**Check if the validity time window is short**|**3-5 minutes**|**90sec in our implementation**| +|**OK**|**Check if the time window is validated**|**Try to use the same SAML Response after it has expired**|| +|**N/A**|**Check for Cross-Site Request Forgery attack**|**Unsolicited Response**|| +|**OK**|**Check if the recipient is validated**|**Token Recipient Confusion**|| +|**N/A**|**Check for Open Redirect in RelayState**||| +|**OK**|**Check the signature algorithm in use**||**SHA256**| +|**OK**|**Check that the SAML response is associated with an AuthnRequest already performed on the IdP**|**Check that the ID field of the SAML Request corresponds to the InResponseTo field of the SAML Response**|| + +## **Sources** +- [SAML – what can go wrong? Security check](https://www.securing.pl/en/saml-what-can-go-wrong-security-check/) +- [How to Hunt Bugs in SAML; a Methodology - Part II](https://epi052.gitlab.io/notes-to-self/blog/2019-03-13-how-to-test-saml-a-methodology-part-two/) +- [On Breaking SAML: Be Whoever You Want to Be](https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf) +- [SAMLRaider](https://github.com/CompassSecurity/SAMLRaider) diff --git a/selfservice/strategy/saml/strategy.go b/selfservice/strategy/saml/strategy.go index 93ecf7533a34..fec366df3359 100644 --- a/selfservice/strategy/saml/strategy.go +++ b/selfservice/strategy/saml/strategy.go @@ -135,7 +135,7 @@ func NewStrategy(d registrationStrategyDependencies) *Strategy { // We indicate here that when the ACS endpoint receives a POST request, we call the handleCallback method to process it func (s *Strategy) setRoutes(r *x.RouterPublic) { - wrappedHandleCallback := strategy.IsDisabled(s.d, s.ID().String(), s.handleCallback) + wrappedHandleCallback := strategy.IsDisabled(s.d, s.ID().String(), s.HandleCallback) if handle, _, _ := r.Lookup("POST", RouteAcs); handle == nil { r.POST(RouteAcs, wrappedHandleCallback) } // ACS SUPPORT @@ -256,18 +256,20 @@ func (s *Strategy) validateCallback(w http.ResponseWriter, r *http.Request) (flo } // Handle /selfservice/methods/saml/acs/:provider | Receive SAML response, parse the attributes and start auth flow -func (s *Strategy) handleCallback(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // We get the provider ID form the URL pid := ps.ByName("provider") if err := r.ParseForm(); err != nil { s.d.SelfServiceErrorManager().Forward(r.Context(), w, r, s.handleError(w, r, nil, pid, nil, err)) + return } req, _, err := s.validateCallback(w, r) if err != nil { if req != nil { s.forwardError(w, r, s.handleError(w, r, req, pid, nil, err)) + return } else { s.d.SelfServiceErrorManager().Forward(r.Context(), w, r, s.handleError(w, r, nil, pid, nil, err)) } @@ -277,13 +279,17 @@ func (s *Strategy) handleCallback(w http.ResponseWriter, r *http.Request, ps htt m, err := GetMiddleware(pid) if err != nil { s.forwardError(w, r, err) + return } // We get the possible SAML request IDs possibleRequestIDs := GetPossibleRequestIDs(r, *m) + + // We parse the SAML Response to get the SAML Assertion assertion, err := m.ServiceProvider.ParseResponse(r, possibleRequestIDs) if err != nil { s.forwardError(w, r, err) + return } // We get the user's attributes from the SAML Response (assertion) @@ -313,6 +319,7 @@ func (s *Strategy) handleCallback(w http.ResponseWriter, r *http.Request, ps htt if ff, err := s.processLoginOrRegister(w, r, a, provider, claims); err != nil { if ff != nil { s.forwardError(w, r, err) + return } s.forwardError(w, r, err) } diff --git a/selfservice/strategy/saml/strategy_helper_test.go b/selfservice/strategy/saml/strategy_helper_test.go index 3fb92f5ee3f0..bb55a8f8fccb 100644 --- a/selfservice/strategy/saml/strategy_helper_test.go +++ b/selfservice/strategy/saml/strategy_helper_test.go @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - "gotest.tools/golden" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" @@ -103,10 +102,11 @@ func InitTestMiddleware(t *testing.T, idpInformation map[string]string) (*samlsp ts, _ := testhelpers.NewKratosServerWithRouters(t, reg, routerP, routerA) attributesMap := make(map[string]string) - attributesMap["id"] = "mail" + attributesMap["id"] = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" attributesMap["firstname"] = "givenName" attributesMap["lastname"] = "sn" - attributesMap["email"] = "mail" + attributesMap["email"] = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" + attributesMap["groups"] = "urn:oid:1.3.6.1.4.1.5923.1.1.1.1" // Initiates the service provider ViperSetProviderConfig( @@ -116,8 +116,8 @@ func InitTestMiddleware(t *testing.T, idpInformation map[string]string) (*samlsp ID: "samlProvider", Label: "samlProviderLabel", Provider: "generic", - PublicCertPath: "file://testdata/myservice.cert", - PrivateKeyPath: "file://testdata/myservice.key", + PublicCertPath: "file://testdata/sp_cert.pem", + PrivateKeyPath: "file://testdata/sp_key.pem", Mapper: "file://testdata/saml.jsonnet", AttributesMap: attributesMap, IDPInformation: idpInformation, @@ -129,15 +129,15 @@ func InitTestMiddleware(t *testing.T, idpInformation map[string]string) (*samlsp conf.MustSet(context.Background(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeSAML.String()), []config.SelfServiceHook{{Name: "session"}}) - t.Logf("Kratos Public URL: %s", ts.URL) + // t.Logf("Kratos Public URL: %s", ts.URL) // Instantiates the MiddleWare _, err := NewTestClient(t, nil).Get(ts.URL + "/self-service/methods/saml/metadata/samlProvider") require.NoError(t, err) middleware, err := saml.GetMiddleware("samlProvider") require.NoError(t, err) - middleware.ServiceProvider.Key = mustParsePrivateKey(golden.Get(t, "key.pem")).(*rsa.PrivateKey) - middleware.ServiceProvider.Certificate = mustParseCertificate(golden.Get(t, "cert.pem")) + //middleware.ServiceProvider.Key = mustParsePrivateKey(golden.Get(t, "sp_key.pem")).(*rsa.PrivateKey) + //middleware.ServiceProvider.Certificate = mustParseCertificate(golden.Get(t, "sp_cert.pem")) return middleware, strategy, ts, err } diff --git a/selfservice/strategy/saml/testdata/TestSPCanHandleOneloginResponse_response b/selfservice/strategy/saml/testdata/TestSPCanHandleOneloginResponse_response deleted file mode 100644 index 1031d30218df..000000000000 --- a/selfservice/strategy/saml/testdata/TestSPCanHandleOneloginResponse_response +++ /dev/null @@ -1 +0,0 @@ -PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJwZnhlZDg4YzQzZC02NTA0LWUxZjEtNWFmMC00MGJlN2YyNzlmYzUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjExWiIgRGVzdGluYXRpb249Imh0dHBzOi8vMjllZTZkMmUubmdyb2suaW8vc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iaWQtZDQwYzE1YzEwNGI1MjY5MWVjY2YwYTJhNWM4YTE1NTk1YmU3NTQyMyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzUwMzk4Mzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+PGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhlZDg4YzQzZC02NTA0LWUxZjEtNWFmMC00MGJlN2YyNzlmYzUiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPlNWQWFRZzh2bW1TUUw2L1lCbVMyeWRLUlA3ST08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+c0JlVFZQMGJab1BSK2JmeUFrVnY2STNDVjdZOFhxbkoycjhmMStXbXIyZ0ZnblJGODVOdnZTUCtyMUJvN250dU9zd080ZkI0Uks0SHlTYnlsZzRiS0hLSDE5WDkxaFZBekpTeXNmbVMvZDV3ZzFDZmlXV3Q1UzJIQTUwOHRoWHVabndHM1h6NktuV0s4a1JkeDFkYytZUldnYUZ5ZDRnTEc5YUJUc1hPWjd2eC83UDRicnpORW00d1A5LzB0dWZ4Rytuc1k2RHB3bkVHQ2psK1ZVS3BnekVxd05OalFxWUZZU0FYRWsrVnQrWDNjMmQwSElyWlF2WW5OaDAyS3h1d1ZCVGhuM01helFOYU54Qy9zeWYza0RRQ1JyWkNZbytZdER1ZHpKVTlwM0EwWVhIVFFjc2RldHNIWlhDTWozbXV2emMwbUVCbHc0TGJjaEttbmJ5Wm1nPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUVDRENDQXZDZ0F3SUJBZ0lVWHVuMDhDc2xMUldTTHFObkRFMU50R0plZmwwd0RRWUpLb1pJaHZjTkFRRUZCUUF3VXpFTE1Ba0dBMVVFQmhNQ1ZWTXhEREFLQmdOVkJBb01BMk4wZFRFVk1CTUdBMVVFQ3d3TVQyNWxURzluYVc0Z1NXUlFNUjh3SFFZRFZRUUREQlpQYm1WTWIyZHBiaUJCWTJOdmRXNTBJRE15TmpFME1CNFhEVEV6TURrek1ERTVNelUwTkZvWERURTRNVEF3TVRFNU16VTBORm93VXpFTE1Ba0dBMVVFQmhNQ1ZWTXhEREFLQmdOVkJBb01BMk4wZFRFVk1CTUdBMVVFQ3d3TVQyNWxURzluYVc0Z1NXUlFNUjh3SFFZRFZRUUREQlpQYm1WTWIyZHBiaUJCWTJOdmRXNTBJRE15TmpFME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBME9HOFY4bWhvdmtqNHJoR2hqcmJFeFJZYnpLVjJaeGZ2R2ZFR1hHVXZYYzZEcWVqWUVkaFoybUlmQ0RvamhRamswQnl3aWlyQUtNT3QxR051SDdhV0lFNDdEMGV3dEs1eWxFQW03ZVZtb1k0a3hMQ2FXNXdZckMxU3pNbnBlaXRVeHF2c2JuS3ozalVLWUhSZ2dwZnZWajRzaUhEWmVJWmE5YTVyVXZwTW5uYk9vRmlaQ0lFTnBxM1RDMzNpdk9TWmhFTlJUem12bms1R0RvTEh3LzhxQWdRaXlUM0QxeENrU0JiNTRQSGdrUTVScTFvZExNL2hKK0wwanpDVVFINGd4cFdsRUFhYjRLOXM4ZnBCVUJCaDVnbUpDWWk4VWJJbGhxTzhOMm15bnVtMzNCVS92SjNQbmF3VDRZWWtUd1JVeDZZKzNmcG1SQkhxbDRoODNTTWV3SURBUUFCbzRIVE1JSFFNQXdHQTFVZEV3RUIvd1FDTUFBd0hRWURWUjBPQkJZRUZPZkZGakhGajlhNnhwbmdiMTFycmhnTWU5QXJNSUdRQmdOVkhTTUVnWWd3Z1lXQUZPZkZGakhGajlhNnhwbmdiMTFycmhnTWU5QXJvVmVrVlRCVE1Rc3dDUVlEVlFRR0V3SlZVekVNTUFvR0ExVUVDZ3dEWTNSMU1SVXdFd1lEVlFRTERBeFBibVZNYjJkcGJpQkpaRkF4SHpBZEJnTlZCQU1NRms5dVpVeHZaMmx1SUVGalkyOTFiblFnTXpJMk1UU0NGRjdwOVBBckpTMFZraTZqWnd4TlRiUmlYbjVkTUE0R0ExVWREd0VCL3dRRUF3SUhnREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBTWdsbjROUE1RbjhHeXZxOENUUCtjMmU2Q1V6Y3ZSRUtuVGhqeFQ5V2N2VjFaVlhNQk5QbTRjVHFUMzYxRWRMelk1eVdMVVdYZDRBdkZuY2lxQjNNSFlhMm5xVG1udkxnbWhrV2UraGRGb05lNStJQThBeEduK25xVUlTbXlCZUN4dVVVQWJSTXVvd2lBcndISXB6cEV5UklZZFNaUk5GMGR2Z2lQWXlyL01pUFhJY3pwSDVuTGt2YkxwY0FGK1I4Wmg5bndZMGcxSlZ5YzZBQjZqN1lleHVVUVpwSEg0czBWZHgvbldtcmNGZUxaS0NUeGNhaEh2VTUwZTF5S1g1dGhmVmFKcUk4UVE3eFp4eXUwVFRzaWFYMHV3NTFKUE96UHVBUHBoMHo2eG9TOW9ZeHV6WjF5OXNOSEg2a0g4R0ZudlMyTXF5SGlOejBoMFNxL3E2bit3PT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJBZDk0NWFlZGEzOGE1MDhmOGZhYzliYzk2MTNkNTk2NDJjMGQyZDhjYiIgSXNzdWVJbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjExWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzUwMzk4Mzwvc2FtbDpJc3N1ZXI+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnJvc3NAa25kci5vcmc8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTYtMDEtMDVUMTc6NTY6MTFaIiBSZWNpcGllbnQ9Imh0dHBzOi8vMjllZTZkMmUubmdyb2suaW8vc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iaWQtZDQwYzE1YzEwNGI1MjY5MWVjY2YwYTJhNWM4YTE1NTk1YmU3NTQyMyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE2LTAxLTA1VDE3OjUwOjExWiIgTm90T25PckFmdGVyPSIyMDE2LTAxLTA1VDE3OjU2OjExWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovLzI5ZWU2ZDJlLm5ncm9rLmlvL3NhbWwvbWV0YWRhdGE8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjEwWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNi0wMS0wNlQxNzo1MzoxMVoiIFNlc3Npb25JbmRleD0iX2ViZGNiZTgwLTk1ZmYtMDEzMy1kODcxLTM4Y2EzYTY2MmYxYyI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyIgTmFtZT0iVXNlci5lbWFpbCI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cm9zc0BrbmRyLm9yZzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIiBOYW1lPSJtZW1iZXJPZiI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyIvPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiIE5hbWU9IlVzZXIuTGFzdE5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPktpbmRlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIiBOYW1lPSJQZXJzb25JbW11dGFibGVJRCI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyIvPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiIE5hbWU9IlVzZXIuRmlyc3ROYW1lIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Sb3NzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+Cgo= \ No newline at end of file diff --git a/selfservice/strategy/saml/testdata/authn_request.url b/selfservice/strategy/saml/testdata/authn_request.url new file mode 100644 index 000000000000..1adc1dbf5f25 --- /dev/null +++ b/selfservice/strategy/saml/testdata/authn_request.url @@ -0,0 +1 @@ +https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO?RelayState=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmkiOiIvIn0.eoUmy2fQduAz--6N82xIOmufY1ZZeRi5x--B7m1pNIY&SAMLRequest=lJJBj9MwEIX%2FSuR7Yzt10sZKIpWtkCotsGqB%2B5BMW4vELp4JsP8et4DYE5Tr%2BPnN957dbGY%2B%2Bz1%2BmZE4%2Bz6NnloxR28DkCPrYUKy3NvD5s2jLXJlLzFw6MMosg0RRnbBPwRP84TxgPGr6%2FHD%2FrEVZ%2BYLWSl1WVXaGJP7UwyfcxckwTQWEnoS2TbtdB6uHn9uuOGSczqgs%2FuUh3i6DmTaenQjyitGIfc4uIg9y8Phnch221a4YVFjpVflcqgM1sUajiWsYGk01KujKVRfJyHRjDtPDJ5bUShdLrReLNX7QtmysrrMK6Pqem3MeqFKq5TInn6lfeX84PypFSL7iJFuwKkN0TU303hPc%2FC7L5G9DnEC%2Frv8OkmxjjepRc%2BOn0X3r14nZBiAoZE%2FwbrmbfLZbZ%2FC6Prn%2F3zgcQzfHiICYys4zii6%2B4E5gieXsBv5kqBr5Msf1%2F0IAAD%2F%2Fw%3D%3D \ No newline at end of file diff --git a/selfservice/strategy/saml/testdata/evilcert.crt b/selfservice/strategy/saml/testdata/evilcert.crt new file mode 100644 index 000000000000..9276d9dcf75d --- /dev/null +++ b/selfservice/strategy/saml/testdata/evilcert.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbTCCA1WgAwIBAgIUWq4yeMsGByiaIrpB6pTtIdcjhcEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAxMTkxMDA1NTFaGA8yMjk2 +MTEwMjEwMDU1MVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAMjjZ/jQqiR+TnOxerWMWnSCOssESP12KF2K5gHV +d6ymjb3/hwuDKFEV02m1VlGgtrqjKfsRrLpuvkFBDhN0Erpi9P9cE/lOdYecMilz +1HUBKneL+k5dJ2AJjV/jkp3FYSmoPhRXZyDpLZthpaBOiiHBcRctrOmY5f9cuyXL +nQpaXHLUbQtaYv2fwgy+wbcnR7sjIHDRTszQjvRkLA7hjMBIIMnlqugt85mEYYqQ +QYNXxu2K/M8tK8vAOxvrwvPGEsOX2G2pXFSHe65EUIhjEeO2yojMHWYz3eTSCKXt +cRnXfZIjXN2v5I0mVlo7SWP1iHC42EiufesoCltmOIHwC1Gg22Cg335cWQAxqacA +pJkkGSgeo1to/QszoTu+VrqjuC+hvLbDzqS8asnRsDwZoXJv/hKMglqo21JrVCJ7 +SnhVXXLfrkx4N55YHqGojdvu7ntkh2IRxGY8mWydSYCGnZNWtNJuOyr0gHcqwMl8 +vKD5EmjeLWcDNja+bvxY+K5OaRZB89QNZc9DeCzuHLOIMDGk40fISQ8vAwWoYECT +y+KPfEBozzUHxf7m4jjJnpm1RI9q0iJOJ1lf65geDuDolMa4iVlnOjmC2+MuzCDz +wd9WAR9XEqhNOAU23COQa+Lwgay21W9Xkor+xVxCr4/b1EmD4fThXoDarVGpau5D +tgSLAgMBAAGjUzBRMB0GA1UdDgQWBBSvI5Jvly7KgEmzj+RBxFZJW72duTAfBgNV +HSMEGDAWgBSvI5Jvly7KgEmzj+RBxFZJW72duTAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQBBSnjAJaPQiprZwyLXhrX6UwDgHLFOduddRa27/ASf +jUWSitPmiTHFsm+rJaLU3HuDCJPfeb9Rs7pJNNWffpMxPgDs+8apzxpfRCrtsGlU +bxJf7F7olWzStAfgXHhTuvx2X0Q1mTHy8cQALjAxY08IvvMSkKoEXZnXBK6jNU1b +srIjAzejTJO3swY0tXu1CDD+Iwr16gtJ4uuev9LDFhO5nooTgwwzdM+LP2R53bwT +pMJon6lWsvyjhBCNn9psO7V70/xMdnDAoJxqR+byngC+E6Y4XuAFunzWjG7Htzno +1D/6VGVcXQJa3hGr9QSx+f3AA6VXUlYojIm+uZkYVxbFrSKMt+ry216Nvr5vqmfZ +IaMBq8/gegvHvdo0IPF/urEduG8ELHrLCYyRZeIYQqQM9N8TeWtX2shEMRd+LpIX +wRjuQfdLUAWI1Sre8iXHF3gk88UY3pc7bDk1dcL9l9tHys39NmOhltOof6397+7S +xGSw2MSwAazTYv1eAsFZXh/r3PYmha2H7mdBvmjhOm5EttPuv9ZZfQTE+r2UaqbP +4iggnJcrnXy6ahwLP1l3dLxU0wtHkRZ2IQD0OQ5OyQpl6ORubwDBbTo5kwVsOmu2 +0yyUl2zdkWWgK2XYnMvrTG7U7n6kZxU755tWfb47f0lDxgTIj5W0Zo0FgDAc4AUH +qw== +-----END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/evilkey.key b/selfservice/strategy/saml/testdata/evilkey.key new file mode 100644 index 000000000000..7048d5784532 --- /dev/null +++ b/selfservice/strategy/saml/testdata/evilkey.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +uRuG0/FW/PEfgJ18NVy8GwjInUbZ6ZU= +-----END PRIVATE KEY----- diff --git a/selfservice/strategy/saml/testdata/expected_metadata.xml b/selfservice/strategy/saml/testdata/expected_metadata.xml index 05039766f75f..a2ddb8cd5479 100644 --- a/selfservice/strategy/saml/testdata/expected_metadata.xml +++ b/selfservice/strategy/saml/testdata/expected_metadata.xml @@ -1,4 +1,4 @@ - + @@ -19,7 +19,8 @@ - - + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + \ No newline at end of file diff --git a/selfservice/strategy/saml/testdata/cert.pem b/selfservice/strategy/saml/testdata/idp_cert.pem similarity index 96% rename from selfservice/strategy/saml/testdata/cert.pem rename to selfservice/strategy/saml/testdata/idp_cert.pem index 52667ef39ff2..cba15632963d 100644 --- a/selfservice/strategy/saml/testdata/cert.pem +++ b/selfservice/strategy/saml/testdata/idp_cert.pem @@ -10,4 +10,4 @@ Rsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgk akpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeT QLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvn OwJlNCASPZRH/JmF8tX0hoHuAQ== ------END CERTIFICATE----- \ No newline at end of file +-----END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/key.pem b/selfservice/strategy/saml/testdata/idp_key.pem similarity index 96% rename from selfservice/strategy/saml/testdata/key.pem rename to selfservice/strategy/saml/testdata/idp_key.pem index 48284dac33a1..c4530a84babb 100644 --- a/selfservice/strategy/saml/testdata/key.pem +++ b/selfservice/strategy/saml/testdata/idp_key.pem @@ -12,4 +12,4 @@ Rq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA yfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr vBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6 hU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA== ------END RSA PRIVATE KEY----- \ No newline at end of file +-----END RSA PRIVATE KEY----- diff --git a/selfservice/strategy/saml/testdata/idp_metadata.xml b/selfservice/strategy/saml/testdata/idp_metadata.xml new file mode 100644 index 000000000000..210c78b7104f --- /dev/null +++ b/selfservice/strategy/saml/testdata/idp_metadata.xml @@ -0,0 +1,31 @@ + + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + \ No newline at end of file diff --git a/selfservice/strategy/saml/testdata/myservice.cert b/selfservice/strategy/saml/testdata/myservice.cert deleted file mode 100755 index a815f8f44742..000000000000 --- a/selfservice/strategy/saml/testdata/myservice.cert +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDITCCAgmgAwIBAgIUAKe3G3G4JRoPJDbHcFfUC0M1vUwwDQYJKoZIhvcNAQEL -BQAwIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1wbGUuY29tMB4XDTIxMTIyODEw -MTcxOFoXDTIyMTIyODEwMTcxOFowIDEeMBwGA1UEAwwVbXlzZXJ2aWNlLmV4YW1w -bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA456eHhpbTabo -JD9IurVIakdb4Y1CtM1cWEgeDB/owu+h13pqj+wk/1AlFUNIYKfzJNmP+CoJv5pS -vUeJaMdA7vKUCHPMY7SNoZdaX0eGV4Z9Q7Q6pSkV+heoamojl+Lq9VIVvWnz4ra9 -3xjvJJ4bACyIz7k9u32jAb+v3Rh3axVlPfYJqCx0gU+tcMxb/Lc7HH7ynAjFGc4N -iG7qOqE2nmzRanKw4dMJhkzhNyFQbqtd4DmEzV70XixyztxmbENVfNdvOrCc34/e -JR4q7w5YEGMwUIPip7/zz/itqsrk0x4/VF1lExMOihf8dfYnqdF3+SdywoBf5UC4 -AUyFS/3FgQIDAQABo1MwUTAdBgNVHQ4EFgQUdG+6zhMmsR2yenGz22Iacjeh6BUw -HwYDVR0jBBgwFoAUdG+6zhMmsR2yenGz22Iacjeh6BUwDwYDVR0TAQH/BAUwAwEB -/zANBgkqhkiG9w0BAQsFAAOCAQEAU5eJKGCBsJpMgL6AgrtpY47iT2KtIkeiI5RC -L+2z2pORG2jFzvY+3kcYA+Nj7EwVyBGmn2lL2JCgk3Qr1YsO4IMJ6sZYbDi6I1SR -z14QMYDRWqPY7VoyqiDzdIS9ENWm80gCG4BChSMtEtN2kmjdTOM++Cr4LY/LLhM4 -9aSNfXHTx4kklP1VVc8dGWw+bFtzZUeP6O+ssrFhcse4V6DoQAxYSU4MAAjePhAP -0IS2I3sSzLe/LCsJMPZv0r1q8YQCGBrijAXSnQiu8KFh8hEQusxilIZV9XPDGB98 -EwTT5cbtUtOIbrZ6kdBs49O27xCTymaIuysidFtywwTaDdrc1g== ------END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/myservice.key b/selfservice/strategy/saml/testdata/myservice.key deleted file mode 100755 index e7b461f2f228..000000000000 --- a/selfservice/strategy/saml/testdata/myservice.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDjnp4eGltNpugk -P0i6tUhqR1vhjUK0zVxYSB4MH+jC76HXemqP7CT/UCUVQ0hgp/Mk2Y/4Kgm/mlK9 -R4lox0Du8pQIc8xjtI2hl1pfR4ZXhn1DtDqlKRX6F6hqaiOX4ur1UhW9afPitr3f -GO8knhsALIjPuT27faMBv6/dGHdrFWU99gmoLHSBT61wzFv8tzscfvKcCMUZzg2I -buo6oTaebNFqcrDh0wmGTOE3IVBuq13gOYTNXvReLHLO3GZsQ1V81286sJzfj94l -HirvDlgQYzBQg+Knv/PP+K2qyuTTHj9UXWUTEw6KF/x19iep0Xf5J3LCgF/lQLgB -TIVL/cWBAgMBAAECggEAAn9H/s6NN+Hf5B3pn1rDy56yzFuvYqpqG/HWmo1zEUht -vx5xstiFY2OutHgDgEP3b+0PHkrfxoFb7QWu5T5iYPy6UQlsMZ/WefJeJHN1btpj -321Hw24a9p5x05EMiOsNZtmasXRLH66fkKYGYaF2bF8QtS60Fa2AL1G6DTPqg3s4 -T+ijNYPr1xUk5GSh8Ea0DjLhzL6WgSHj+eBKgfEdYPDlOaQaYQuV2OJg9JyqxV6h -/Fa1HDc6RgpIhalLhP+9OqhSr9vmXSzEidzu+WTQSPpabwlVIae30Qh8XT9bYF5v -TElDXv5e5FwFmIJTnhAHyGlpnJ3KzaEHkmGbAxLOQQKBgQD2P4++d0WzrugKnfpz -hMpIVwk4jl1l2LUe3LoKEtF85lj6NjmvUNEPfJ0MIwKAjQYZ9AJWgCPP2/kjDBRv -dwwtSDIjFf79y810MNTGhAKv8nf7Lf5tSiJbvWgwtiiqF/ivUlxOKL9jqc6qj2s9 -psFoPOSAHQz6NqNpGyNza/7+CQKBgQDsojNWLJUXVzeUCMCzF+tn8lgs1aGrjHB7 -ZMHpr5nZCBdXjAzZR6yQH653Fa3OzNnVjq8CiO1ZdvbwW/KgVUHB4Mb/4kJ0Uxbm -WOF7zQjsMleoABFTi5mCcSqEK+u1qnrG8Ful9L6F8WhP7mdDmRXQM3f9rG2NDb1H -/OJuj/LpuQKBgQDK0+31Z069QtsUK62oSv9G+JG6yOC7S/Vbt1lxhLCSnTU620FG -W13n0K+W2JtuATq+U9M9JozY4ApkyMVoTnl0LtxFNA/1QlI3WyVXYlLIVAJpnSfN -I1wLjoZsYQ47lEUdO8yWAFAsqih1Km6duGXkEwvvTn5q9mhA4b6giprc6QKBgQCR -knMcd068ziXdxsitJHDoQHkoE8BiZYIpFuIIHcP6dPTPIdQhsusguqy8i7Sh/Pmh -XCaj25KQMBRX52jKY8iROfOSJSIWp6r1yAXnAEqV655rNqdyCvZD/dRW/SIDXz4q -tmDbJkYy5kDys0oJltqJe7A8eV/nn2UrLRIrTBj22QKBgQCFMmXVRqRje9k0Aqfe -KGYYCEPzeFzY4PzufwoOyhsGkLCwKthf43jXjWy53+u82Od1oKiNCjIhQHOtL720 -mTIhl2AzTJ1VMWoqUIHtGxhaIC3zhDjAaTMHZNDXFU78hPOhcBPtKikh3Hj2bfGG -TK1KTG49VMcWHmYJhJXwVevKAg== ------END PRIVATE KEY----- diff --git a/selfservice/strategy/saml/testdata/saml.jsonnet b/selfservice/strategy/saml/testdata/saml.jsonnet index 87103e26bc6b..2b97922447ec 100644 --- a/selfservice/strategy/saml/testdata/saml.jsonnet +++ b/selfservice/strategy/saml/testdata/saml.jsonnet @@ -12,6 +12,7 @@ local claims = { // Therefore we only return the email if it (a) exists and (b) is marked verified // by Discord. [if "email" in claims && claims.email_verified then "email" else null]: claims.email, + [if "groups" in claims then "groups" else null]: claims.groups, }, }, } \ No newline at end of file diff --git a/selfservice/strategy/saml/testdata/saml_response.xml b/selfservice/strategy/saml/testdata/saml_response.xml index 22ea0920a014..05b53a365821 100644 --- a/selfservice/strategy/saml/testdata/saml_response.xml +++ b/selfservice/strategy/saml/testdata/saml_response.xml @@ -1,5 +1,5 @@ https://idp.testshib.org/idp/shibbolethMIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UE CAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoX DTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28x diff --git a/selfservice/strategy/saml/testdata/samlkratos.crt b/selfservice/strategy/saml/testdata/samlkratos.crt deleted file mode 100755 index 3dfdeb703e1c..000000000000 --- a/selfservice/strategy/saml/testdata/samlkratos.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUVREfiVXf4z/hq8AsbyNnkuWn6i8wDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAyMjExMTA4MjBaFw0yMzAy -MjExMTA4MjBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCjvij3wZV+OhbEbwcs7cpc1hGR+uK4Y0y/ItHkAqlV -ddl+D28iDJeHci4LA8XmG0loFMTxdC9PG5t4ewn8G18+EeYRV0K3BMMWfxrO6ibG -z1ElTxQvVSw9tgPpjIgZqL8Qso8UO1ji98yoPhqP77F29pCNqiHrKJI1c52OCPHq -NBCZa76DmCGcXKAwRQaTo+tig6HJ1/3qCLGq57O396mQRFvjB535mceLzKSpFHsh -45beytXiBjTkvOEmNIUGVKIidXxqDtuTHz5QqhHTHMSsFH8cT648sSB9K9jPZ6ai -VCq5z/McyaYFlb/wt7PApJTSRjU0Any4876eBca59ca/AgMBAAGjUzBRMB0GA1Ud -DgQWBBQml5ORluABegdU+rLlpn++esD9fjAfBgNVHSMEGDAWgBQml5ORluABegdU -+rLlpn++esD9fjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCL -X5bpRKtMY7FsPtMsO/KBz5GT7P6aqe8pS0m3uXap6KkQwxa2wyyyH+in6uds8Sxm -bsdsGpSpCfGQCMqmu0yCjhfwI8nFA6q1YxLNgmx7kEIAQQQG2+jZJE7adXzSk2vT -tiNQ55mfiO9Wv+JpaB7ldAX3Q+O2uqVLJG/NlvC3ZAq0FXMyeitddLYSmEE0xrcM -QTB7vb7LpZk7Owa2UJ2VcQyZcxLWMonikIg4u3ALHGR0SvEgMwGhWr354RDGLYSO -Ii5O1foUR1O71jffr7CgELauyz3AXv6PNYLkyOCQP5gNB2NEMLJBRn5U4IhCHKzD -t1/BujsTuZV5r6aj3J9+ ------END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/sp2_cert.pem b/selfservice/strategy/saml/testdata/sp2_cert.pem new file mode 100644 index 000000000000..e74d8fcb8c00 --- /dev/null +++ b/selfservice/strategy/saml/testdata/sp2_cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbTCCA1WgAwIBAgIUDxKgJwZpe1Fmo4nFUVYTXwvIgOAwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAxMTExMzQ4MTlaGA8yMjk2 +MTAyNTEzNDgxOVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAM6XqltTvOTIqGZeUKnRfWqZdmjiJWU4XtSh4D8v +DUmBRIYXEBbcSqcNKsHjNoHoTXBqa9jkxl5ZIOwE3mCWr5heXTR9T4IhtsWRopWB +Aio8AIdAPuHiQoMxGm0jwZ4KCaZfLsHRIflJloSFEtebungmpDqo4hwsM6IUz5z3 +bKX1rlCCtupHpLihaLMVmgSSBvLDeAPvtHIBtNjNyhhi9TMS8SxFbCI7+PXRTRrR +G2ozUrR8qIBQxh/kA/rQWH4GGLt6phBdsxnmdn2idLhyvTjT8nA09gglvXZZ7gI9 +m8jbmUgpVGzmk38cAw+oENVMufAZWJBdAfrIfK0TR5YqXqhfj6e5BBf4OyM4rMlo +xlJnbQTI30uB2uzPfjry43NSu9jlFc/+r4ufW+ptEH9YIx9HbSz8y+hrpFdofSEQ +8J6w71x3NzN1L/hFKYqhQ8Gv2Flk1kp0iLkZ3tP1UCK2eDFhM9BBD3ZHYyByboWj +sKffst67LUAlufiLW4q0tZkpWwUR5fExAsFF//CciKPIpe8WBKlHxw6FYGPRGva5 +KK10jGZTJoM+KYwcZFpHCaWt8tFQB8ti9bQIP2etmrpsBZuR4CDuZEkMuvSLsJE5 +6SI+rxT3WKwK9zAginomFvEEvaqp43RT2a1zzYAfE3pRETis1tyoNN2I384ywL41 +lofhAgMBAAGjUzBRMB0GA1UdDgQWBBSmECM2phplGyTKV/dGef+JUPVpgjAfBgNV +HSMEGDAWgBSmECM2phplGyTKV/dGef+JUPVpgjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4ICAQCxbwfNQvpw68pTmyCIipb5pkuVDnjp65RV0wJbOfDR +qiQHVQsJexY1xmptOADzCvBQIkAAKCeLfJ8tKS6473Xc3BayREyJpN3oQsr1MDep +j6/ae8I1wt6uJ18M93wArWou/nuDHlkBeEKYlwCQYRZPW++9E38v6ZzKK7qHN+6M +vFKXx/Q98WpaNo5Oj0o8ngEYxS5/9Axn7EUBKLpikb8KNIO+icqc6DPs4GTqKb4/ +wC5FPcROoQAau3RsrZ8cAMU+zGt6OeYWU2Sabsnm1lo3bYMx2XuQOEQvLcrTjBLm +041LYk3SbPotBWc4ahVF4SUZWHKZst76+cZtR5RLZt3jjKjTguq76itPnuZxdM0g +JmdhophvFNwyKjxQ3jbJc9W1mpq5ILrtzO0pWTjOrBDWdZ4GF078GmjYrtJJ2e7T +LI0uuXwKB0K5SktluIM+7PVXYqt3ZnPJ6zjMCuYoQT5ua29hs9Qi/zjAf2Mf8JLo +t2MdAmvVZDr8bSVkyx0RrIKwYKLJ6b+KgdSACb618GV6dLpqMbe3mC+yPPa/FKIS +M64SBf/gBlMQpcUWdH4IWvQXu1Lmn+TPr4+BXi7loMGwAcGH7pcbYouOMsZlJ/CG +1cQ5cf3kevKolmJVaxJC+ZEBCqvM3/FySSmNNCmQidXi5QLHP87uYn/9aRaHh1kM +eA== +-----END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/sp2_key.pem b/selfservice/strategy/saml/testdata/sp2_key.pem new file mode 100644 index 000000000000..9bf6aeab4b89 --- /dev/null +++ b/selfservice/strategy/saml/testdata/sp2_key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +d7y+GE7k0jSTLxP851L5BvfqKi2Ay0w= +-----END PRIVATE KEY----- diff --git a/selfservice/strategy/saml/testdata/sp_cert.pem b/selfservice/strategy/saml/testdata/sp_cert.pem new file mode 100644 index 000000000000..cba15632963d --- /dev/null +++ b/selfservice/strategy/saml/testdata/sp_cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0 +MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9 +ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmH +O8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKv +Rsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgk +akpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeT +QLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvn +OwJlNCASPZRH/JmF8tX0hoHuAQ== +-----END CERTIFICATE----- diff --git a/selfservice/strategy/saml/testdata/sp_key.pem b/selfservice/strategy/saml/testdata/sp_key.pem new file mode 100644 index 000000000000..c4530a84babb --- /dev/null +++ b/selfservice/strategy/saml/testdata/sp_key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDU8wdiaFmPfTyRYuFlVPi866WrH/2JubkHzp89bBQopDaLXYxi +3PTu3O6Q/KaKxMOFBqrInwqpv/omOGZ4ycQ51O9I+Yc7ybVlW94lTo2gpGf+Y/8E +PsVbnZaFutRctJ4dVIp9aQ2TpLiGT0xX1OzBO/JEgq9GzDRf+B+eqSuglwIDAQAB +AoGBAMuy1eN6cgFiCOgBsB3gVDdTKpww87Qk5ivjqEt28SmXO13A1KNVPS6oQ8SJ +CT5Azc6X/BIAoJCURVL+LHdqebogKljhH/3yIel1kH19vr4E2kTM/tYH+qj8afUS +JEmArUzsmmK8ccuNqBcllqdwCZjxL4CHDUmyRudFcHVX9oyhAkEA/OV1OkjM3CLU +N3sqELdMmHq5QZCUihBmk3/N5OvGdqAFGBlEeewlepEVxkh7JnaNXAXrKHRVu/f/ +fbCQxH+qrwJBANeQERF97b9Sibp9xgolb749UWNlAdqmEpmlvmS202TdcaaT1msU +4rRLiQN3X9O9mq4LZMSVethrQAdX1whawpkCQQDk1yGf7xZpMJ8F4U5sN+F4rLyM +Rq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA +yfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr +vBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6 +hU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA== +-----END RSA PRIVATE KEY----- diff --git a/selfservice/strategy/saml/testdata/token.json b/selfservice/strategy/saml/testdata/token.json new file mode 100644 index 000000000000..c85dbac81d7f --- /dev/null +++ b/selfservice/strategy/saml/testdata/token.json @@ -0,0 +1,46 @@ +{ + "aud": "https://15661444.ngrok.io/", + "iss": "https://15661444.ngrok.io/", + "exp": 1448942229, + "iat": 1448935029, + "nbf": 1448935029, + "sub": "_41bd295976dadd70e1480f318e772841", + "attr": { + "SessionIndex": [ + "_6149230ee8fb88d3635c238509d9a35a" + ], + "cn": [ + "Me Myself And I" + ], + "eduPersonAffiliation": [ + "Member", + "Staff" + ], + "eduPersonEntitlement": [ + "urn:mace:dir:entitlement:common-lib-terms" + ], + "eduPersonPrincipalName": [ + "myself@testshib.org" + ], + "eduPersonScopedAffiliation": [ + "Member@testshib.org", + "Staff@testshib.org" + ], + "eduPersonTargetedID": [ + "" + ], + "givenName": [ + "Me Myself" + ], + "sn": [ + "And I" + ], + "telephoneNumber": [ + "555-5555" + ], + "uid": [ + "myself" + ] + }, + "saml-session": true + } \ No newline at end of file diff --git a/selfservice/strategy/saml/vulnerabilities_helper_test.go b/selfservice/strategy/saml/vulnerabilities_helper_test.go new file mode 100644 index 000000000000..b49f433d81b8 --- /dev/null +++ b/selfservice/strategy/saml/vulnerabilities_helper_test.go @@ -0,0 +1,330 @@ +package saml_test + +import ( + "bytes" + "crypto" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/xml" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + "time" + + "github.com/beevik/etree" + "github.com/crewjam/saml" + "github.com/crewjam/saml/logger" + "github.com/crewjam/saml/samlsp" + "github.com/crewjam/saml/xmlenc" + "github.com/julienschmidt/httprouter" + "github.com/ory/kratos/continuity" + "github.com/ory/kratos/selfservice/flow" + "github.com/ory/kratos/selfservice/flow/login" + samlhandler "github.com/ory/kratos/selfservice/strategy/saml" + "github.com/ory/kratos/x" + + "github.com/gofrs/uuid" + "github.com/golang-jwt/jwt/v4" + dsig "github.com/russellhaering/goxmldsig" + "github.com/stretchr/testify/require" + "gotest.tools/assert" + "gotest.tools/golden" +) + +type MiddlewareTest struct { + AuthnRequest []byte + SamlResponse []byte + Key *rsa.PrivateKey + Certificate *x509.Certificate + IDPMetadata []byte + Middleware *samlsp.Middleware +} + +type IdentityProviderTest struct { + SPKey *rsa.PrivateKey + SPCertificate *x509.Certificate + SP saml.ServiceProvider + + Key crypto.PrivateKey + Certificate *x509.Certificate + IDP saml.IdentityProvider +} + +type mockServiceProviderProvider struct { + GetServiceProviderFunc func(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) +} + +func (mspp *mockServiceProviderProvider) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { + return mspp.GetServiceProviderFunc(r, serviceProviderID) +} + +func mustParseURL(s string) url.URL { + rv, err := url.Parse(s) + if err != nil { + panic(err) + } + return *rv +} + +func setSAMLTimeNow(timeStr string) { + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 MST 2006", timeStr) + return rv + } + + saml.TimeNow = TimeNow + jwt.TimeFunc = TimeNow + saml.Clock = dsig.NewFakeClockAt(TimeNow()) +} + +func (test *MiddlewareTest) makeTrackedRequest(id string) (string, string) { + uuid, _ := uuid.NewV4() + + codec := test.Middleware.RequestTracker.(samlsp.CookieRequestTracker).Codec + index := uuid.String() + token, err := codec.Encode(samlsp.TrackedRequest{ + Index: index, + SAMLRequestID: id, + URI: "/frob", + }) + if err != nil { + panic(err) + } + return token, index +} + +func NewMiddlewareTest(t *testing.T) (*MiddlewareTest, *samlhandler.Strategy, *httptest.Server) { + middlewareTest := MiddlewareTest{} + + samlhandler.DestroyMiddlewareIfExists("samlProvider") + + middleWare, strategy, ts, err := InitTestMiddlewareWithMetadata(t, "file://testdata/idp_metadata.xml") + if err != nil { + return nil, nil, nil + } + + middlewareTest.Middleware = middleWare + + middlewareTest.Key = middlewareTest.Middleware.ServiceProvider.Key + middlewareTest.Certificate = middlewareTest.Middleware.ServiceProvider.Certificate + middlewareTest.IDPMetadata = golden.Get(t, "idp_metadata.xml") + + var metadata saml.EntityDescriptor + if err := xml.Unmarshal(middlewareTest.IDPMetadata, &metadata); err != nil { + panic(err) + } + + return &middlewareTest, strategy, ts +} + +func NewIdentifyProviderTest(t *testing.T, serviceProvider saml.ServiceProvider, tsURL string) *IdentityProviderTest { + IDPtest := IdentityProviderTest{} + + IDPtest.SP = serviceProvider + IDPtest.SPKey = IDPtest.SP.Key + IDPtest.SPCertificate = IDPtest.SP.Certificate + + IDPtest.Key = mustParsePrivateKey(golden.Get(t, "idp_key.pem")) + IDPtest.Certificate = mustParseCertificate(golden.Get(t, "idp_cert.pem")) + + IDPtest.IDP = saml.IdentityProvider{ + Key: IDPtest.Key, + Certificate: IDPtest.Certificate, + Logger: logger.DefaultLogger, + MetadataURL: mustParseURL("https://idp.example.com/saml/metadata"), + SSOURL: mustParseURL("https://idp.example.com/saml/sso"), + ServiceProviderProvider: &mockServiceProviderProvider{ + GetServiceProviderFunc: func(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { + if serviceProviderID == IDPtest.SP.MetadataURL.String() { + return IDPtest.SP.Metadata(), nil + } + return nil, os.ErrNotExist + }, + }, + } + + IDPtest.SP.IDPMetadata = IDPtest.IDP.Metadata() + + return &IDPtest +} + +func NewIdpAuthnRequest(t *testing.T, idp *saml.IdentityProvider, acsURL string, issuer string, destination string, issueInstant string) (saml.IdpAuthnRequest, string) { + uuid, err := uuid.NewV4() + assert.NilError(t, err) + id := "id-" + strings.Replace(uuid.String(), "-", "", -1) + + authnRequest := saml.IdpAuthnRequest{ + Now: TimeNow(), + IDP: idp, + RequestBuffer: []byte("" + + "" + + " " + issuer + "" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + ""), + } + + authnRequest.HTTPRequest, err = http.NewRequest("POST", acsURL, nil) + assert.NilError(t, err) + assert.NilError(t, authnRequest.Validate()) + + return authnRequest, id +} + +func NewTestIdpAuthnRequest(t *testing.T, idp *saml.IdentityProvider, acsURL string, issuer string) (saml.IdpAuthnRequest, string) { + authnRequest, id := NewIdpAuthnRequest(t, idp, acsURL, issuer, "https://idp.example.com/saml/sso", "2014-01-01T01:57:09Z") + return authnRequest, id +} + +func MakeAssertion(t *testing.T, authnRequest *saml.IdpAuthnRequest, userSession *saml.Session) { + err := saml.DefaultAssertionMaker{}.MakeAssertion(authnRequest, userSession) + assert.NilError(t, err) +} + +func prepareTestEnvironment(t *testing.T) (*MiddlewareTest, *samlhandler.Strategy, *IdentityProviderTest, saml.IdpAuthnRequest, string) { + // Set timeNow for SAML Requests and Responses + setSAMLTimeNow("Wed Jan 1 01:57:09.123456789 UTC 2014") + + // Create a SAML SP + testMiddleware, strategy, ts := NewMiddlewareTest(t) + + // Create a SAML IdP + testIDP := NewIdentifyProviderTest(t, testMiddleware.Middleware.ServiceProvider, ts.URL) + + // SP ACS URL + acsURL := ts.URL + "/self-service/methods/saml/acs/samlProvider" + + // Create a SAML AuthnRequest as it would be taken into account by the IdP + // so that it can send the SAML Response back to the SP via the SP ACS + authnRequest, authnRequestID := NewTestIdpAuthnRequest(t, &testIDP.IDP, acsURL, testMiddleware.Middleware.ServiceProvider.EntityID) + + return testMiddleware, strategy, testIDP, authnRequest, authnRequestID +} + +func startContinuity(resp *httptest.ResponseRecorder, r *http.Request, strategy *samlhandler.Strategy) { + conf := strategy.D().Config() + f, _ := login.NewFlow(conf, conf.SelfServiceFlowLoginRequestLifespan(r.Context()), strategy.D().GenerateCSRFToken(r), r, flow.TypeBrowser) + strategy.D().LoginFlowPersister().CreateLoginFlow(r.Context(), f) + state := x.NewUUID().String() + + strategy.D().RelayStateContinuityManager().Pause(r.Context(), resp, r, "ory_kratos_saml_auth_code_session", + continuity.WithPayload(&authCodeContainer{ + State: state, + FlowID: f.ID.String(), + }), + continuity.WithLifespan(time.Minute*30)) +} + +func initRouterParams() httprouter.Params { + ps := httprouter.Params{ + httprouter.Param{ + Key: "provider", + Value: "samlProvider", + }, + } + return ps +} + +func prepareTestEnvironmentTwoServiceProvider(t *testing.T) (*MiddlewareTest, *MiddlewareTest, *samlhandler.Strategy, *IdentityProviderTest, saml.IdpAuthnRequest, string) { + // Set timeNow for SAML Requests and Responses + setSAMLTimeNow("Wed Jan 1 01:57:09.123456789 UTC 2014") + + // Create a SAML SP + testMiddleware, strategy, ts := NewMiddlewareTest(t) + + // Create a SAML IdP + testIDP := NewIdentifyProviderTest(t, testMiddleware.Middleware.ServiceProvider, ts.URL) + + // SP ACS URL + acsURL := ts.URL + "/self-service/methods/saml/acs/samlProvider" + + // Create a SAML AuthnRequest as it would be taken into account by the IdP + // so that it can send the SAML Response back to the SP via the SP ACS + authnRequest, authnRequestID := NewTestIdpAuthnRequest(t, &testIDP.IDP, acsURL, testMiddleware.Middleware.ServiceProvider.EntityID) + + return testMiddleware, nil, strategy, testIDP, authnRequest, authnRequestID +} + +func PrepareTestSAMLResponse(t *testing.T, testMiddleware *MiddlewareTest, authnRequest saml.IdpAuthnRequest, authnRequestID string) saml.IdpAuthnRequest { + // User session + userSession := &saml.Session{ + ID: "f00df00df00d", + UserEmail: "alice@example.com", + } + + return PrepareTestSAMLResponseWithSession(t, testMiddleware, authnRequest, authnRequestID, userSession) +} + +func PrepareTestSAMLResponseWithSession(t *testing.T, testMiddleware *MiddlewareTest, authnRequest saml.IdpAuthnRequest, authnRequestID string, userSession *saml.Session) saml.IdpAuthnRequest { + // Make SAML Assertion + MakeAssertion(t, &authnRequest, userSession) + + // Make SAML Response + authnRequest.MakeResponse() + + return authnRequest +} + +func PrepareTestSAMLResponseHTTPRequest(t *testing.T, testMiddleware *MiddlewareTest, authnRequest saml.IdpAuthnRequest, authnRequestID string, responseStr string) *http.Request { + // Prepare SAMLResponse body attribute + v1 := &url.Values{} + v1.Set("SAMLResponse", base64.StdEncoding.EncodeToString([]byte(responseStr))) + + // Set SAML AuthnRequest HTTP Request body with the SAML Response + req := authnRequest.HTTPRequest + req, err := http.NewRequest(req.Method, req.URL.String(), bytes.NewReader([]byte(v1.Encode()))) + assert.NilError(t, err) + + // Make tracked request and get its index + trackedRequestToken, trackedRequestIndex := testMiddleware.makeTrackedRequest(authnRequestID) + + // Set SAML AuthnRequest HTTP Request headers Content-Type and session cookie + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cookie", ""+ + "saml_"+trackedRequestIndex+"="+trackedRequestToken) + + return req +} + +func GetAndDecryptAssertionEl(t *testing.T, testMiddleware *MiddlewareTest, responseDoc *etree.Document) *etree.Element { + // Get the Encrypted Assertion Data + spKey := testMiddleware.Middleware.ServiceProvider.Key + encryptedAssertionDataEl := responseDoc.Element.FindElement("//EncryptedAssertion/EncryptedData") + + // Decrypt the Encrypted Assertion + plaintextAssertion, err := xmlenc.Decrypt(spKey, encryptedAssertionDataEl) + require.NoError(t, err) + stringAssertion := string(plaintextAssertion) + newAssertion := etree.NewDocument() + newAssertion.ReadFromString(stringAssertion) + + return newAssertion.Root() +} + +// Replace the Encrypted Assertion by the modified Assertion +func ReplaceResponseAssertion(t *testing.T, responseEl *etree.Element, newAssertionEl *etree.Element) { + encryptedAssertionEl := responseEl.FindElement("//EncryptedAssertion") + encryptedAssertionEl.Parent().RemoveChild(encryptedAssertionEl) + responseEl.AddChild(newAssertionEl) +} + +// Remove the SAML Response signature +func RemoveResponseSignature(responseDoc *etree.Document) { + responseSignatureEl := responseDoc.FindElement("//Signature") + responseSignatureEl.Parent().RemoveChild(responseSignatureEl) +} + +func RemoveAssertionSignature(responseDoc *etree.Document) { + assertionSignatureEl := responseDoc.FindElement("//Assertion/Signature") + assertionSignatureEl.Parent().RemoveChild(assertionSignatureEl) +} diff --git a/selfservice/strategy/saml/vulnerabilities_test.go b/selfservice/strategy/saml/vulnerabilities_test.go new file mode 100644 index 000000000000..fe8f05c447a4 --- /dev/null +++ b/selfservice/strategy/saml/vulnerabilities_test.go @@ -0,0 +1,1336 @@ +package saml_test + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/beevik/etree" + "github.com/crewjam/saml" + + dsig "github.com/russellhaering/goxmldsig" + "gotest.tools/assert" +) + +type authCodeContainer struct { + FlowID string `json:"flow_id"` + State string `json:"state"` + Traits json.RawMessage `json:"traits"` +} + +type ory_kratos_continuity struct{} + +func TestHappyPath(t *testing.T) { + + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + _ = ids + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, !strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestAddSAMLResponseAttribute(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + + // Add an attribute to the Response + responseEl.CreateAttr("newAttr", "randomValue") + + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestAddSAMLResponseElement(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + + // Add an attribute to the Response + responseEl.CreateElement("newEl") + + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestAddSAMLAssertionAttribute(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Add an attribute to the Response + decryptedAssertion.CreateAttr("newAttr", "randomValue") + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestAddSAMLAssertionElement(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Add an attribute to the Response + decryptedAssertion.CreateElement("newEl") + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestRemoveSAMLResponseSignatureValue(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove SignatureValue element of Signature element + signatureValueEl := doc.FindElement("//Signature/SignatureValue") + signatureValueEl.Parent().RemoveChild(signatureValueEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestRemoveSAMLResponseSignature(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + signatureEl := doc.FindElement("//Signature") + signatureEl.Parent().RemoveChild(signatureEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, !strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestRemoveSAMLAssertionSignatureValue(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Remove the Signature Value from the decrypted assertion + signatureValueEl := decryptedAssertion.FindElement("//Signature/SignatureValue") + signatureValueEl.Parent().RemoveChild(signatureValueEl) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // This is the Happy Path, the HTTP response code should be 302 (Found status) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestRemoveSAMLAssertionSignature(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Remove the Signature Value from the decrypted assertion + signatureEl := decryptedAssertion.FindElement("//Signature") + signatureEl.Parent().RemoveChild(signatureEl) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // The SAML Assertion signature has been removed but the SAML Response is still signed + // The SAML Response has been modified, the SAML Response signature is invalid, so there is an error + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestRemoveBothSAMLResponseSignatureAndSAMLAssertionSignatureValue(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Remove the Signature Value from the decrypted assertion + assertionSignatureEl := decryptedAssertion.FindElement("//Signature") + assertionSignatureEl.Parent().RemoveChild(assertionSignatureEl) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestAddXMLCommentsInSAMLAttributes(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + groups := []string{"admin@test.ovh", "not-adminc@test.ovh", "regular@test.ovh", "manager@test.ovh"} + commentedGroups := []string{"admin@test.ovh", "not-adminc@test.ovh", "regular@test.ovh", "manager@test.ovh"} + + // User session + userSession := &saml.Session{ + ID: "f00df00df00d", + UserEmail: "alice@example.com", + Groups: commentedGroups, + } + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponseWithSession(t, testMiddleware, authnRequest, authnRequestID, userSession) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Remove the whole Signature element + RemoveResponseSignature(doc) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, responseEl, decryptedAssertion) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + traitsMap := make(map[string]interface{}) + json.Unmarshal(ids[0].Traits, &traitsMap) + + // Get the groups of the identity + identityGroups := traitsMap["groups"].([]interface{}) + + // We have to check that either the comments are still there, or that they have been deleted by the canonicalizer but that the parser recovers the whole string + for i := 0; i < len(identityGroups); i++ { + identityGroup := identityGroups[i].(string) + if commentedGroups[i] != identityGroup { + assert.Check(t, groups[i] == identityGroup) + } + } +} + +// More information about the 9 next tests about XSW attacks: +// https://epi052.gitlab.io/notes-to-self/blog/2019-03-13-how-to-test-saml-a-methodology-part-two + +// XSW #1 manipulates SAML Responses. +// It does this by making a copy of the SAML Response and Assertion, +// then inserting the original Signature into the XML as a child element of the copied Response. +// The assumption being that the XML parser finds and uses the copied Response at the top of +// the document after signature validation instead of the original signed Response. +func TestXSW1ResponseWrap1(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + // Remove the whole Signature element of the copied Response Element + RemoveResponseSignature(originalResponseDoc) + + // Get the original Response Signature element + evilResponseDoc.FindElement("//Signature").AddChild(originalResponseEl) + + // Modify the ID attribute of the original Response Element + evilResponseEl.RemoveAttr("ID") + evilResponseEl.CreateAttr("ID", "id-evil") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +// Similar to XSW #1, XSW #2 manipulates SAML Responses. +// The key difference between #1 and #2 is that the type of Signature used is a detached signature where XSW #1 used an enveloping signature. +// The location of the malicious Response remains the same. +func TestXSW2ResponseWrap2(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + // Remove the whole Signature element of the copied Response Element + RemoveResponseSignature(originalResponseDoc) + + // We put the orignal response and its signature on the same level, just under the evil reponse + evilResponseDoc.FindElement("//Response").AddChild(originalResponseEl) + evilResponseDoc.FindElement("//Response").AddChild(evilResponseDoc.FindElement("//Signature")) + + // Modify the ID attribute of the original Response Element + evilResponseEl.RemoveAttr("ID") + evilResponseEl.CreateAttr("ID", "id-evil") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +// XSW #3 is the first example of an XSW that wraps the Assertion element. +// It inserts the copied Assertion as the first child of the root Response element. +// The original Assertion is a sibling of the copied Assertion. +func TestXSW3AssertionWrap1(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + // We have to delete the signature of the evil assertion + RemoveAssertionSignature(evilResponseDoc) + evilResponseDoc.FindElement("//Assertion").RemoveAttr("ID") + evilResponseDoc.FindElement("//Assertion").CreateAttr("ID", "id-evil") + + evilResponseDoc.FindElement("//Response").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change one attribute + evilResponseDoc.FindElement("//Response/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// XSW #4 is similar to #3, except in this case the original Assertion becomes a child of the copied Assertion. +func TestXSW4AssertionWrap2(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + // We have to delete the signature of the evil assertion + RemoveAssertionSignature(evilResponseDoc) + evilResponseDoc.FindElement("//Assertion").RemoveAttr("ID") + evilResponseDoc.FindElement("//Assertion").CreateAttr("ID", "id-evil") + + evilResponseDoc.FindElement("//Assertion").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change the username + evilResponseDoc.FindElement("//Response/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// XSW #5 is the first instance of Assertion wrapping we see where the Signature and the original Assertion aren’t in one of the three standard configurations (enveloped/enveloping/detached). +// In this case, the copied Assertion envelopes the Signature. +func TestXSW5AssertionWrap3(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + evilResponseDoc.FindElement("//Assertion").RemoveAttr("ID") + evilResponseDoc.FindElement("//Assertion").CreateAttr("ID", "id-evil") + + RemoveAssertionSignature(originalResponseDoc) + evilResponseDoc.FindElement("//Response").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change the username + evilResponseDoc.FindElement("//Response/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// XSW #6 inserts its copied Assertion into the same location as #’s 4 and 5. +// The interesting piece here is that the copied Assertion envelopes the Signature, which in turn envelopes the original Assertion. +func TestXSW6AssertionWrap4(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + evilResponseDoc.FindElement("//Assertion").RemoveAttr("ID") + evilResponseDoc.FindElement("//Assertion").CreateAttr("ID", "id-evil") + + RemoveAssertionSignature(originalResponseDoc) + evilResponseDoc.FindElement("//Assertion").FindElement("//Signature").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change the username + evilResponseDoc.FindElement("//Response/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// XSW #7 inserts an Extensions element and adds the copied Assertion as a child. Extensions is a valid XML element with a less restrictive schema definition. +func TestXSW7AssertionWrap5(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + // We have to delete the signature of the evil assertion + RemoveAssertionSignature(evilResponseDoc) + + evilResponseDoc.FindElement("//Response").AddChild(etree.NewElement("Extension")) + evilResponseDoc.FindElement("//Response").FindElement("//Extension").AddChild(evilResponseDoc.FindElement("//Assertion")) + evilResponseDoc.FindElement("//Response").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change the username + evilResponseDoc.FindElement("//Response/Extension/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// XSW #8 uses another less restrictive XML element to perform a variation of the attack pattern used in XSW #7. +// This time around the original Assertion is the child of the less restrictive element instead of the copied Assertion. +func TestXSW8AssertionWrap6(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + evilResponseEl := authnRequest.ResponseEl + evilResponseDoc := etree.NewDocument() + evilResponseDoc.SetRoot(evilResponseEl) + + // Get and Decrypt SAML Assertion + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, evilResponseDoc) + + // Replace the SAML crypted Assertion in the SAML Response by SAML decrypted Assertion + ReplaceResponseAssertion(t, evilResponseEl, decryptedAssertion) + + // Copy the Response Element + // This copy will not be changed and contain the original Response content + originalResponseEl := evilResponseEl.Copy() + originalResponseDoc := etree.NewDocument() + originalResponseDoc.SetRoot(originalResponseEl) + + RemoveResponseSignature(evilResponseDoc) + + RemoveAssertionSignature(originalResponseDoc) + evilResponseDoc.FindElement("//Response/Assertion/Signature").AddChild(etree.NewElement("Object")) + evilResponseDoc.FindElement("//Assertion/Signature/Object").AddChild(originalResponseDoc.FindElement("//Assertion")) + + // Change the username + evilResponseDoc.FindElement("//Response/Assertion/AttributeStatement/Attribute/AttributeValue").SetText("evil_alice@example.com") + + // Get Reponse string + responseStr, err := evilResponseDoc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + // Get all identities + ids, _ := strategy.D().PrivilegedIdentityPool().ListIdentities(context.Background(), 0, 1000) + + // We have to check that there is either an error or an identity created without the modified attribute + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error") || strings.Contains(string(ids[0].Traits), "alice@example.com")) +} + +// If the response was meant for a different Service Provider, the current Service Provider should notice it and reject the authentication +func TestTokenRecipientConfusion(t *testing.T) { + + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Change the ACS Endpoint location in order to change the recipient in the SAML Assertion + authnRequest.ACSEndpoint.Location = "https://test.com" + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) + +} + +func TestXMLExternalEntity(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Payload XEE + xee := "]>&xxe;" + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, xee+responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestExtensibleStylesheetLanguageTransformation(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Payload XSLT + xslt := "" + xsltDoc := etree.NewDocument() + xsltDoc.ReadFromString(xslt) + xsltElement := xsltDoc.SelectElement("stylesheet") + doc.FindElement("//Transforms").AddChild(xsltElement) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestExpiredSAMLResponse(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // The answer was forged on January 1 and therefore we set the current date to January 2 so that it is expired + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 MST 2006", "Wed Jan 2 01:57:09.123456789 UTC 2014") + return rv + } + + saml.TimeNow = TimeNow + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestSignSAMLAssertionWithOwnKeypair(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get and Decrypt SAML Assertion in order to encrypt it afterwards + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Sign the SAML assertion with an evil key pair + keyPair, err := tls.LoadX509KeyPair("./testdata/evilcert.crt", "./testdata/evilkey.key") + keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + keyStore := dsig.TLSCertKeyStore(keyPair) + + signingContext := dsig.NewDefaultSigningContext(keyStore) + signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("") + signingContext.SetSignatureMethod(dsig.RSASHA256SignatureMethod) + + signedAssertionEl, err := signingContext.SignEnveloped(decryptedAssertion) + + // Replace the SAML crypted Assertion in the SAML Response by the assertion signed by our keys + ReplaceResponseAssertion(t, responseEl, signedAssertionEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestSignSAMLResponseWithOwnKeypair(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + + // Sign the SAML response with an evil key pair + keyPair, err := tls.LoadX509KeyPair("./testdata/evilcert.crt", "./testdata/evilkey.key") + keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + keyStore := dsig.TLSCertKeyStore(keyPair) + + signingContext := dsig.NewDefaultSigningContext(keyStore) + signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("") + signingContext.SetSignatureMethod(dsig.RSASHA256SignatureMethod) + + // Sign the whole response + signedResponseEl, err := signingContext.SignEnveloped(responseEl) + doc.SetRoot(signedResponseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +func TestSignBothResponseAndAssertionWithOwnKeypair(t *testing.T) { + // Create the SP, the IdP and the AnthnRequest + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get and Decrypt SAML Assertion in order to encrypt it afterwards + decryptedAssertion := GetAndDecryptAssertionEl(t, testMiddleware, doc) + + // Sign the SAML assertion with an evil key pair + keyPair, err := tls.LoadX509KeyPair("./testdata/evilcert.crt", "./testdata/evilkey.key") + keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) + keyStore := dsig.TLSCertKeyStore(keyPair) + + signingContext := dsig.NewDefaultSigningContext(keyStore) + signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("") + signingContext.SetSignatureMethod(dsig.RSASHA256SignatureMethod) + + signedAssertionEl, err := signingContext.SignEnveloped(decryptedAssertion) + + // Replace the SAML crypted Assertion in the SAML Response by the assertion signed by our keys + ReplaceResponseAssertion(t, responseEl, signedAssertionEl) + + // Sign the whole response with own keys pairs + signedResponseEl, err := signingContext.SignEnveloped(responseEl) + doc.SetRoot(signedResponseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + + // Send the SAML Response to the SP ACS + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request to Kratos + strategy.HandleCallback(resp, req, ps) + + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +} + +// Check if it is possible to send the same SAML Response twice (Replay Attack) +func TestReplayAttack(t *testing.T) { + + testMiddleware, strategy, _, authnRequest, authnRequestID := prepareTestEnvironment(t) + + // Generate the SAML Assertion and the SAML Response + authnRequest = PrepareTestSAMLResponse(t, testMiddleware, authnRequest, authnRequestID) + + // Get Response Element + responseEl := authnRequest.ResponseEl + doc := etree.NewDocument() + doc.SetRoot(responseEl) + + // Get Reponse string + responseStr, err := doc.WriteToString() + assert.NilError(t, err) + + req := PrepareTestSAMLResponseHTTPRequest(t, testMiddleware, authnRequest, authnRequestID, responseStr) + resp := httptest.NewRecorder() + + // Start the continuity + startContinuity(resp, req, strategy) + + // We make sure that continuity is respected + ps := initRouterParams() + + // We send the request once to Kratos, everything is in order so there should be no error. + strategy.HandleCallback(resp, req, ps) + assert.Check(t, !strings.Contains(resp.HeaderMap["Location"][0], "error")) + + // We send the same request a second time to Kratos, it has already been received by Kratos so there must be an error + strategy.HandleCallback(resp, req, ps) + assert.Check(t, strings.Contains(resp.HeaderMap["Location"][0], "error")) +}