From d321463f84da59c450efac778a079ba3db080639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Louren=C3=A7o?= Date: Fri, 10 Dec 2021 14:57:55 +0000 Subject: [PATCH 01/58] Switch from github.com/form3tech-oss/jwt-go to github.com/golang-jwt/jwt/v4 (#383) * Switch from github.com/form3tech-oss/jwt-go to github.com/golang-jwt/jwt/v4 * Fix tests for Go 1.17 and update CI accordingly --- .github/workflows/go.yml | 24 ---- .github/workflows/lint.yml | 18 +++ .github/workflows/test.yml | 24 ++++ go.mod | 2 +- go.sum | 4 +- identity_provider_go116_test.go | 57 ++++++++ identity_provider_go117_test.go | 57 ++++++++ identity_provider_test.go | 45 +----- samlidp/samlidp_test.go | 2 +- samlidp/{util_test.go => util_go116_test.go} | 3 + samlidp/util_go117_test.go | 26 ++++ samlsp/fetch_metadata_go116_test.go | 34 +++++ samlsp/fetch_metadata_go117_test.go | 34 +++++ samlsp/fetch_metadata_test.go | 18 --- samlsp/middleware_test.go | 2 +- samlsp/request_tracker_jwt.go | 4 +- samlsp/session_jwt.go | 4 +- samlsp/testdata/token.json | 4 +- service_provider_go116_test.go | 136 +++++++++++++++++++ service_provider_go117_test.go | 136 +++++++++++++++++++ service_provider_test.go | 117 ---------------- 21 files changed, 536 insertions(+), 215 deletions(-) delete mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test.yml create mode 100644 identity_provider_go116_test.go create mode 100644 identity_provider_go117_test.go rename samlidp/{util_test.go => util_go116_test.go} (97%) create mode 100644 samlidp/util_go117_test.go create mode 100644 samlsp/fetch_metadata_go116_test.go create mode 100644 samlsp/fetch_metadata_go117_test.go create mode 100644 service_provider_go116_test.go create mode 100644 service_provider_go117_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 1512e3ec..00000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Presubmit -on: - push: - branches: [main] - pull_request: - branches: [main] -jobs: - check: - name: Presubmit checks - runs-on: ubuntu-latest - steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ^1.13 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - name: Get dependencies - run: | - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.24.0 - - name: Lint - run: golangci-lint run - - name: Test - run: go test -v ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..7a0426e4 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,18 @@ +name: lint + +on: + push: + branches: [ 'main' ] + pull_request: + branches: [ 'main' ] + +jobs: + golangci: + name: Run golangci-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.24.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..fa51407b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: test + +on: + push: + branches: [ 'main' ] + pull_request: + branches: [ 'main' ] + +jobs: + tests: + name: Run tests + runs-on: ubuntu-latest + strategy: + matrix: + go: [ '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x' ] + steps: + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@v2 + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + with: + go-version: ${{ matrix.go }} + - name: Run Go tests + run: go test -v ./... diff --git a/go.mod b/go.mod index dceabc4f..e8937b75 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 - github.com/form3tech-oss/jwt-go v3.2.2+incompatible + github.com/golang-jwt/jwt/v4 v4.1.0 github.com/google/go-cmp v0.5.5 github.com/jonboulle/clockwork v0.2.2 // indirect github.com/kr/pretty v0.2.1 diff --git a/go.sum b/go.sum index c46f4a4f..4ca82ff5 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ 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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= diff --git a/identity_provider_go116_test.go b/identity_provider_go116_test.go new file mode 100644 index 00000000..ead0a780 --- /dev/null +++ b/identity_provider_go116_test.go @@ -0,0 +1,57 @@ +//go:build !go1.17 +// +build !go1.17 + +package saml + +import ( + "bytes" + "compress/flate" + "encoding/base64" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +func TestIDPHTTPCanHandleSSORequest(t *testing.T) { + test := NewIdentifyProviderTest(t) + w := httptest.NewRecorder() + + const validRequest = `lJJBayoxFIX%2FypC9JhnU5wszAz7lgWCLaNtFd5fMbQ1MkmnunVb%2FfUfbUqEgdhs%2BTr5zkmLW8S5s8KVD4mzvm0Cl6FIwEciRCeCRDFuznd2sTD5Upk2Ro42NyGZEmNjFMI%2BBOo9pi%2BnVWbzfrEqxY27JSEntEPfg2waHNnpJ4JtcgiWRLfoLXYBjwDfu6p%2B8JIoiWy5K4eqBUipXIzVRUwXKKtRK53qkJ3qqQVuNPUjU4TIQQ%2BBS5EqPBzofKH2ntBn%2FMervo8jWnyX%2BuVC78FwKkT1gopNKX1JUxSklXTMIfM0gsv8xeeDL%2BPGk7%2FF0Qg0GdnwQ1cW5PDLUwFDID6uquO1Dlot1bJw9%2FPLRmia%2BzRMCYyk4dSiq6205QSDXOxfy3KAq5Pkvqt4DAAD%2F%2Fw%3D%3D` + + r, _ := http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&"+ + "SAMLRequest="+validRequest, nil) + test.IDP.Handler().ServeHTTP(w, r) + assert.Check(t, is.Equal(http.StatusOK, w.Code)) + + // rejects requests that are invalid + w = httptest.NewRecorder() + r, _ = http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&"+ + "SAMLRequest=PEF1dGhuUmVxdWVzdA%3D%3D", nil) + test.IDP.Handler().ServeHTTP(w, r) + assert.Check(t, is.Equal(http.StatusBadRequest, w.Code)) + + // rejects requests that contain malformed XML + { + a, _ := url.QueryUnescape(validRequest) + b, _ := base64.StdEncoding.DecodeString(a) + c, _ := ioutil.ReadAll(flate.NewReader(bytes.NewReader(b))) + d := bytes.Replace(c, []byte("]]"), 1) + f := bytes.Buffer{} + e, _ := flate.NewWriter(&f, flate.DefaultCompression) + e.Write(d) + e.Close() + g := base64.StdEncoding.EncodeToString(f.Bytes()) + invalidRequest := url.QueryEscape(g) + + w = httptest.NewRecorder() + r, _ = http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&"+ + "SAMLRequest="+invalidRequest, nil) + test.IDP.Handler().ServeHTTP(w, r) + assert.Check(t, is.Equal(http.StatusBadRequest, w.Code)) + } +} diff --git a/identity_provider_test.go b/identity_provider_test.go index 69840f2a..dcc55e82 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -1,8 +1,6 @@ package saml import ( - "bytes" - "compress/flate" "crypto" "crypto/rsa" "crypto/x509" @@ -10,7 +8,6 @@ import ( "encoding/pem" "encoding/xml" "fmt" - "io/ioutil" "math/rand" "net/http" "net/http/httptest" @@ -25,7 +22,7 @@ import ( "gotest.tools/golden" "github.com/beevik/etree" - "github.com/form3tech-oss/jwt-go" + "github.com/golang-jwt/jwt/v4" "github.com/crewjam/saml/logger" "github.com/crewjam/saml/testsaml" @@ -204,46 +201,6 @@ func TestIDPHTTPCanHandleMetadataRequest(t *testing.T) { string(w.Body.Bytes())) } -func TestIDPHTTPCanHandleSSORequest(t *testing.T) { - test := NewIdentifyProviderTest(t) - w := httptest.NewRecorder() - - const validRequest = `lJJBayoxFIX%2FypC9JhnU5wszAz7lgWCLaNtFd5fMbQ1MkmnunVb%2FfUfbUqEgdhs%2BTr5zkmLW8S5s8KVD4mzvm0Cl6FIwEciRCeCRDFuznd2sTD5Upk2Ro42NyGZEmNjFMI%2BBOo9pi%2BnVWbzfrEqxY27JSEntEPfg2waHNnpJ4JtcgiWRLfoLXYBjwDfu6p%2B8JIoiWy5K4eqBUipXIzVRUwXKKtRK53qkJ3qqQVuNPUjU4TIQQ%2BBS5EqPBzofKH2ntBn%2FMervo8jWnyX%2BuVC78FwKkT1gopNKX1JUxSklXTMIfM0gsv8xeeDL%2BPGk7%2FF0Qg0GdnwQ1cW5PDLUwFDID6uquO1Dlot1bJw9%2FPLRmia%2BzRMCYyk4dSiq6205QSDXOxfy3KAq5Pkvqt4DAAD%2F%2Fw%3D%3D` - - r, _ := http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&"+ - "SAMLRequest="+validRequest, nil) - test.IDP.Handler().ServeHTTP(w, r) - assert.Check(t, is.Equal(http.StatusOK, w.Code)) - - // rejects requests that are invalid - w = httptest.NewRecorder() - r, _ = http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&"+ - "SAMLRequest=PEF1dGhuUmVxdWVzdA%3D%3D", nil) - test.IDP.Handler().ServeHTTP(w, r) - assert.Check(t, is.Equal(http.StatusBadRequest, w.Code)) - - // rejects requests that contain malformed XML - { - a, _ := url.QueryUnescape(validRequest) - b, _ := base64.StdEncoding.DecodeString(a) - c, _ := ioutil.ReadAll(flate.NewReader(bytes.NewReader(b))) - d := bytes.Replace(c, []byte("\n" + + "" + _, err := getSPMetadata(strings.NewReader(good)) + assert.Check(t, err) + + bad := "" + + "]]>\n" + + "" + _, err = getSPMetadata(strings.NewReader(bad)) + assert.Check(t, is.Error(err, "XML syntax error on line 1: unescaped ]]> not in CDATA section")) +} diff --git a/samlsp/fetch_metadata_go116_test.go b/samlsp/fetch_metadata_go116_test.go new file mode 100644 index 00000000..91c3aa69 --- /dev/null +++ b/samlsp/fetch_metadata_go116_test.go @@ -0,0 +1,34 @@ +//go:build !go1.17 +// +build !go1.17 + +package samlsp + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +func TestFetchMetadataRejectsInvalid(t *testing.T) { + test := NewMiddlewareTest(t) + test.IDPMetadata = bytes.Replace(test.IDPMetadata, + []byte("]]"), -1) + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Check(t, is.Equal("/metadata", r.URL.String())) + w.Write(test.IDPMetadata) + })) + + fmt.Println(testServer.URL + "/metadata") + u, _ := url.Parse(testServer.URL + "/metadata") + md, err := FetchMetadata(context.Background(), testServer.Client(), *u) + assert.Check(t, is.Error(err, "expected element in name space urn:oasis:names:tc:SAML:2.0:metadata but have no name space")) + assert.Check(t, is.Nil(md)) +} diff --git a/samlsp/fetch_metadata_test.go b/samlsp/fetch_metadata_test.go index 2bc2caf1..f1da1320 100644 --- a/samlsp/fetch_metadata_test.go +++ b/samlsp/fetch_metadata_test.go @@ -1,7 +1,6 @@ package samlsp import ( - "bytes" "context" "fmt" "net/http" @@ -27,20 +26,3 @@ func TestFetchMetadata(t *testing.T) { assert.Check(t, err) assert.Check(t, is.Equal("https://idp.testshib.org/idp/shibboleth", md.EntityID)) } - -func TestFetchMetadataRejectsInvalid(t *testing.T) { - test := NewMiddlewareTest(t) - test.IDPMetadata = bytes.Replace(test.IDPMetadata, - []byte("World!"))) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot unmarshal response: expected element type but have ")) + + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"wrongRequestID"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "`InResponseTo` does not match any of the possible request IDs (expected [wrongRequestID])")) + + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Nov 30 20:57:09 UTC 2016") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response IssueInstant expired at 2015-12-01 01:57:51.375 +0000 UTC")) + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + + s.IDPMetadata.EntityID = "http://snakeoil.com" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response Issuer does not match the IDP metadata (expected \"http://snakeoil.com\")")) + s.IDPMetadata.EntityID = "https://idp.testshib.org/idp/shibboleth" + + oldSpStatusSuccess := StatusSuccess + StatusSuccess = "not:the:success:value" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "urn:oasis:names:tc:SAML:2.0:status:Success")) + StatusSuccess = oldSpStatusSuccess + + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) + + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "aW52YWxpZA==" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot validate signature on Response: asn1: structure error: tags don't match (16 vs {class:1 tag:9 length:110 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} certificate @2")) +} diff --git a/service_provider_go117_test.go b/service_provider_go117_test.go new file mode 100644 index 00000000..1e2953b9 --- /dev/null +++ b/service_provider_go117_test.go @@ -0,0 +1,136 @@ +//go:build go1.17 +// +build go1.17 + +package saml + +import ( + "encoding/base64" + "encoding/xml" + "net/http" + "net/url" + "strings" + "testing" + "time" + + dsig "github.com/russellhaering/goxmldsig" + "gotest.tools/assert" + is "gotest.tools/assert/cmp" + "gotest.tools/golden" +) + +func TestSPRejectsMalformedResponse(t *testing.T) { + test := NewServiceProviderTest(t) + // An actual response from google + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + SamlResponse := golden.Get(t, "TestSPRejectsMalformedResponse_response") + test.IDPMetadata = golden.Get(t, "TestSPRejectsMalformedResponse_IDPMetadata") + + s := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"), + AcsURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"), + IDPMetadata: &EntityDescriptor{}, + } + err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) + assert.Check(t, err) + + // this is a valid response + { + req := http.Request{PostForm: url.Values{}} + req.PostForm.Set("SAMLResponse", string(SamlResponse)) + assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"}) + assert.Check(t, err) + assert.Check(t, is.Equal("ross@octolabs.io", assertion.Subject.NameID.Value)) + } + + // this is a valid response but with a comment injected + { + x, _ := base64.StdEncoding.DecodeString(string(SamlResponse)) + y := strings.Replace(string(x), "World!"))) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot unmarshal response: expected element type but have ")) + + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"wrongRequestID"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "`InResponseTo` does not match any of the possible request IDs (expected [wrongRequestID])")) + + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Nov 30 20:57:09 UTC 2016") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response IssueInstant expired at 2015-12-01 01:57:51.375 +0000 UTC")) + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + + s.IDPMetadata.EntityID = "http://snakeoil.com" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response Issuer does not match the IDP metadata (expected \"http://snakeoil.com\")")) + s.IDPMetadata.EntityID = "https://idp.testshib.org/idp/shibboleth" + + oldSpStatusSuccess := StatusSuccess + StatusSuccess = "not:the:success:value" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "urn:oasis:names:tc:SAML:2.0:status:Success")) + StatusSuccess = oldSpStatusSuccess + + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) + + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "aW52YWxpZA==" + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) + _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) + + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot validate signature on Response: x509: malformed certificate")) +} diff --git a/service_provider_test.go b/service_provider_test.go index 72d95070..7d897663 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -742,51 +742,6 @@ func TestSPRejectsInjectedComment(t *testing.T) { } } -func TestSPRejectsMalformedResponse(t *testing.T) { - test := NewServiceProviderTest(t) - // An actual response from google - TimeNow = func() time.Time { - rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016") - return rv - } - Clock = dsig.NewFakeClockAt(TimeNow()) - SamlResponse := golden.Get(t, "TestSPRejectsMalformedResponse_response") - test.IDPMetadata = golden.Get(t, "TestSPRejectsMalformedResponse_IDPMetadata") - - s := ServiceProvider{ - Key: test.Key, - Certificate: test.Certificate, - MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"), - AcsURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"), - IDPMetadata: &EntityDescriptor{}, - } - err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) - assert.Check(t, err) - - // this is a valid response - { - req := http.Request{PostForm: url.Values{}} - req.PostForm.Set("SAMLResponse", string(SamlResponse)) - assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"}) - assert.Check(t, err) - assert.Check(t, is.Equal("ross@octolabs.io", assertion.Subject.NameID.Value)) - } - - // this is a valid response but with a comment injected - { - x, _ := base64.StdEncoding.DecodeString(string(SamlResponse)) - y := strings.Replace(string(x), "World!"))) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot unmarshal response: expected element type but have ")) - - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"wrongRequestID"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "`InResponseTo` does not match any of the possible request IDs (expected [wrongRequestID])")) - - TimeNow = func() time.Time { - rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Nov 30 20:57:09 UTC 2016") - return rv - } - Clock = dsig.NewFakeClockAt(TimeNow()) - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "response IssueInstant expired at 2015-12-01 01:57:51.375 +0000 UTC")) - TimeNow = func() time.Time { - rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015") - return rv - } - Clock = dsig.NewFakeClockAt(TimeNow()) - - s.IDPMetadata.EntityID = "http://snakeoil.com" - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "response Issuer does not match the IDP metadata (expected \"http://snakeoil.com\")")) - s.IDPMetadata.EntityID = "https://idp.testshib.org/idp/shibboleth" - - oldSpStatusSuccess := StatusSuccess - StatusSuccess = "not:the:success:value" - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "urn:oasis:names:tc:SAML:2.0:status:Success")) - StatusSuccess = oldSpStatusSuccess - - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) - - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "aW52YWxpZA==" - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: asn1: structure error: tags don't match (16 vs {class:1 tag:9 length:110 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} certificate @2")) -} - func TestSPInvalidAssertions(t *testing.T) { test := NewServiceProviderTest(t) s := ServiceProvider{ From 324aadd28430a15fe7fc984c50e872e1fd267e80 Mon Sep 17 00:00:00 2001 From: Matt Magurany Date: Fri, 10 Dec 2021 09:58:40 -0500 Subject: [PATCH 02/58] Add the NotOnOrAfter optional attribute to (#386) --- schema.go | 8 ++++++ schema_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/schema.go b/schema.go index 3c271138..6fd16db3 100644 --- a/schema.go +++ b/schema.go @@ -51,6 +51,7 @@ type LogoutRequest struct { ID string `xml:",attr"` Version string `xml:",attr"` IssueInstant time.Time `xml:",attr"` + NotOnOrAfter *time.Time `xml:",attr"` Destination string `xml:",attr"` Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` NameID *NameID @@ -67,6 +68,9 @@ func (r *LogoutRequest) Element() *etree.Element { el.CreateAttr("ID", r.ID) el.CreateAttr("Version", r.Version) el.CreateAttr("IssueInstant", r.IssueInstant.Format(timeFormat)) + if r.NotOnOrAfter != nil { + el.CreateAttr("NotOnOrAfter", r.NotOnOrAfter.Format(timeFormat)) + } if r.Destination != "" { el.CreateAttr("Destination", r.Destination) } @@ -90,9 +94,11 @@ func (r *LogoutRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error type Alias LogoutRequest aux := &struct { IssueInstant RelaxedTime `xml:",attr"` + NotOnOrAfter *RelaxedTime `xml:",attr"` *Alias }{ IssueInstant: RelaxedTime(r.IssueInstant), + NotOnOrAfter: (*RelaxedTime)(r.NotOnOrAfter), Alias: (*Alias)(r), } return e.Encode(aux) @@ -103,6 +109,7 @@ func (r *LogoutRequest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err type Alias LogoutRequest aux := &struct { IssueInstant RelaxedTime `xml:",attr"` + NotOnOrAfter *RelaxedTime `xml:",attr"` *Alias }{ Alias: (*Alias)(r), @@ -111,6 +118,7 @@ func (r *LogoutRequest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err return err } r.IssueInstant = time.Time(aux.IssueInstant) + r.NotOnOrAfter = (*time.Time)(aux.NotOnOrAfter) return nil } diff --git a/schema_test.go b/schema_test.go index 22e58793..7bd27466 100644 --- a/schema_test.go +++ b/schema_test.go @@ -94,3 +94,78 @@ func TestAuthnStatementMarshalWithoutSessionNotOnOrAfter(t *testing.T) { assert.Check(t, err) assert.Check(t, is.DeepEqual(expected, actual)) } + +func TestLogoutRequestXMLRoundTrip(t *testing.T) { + issueInstant := time.Date(2021, 10, 8, 12, 30, 0, 0, time.UTC) + notOnOrAfter := time.Date(2021, 10, 8, 12, 35, 0, 0, time.UTC) + expected := LogoutRequest{ + ID: "request-id", + Version: "2.0", + IssueInstant: issueInstant, + NotOnOrAfter: ¬OnOrAfter, + Issuer: &Issuer{ + XMLName: xml.Name{ + Space: "urn:oasis:names:tc:SAML:2.0:assertion", + Local: "Issuer", + }, + Value: "uri:issuer", + }, + NameID: &NameID{ + Value: "name-id", + }, + SessionIndex: &SessionIndex{ + Value: "index", + }, + } + + doc := etree.NewDocument() + doc.SetRoot(expected.Element()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(`uri:issuername-idindex`, + string(x))) + + var actual LogoutRequest + err = xml.Unmarshal(x, &actual) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(expected, actual)) + + x, err = xml.Marshal(expected) + assert.Check(t, err) + assert.Check(t, is.Equal(`uri:issuername-idindex`, + string(x))) +} + +func TestLogoutRequestMarshalWithoutNotOnOrAfter(t *testing.T) { + issueInstant := time.Date(2021, 10, 8, 12, 30, 0, 0, time.UTC) + expected := LogoutRequest{ + ID: "request-id", + Version: "2.0", + IssueInstant: issueInstant, + Issuer: &Issuer{ + XMLName: xml.Name{ + Space: "urn:oasis:names:tc:SAML:2.0:assertion", + Local: "Issuer", + }, + Value: "uri:issuer", + }, + NameID: &NameID{ + Value: "name-id", + }, + SessionIndex: &SessionIndex{ + Value: "index", + }, + } + + doc := etree.NewDocument() + doc.SetRoot(expected.Element()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(`uri:issuername-idindex`, + string(x))) + + var actual LogoutRequest + err = xml.Unmarshal(x, &actual) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(expected, actual)) +} From 23acdd225ab1a203272d77942c6e0d5b8c364e5e Mon Sep 17 00:00:00 2001 From: Francesco Cartier <12899699+antipopp@users.noreply.github.com> Date: Fri, 10 Dec 2021 15:59:00 +0100 Subject: [PATCH 03/58] Updated the trivial example to handle a basic logout flow (#382) * add /logout to initiate SLO request * fix * unused package * gofmt-ed --- example/trivial/trivial.go | 52 ++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/example/trivial/trivial.go b/example/trivial/trivial.go index e8be7cb9..0da34c85 100644 --- a/example/trivial/trivial.go +++ b/example/trivial/trivial.go @@ -12,8 +12,26 @@ import ( "github.com/crewjam/saml/samlsp" ) +var samlMiddleware *samlsp.Middleware + func hello(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "cn")) + fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName")) +} + +func logout(w http.ResponseWriter, r *http.Request) { + nameID := samlsp.AttributeFromContext(r.Context(), "urn:oasis:names:tc:SAML:attribute:subject-id") + url, err := samlMiddleware.ServiceProvider.MakeRedirectLogoutRequest(nameID, "") + if err != nil { + panic(err) // TODO handle error + } + + err = samlMiddleware.Session.DeleteSession(w, r) + if err != nil { + panic(err) // TODO handle error + } + + w.Header().Add("Location", url.String()) + w.WriteHeader(http.StatusFound) } func main() { @@ -26,30 +44,32 @@ func main() { panic(err) // TODO handle error } - rootURL, _ := url.Parse("http://localhost:8000") - idpMetadataURL, _ := url.Parse("https://samltest.id/saml/idp") - - idpMetadata, err := samlsp.FetchMetadata( - context.Background(), - http.DefaultClient, + idpMetadataURL, err := url.Parse("https://samltest.id/saml/idp") + if err != nil { + panic(err) // TODO handle error + } + idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient, *idpMetadataURL) if err != nil { panic(err) // TODO handle error } - samlSP, err := samlsp.New(samlsp.Options{ - URL: *rootURL, - IDPMetadata: idpMetadata, - Key: keyPair.PrivateKey.(*rsa.PrivateKey), - Certificate: keyPair.Leaf, - SignRequest: true, - }) + rootURL, err := url.Parse("http://localhost:8000") if err != nil { panic(err) // TODO handle error } + samlMiddleware, _ = samlsp.New(samlsp.Options{ + URL: *rootURL, + Key: keyPair.PrivateKey.(*rsa.PrivateKey), + Certificate: keyPair.Leaf, + IDPMetadata: idpMetadata, + SignRequest: true, // some IdP require the SLO request to be signed + }) app := http.HandlerFunc(hello) - http.Handle("/hello", samlSP.RequireAccount(app)) - http.Handle("/saml/", samlSP) + slo := http.HandlerFunc(logout) + http.Handle("/hello", samlMiddleware.RequireAccount(app)) + http.Handle("/saml/", samlMiddleware) + http.Handle("/logout", slo) http.ListenAndServe(":8000", nil) } From 96274277388132e4ae491695f0b2b303d97f39a5 Mon Sep 17 00:00:00 2001 From: Bharat <13206972+bharat-p@users.noreply.github.com> Date: Fri, 10 Dec 2021 06:59:28 -0800 Subject: [PATCH 04/58] Update github.com/russellhaering/goxmldsig to v1.1.1 to address CVE-2020-7711 (#391) --- go.mod | 7 ++----- go.sum | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e8937b75..8308cd46 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,12 @@ require ( github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 github.com/golang-jwt/jwt/v4 v4.1.0 github.com/google/go-cmp v0.5.5 - github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/kr/pretty v0.2.1 - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect - github.com/russellhaering/goxmldsig v1.1.0 + github.com/russellhaering/goxmldsig v1.1.1 github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index 4ca82ff5..cd11d2df 100644 --- a/go.sum +++ b/go.sum @@ -12,24 +12,29 @@ github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkS github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russellhaering/goxmldsig v1.1.0 h1:lK/zeJie2sqG52ZAlPNn1oBBqsIsEKypUUBGpYYF6lk= -github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= @@ -47,10 +52,13 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From 2db94e7bc785369d43a4f8984ca7f5c4c419a23a Mon Sep 17 00:00:00 2001 From: Olfa Karoui <36195894+OlfaKaroui@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:02:40 +0100 Subject: [PATCH 05/58] Service Provider support for aes128-gcm algorithm (#371) * Add support for aes128-gcm algorithm * Pass nonce to encrypt function and add some tests --- identity_provider.go | 2 +- xmlenc/cbc.go | 2 +- xmlenc/decrypt_test.go | 126 ++++++++++++++++++-------- xmlenc/encrypt_test.go | 61 +++++++++---- xmlenc/gcm.go | 141 +++++++++++++++++++++++++++++ xmlenc/pubkey.go | 6 +- xmlenc/testdata/cert.cert | 28 ++++++ xmlenc/testdata/cert.key | 52 +++++++++++ xmlenc/testdata/ciphertext_gcm.xml | 21 +++++ xmlenc/testdata/input_gcm.xml | 131 +++++++++++++++++++++++++++ xmlenc/testdata/plaintext_gcm.xml | 104 +++++++++++++++++++++ xmlenc/xmlenc.go | 2 +- xmlenc/xmlenc_test.go | 96 ++++++++++++-------- 13 files changed, 668 insertions(+), 104 deletions(-) create mode 100644 xmlenc/gcm.go create mode 100644 xmlenc/testdata/cert.cert create mode 100644 xmlenc/testdata/cert.key create mode 100644 xmlenc/testdata/ciphertext_gcm.xml create mode 100644 xmlenc/testdata/input_gcm.xml create mode 100644 xmlenc/testdata/plaintext_gcm.xml diff --git a/identity_provider.go b/identity_provider.go index 20258daf..40b86e7f 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -854,7 +854,7 @@ func (req *IdpAuthnRequest) MakeAssertionEl() error { encryptor := xmlenc.OAEP() encryptor.BlockCipher = xmlenc.AES128CBC encryptor.DigestMethod = &xmlenc.SHA1 - encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf) + encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf, nil) if err != nil { return err } diff --git a/xmlenc/cbc.go b/xmlenc/cbc.go index 11ee210d..77ddb3b2 100644 --- a/xmlenc/cbc.go +++ b/xmlenc/cbc.go @@ -31,7 +31,7 @@ func (e CBC) Algorithm() string { // Encrypt encrypts plaintext with key, which should be a []byte of length KeySize(). // It returns an xenc:EncryptedData element. -func (e CBC) Encrypt(key interface{}, plaintext []byte) (*etree.Element, error) { +func (e CBC) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) { keyBuf, ok := key.([]byte) if !ok { return nil, ErrIncorrectKeyType("[]byte") diff --git a/xmlenc/decrypt_test.go b/xmlenc/decrypt_test.go index ecf46dda..2e23f8f6 100644 --- a/xmlenc/decrypt_test.go +++ b/xmlenc/decrypt_test.go @@ -13,47 +13,93 @@ import ( ) func TestCanDecrypt(t *testing.T) { - doc := etree.NewDocument() - err := doc.ReadFromBytes(golden.Get(t, "input.xml")) - assert.Check(t, err) - - keyPEM := "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDU8wdiaFmPfTyRYuFlVPi866WrH/2JubkHzp89bBQopDaLXYxi\n3PTu3O6Q/KaKxMOFBqrInwqpv/omOGZ4ycQ51O9I+Yc7ybVlW94lTo2gpGf+Y/8E\nPsVbnZaFutRctJ4dVIp9aQ2TpLiGT0xX1OzBO/JEgq9GzDRf+B+eqSuglwIDAQAB\nAoGBAMuy1eN6cgFiCOgBsB3gVDdTKpww87Qk5ivjqEt28SmXO13A1KNVPS6oQ8SJ\nCT5Azc6X/BIAoJCURVL+LHdqebogKljhH/3yIel1kH19vr4E2kTM/tYH+qj8afUS\nJEmArUzsmmK8ccuNqBcllqdwCZjxL4CHDUmyRudFcHVX9oyhAkEA/OV1OkjM3CLU\nN3sqELdMmHq5QZCUihBmk3/N5OvGdqAFGBlEeewlepEVxkh7JnaNXAXrKHRVu/f/\nfbCQxH+qrwJBANeQERF97b9Sibp9xgolb749UWNlAdqmEpmlvmS202TdcaaT1msU\n4rRLiQN3X9O9mq4LZMSVethrQAdX1whawpkCQQDk1yGf7xZpMJ8F4U5sN+F4rLyM\nRq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA\nyfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr\nvBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6\nhU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA==\n-----END RSA PRIVATE KEY-----\n" - b, _ := pem.Decode([]byte(keyPEM)) - key, err := x509.ParsePKCS1PrivateKey(b.Bytes) - assert.Check(t, err) - - el := doc.Root().FindElement("//EncryptedKey") - buf, err := Decrypt(key, el) - assert.Check(t, err) - assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, - buf)) - - el = doc.Root().FindElement("//EncryptedData") - buf, err = Decrypt(key, el) - assert.Check(t, err) - golden.Assert(t, string(buf), "plaintext.xml") + t.Run("CBC", func(t *testing.T) { + doc := etree.NewDocument() + err := doc.ReadFromBytes(golden.Get(t, "input.xml")) + assert.Check(t, err) + + keyPEM := "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDU8wdiaFmPfTyRYuFlVPi866WrH/2JubkHzp89bBQopDaLXYxi\n3PTu3O6Q/KaKxMOFBqrInwqpv/omOGZ4ycQ51O9I+Yc7ybVlW94lTo2gpGf+Y/8E\nPsVbnZaFutRctJ4dVIp9aQ2TpLiGT0xX1OzBO/JEgq9GzDRf+B+eqSuglwIDAQAB\nAoGBAMuy1eN6cgFiCOgBsB3gVDdTKpww87Qk5ivjqEt28SmXO13A1KNVPS6oQ8SJ\nCT5Azc6X/BIAoJCURVL+LHdqebogKljhH/3yIel1kH19vr4E2kTM/tYH+qj8afUS\nJEmArUzsmmK8ccuNqBcllqdwCZjxL4CHDUmyRudFcHVX9oyhAkEA/OV1OkjM3CLU\nN3sqELdMmHq5QZCUihBmk3/N5OvGdqAFGBlEeewlepEVxkh7JnaNXAXrKHRVu/f/\nfbCQxH+qrwJBANeQERF97b9Sibp9xgolb749UWNlAdqmEpmlvmS202TdcaaT1msU\n4rRLiQN3X9O9mq4LZMSVethrQAdX1whawpkCQQDk1yGf7xZpMJ8F4U5sN+F4rLyM\nRq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA\nyfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr\nvBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6\nhU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA==\n-----END RSA PRIVATE KEY-----\n" + b, _ := pem.Decode([]byte(keyPEM)) + key, err := x509.ParsePKCS1PrivateKey(b.Bytes) + assert.Check(t, err) + + el := doc.Root().FindElement("//EncryptedKey") + buf, err := Decrypt(key, el) + assert.Check(t, err) + assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, + buf)) + + el = doc.Root().FindElement("//EncryptedData") + buf, err = Decrypt(key, el) + assert.Check(t, err) + golden.Assert(t, string(buf), "plaintext.xml") + }) + + t.Run("GCM", func(t *testing.T) { + doc := etree.NewDocument() + err := doc.ReadFromBytes(golden.Get(t, "input_gcm.xml")) + assert.Check(t, err) + + keyPEM := golden.Get(t, "cert.key") + b, _ := pem.Decode(keyPEM) + key, err := x509.ParsePKCS8PrivateKey(b.Bytes) + assert.Check(t, err) + + el := doc.Root().FindElement("//EncryptedKey") + _, err = Decrypt(key, el) + assert.Check(t, err) + + el = doc.Root().FindElement("//EncryptedData") + _, err = Decrypt(key, el) + assert.Check(t, err) + }) } func TestCanDecryptWithoutCertificate(t *testing.T) { - doc := etree.NewDocument() - err := doc.ReadFromBytes(golden.Get(t, "input.xml")) - assert.Check(t, err) - - el := doc.FindElement("//ds:X509Certificate") - el.Parent().RemoveChild(el) - - keyPEM := golden.Get(t, "key.pem") - b, _ := pem.Decode(keyPEM) - key, err := x509.ParsePKCS1PrivateKey(b.Bytes) - assert.Check(t, err) - - el = doc.Root().FindElement("//EncryptedKey") - buf, err := Decrypt(key, el) - assert.Check(t, err) - assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, buf)) - - el = doc.Root().FindElement("//EncryptedData") - buf, err = Decrypt(key, el) - assert.Check(t, err) - golden.Assert(t, string(buf), "plaintext.xml") + t.Run("CBC", func(t *testing.T) { + doc := etree.NewDocument() + err := doc.ReadFromBytes(golden.Get(t, "input.xml")) + assert.Check(t, err) + + el := doc.FindElement("//ds:X509Certificate") + el.Parent().RemoveChild(el) + + keyPEM := golden.Get(t, "key.pem") + b, _ := pem.Decode(keyPEM) + key, err := x509.ParsePKCS1PrivateKey(b.Bytes) + assert.Check(t, err) + + el = doc.Root().FindElement("//EncryptedKey") + buf, err := Decrypt(key, el) + assert.Check(t, err) + assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, buf)) + + el = doc.Root().FindElement("//EncryptedData") + buf, err = Decrypt(key, el) + assert.Check(t, err) + golden.Assert(t, string(buf), "plaintext.xml") + }) + + t.Run("GCM", func(t *testing.T) { + doc := etree.NewDocument() + err := doc.ReadFromBytes(golden.Get(t, "input_gcm.xml")) + assert.Check(t, err) + + el := doc.FindElement("//ds:X509Certificate") + el.Parent().RemoveChild(el) + + keyPEM := golden.Get(t, "cert.key") + b, _ := pem.Decode(keyPEM) + key, err := x509.ParsePKCS8PrivateKey(b.Bytes) + assert.Check(t, err) + + el = doc.Root().FindElement("//EncryptedKey") + _, err = Decrypt(key, el) + assert.Check(t, err) + + el = doc.Root().FindElement("//EncryptedData") + _, err = Decrypt(key, el) + assert.Check(t, err) + //assertion.NotNil(t, plaintext) + }) } diff --git a/xmlenc/encrypt_test.go b/xmlenc/encrypt_test.go index cca38604..8e69d035 100644 --- a/xmlenc/encrypt_test.go +++ b/xmlenc/encrypt_test.go @@ -3,33 +3,56 @@ package xmlenc import ( "crypto/x509" "encoding/pem" - "math/rand" - "testing" - + "github.com/beevik/etree" "gotest.tools/assert" "gotest.tools/golden" - - "github.com/beevik/etree" + "math/rand" + "testing" ) func TestCanEncryptOAEP(t *testing.T) { - RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests + t.Run("CBC", func(t *testing.T) { + + RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests + + pemBlock, _ := pem.Decode(golden.Get(t, "cert.pem")) + certificate, err := x509.ParseCertificate(pemBlock.Bytes) + assert.Check(t, err) + + e := OAEP() + e.BlockCipher = AES128CBC + e.DigestMethod = &SHA1 + + el, err := e.Encrypt(certificate, golden.Get(t, "plaintext.xml"), nil) + assert.Check(t, err) + + doc := etree.NewDocument() + doc.SetRoot(el) + doc.IndentTabs() + ciphertext, _ := doc.WriteToString() + + golden.Assert(t, ciphertext, "ciphertext.xml") + }) - pemBlock, _ := pem.Decode(golden.Get(t, "cert.pem")) - certificate, err := x509.ParseCertificate(pemBlock.Bytes) - assert.Check(t, err) + t.Run("GCM", func(t *testing.T) { + RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests - e := OAEP() - e.BlockCipher = AES128CBC - e.DigestMethod = &SHA1 + cert := golden.Get(t, "cert.cert") + b, _ := pem.Decode(cert) + certificate, err := x509.ParseCertificate(b.Bytes) + assert.Check(t, err) - el, err := e.Encrypt(certificate, golden.Get(t, "plaintext.xml")) - assert.Check(t, err) + e := OAEP() + e.BlockCipher = AES128GCM + e.DigestMethod = &SHA1 - doc := etree.NewDocument() - doc.SetRoot(el) - doc.IndentTabs() - ciphertext, _ := doc.WriteToString() + el, err := e.Encrypt(certificate, golden.Get(t, "plaintext_gcm.xml"), []byte("1234567890AZ")) + assert.Check(t, err) - golden.Assert(t, ciphertext, "ciphertext.xml") + doc := etree.NewDocument() + doc.SetRoot(el) + doc.Indent(4) + ciphertext, _ := doc.WriteToString() + golden.Assert(t, ciphertext, "ciphertext_gcm.xml") + }) } diff --git a/xmlenc/gcm.go b/xmlenc/gcm.go new file mode 100644 index 00000000..94958327 --- /dev/null +++ b/xmlenc/gcm.go @@ -0,0 +1,141 @@ +package xmlenc + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "github.com/beevik/etree" + "io" +) + +// struct implements Decrypter and Encrypter for block ciphers in struct mode +type GCM struct { + keySize int + algorithm string + cipher func([]byte) (cipher.Block, error) +} + +// KeySize returns the length of the key required. +func (e GCM) KeySize() int { + return e.keySize +} + +// Algorithm returns the name of the algorithm, as will be found +// in an xenc:EncryptionMethod element. +func (e GCM) Algorithm() string { + return e.algorithm +} + +func (e GCM) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) { + keyBuf, ok := key.([]byte) + if !ok { + return nil, ErrIncorrectKeyType("[]byte") + } + if len(keyBuf) != e.keySize { + return nil, ErrIncorrectKeyLength(e.keySize) + } + + block, err := e.cipher(keyBuf) + if err != nil { + return nil, err + } + + encryptedDataEl := etree.NewElement("xenc:EncryptedData") + encryptedDataEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#") + { + randBuf := make([]byte, 16) + if _, err := RandReader.Read(randBuf); err != nil { + return nil, err + } + encryptedDataEl.CreateAttr("Id", fmt.Sprintf("_%x", randBuf)) + } + + em := encryptedDataEl.CreateElement("xenc:EncryptionMethod") + em.CreateAttr("Algorithm", e.algorithm) + em.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#") + + plaintext = appendPadding(plaintext, block.BlockSize()) + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if nonce == nil { + // generate random nonce when it's nil + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + panic(err.Error()) + } + } + + ciphertext := make([]byte, len(plaintext)) + text := aesgcm.Seal(nil, nonce, ciphertext, nil) + + cd := encryptedDataEl.CreateElement("xenc:CipherData") + cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#") + cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(text)) + return encryptedDataEl, nil +} + +// Decrypt decrypts an encrypted element with key. If the ciphertext contains an +// EncryptedKey element, then the type of `key` is determined by the registered +// Decryptor for the EncryptedKey element. Otherwise, `key` must be a []byte of +// length KeySize(). +func (e GCM) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) { + if encryptedKeyEl := ciphertextEl.FindElement("./KeyInfo/EncryptedKey"); encryptedKeyEl != nil { + var err error + key, err = Decrypt(key, encryptedKeyEl) + if err != nil { + return nil, err + } + } + + keyBuf, ok := key.([]byte) + + if !ok { + return nil, ErrIncorrectKeyType("[]byte") + } + if len(keyBuf) != e.KeySize() { + return nil, ErrIncorrectKeyLength(e.KeySize()) + } + + block, err := e.cipher(keyBuf) + if err != nil { + return nil, err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + ciphertext, err := getCiphertext(ciphertextEl) + if err != nil { + return nil, err + } + + nonce := ciphertext[:aesgcm.NonceSize()] + text := ciphertext[aesgcm.NonceSize():] + + plainText, err := aesgcm.Open(nil, nonce, text, nil) + if err != nil { + return nil, err + } + return plainText, nil +} + +var ( + // AES128GCM implements AES128-GCM mode for encryption and decryption + AES128GCM BlockCipher = GCM{ + keySize: 16, + algorithm: "http://www.w3.org/2009/xmlenc11#aes128-gcm", + cipher: aes.NewCipher, + } +) + +func init() { + RegisterDecrypter(AES128GCM) +} diff --git a/xmlenc/pubkey.go b/xmlenc/pubkey.go index 72286863..13d4d9e7 100644 --- a/xmlenc/pubkey.go +++ b/xmlenc/pubkey.go @@ -29,7 +29,7 @@ func (e RSA) Algorithm() string { // Encrypt implements encrypter. certificate must be a []byte containing the ASN.1 bytes // of certificate containing an RSA public key. -func (e RSA) Encrypt(certificate interface{}, plaintext []byte) (*etree.Element, error) { +func (e RSA) Encrypt(certificate interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) { cert, ok := certificate.(*x509.Certificate) if !ok { return nil, ErrIncorrectKeyType("*x.509 certificate") @@ -83,11 +83,11 @@ func (e RSA) Encrypt(certificate interface{}, plaintext []byte) (*etree.Element, cd := encryptedKey.CreateElement("xenc:CipherData") cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#") cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(buf)) - encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext) + encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext, nonce) if err != nil { return nil, err } - encryptedDataEl.InsertChild(encryptedDataEl.FindElement("./CipherData"), keyInfoEl) + encryptedDataEl.InsertChildAt(encryptedDataEl.FindElement("./CipherData").Index(), keyInfoEl) return encryptedDataEl, nil } diff --git a/xmlenc/testdata/cert.cert b/xmlenc/testdata/cert.cert new file mode 100644 index 00000000..fea41ca9 --- /dev/null +++ b/xmlenc/testdata/cert.cert @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE0DCCArgCCQDQ3vxsffYA7DANBgkqhkiG9w0BAQsFADAqMSgwJgYDVQQDDB9t +YWluLmF1dGgtc3NvLmRlLnFhLm1lZGljdWphLmRlMB4XDTIxMDcxOTEzMzkxOFoX +DTIyMDcxOTEzMzkxOFowKjEoMCYGA1UEAwwfbWFpbi5hdXRoLXNzby5kZS5xYS5t +ZWRpY3VqYS5kZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJe50Ka+ +qNwgmKeoLEDYTmIbLRAyVX6lgO2RKzVzDzf6LyrWtZL1SKbbA6VdmzPlo4kzA3di +f4/XgjS6hWeXXKaqXV8g/wKSSIhFTkp/WIrZY7KHF9mTYXdOrPGkR9IENiMevsVg +RfcBnnHTGgczeiAFZm1xnkHBmVPZZjfuoi3nbjrMCh8uR3EqDsLzIHJ9PB2F4KvW +KDuDaqdNOocI3fxEZuy/Ahwpr5FORJYZDk24ZusTPniy/+xFREneHPZaB+tFCxtj +3PdmCpIF54tj9dVLOMkQzbtPiTCGx2own9yjwa7NYYV8y8fBUcpuexbk+576J/rL +MIFvFusf8PaL0QfMnkCJGL1T5O8wTEdpZ6h17rVzJtaK4lLNsO9+7vtW2gYFgLGj +nr/Zg6ZKfUfZLm6h3bfjtKRKnAzrqI4E6f1nvwyEglqE5BHEXriI2+tbsrMwHQoy +XVray8KY1WVrCnFFQFtb22BPSQLHu/n4GIOz7k34h7mgWOPnlW2p625UXiuFbWJD +4sdNw7encs3gkILStbaRX1WZHwb2QjfFDvMS1k1KhdktELxmZKR8bDXRzymMYwls +KInPC2s92u+L7wesuqQI2GKeMnH6jEdFdqPwXo8ObgI6GPOxzVS6Q3BnI4SXTxeC +4wQq6hQpqZS4rge0dfxHttwRVywdzjSKVw5ZAgMBAAEwDQYJKoZIhvcNAQELBQAD +ggIBABVWyAQh+880dOqd/LEX7DR4mwVnaRnOn+Xl6gbdoKJK0KyoQWEigW+Rj4Rt +uBdi56NvxbBQk8UIamO0zFJzCpJ936OhoMwE3ZZETto0YAVtGqPlZRBJxLQo56Wz +Rtmpy3bHOpQsYZRTYOXjoFxG+pHhiOjJ5W1djRqyOPa3I8H3tsHixUOZcycHIIo6 +gLRBvsVuDgrI4YDtX8mw695nmbFzwDbpkf0kPAb18eihrFqKlI4JNegn1RE/zkHe +4kC3xpaqt/XlsU39evgUb78S8ZsVgtyt+S5ywChcRunyyv5fyQm79ZRtVPVS7oPJ +OHWG4EpzuU1XAqDu7mGfZW7Aks3j8OrGzmzt72CfcBuRaAomgdllMalO6iUkDVdh +q2RmjBKaVbh+9aq4JB+cf88TfcrzuB4lBWeOdwOgQuhn8zB0qiuHJ9TgTUAuVeIX +HI+/I5s0FFbqSCfOraFAwbfxecSs9rEaZJvLMQ5mB9g7LW3tRSYGNT2ujOQQygKI ++RDNMDNLS5wO/M2DdSDJcyrrxZXiZp2/ob6ovRERmIIlxIoaQkON2pJ0eLnt0Qz5 +wrqOhgbJSPtNUdMMD3GtGm3eZn+F0Gm0WzpMD3VVHpJYxcnvFe2pgQrS6DhRCjkr +paYGxi0gsWxwWE+H5dFPayliO0EoLQIojSKTdRVIogvy62S9 +-----END CERTIFICATE----- diff --git a/xmlenc/testdata/cert.key b/xmlenc/testdata/cert.key new file mode 100644 index 00000000..c9bdc52e --- /dev/null +++ b/xmlenc/testdata/cert.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +xESNSIqhp4ubdwD87Ec0oeEcIqHzwRM= +-----END PRIVATE KEY----- diff --git a/xmlenc/testdata/ciphertext_gcm.xml b/xmlenc/testdata/ciphertext_gcm.xml new file mode 100644 index 00000000..f722e6fc --- /dev/null +++ b/xmlenc/testdata/ciphertext_gcm.xml @@ -0,0 +1,21 @@ + + + + + + + + + + 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 + + diff --git a/xmlenc/testdata/input_gcm.xml b/xmlenc/testdata/input_gcm.xml new file mode 100644 index 00000000..71e0dc9c --- /dev/null +++ b/xmlenc/testdata/input_gcm.xml @@ -0,0 +1,131 @@ + + + https://testidp2.aai.dfn.de/idp/shibboleth + + + + + + + + + + + + 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 + bC1nMi9wdWIvY3JsL2NhY3JsLmNybDA/oD2gO4Y5aHR0cDovL2NkcDIucGNhLmRmbi5kZS9kZm4t + Y2EtZ2xvYmFsLWcyL3B1Yi9jcmwvY2FjcmwuY3JsMIHbBggrBgEFBQcBAQSBzjCByzAzBggrBgEF + BQcwAYYnaHR0cDovL29jc3AucGNhLmRmbi5kZS9PQ1NQLVNlcnZlci9PQ1NQMEkGCCsGAQUFBzAC + hj1odHRwOi8vY2RwMS5wY2EuZGZuLmRlL2Rmbi1jYS1nbG9iYWwtZzIvcHViL2NhY2VydC9jYWNl + cnQuY3J0MEkGCCsGAQUFBzAChj1odHRwOi8vY2RwMi5wY2EuZGZuLmRlL2Rmbi1jYS1nbG9iYWwt + ZzIvcHViL2NhY2VydC9jYWNlcnQuY3J0MIIB+AYKKwYBBAHWeQIEAgSCAegEggHkAeIAdgBGpVXr + dfqRIDC1oolp9PN9ESxBdL79SbiFq/L8cP5tRwAAAXrs2cfNAAAEAwBHMEUCIQDNfyPxXrQl7gIc + Lw7wEH537JUD41i06NNZUTxBdn4iHwIgK990g8JF36529aiweqqQC59H8/T03I9yHi2N/lMthY8A + dgApeb7wnjk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXrs2cz2AAAEAwBHMEUCIQCLlz4B + upCeqi8KyO7T7jp8+GRlxRyWyO2C8vqbeiFD1gIgHanhzYpnfD5JwyATOH5/iCc6vqR9vJIW8ttj + DADOqSkAdwBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZEwAAAXrs2cgeAAAEAwBIMEYC + IQD/h0+qUXYOK8sj+F+qoypjQ+uCHFu1b+wFJpnvQ00D/gIhAJFNPtbfAFl1m0m11u7kAuM2bPk3 + LCx6471dRixZvrLpAHcAVYHUwhaQNgFK6gubVzxT8MDkOHhwJQgXL6OqHQcT0wwAAAF67NnJKgAA + BAMASDBGAiEA/+o2fEeFg73eCZ2UawSnZcZIXycHs+9CXNRntbfRmIUCIQDPSvvsmphFvYPeQy7B + QDG+3EyrvyKqichkwKLNjgIc9TANBgkqhkiG9w0BAQsFAAOCAQEAVg7v+aFqn5443l88dXR1JGeP + 6qzL0jDB6EYREhWvxeb2JEl1kn7jvLPMF+LKatADykBWxV3L2IHxEcmtP9hDnv39t7P92FN9zssn + hHs49LZPwl3gsoErdbB1jMCkVC+0qTA0JoeEbkixlZXwarUf6UF/17jBKSLdlA3CkTv51Td7dqsl + FBihFzLxzTLpkuFYxtN8Ax5BfqbCPnNQ+XAlTenClyrgB7wzZ3qgoCS+saW7rn1MbdBcuOmUS8+A + jQnr+mBWWZJPXpZnlR7FIo/krCmxhEWpwsBf5taIguDbZ3oE92oQOtYsJ561ATAtDpxZMr91ljmk + hVoyt2aEjDtCgg== + + + + + + + + + + + + + + + + + + + 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 + + + + + \ No newline at end of file diff --git a/xmlenc/testdata/plaintext_gcm.xml b/xmlenc/testdata/plaintext_gcm.xml new file mode 100644 index 00000000..a3747e57 --- /dev/null +++ b/xmlenc/testdata/plaintext_gcm.xml @@ -0,0 +1,104 @@ + + https://testidp2.com/idp/shibboleth + + + + + + + + + + + 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 + bC1nMi9wdWIvY3JsL2NhY3JsLmNybDA/oD2gO4Y5aHR0cDovL2NkcDIucGNhLmRmbi5kZS9kZm4t + Y2EtZ2xvYmFsLWcyL3B1Yi9jcmwvY2FjcmwuY3JsMIHbBggrBgEFBQcBAQSBzjCByzAzBggrBgEF + BQcwAYYnaHR0cDovL29jc3AucGNhLmRmbi5kZS9PQ1NQLVNlcnZlci9PQ1NQMEkGCCsGAQUFBzAC + hj1odHRwOi8vY2RwMS5wY2EuZGZuLmRlL2Rmbi1jYS1nbG9iYWwtZzIvcHViL2NhY2VydC9jYWNl + cnQuY3J0MEkGCCsGAQUFBzAChj1odHRwOi8vY2RwMi5wY2EuZGZuLmRlL2Rmbi1jYS1nbG9iYWwt + ZzIvcHViL2NhY2VydC9jYWNlcnQuY3J0MIIB+AYKKwYBBAHWeQIEAgSCAegEggHkAeIAdgBGpVXr + dfqRIDC1oolp9PN9ESxBdL79SbiFq/L8cP5tRwAAAXrs2cfNAAAEAwBHMEUCIQDNfyPxXrQl7gIc + Lw7wEH537JUD41i06NNZUTxBdn4iHwIgK990g8JF36529aiweqqQC59H8/T03I9yHi2N/lMthY8A + dgApeb7wnjk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXrs2cz2AAAEAwBHMEUCIQCLlz4B + upCeqi8KyO7T7jp8+GRlxRyWyO2C8vqbeiFD1gIgHanhzYpnfD5JwyATOH5/iCc6vqR9vJIW8ttj + DADOqSkAdwBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZEwAAAXrs2cgeAAAEAwBIMEYC + IQD/h0+qUXYOK8sj+F+qoypjQ+uCHFu1b+wFJpnvQ00D/gIhAJFNPtbfAFl1m0m11u7kAuM2bPk3 + LCx6471dRixZvrLpAHcAVYHUwhaQNgFK6gubVzxT8MDkOHhwJQgXL6OqHQcT0wwAAAF67NnJKgAA + BAMASDBGAiEA/+o2fEeFg73eCZ2UawSnZcZIXycHs+9CXNRntbfRmIUCIQDPSvvsmphFvYPeQy7B + QDG+3EyrvyKqichkwKLNjgIc9TANBgkqhkiG9w0BAQsFAAOCAQEAVg7v+aFqn5443l88dXR1JGeP + 6qzL0jDB6EYREhWvxeb2JEl1kn7jvLPMF+LKatADykBWxV3L2IHxEcmtP9hDnv39t7P92FN9zssn + hHs49LZPwl3gsoErdbB1jMCkVC+0qTA0JoeEbkixlZXwarUf6UF/17jBKSLdlA3CkTv51Td7dqsl + FBihFzLxzTLpkuFYxtN8Ax5BfqbCPnNQ+XAlTenClyrgB7wzZ3qgoCS+saW7rn1MbdBcuOmUS8+A + jQnr+mBWWZJPXpZnlR7FIo/krCmxhEWpwsBf5taIguDbZ3oE92oQOtYsJ561ATAtDpxZMr91ljmk + hVoyt2aEjDtCgg== + + + + + + + AAdzZWNyZXQxm924IEWIZegn9l1NChK4GXWETDW/ca4xRwNHuV21SA25MzW2bWqqCudhmNUrUsXk+Ci8W5MrwFiLKqJkNm4NwmHFsnvpUMVHlH8raI+xLVwwa2lf/poCXml0kE8D6cbtEBBACazlvgYMHHLud5+6uSDbta1xlp8S2G6aDOzWWYJluw== + + + + + + + + https://example.com/saml/metadata + + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + + urn:mace:dir:entitlement:common-lib-terms + + + member@testscope.aai.dfn.de + + + \ No newline at end of file diff --git a/xmlenc/xmlenc.go b/xmlenc/xmlenc.go index b0ed5bf1..719c523f 100644 --- a/xmlenc/xmlenc.go +++ b/xmlenc/xmlenc.go @@ -18,7 +18,7 @@ var RandReader = rand.Reader // XML EncryptedData or EncryptedKey element. The required type of `key` varies // depending on the implementation. type Encrypter interface { - Encrypt(key interface{}, plaintext []byte) (*etree.Element, error) + Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) } // Decrypter is an interface that decrypts things. The Decrypt() method returns the diff --git a/xmlenc/xmlenc_test.go b/xmlenc/xmlenc_test.go index 3bc0c3a3..7b6f06bc 100644 --- a/xmlenc/xmlenc_test.go +++ b/xmlenc/xmlenc_test.go @@ -1,55 +1,73 @@ package xmlenc import ( - "io/ioutil" - "math/rand" - "testing" - "github.com/beevik/etree" "gotest.tools/assert" is "gotest.tools/assert/cmp" + "io/ioutil" + "math/rand" + "testing" ) -func TestDataAES128CBC(t *testing.T) { - RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests - plaintext, err := ioutil.ReadFile("testdata/encrypt-data-aes128-cbc.data") - assert.Check(t, err) - - var ciphertext string - { - encrypter := AES128CBC - cipherEl, encErr := encrypter.Encrypt([]byte("abcdefghijklmnop"), plaintext) - assert.Check(t, encErr) - - doc := etree.NewDocument() - doc.SetRoot(cipherEl) - doc.IndentTabs() - ciphertext, err = doc.WriteToString() +func TestDataAES128(t *testing.T) { + t.Run("CBC", func(t *testing.T) { + RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests + plaintext, err := ioutil.ReadFile("testdata/encrypt-data-aes128-cbc.data") assert.Check(t, err) - } - { - decrypter := AES128CBC - doc := etree.NewDocument() - err = doc.ReadFromString(ciphertext) - assert.Check(t, err) + var ciphertext string + { + encrypter := AES128CBC + cipherEl, encErr := encrypter.Encrypt([]byte("abcdefghijklmnop"), plaintext, nil) + assert.Check(t, encErr) - actualPlaintext, err := decrypter.Decrypt( - []byte("abcdefghijklmnop"), doc.Root()) - assert.Check(t, err) - assert.Check(t, is.DeepEqual(plaintext, actualPlaintext)) - } + doc := etree.NewDocument() + doc.SetRoot(cipherEl) + doc.IndentTabs() + ciphertext, err = doc.WriteToString() + assert.Check(t, err) + } - { - decrypter := AES128CBC - doc := etree.NewDocument() - err := doc.ReadFromFile("testdata/encrypt-data-aes128-cbc.xml") - assert.Check(t, err) + { + decrypter := AES128CBC + doc := etree.NewDocument() + err = doc.ReadFromString(ciphertext) + assert.Check(t, err) - actualPlaintext, err := decrypter.Decrypt([]byte("abcdefghijklmnop"), doc.Root()) - assert.Check(t, err) - assert.Check(t, is.DeepEqual(plaintext, actualPlaintext)) - } + actualPlaintext, err := decrypter.Decrypt( + []byte("abcdefghijklmnop"), doc.Root()) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(plaintext, actualPlaintext)) + } + + { + decrypter := AES128CBC + doc := etree.NewDocument() + err := doc.ReadFromFile("testdata/encrypt-data-aes128-cbc.xml") + assert.Check(t, err) + + actualPlaintext, err := decrypter.Decrypt([]byte("abcdefghijklmnop"), doc.Root()) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(plaintext, actualPlaintext)) + } + }) + + t.Run("GCM", func(t *testing.T) { + RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests + plaintext := "top secret message to use with gcm" + + { + encrypter := AES128GCM + cipherEl, encErr := encrypter.Encrypt([]byte("abcdefghijklmnop"), []byte(plaintext), []byte("1234567890AZ")) + assert.Check(t, encErr) + + doc := etree.NewDocument() + doc.SetRoot(cipherEl) + doc.IndentTabs() + _, err := doc.WriteToString() + assert.Check(t, err) + } + }) } /* From 0771d8f630005c6707e0cd295a1d7ec1cc303289 Mon Sep 17 00:00:00 2001 From: Matt Magurany Date: Fri, 10 Dec 2021 10:05:34 -0500 Subject: [PATCH 06/58] Add XMLName field and xml tags to StatusMessage struct (#374) --- schema.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema.go b/schema.go index 6fd16db3..01f488b4 100644 --- a/schema.go +++ b/schema.go @@ -523,7 +523,8 @@ const ( // // See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf ยง3.2.2.3 type StatusMessage struct { - Value string + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage"` + Value string `xml:",chardata"` } // Element returns an etree.Element representing the object in XML form. From d5716f6b5729dc13632d8905661f83117981d5fc Mon Sep 17 00:00:00 2001 From: Philipp Ritter <2619327+pheelee@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:06:17 +0100 Subject: [PATCH 07/58] added option defaultRedirectURI (#366) --- samlsp/middleware.go | 5 ++--- samlsp/new.go | 50 +++++++++++++++++++++++++------------------- service_provider.go | 3 +++ 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/samlsp/middleware.go b/samlsp/middleware.go index e01bf89e..9bc9c54d 100644 --- a/samlsp/middleware.go +++ b/samlsp/middleware.go @@ -91,7 +91,7 @@ func (m *Middleware) ServeACS(w http.ResponseWriter, r *http.Request) { return } - m.CreateSessionFromAssertion(w, r, assertion) + m.CreateSessionFromAssertion(w, r, assertion, m.ServiceProvider.DefaultRedirectURI) return } @@ -181,8 +181,7 @@ func (m *Middleware) HandleStartAuthFlow(w http.ResponseWriter, r *http.Request) } // CreateSessionFromAssertion is invoked by ServeHTTP when we have a new, valid SAML assertion. -func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.Request, assertion *saml.Assertion) { - redirectURI := "/" +func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.Request, assertion *saml.Assertion, redirectURI string) { if trackedRequestIndex := r.Form.Get("RelayState"); trackedRequestIndex != "" { trackedRequest, err := m.RequestTracker.GetTrackedRequest(r, trackedRequestIndex) if err != nil { diff --git a/samlsp/new.go b/samlsp/new.go index 33397435..14ecf87f 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -14,17 +14,18 @@ import ( // Options represents the parameters for creating a new middleware type Options struct { - EntityID string - URL url.URL - Key *rsa.PrivateKey - Certificate *x509.Certificate - Intermediates []*x509.Certificate - AllowIDPInitiated bool - IDPMetadata *saml.EntityDescriptor - SignRequest bool - ForceAuthn bool // TODO(ross): this should be *bool - CookieSameSite http.SameSite - RelayStateFunc func(w http.ResponseWriter, r *http.Request) string + EntityID string + URL url.URL + Key *rsa.PrivateKey + Certificate *x509.Certificate + Intermediates []*x509.Certificate + AllowIDPInitiated bool + DefaultRedirectURI string + IDPMetadata *saml.EntityDescriptor + SignRequest bool + ForceAuthn bool // TODO(ross): this should be *bool + CookieSameSite http.SameSite + RelayStateFunc func(w http.ResponseWriter, r *http.Request) string } // DefaultSessionCodec returns the default SessionCodec for the provided options, @@ -94,18 +95,23 @@ func DefaultServiceProvider(opts Options) saml.ServiceProvider { signatureMethod = "" } + if opts.DefaultRedirectURI == "" { + opts.DefaultRedirectURI = "/" + } + return saml.ServiceProvider{ - EntityID: opts.EntityID, - Key: opts.Key, - Certificate: opts.Certificate, - Intermediates: opts.Intermediates, - MetadataURL: *metadataURL, - AcsURL: *acsURL, - SloURL: *sloURL, - IDPMetadata: opts.IDPMetadata, - ForceAuthn: forceAuthn, - SignatureMethod: signatureMethod, - AllowIDPInitiated: opts.AllowIDPInitiated, + EntityID: opts.EntityID, + Key: opts.Key, + Certificate: opts.Certificate, + Intermediates: opts.Intermediates, + MetadataURL: *metadataURL, + AcsURL: *acsURL, + SloURL: *sloURL, + IDPMetadata: opts.IDPMetadata, + ForceAuthn: forceAuthn, + SignatureMethod: signatureMethod, + AllowIDPInitiated: opts.AllowIDPInitiated, + DefaultRedirectURI: opts.DefaultRedirectURI, } } diff --git a/service_provider.go b/service_provider.go index 3c7b94f4..8d7222cd 100644 --- a/service_provider.go +++ b/service_provider.go @@ -102,6 +102,9 @@ type ServiceProvider struct { // AllowIdpInitiated AllowIDPInitiated bool + // DefaultRedirectURI where untracked requests (as of IDPInitiated) are redirected to + DefaultRedirectURI string + // SignatureVerifier, if non-nil, allows you to implement an alternative way // to verify signatures. SignatureVerifier SignatureVerifier From 71e1cdcdcf486d99e6ac5e8cfb5f7274299969f7 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Fri, 10 Dec 2021 10:16:47 -0500 Subject: [PATCH 08/58] artifact binding (#397) * Implemented SP support for receiving authentication results via artifact binding. * update test expectations Co-authored-by: David Venhoek --- identity_provider_test.go | 2 +- metadata.go | 7 + samlsp/middleware.go | 3 +- samlsp/new.go | 5 + .../testdata/expected_middleware_metadata.xml | 1 + schema.go | 154 ++++++++ service_provider.go | 373 ++++++++++++++---- service_provider_test.go | 8 +- .../TestCanProduceMetadataEntityID_metadata | 1 + .../TestCanProduceMetadataNoCerts_metadata | 1 + ...SPCanProduceMetadataWithBothCerts_metadata | 1 + ...ProduceMetadataWithEncryptionCert_metadata | 1 + 12 files changed, 473 insertions(+), 84 deletions(-) diff --git a/identity_provider_test.go b/identity_provider_test.go index dcc55e82..dcb78433 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -265,7 +265,7 @@ func TestIDPCanHandlePostRequestWithExistingSession(t *testing.T) { w := httptest.NewRecorder() - authRequest, err := test.SP.MakeAuthenticationRequest(test.SP.GetSSOBindingLocation(HTTPRedirectBinding), HTTPRedirectBinding) + authRequest, err := test.SP.MakeAuthenticationRequest(test.SP.GetSSOBindingLocation(HTTPRedirectBinding), HTTPRedirectBinding, HTTPPostBinding) assert.Check(t, err) authRequestBuf, err := xml.Marshal(authRequest) assert.Check(t, err) diff --git a/metadata.go b/metadata.go index 0f7dfe97..8f92ada2 100644 --- a/metadata.go +++ b/metadata.go @@ -13,6 +13,12 @@ const HTTPPostBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" // HTTPRedirectBinding is the official URN for the HTTP-Redirect binding (transport) const HTTPRedirectBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" +// HTTPArtifactBinding is the official URN for the HTTP-Artifact binding (transport) +const HTTPArtifactBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" + +// SOAPBinding is the official URN for the SOAP binding (transport) +const SOAPBinding = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" + // EntitiesDescriptor represents the SAML object of the same name. // // See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf ยง2.3.1 @@ -203,6 +209,7 @@ type IDPSSODescriptor struct { WantAuthnRequestsSigned *bool `xml:",attr"` SingleSignOnServices []Endpoint `xml:"SingleSignOnService"` + ArtifactResolutionServices []Endpoint `xml:"ArtifactResolutionService"` NameIDMappingServices []Endpoint `xml:"NameIDMappingService"` AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"` AttributeProfiles []string `xml:"AttributeProfile"` diff --git a/samlsp/middleware.go b/samlsp/middleware.go index 9bc9c54d..5ba75c62 100644 --- a/samlsp/middleware.go +++ b/samlsp/middleware.go @@ -42,6 +42,7 @@ type Middleware struct { ServiceProvider saml.ServiceProvider OnError func(w http.ResponseWriter, r *http.Request, err error) Binding string // either saml.HTTPPostBinding or saml.HTTPRedirectBinding + ResponseBinding string // either saml.HTTPPostBinding or saml.HTTPArtifactBinding RequestTracker RequestTracker Session SessionProvider } @@ -140,7 +141,7 @@ func (m *Middleware) HandleStartAuthFlow(w http.ResponseWriter, r *http.Request) } } - authReq, err := m.ServiceProvider.MakeAuthenticationRequest(bindingLocation, binding) + authReq, err := m.ServiceProvider.MakeAuthenticationRequest(bindingLocation, binding, m.ResponseBinding) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/samlsp/new.go b/samlsp/new.go index 14ecf87f..55f7f7e8 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -23,6 +23,7 @@ type Options struct { DefaultRedirectURI string IDPMetadata *saml.EntityDescriptor SignRequest bool + UseArtifactResponse bool ForceAuthn bool // TODO(ross): this should be *bool CookieSameSite http.SameSite RelayStateFunc func(w http.ResponseWriter, r *http.Request) string @@ -125,10 +126,14 @@ func New(opts Options) (*Middleware, error) { m := &Middleware{ ServiceProvider: DefaultServiceProvider(opts), Binding: "", + ResponseBinding: saml.HTTPPostBinding, OnError: DefaultOnError, Session: DefaultSessionProvider(opts), } m.RequestTracker = DefaultRequestTracker(opts, &m.ServiceProvider) + if opts.UseArtifactResponse { + m.ResponseBinding = saml.HTTPArtifactBinding + } return m, nil } diff --git a/samlsp/testdata/expected_middleware_metadata.xml b/samlsp/testdata/expected_middleware_metadata.xml index 77a0ea58..dbcbb8dd 100644 --- a/samlsp/testdata/expected_middleware_metadata.xml +++ b/samlsp/testdata/expected_middleware_metadata.xml @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/schema.go b/schema.go index 01f488b4..58530955 100644 --- a/schema.go +++ b/schema.go @@ -304,6 +304,160 @@ func (a *NameIDPolicy) Element() *etree.Element { return el } +// ArtifactRequest represents the SAML object of the same name. +type ArtifactResolve struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol ArtifactResponse"` + ID string `xml:",attr"` + Version string `xml:",attr"` + IssueInstant time.Time `xml:",attr"` + Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` + Signature *etree.Element + Artifact string `xml:"urn:oasis:names:tc:SAML:2.0:protocol Artifact"` +} + +func (r *ArtifactResolve) Element() *etree.Element { + el := etree.NewElement("samlp:ArtifactResolve") + el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion") + el.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol") + + // Note: This namespace is not used by any element or attribute name, but + // is required so that the AttributeValue type element can have a value like + // "xs:string". If we don't declare it here, then it will be stripped by the + // cannonicalizer. This could be avoided by providing a prefix list to the + // cannonicalizer, but prefix lists do not appear to be implemented correctly + // in some libraries, so the safest action is to always produce XML that is + // (a) in canonical form and (b) does not require prefix lists. + el.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema") + + el.CreateAttr("ID", r.ID) + el.CreateAttr("Version", r.Version) + el.CreateAttr("IssueInstant", r.IssueInstant.Format(timeFormat)) + if r.Issuer != nil { + el.AddChild(r.Issuer.Element()) + } + artifact := etree.NewElement("samlp:Artifact") + artifact.SetText(r.Artifact) + el.AddChild(artifact) + if r.Signature != nil { + el.AddChild(r.Signature) + } + return el +} + +// Return a SOAP Envelope contining the ArtifactResolve request +func (r *ArtifactResolve) SoapRequest() *etree.Element { + envelope := etree.NewElement("soapenv:Envelope") + envelope.CreateAttr("xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/") + envelope.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + body := etree.NewElement("soapenv:Body") + envelope.AddChild(body) + body.AddChild(r.Element()) + return envelope +} + +// MarshalXML implements xml.Marshaler +func (r *ArtifactResolve) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type Alias ArtifactResolve + aux := &struct { + IssueInstant RelaxedTime `xml:",attr"` + *Alias + }{ + IssueInstant: RelaxedTime(r.IssueInstant), + Alias: (*Alias)(r), + } + return e.Encode(aux) +} + +// UnmarshalXML implements xml.Unmarshaler +func (r *ArtifactResolve) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type Alias ArtifactResolve + aux := &struct { + IssueInstant RelaxedTime `xml:",attr"` + *Alias + }{ + Alias: (*Alias)(r), + } + if err := d.DecodeElement(&aux, &start); err != nil { + return err + } + r.IssueInstant = time.Time(aux.IssueInstant) + return nil +} + +// ArtifactResponse represents the SAML object of the same name. +type ArtifactResponse struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol ArtifactResponse"` + ID string `xml:",attr"` + InResponseTo string `xml:",attr"` + Version string `xml:",attr"` + IssueInstant time.Time `xml:",attr"` + Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` + Signature *etree.Element + Status Status `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"` + Response Response `xml:"urn:oasis:names:tc:SAML:2.0:protocol Response"` +} + +// Element returns an etree.Element representing the object in XML form. +func (r *ArtifactResponse) Element() *etree.Element { + el := etree.NewElement("samlp:ArtifactResponse") + el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion") + el.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol") + + // Note: This namespace is not used by any element or attribute name, but + // is required so that the AttributeValue type element can have a value like + // "xs:string". If we don't declare it here, then it will be stripped by the + // cannonicalizer. This could be avoided by providing a prefix list to the + // cannonicalizer, but prefix lists do not appear to be implemented correctly + // in some libraries, so the safest action is to always produce XML that is + // (a) in canonical form and (b) does not require prefix lists. + el.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema") + + el.CreateAttr("ID", r.ID) + if r.InResponseTo != "" { + el.CreateAttr("InResponseTo", r.InResponseTo) + } + el.CreateAttr("Version", r.Version) + el.CreateAttr("IssueInstant", r.IssueInstant.Format(timeFormat)) + if r.Issuer != nil { + el.AddChild(r.Issuer.Element()) + } + if r.Signature != nil { + el.AddChild(r.Signature) + } + el.AddChild(r.Status.Element()) + el.AddChild(r.Response.Element()) + return el +} + +// MarshalXML implements xml.Marshaler +func (r *ArtifactResponse) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type Alias ArtifactResponse + aux := &struct { + IssueInstant RelaxedTime `xml:",attr"` + *Alias + }{ + IssueInstant: RelaxedTime(r.IssueInstant), + Alias: (*Alias)(r), + } + return e.Encode(aux) +} + +// UnmarshalXML implements xml.Unmarshaler +func (r *ArtifactResponse) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type Alias ArtifactResponse + aux := &struct { + IssueInstant RelaxedTime `xml:",attr"` + *Alias + }{ + Alias: (*Alias)(r), + } + if err := d.DecodeElement(&aux, &start); err != nil { + return err + } + r.IssueInstant = time.Time(aux.IssueInstant) + return nil +} + // Response represents the SAML object of the same name. // // See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf diff --git a/service_provider.go b/service_provider.go index 8d7222cd..a2ae9c2d 100644 --- a/service_provider.go +++ b/service_provider.go @@ -199,6 +199,11 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { Location: sp.AcsURL.String(), Index: 1, }, + { + Binding: HTTPArtifactBinding, + Location: sp.AcsURL.String(), + Index: 2, + }, }, }, }, @@ -209,7 +214,7 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { // the HTTP-Redirect binding. It returns a URL that we will redirect the user to // in order to start the auth process. func (sp *ServiceProvider) MakeRedirectAuthenticationRequest(relayState string) (*url.URL, error) { - req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPRedirectBinding), HTTPRedirectBinding) + req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPRedirectBinding), HTTPRedirectBinding, HTTPPostBinding) if err != nil { return nil, err } @@ -274,6 +279,19 @@ func (sp *ServiceProvider) GetSSOBindingLocation(binding string) string { return "" } +// GetArtifactBindingLocation returns URL for the IDP's Artifact binding of the +// specified type +func (sp *ServiceProvider) GetArtifactBindingLocation(binding string) string { + for _, idpSSODescriptor := range sp.IDPMetadata.IDPSSODescriptors { + for _, artifactResolutionService := range idpSSODescriptor.ArtifactResolutionServices { + if artifactResolutionService.Binding == binding { + return artifactResolutionService.Location + } + } + } + return "" +} + // GetSLOBindingLocation returns URL for the IDP's Single Log Out Service binding // of the specified type (HTTPRedirectBinding or HTTPPostBinding) func (sp *ServiceProvider) GetSLOBindingLocation(binding string) string { @@ -330,16 +348,38 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) { return certs, nil } +// MakeArtifactResolveRequest produces a new ArtifactResolve object to send to the idp's Artifact resolver +func (sp *ServiceProvider) MakeArtifactResolveRequest(artifactID string) (*ArtifactResolve, error) { + req := ArtifactResolve{ + ID: fmt.Sprintf("id-%x", randomBytes(20)), + IssueInstant: TimeNow(), + Version: "2.0", + Issuer: &Issuer{ + Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity", + Value: firstSet(sp.EntityID, sp.MetadataURL.String()), + }, + Artifact: artifactID, + } + + if len(sp.SignatureMethod) > 0 { + if err := sp.SignArtifactResolve(&req); err != nil { + return nil, err + } + } + + return &req, nil +} + // MakeAuthenticationRequest produces a new AuthnRequest object to send to the idpURL // that uses the specified binding (HTTPRedirectBinding or HTTPPostBinding) -func (sp *ServiceProvider) MakeAuthenticationRequest(idpURL string, binding string) (*AuthnRequest, error) { +func (sp *ServiceProvider) MakeAuthenticationRequest(idpURL string, binding string, resultBinding string) (*AuthnRequest, error) { allowCreate := true nameIDFormat := sp.nameIDFormat() req := AuthnRequest{ AssertionConsumerServiceURL: sp.AcsURL.String(), Destination: idpURL, - ProtocolBinding: HTTPPostBinding, // default binding for the response + ProtocolBinding: resultBinding, // default binding for the response ID: fmt.Sprintf("id-%x", randomBytes(20)), IssueInstant: TimeNow(), Version: "2.0", @@ -393,6 +433,24 @@ func GetSigningContext(sp *ServiceProvider) (*dsig.SigningContext, error) { return signingContext, nil } +// SignArtifactResolve adds the `Signature` element to the `ArtifactResolve`. +func (sp *ServiceProvider) SignArtifactResolve(req *ArtifactResolve) error { + signingContext, err := GetSigningContext(sp) + if err != nil { + return err + } + assertionEl := req.Element() + + signedRequestEl, err := signingContext.SignEnveloped(assertionEl) + if err != nil { + return err + } + + sigEl := signedRequestEl.Child[len(signedRequestEl.Child)-1] + req.Signature = sigEl.(*etree.Element) + return nil +} + // SignAuthnRequest adds the `Signature` element to the `AuthnRequest`. func (sp *ServiceProvider) SignAuthnRequest(req *AuthnRequest) error { @@ -416,7 +474,7 @@ func (sp *ServiceProvider) SignAuthnRequest(req *AuthnRequest) error { // the HTTP-POST binding. It returns HTML text representing an HTML form that // can be sent presented to a browser to initiate the login process. func (sp *ServiceProvider) MakePostAuthenticationRequest(relayState string) ([]byte, error) { - req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPPostBinding), HTTPPostBinding) + req, err := sp.MakeAuthenticationRequest(sp.GetSSOBindingLocation(HTTPPostBinding), HTTPPostBinding, HTTPPostBinding) if err != nil { return nil, err } @@ -508,8 +566,8 @@ func (e ErrBadStatus) Error() string { return e.Status } -func responseIsSigned(response *etree.Document) (bool, error) { - signatureElement, err := findChild(response.Root(), "http://www.w3.org/2000/09/xmldsig#", "Signature") +func responseIsSigned(response *etree.Element) (bool, error) { + signatureElement, err := findChild(response, "http://www.w3.org/2000/09/xmldsig#", "Signature") if err != nil { return false, err } @@ -518,14 +576,8 @@ func responseIsSigned(response *etree.Document) (bool, error) { // validateDestination validates the Destination attribute. // If the response is signed, the Destination is required to be present. -func (sp *ServiceProvider) validateDestination(response []byte, responseDom *Response) error { - responseXML := etree.NewDocument() - err := responseXML.ReadFromBytes(response) - if err != nil { - return err - } - - signed, err := responseIsSigned(responseXML) +func (sp *ServiceProvider) validateDestination(response *etree.Element, responseDom *Response) error { + signed, err := responseIsSigned(response) if err != nil { return err } @@ -541,31 +593,162 @@ func (sp *ServiceProvider) validateDestination(response []byte, responseDom *Res return nil } -// ParseResponse extracts the SAML IDP response received in req, validates -// it, and returns the verified assertion. +// ParseResponse extracts the SAML IDP response received in req, resolves +// artifacts when neccessary, validates it, and returns the verified assertion. func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs []string) (*Assertion, error) { now := TimeNow() + + var assertion *Assertion + retErr := &InvalidResponseError{ Now: now, Response: req.PostForm.Get("SAMLResponse"), } - rawResponseBuf, err := base64.StdEncoding.DecodeString(req.PostForm.Get("SAMLResponse")) - if err != nil { - retErr.PrivateErr = fmt.Errorf("cannot parse base64: %s", err) + if req.Form.Get("SAMLart") != "" { + retErr.Response = req.Form.Get("SAMLart") + + req, err := sp.MakeArtifactResolveRequest(req.Form.Get("SAMLart")) + if err != nil { + retErr.PrivateErr = fmt.Errorf("Cannot generate artifact resolution request: %s", err) + return nil, retErr + } + + doc := etree.NewDocument() + doc.SetRoot(req.SoapRequest()) + + var requestBuffer bytes.Buffer + doc.WriteTo(&requestBuffer) + response, err := http.Post(sp.GetArtifactBindingLocation(SOAPBinding), "text/xml", &requestBuffer) + if err != nil { + retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) + return nil, retErr + } + defer response.Body.Close() + if response.StatusCode != 200 { + retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: HTTP status %d (%s)", response.StatusCode, response.Status) + return nil, retErr + } + rawResponseBuf, err := ioutil.ReadAll(response.Body) + if err != nil { + retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) + return nil, retErr + } + assertion, err = sp.ParseXMLArtifactResponse(rawResponseBuf, possibleRequestIDs, req.ID) + if err != nil { + return nil, err + } + } else { + rawResponseBuf, err := base64.StdEncoding.DecodeString(req.PostForm.Get("SAMLResponse")) + if err != nil { + retErr.PrivateErr = fmt.Errorf("cannot parse base64: %s", err) + return nil, retErr + } + retErr.Response = string(rawResponseBuf) + assertion, err = sp.ParseXMLResponse(rawResponseBuf, possibleRequestIDs) + if err != nil { + return nil, err + } + } + + return assertion, nil + +} + +// ParseXMLArtifactResponse validates the SAML Artifact resolver response +// and returns the verified assertion. +// +// This function handles verifying the digital signature, and verifying +// that the specified conditions and properties are met. +// +// If the function fails it will return an InvalidResponseError whose +// properties are useful in describing which part of the parsing process +// failed. However, to discourage inadvertent disclosure the diagnostic +// information, the Error() method returns a static string. +func (sp *ServiceProvider) ParseXMLArtifactResponse(decodedResponseXML []byte, possibleRequestIDs []string, artifactRequestID string) (*Assertion, error) { + now := TimeNow() + //var err error + retErr := &InvalidResponseError{ + Now: now, + Response: string(decodedResponseXML), + } + + // ensure that the response XML is well formed before we parse it + if err := xrv.Validate(bytes.NewReader(decodedResponseXML)); err != nil { + retErr.PrivateErr = fmt.Errorf("invalid xml: %s", err) + return nil, retErr + } + + envelope := &struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + Body struct { + ArtifactResponse ArtifactResponse + } `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` + }{} + if err := xml.Unmarshal(decodedResponseXML, &envelope); err != nil { + retErr.PrivateErr = fmt.Errorf("cannot unmarshal response: %s", err) + return nil, retErr + } + + resp := envelope.Body.ArtifactResponse + + // Validate ArtifactResponse + if resp.InResponseTo != artifactRequestID { + retErr.PrivateErr = fmt.Errorf("`InResponseTo` does not match the artifact request ID (expected %v)", artifactRequestID) + return nil, retErr + } + if resp.IssueInstant.Add(MaxIssueDelay).Before(now) { + retErr.PrivateErr = fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)) + return nil, retErr + } + if resp.Issuer != nil && resp.Issuer.Value != sp.IDPMetadata.EntityID { + retErr.PrivateErr = fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) + return nil, retErr + } + if resp.Status.StatusCode.Value != StatusSuccess { + retErr.PrivateErr = ErrBadStatus{Status: resp.Status.StatusCode.Value} + return nil, retErr + } + + doc := etree.NewDocument() + if err := doc.ReadFromBytes(decodedResponseXML); err != nil { + retErr.PrivateErr = err + return nil, retErr + } + + artifactEl := doc.FindElement("Envelope/Body/ArtifactResponse") + if artifactEl == nil { + retErr.PrivateErr = fmt.Errorf("missing ArtifactResponse") + return nil, retErr + } + responseEl := doc.FindElement("Envelope/Body/ArtifactResponse/Response") + if responseEl == nil { + retErr.PrivateErr = fmt.Errorf("missing inner Response") + return nil, retErr + } + + haveSignature := false + var err error + if err = sp.validateArtifactSigned(artifactEl); err != nil && err.Error() != "either the Response or Assertion must be signed" { + retErr.PrivateErr = err return nil, retErr } - retErr.Response = string(rawResponseBuf) - assertion, err := sp.ParseXMLResponse(rawResponseBuf, possibleRequestIDs) + if err == nil { + haveSignature = true + } + assertion, err, updatedResponse := sp.validateXMLResponse(&resp.Response, responseEl, possibleRequestIDs, now, !haveSignature) if err != nil { - return nil, err + retErr.PrivateErr = err + if updatedResponse != nil { + retErr.Response = *updatedResponse + } + return nil, retErr } return assertion, nil - } -// ParseXMLResponse validates the SAML IDP response and +// ParseXMLResponse parses and validates the SAML IDP response and // returns the verified assertion. // // This function handles decrypting the message, verifying the digital @@ -597,11 +780,37 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR return nil, retErr } - if err := sp.validateDestination(decodedResponseXML, &resp); err != nil { + doc := etree.NewDocument() + if err := doc.ReadFromBytes(decodedResponseXML); err != nil { + retErr.PrivateErr = err + return nil, retErr + } + + assertion, err, updatedResponse := sp.validateXMLResponse(&resp, doc.Root(), possibleRequestIDs, now, true) + if err != nil { retErr.PrivateErr = err + if updatedResponse != nil { + retErr.Response = *updatedResponse + } return nil, retErr } + return assertion, nil +} + +// validateXMLResponse validates the SAML IDP response and returns +// the verified assertion. +// +// This function handles decrypting the message, verifying the digital +// signature on the assertion, and verifying that the specified conditions +// and properties are met. +func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree.Element, possibleRequestIDs []string, now time.Time, needSig bool) (*Assertion, error, *string) { + var err error + var updatedResponse *string + if err := sp.validateDestination(responseEl, resp); err != nil { + return nil, err, updatedResponse + } + requestIDvalid := false if sp.AllowIDPInitiated { @@ -615,42 +824,28 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR } if !requestIDvalid { - retErr.PrivateErr = fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs) - return nil, retErr + return nil, fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs), updatedResponse } if resp.IssueInstant.Add(MaxIssueDelay).Before(now) { - retErr.PrivateErr = fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)) - return nil, retErr + return nil, fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)), updatedResponse } if resp.Issuer != nil && resp.Issuer.Value != sp.IDPMetadata.EntityID { - retErr.PrivateErr = fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) - return nil, retErr + return nil, fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID), updatedResponse } if resp.Status.StatusCode.Value != StatusSuccess { - retErr.PrivateErr = ErrBadStatus{Status: resp.Status.StatusCode.Value} - return nil, retErr + return nil, ErrBadStatus{Status: resp.Status.StatusCode.Value}, updatedResponse } var assertion *Assertion if resp.EncryptedAssertion == nil { - - doc := etree.NewDocument() - if err := doc.ReadFromBytes(decodedResponseXML); err != nil { - retErr.PrivateErr = err - return nil, retErr - } - // TODO(ross): verify that the namespace is urn:oasis:names:tc:SAML:2.0:protocol - responseEl := doc.Root() if responseEl.Tag != "Response" { - retErr.PrivateErr = fmt.Errorf("expected to find a response object, not %s", doc.Root().Tag) - return nil, retErr + return nil, fmt.Errorf("expected to find a response object, not %s", responseEl.Tag), updatedResponse } - if err = sp.validateSigned(responseEl); err != nil { - retErr.PrivateErr = err - return nil, retErr + if err = sp.validateSigned(responseEl); err != nil && !(!needSig && err.Error() == "either the Response or Assertion must be signed") { + return nil, err, updatedResponse } assertion = resp.Assertion @@ -658,78 +853,64 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR // decrypt the response if resp.EncryptedAssertion != nil { - doc := etree.NewDocument() - if err := doc.ReadFromBytes(decodedResponseXML); err != nil { - retErr.PrivateErr = err - return nil, retErr - } - // encrypted assertions are part of the signature // before decrypting the response verify that - responseSigned, err := responseIsSigned(doc) + responseSigned, err := responseIsSigned(responseEl) if err != nil { - retErr.PrivateErr = err - return nil, retErr + return nil, err, updatedResponse } if responseSigned { - if err := sp.validateSigned(doc.Root()); err != nil { - retErr.PrivateErr = err - return nil, retErr + if err := sp.validateSigned(responseEl); err != nil { + return nil, err, updatedResponse } } var key interface{} = sp.Key - keyEl := doc.FindElement("//EncryptedAssertion/EncryptedKey") + keyEl := responseEl.FindElement("//EncryptedAssertion/EncryptedKey") if keyEl != nil { key, err = xmlenc.Decrypt(sp.Key, keyEl) if err != nil { - retErr.PrivateErr = fmt.Errorf("failed to decrypt key from response: %s", err) - return nil, retErr + return nil, fmt.Errorf("failed to decrypt key from response: %s", err), updatedResponse } } - el := doc.FindElement("//EncryptedAssertion/EncryptedData") + el := responseEl.FindElement("//EncryptedAssertion/EncryptedData") plaintextAssertion, err := xmlenc.Decrypt(key, el) if err != nil { - retErr.PrivateErr = fmt.Errorf("failed to decrypt response: %s", err) - return nil, retErr + return nil, fmt.Errorf("failed to decrypt response: %s", err), updatedResponse } - retErr.Response = string(plaintextAssertion) + updatedResponse = new(string) + *updatedResponse = string(plaintextAssertion) // TODO(ross): add test case for this if err := xrv.Validate(bytes.NewReader(plaintextAssertion)); err != nil { - retErr.PrivateErr = fmt.Errorf("plaintext response contains invalid XML: %s", err) - return nil, retErr + return nil, fmt.Errorf("plaintext response contains invalid XML: %s", err), updatedResponse } - doc = etree.NewDocument() + doc := etree.NewDocument() if err := doc.ReadFromBytes(plaintextAssertion); err != nil { - retErr.PrivateErr = fmt.Errorf("cannot parse plaintext response %v", err) - return nil, retErr + return nil, fmt.Errorf("cannot parse plaintext response %v", err), updatedResponse } // the decrypted assertion may be signed too // otherwise, a signed response is sufficient - if err := sp.validateSigned(doc.Root()); err != nil && !responseSigned { - retErr.PrivateErr = err - return nil, retErr + if err := sp.validateSigned(doc.Root()); err != nil && !((responseSigned || !needSig) && err.Error() == "either the Response or Assertion must be signed") { + return nil, err, updatedResponse } assertion = &Assertion{} // Note: plaintextAssertion is known to be safe to parse because // plaintextAssertion is unmodified from when xrv.Validate() was called above. if err := xml.Unmarshal(plaintextAssertion, assertion); err != nil { - retErr.PrivateErr = err - return nil, retErr + return nil, err, updatedResponse } } if err := sp.validateAssertion(assertion, possibleRequestIDs, now); err != nil { - retErr.PrivateErr = fmt.Errorf("assertion invalid: %s", err) - return nil, retErr + return nil, fmt.Errorf("assertion invalid: %s", err), updatedResponse } - return assertion, nil + return assertion, nil, updatedResponse } // validateAssertion checks that the conditions specified in assertion match @@ -829,6 +1010,42 @@ func findChild(parentEl *etree.Element, childNS string, childTag string) (*etree return nil, nil } +// validateArtifactSigned returns a nil error iff each of the signatures on the ArtifactResponse, Response +// and Assertion elements are valid and there is at least one signature. +func (sp *ServiceProvider) validateArtifactSigned(artifactEl *etree.Element) error { + haveSignature := false + + sigEl, err := findChild(artifactEl, "http://www.w3.org/2000/09/xmldsig#", "Signature") + if err != nil { + return err + } + if sigEl != nil { + if err = sp.validateSignature(artifactEl); err != nil { + return fmt.Errorf("cannot validate signature on Response: %v", err) + } + haveSignature = true + } + + responseEl, err := findChild(artifactEl, "urn:oasis:names:tc:SAML:2.0:protocol", "Response") + if err != nil { + return err + } + if responseEl != nil { + err = sp.validateSigned(responseEl) + if err != nil && err.Error() != "either the Response or Assertion must be signed" { + return err + } + if err == nil { + haveSignature = true // guaranteed by validateSigned + } + } + + if !haveSignature { + return errors.New("either the ArtifactResponse, Response or Assertion must be signed") + } + return nil +} + // validateSigned returns a nil error iff each of the signatures on the Response and Assertion elements // are valid and there is at least one signature. func (sp *ServiceProvider) validateSigned(responseEl *etree.Element) error { diff --git a/service_provider_test.go b/service_provider_test.go index 7d897663..434aa35d 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -81,25 +81,25 @@ func TestSPCanSetAuthenticationNameIDFormat(t *testing.T) { } // defaults to "transient" - req, err := s.MakeAuthenticationRequest("", HTTPRedirectBinding) + req, err := s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding) assert.Check(t, err) assert.Check(t, is.Equal(string(TransientNameIDFormat), *req.NameIDPolicy.Format)) // explicitly set to "transient" s.AuthnNameIDFormat = TransientNameIDFormat - req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding) + req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding) assert.Check(t, err) assert.Check(t, is.Equal(string(TransientNameIDFormat), *req.NameIDPolicy.Format)) // explicitly set to "unspecified" s.AuthnNameIDFormat = UnspecifiedNameIDFormat - req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding) + req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding) assert.Check(t, err) assert.Check(t, is.Equal("", *req.NameIDPolicy.Format)) // explicitly set to "emailAddress" s.AuthnNameIDFormat = EmailAddressNameIDFormat - req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding) + req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding) assert.Check(t, err) assert.Check(t, is.Equal(string(EmailAddressNameIDFormat), *req.NameIDPolicy.Format)) } diff --git a/testdata/TestCanProduceMetadataEntityID_metadata b/testdata/TestCanProduceMetadataEntityID_metadata index 79686c48..68afb039 100644 --- a/testdata/TestCanProduceMetadataEntityID_metadata +++ b/testdata/TestCanProduceMetadataEntityID_metadata @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/testdata/TestCanProduceMetadataNoCerts_metadata b/testdata/TestCanProduceMetadataNoCerts_metadata index 3e802f77..f12f464a 100644 --- a/testdata/TestCanProduceMetadataNoCerts_metadata +++ b/testdata/TestCanProduceMetadataNoCerts_metadata @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata index 428cc13b..7eb50a45 100644 --- a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata +++ b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata @@ -20,5 +20,6 @@ + \ No newline at end of file diff --git a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata index 02d7aeea..82637492 100644 --- a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata +++ b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata @@ -13,5 +13,6 @@ + \ No newline at end of file From cc1e81b5a55dbd8c960b3d1a505e377540ba0de4 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Fri, 10 Dec 2021 10:41:57 -0500 Subject: [PATCH 09/58] make linter happy (#398) --- .github/workflows/lint.yml | 2 +- .golangci.yml | 2 +- samlsp/new.go | 24 ++++++++--------- schema.go | 23 ++++++++-------- service_provider.go | 54 ++++++++++++++++---------------------- xmlenc/encrypt_test.go | 5 ++-- xmlenc/gcm.go | 6 +++-- xmlenc/xmlenc_test.go | 7 ++--- 8 files changed, 60 insertions(+), 63 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7a0426e4..69f6627a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,4 +15,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.24.0 + version: v1.43.0 diff --git a/.golangci.yml b/.golangci.yml index 1392c902..4fbc0405 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,7 +12,7 @@ linters: - gosec # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false] - misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] - deadcode # Finds unused code [fast: true, auto-fix: false] - - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] + - revive # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] - unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false] disable: diff --git a/samlsp/new.go b/samlsp/new.go index 55f7f7e8..a1ab584e 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -14,19 +14,19 @@ import ( // Options represents the parameters for creating a new middleware type Options struct { - EntityID string - URL url.URL - Key *rsa.PrivateKey - Certificate *x509.Certificate - Intermediates []*x509.Certificate - AllowIDPInitiated bool - DefaultRedirectURI string - IDPMetadata *saml.EntityDescriptor - SignRequest bool + EntityID string + URL url.URL + Key *rsa.PrivateKey + Certificate *x509.Certificate + Intermediates []*x509.Certificate + AllowIDPInitiated bool + DefaultRedirectURI string + IDPMetadata *saml.EntityDescriptor + SignRequest bool UseArtifactResponse bool - ForceAuthn bool // TODO(ross): this should be *bool - CookieSameSite http.SameSite - RelayStateFunc func(w http.ResponseWriter, r *http.Request) string + ForceAuthn bool // TODO(ross): this should be *bool + CookieSameSite http.SameSite + RelayStateFunc func(w http.ResponseWriter, r *http.Request) string } // DefaultSessionCodec returns the default SessionCodec for the provided options, diff --git a/schema.go b/schema.go index 58530955..eefd2c66 100644 --- a/schema.go +++ b/schema.go @@ -48,12 +48,12 @@ type AuthnRequest struct { type LogoutRequest struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol LogoutRequest"` - ID string `xml:",attr"` - Version string `xml:",attr"` - IssueInstant time.Time `xml:",attr"` + ID string `xml:",attr"` + Version string `xml:",attr"` + IssueInstant time.Time `xml:",attr"` NotOnOrAfter *time.Time `xml:",attr"` - Destination string `xml:",attr"` - Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` + Destination string `xml:",attr"` + Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` NameID *NameID Signature *etree.Element @@ -93,7 +93,7 @@ func (r *LogoutRequest) Element() *etree.Element { func (r *LogoutRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error { type Alias LogoutRequest aux := &struct { - IssueInstant RelaxedTime `xml:",attr"` + IssueInstant RelaxedTime `xml:",attr"` NotOnOrAfter *RelaxedTime `xml:",attr"` *Alias }{ @@ -108,7 +108,7 @@ func (r *LogoutRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error func (r *LogoutRequest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { type Alias LogoutRequest aux := &struct { - IssueInstant RelaxedTime `xml:",attr"` + IssueInstant RelaxedTime `xml:",attr"` NotOnOrAfter *RelaxedTime `xml:",attr"` *Alias }{ @@ -304,7 +304,7 @@ func (a *NameIDPolicy) Element() *etree.Element { return el } -// ArtifactRequest represents the SAML object of the same name. +// ArtifactResolve represents the SAML object of the same name. type ArtifactResolve struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol ArtifactResponse"` ID string `xml:",attr"` @@ -315,6 +315,7 @@ type ArtifactResolve struct { Artifact string `xml:"urn:oasis:names:tc:SAML:2.0:protocol Artifact"` } +// Element returns an etree.Element representing the object in XML form. func (r *ArtifactResolve) Element() *etree.Element { el := etree.NewElement("samlp:ArtifactResolve") el.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion") @@ -344,7 +345,7 @@ func (r *ArtifactResolve) Element() *etree.Element { return el } -// Return a SOAP Envelope contining the ArtifactResolve request +// SoapRequest returns a SOAP Envelope contining the ArtifactResolve request func (r *ArtifactResolve) SoapRequest() *etree.Element { envelope := etree.NewElement("soapenv:Envelope") envelope.CreateAttr("xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/") @@ -677,8 +678,8 @@ const ( // // See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf ยง3.2.2.3 type StatusMessage struct { - XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage"` - Value string `xml:",chardata"` + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage"` + Value string `xml:",chardata"` } // Element returns an etree.Element representing the object in XML form. diff --git a/service_provider.go b/service_provider.go index a2ae9c2d..654d456d 100644 --- a/service_provider.go +++ b/service_provider.go @@ -594,7 +594,7 @@ func (sp *ServiceProvider) validateDestination(response *etree.Element, response } // ParseResponse extracts the SAML IDP response received in req, resolves -// artifacts when neccessary, validates it, and returns the verified assertion. +// artifacts when necessary, validates it, and returns the verified assertion. func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs []string) (*Assertion, error) { now := TimeNow() @@ -736,7 +736,7 @@ func (sp *ServiceProvider) ParseXMLArtifactResponse(decodedResponseXML []byte, p if err == nil { haveSignature = true } - assertion, err, updatedResponse := sp.validateXMLResponse(&resp.Response, responseEl, possibleRequestIDs, now, !haveSignature) + assertion, updatedResponse, err := sp.validateXMLResponse(&resp.Response, responseEl, possibleRequestIDs, now, !haveSignature) if err != nil { retErr.PrivateErr = err if updatedResponse != nil { @@ -786,7 +786,7 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR return nil, retErr } - assertion, err, updatedResponse := sp.validateXMLResponse(&resp, doc.Root(), possibleRequestIDs, now, true) + assertion, updatedResponse, err := sp.validateXMLResponse(&resp, doc.Root(), possibleRequestIDs, now, true) if err != nil { retErr.PrivateErr = err if updatedResponse != nil { @@ -804,11 +804,11 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR // This function handles decrypting the message, verifying the digital // signature on the assertion, and verifying that the specified conditions // and properties are met. -func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree.Element, possibleRequestIDs []string, now time.Time, needSig bool) (*Assertion, error, *string) { +func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree.Element, possibleRequestIDs []string, now time.Time, needSig bool) (*Assertion, *string, error) { var err error var updatedResponse *string if err := sp.validateDestination(responseEl, resp); err != nil { - return nil, err, updatedResponse + return nil, updatedResponse, err } requestIDvalid := false @@ -824,28 +824,28 @@ func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree } if !requestIDvalid { - return nil, fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs), updatedResponse + return nil, updatedResponse, fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs) } if resp.IssueInstant.Add(MaxIssueDelay).Before(now) { - return nil, fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)), updatedResponse + return nil, updatedResponse, fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)) } if resp.Issuer != nil && resp.Issuer.Value != sp.IDPMetadata.EntityID { - return nil, fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID), updatedResponse + return nil, updatedResponse, fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) } if resp.Status.StatusCode.Value != StatusSuccess { - return nil, ErrBadStatus{Status: resp.Status.StatusCode.Value}, updatedResponse + return nil, updatedResponse, ErrBadStatus{Status: resp.Status.StatusCode.Value} } var assertion *Assertion if resp.EncryptedAssertion == nil { // TODO(ross): verify that the namespace is urn:oasis:names:tc:SAML:2.0:protocol if responseEl.Tag != "Response" { - return nil, fmt.Errorf("expected to find a response object, not %s", responseEl.Tag), updatedResponse + return nil, updatedResponse, fmt.Errorf("expected to find a response object, not %s", responseEl.Tag) } if err = sp.validateSigned(responseEl); err != nil && !(!needSig && err.Error() == "either the Response or Assertion must be signed") { - return nil, err, updatedResponse + return nil, updatedResponse, err } assertion = resp.Assertion @@ -857,11 +857,11 @@ func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree // before decrypting the response verify that responseSigned, err := responseIsSigned(responseEl) if err != nil { - return nil, err, updatedResponse + return nil, updatedResponse, err } if responseSigned { if err := sp.validateSigned(responseEl); err != nil { - return nil, err, updatedResponse + return nil, updatedResponse, err } } @@ -870,47 +870,47 @@ func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree if keyEl != nil { key, err = xmlenc.Decrypt(sp.Key, keyEl) if err != nil { - return nil, fmt.Errorf("failed to decrypt key from response: %s", err), updatedResponse + return nil, updatedResponse, fmt.Errorf("failed to decrypt key from response: %s", err) } } el := responseEl.FindElement("//EncryptedAssertion/EncryptedData") plaintextAssertion, err := xmlenc.Decrypt(key, el) if err != nil { - return nil, fmt.Errorf("failed to decrypt response: %s", err), updatedResponse + return nil, updatedResponse, fmt.Errorf("failed to decrypt response: %s", err) } updatedResponse = new(string) *updatedResponse = string(plaintextAssertion) // TODO(ross): add test case for this if err := xrv.Validate(bytes.NewReader(plaintextAssertion)); err != nil { - return nil, fmt.Errorf("plaintext response contains invalid XML: %s", err), updatedResponse + return nil, updatedResponse, fmt.Errorf("plaintext response contains invalid XML: %s", err) } doc := etree.NewDocument() if err := doc.ReadFromBytes(plaintextAssertion); err != nil { - return nil, fmt.Errorf("cannot parse plaintext response %v", err), updatedResponse + return nil, updatedResponse, fmt.Errorf("cannot parse plaintext response %v", err) } // the decrypted assertion may be signed too // otherwise, a signed response is sufficient if err := sp.validateSigned(doc.Root()); err != nil && !((responseSigned || !needSig) && err.Error() == "either the Response or Assertion must be signed") { - return nil, err, updatedResponse + return nil, updatedResponse, err } assertion = &Assertion{} // Note: plaintextAssertion is known to be safe to parse because // plaintextAssertion is unmodified from when xrv.Validate() was called above. if err := xml.Unmarshal(plaintextAssertion, assertion); err != nil { - return nil, err, updatedResponse + return nil, updatedResponse, err } } if err := sp.validateAssertion(assertion, possibleRequestIDs, now); err != nil { - return nil, fmt.Errorf("assertion invalid: %s", err), updatedResponse + return nil, updatedResponse, fmt.Errorf("assertion invalid: %s", err) } - return assertion, nil, updatedResponse + return assertion, updatedResponse, nil } // validateAssertion checks that the conditions specified in assertion match @@ -1492,11 +1492,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseForm(postFormData string) error } responseEl := doc.Root() - if err = sp.validateSigned(responseEl); err != nil { - return err - } - - return nil + return sp.validateSigned(responseEl) } // ValidateLogoutResponseRedirect returns a nil error if the logout response is valid. @@ -1537,11 +1533,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str } responseEl := doc.Root() - if err = sp.validateSigned(responseEl); err != nil { - return err - } - - return nil + return sp.validateSigned(responseEl) } // validateLogoutResponse validates the LogoutResponse fields. Returns a nil error if the LogoutResponse is valid. diff --git a/xmlenc/encrypt_test.go b/xmlenc/encrypt_test.go index 8e69d035..2cfa86c5 100644 --- a/xmlenc/encrypt_test.go +++ b/xmlenc/encrypt_test.go @@ -3,11 +3,12 @@ package xmlenc import ( "crypto/x509" "encoding/pem" + "math/rand" + "testing" + "github.com/beevik/etree" "gotest.tools/assert" "gotest.tools/golden" - "math/rand" - "testing" ) func TestCanEncryptOAEP(t *testing.T) { diff --git a/xmlenc/gcm.go b/xmlenc/gcm.go index 94958327..91911319 100644 --- a/xmlenc/gcm.go +++ b/xmlenc/gcm.go @@ -6,11 +6,12 @@ import ( "crypto/rand" "encoding/base64" "fmt" - "github.com/beevik/etree" "io" + + "github.com/beevik/etree" ) -// struct implements Decrypter and Encrypter for block ciphers in struct mode +// GCM implements Decrypter and Encrypter for block ciphers in struct mode type GCM struct { keySize int algorithm string @@ -28,6 +29,7 @@ func (e GCM) Algorithm() string { return e.algorithm } +// Encrypt encrypts plaintext with key and nonce func (e GCM) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) { keyBuf, ok := key.([]byte) if !ok { diff --git a/xmlenc/xmlenc_test.go b/xmlenc/xmlenc_test.go index 7b6f06bc..cad9b90f 100644 --- a/xmlenc/xmlenc_test.go +++ b/xmlenc/xmlenc_test.go @@ -1,12 +1,13 @@ package xmlenc import ( - "github.com/beevik/etree" - "gotest.tools/assert" - is "gotest.tools/assert/cmp" "io/ioutil" "math/rand" "testing" + + "github.com/beevik/etree" + "gotest.tools/assert" + is "gotest.tools/assert/cmp" ) func TestDataAES128(t *testing.T) { From 8308fb0c667fa0f8caa4169f4bc823779c6c5719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Dec 2021 10:42:12 -0500 Subject: [PATCH 10/58] Bump github.com/google/go-cmp from 0.5.5 to 0.5.6 (#356) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.5 to 0.5.6. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.5...v0.5.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8308cd46..6d9d423e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 github.com/golang-jwt/jwt/v4 v4.1.0 - github.com/google/go-cmp v0.5.5 + github.com/google/go-cmp v0.5.6 github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index cd11d2df..9fd6eb8a 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0 github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= 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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= From 60a32b32095ab361c827116afd3f0041874c6c9c Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Fri, 10 Dec 2021 10:49:29 -0500 Subject: [PATCH 11/58] metadata cert chain (#399) * Support multiple X509Certificate elements in X509Data * Update test files * Remove unnecessary comments * add comments Co-authored-by: Alexander Zobnin --- identity_provider.go | 18 +++++++++++++----- identity_provider_test.go | 16 ++++++++++++---- metadata.go | 18 ++++++++++++++---- metadata_test.go | 16 ++++++++++++++-- samlidp/testdata/http_metadata_response.html | 8 ++++---- samlidp/testdata/sp_metadata.xml | 2 +- .../testdata/expected_middleware_metadata.xml | 4 ++-- service_provider.go | 18 ++++++++++++++---- service_provider_go116_test.go | 4 ++-- service_provider_go117_test.go | 4 ++-- service_provider_test.go | 2 +- testdata/TestCanProduceSPMetadata_expected | 8 ++++---- ...TestSPCanHandleOneloginResponse_IDPMetadata | 2 +- ...tSPCanProduceMetadataWithBothCerts_metadata | 8 ++++---- ...nProduceMetadataWithEncryptionCert_metadata | 4 ++-- ...nHandleSignedAssertionsResponse_IDPMetadata | 2 +- .../encrypt-content-aes192-cbc-dh-sha512.xml | 4 ++-- ...ata-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml | 4 ++-- ...crypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml | 4 ++-- .../encrypt-element-aes128-cbc-rsa-1_5.xml | 4 ++-- ...ement-aes256-cbc-kw-aes256-dh-ripemd160.xml | 4 ++-- xmlenc/corpus/encsig-hmac-sha256-dh.xml | 4 ++-- .../encsig-hmac-sha256-kw-tripledes-dh.xml | 4 ++-- xmlenc/corpus/encsig-hmac-sha256-rsa-1_5.xml | 4 ++-- .../encsig-hmac-sha256-rsa-oaep-mgf1p.xml | 4 ++-- .../255c4741516851ba13e8151340541d1d247a1d5f | 2 +- .../4d7686c884eb4d4e41d69446018d7c3122c9bc11 | 2 +- .../5837247e7ed40387b811a2a1be52cd91b873d349 | 2 +- .../655d116d6e8b8d9c1de179459ee71293a6151792 | 2 +- 29 files changed, 113 insertions(+), 65 deletions(-) diff --git a/identity_provider.go b/identity_provider.go index 40b86e7f..4c7282ae 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -131,13 +131,21 @@ func (idp *IdentityProvider) Metadata() *EntityDescriptor { { Use: "signing", KeyInfo: KeyInfo{ - Certificate: certStr, + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: certStr}, + }, + }, }, }, { Use: "encryption", KeyInfo: KeyInfo{ - Certificate: certStr, + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: certStr}, + }, + }, }, EncryptionMethods: []EncryptionMethod{ {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"}, @@ -927,7 +935,7 @@ func (req *IdpAuthnRequest) getSPEncryptionCert() (*x509.Certificate, error) { certStr := "" for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors { if keyDescriptor.Use == "encryption" { - certStr = keyDescriptor.KeyInfo.Certificate + certStr = keyDescriptor.KeyInfo.X509Data.X509Certificates[0].Data break } } @@ -936,8 +944,8 @@ func (req *IdpAuthnRequest) getSPEncryptionCert() (*x509.Certificate, error) { // non-empty cert we find. if certStr == "" { for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors { - if keyDescriptor.Use == "" && keyDescriptor.KeyInfo.Certificate != "" { - certStr = keyDescriptor.KeyInfo.Certificate + if keyDescriptor.Use == "" && len(keyDescriptor.KeyInfo.X509Data.X509Certificates) != 0 && keyDescriptor.KeyInfo.X509Data.X509Certificates[0].Data != "" { + certStr = keyDescriptor.KeyInfo.X509Data.X509Certificates[0].Data break } } diff --git a/identity_provider_test.go b/identity_provider_test.go index dcb78433..0c602b53 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -152,16 +152,24 @@ func TestIDPCanProduceMetadata(t *testing.T) { { Use: "signing", KeyInfo: KeyInfo{ - XMLName: xml.Name{}, - Certificate: "MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==", + XMLName: xml.Name{}, + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: "MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ=="}, + }, + }, }, EncryptionMethods: nil, }, { Use: "encryption", KeyInfo: KeyInfo{ - XMLName: xml.Name{}, - Certificate: "MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==", + XMLName: xml.Name{}, + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: "MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ=="}, + }, + }, }, EncryptionMethods: []EncryptionMethod{ {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"}, diff --git a/metadata.go b/metadata.go index 8f92ada2..f7486d38 100644 --- a/metadata.go +++ b/metadata.go @@ -162,11 +162,21 @@ type EncryptionMethod struct { } // KeyInfo represents the XMLSEC object of the same name -// -// TODO(ross): revisit xmldsig and make this type more complete type KeyInfo struct { - XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"` - Certificate string `xml:"X509Data>X509Certificate"` + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"` + X509Data X509Data `xml:"X509Data"` +} + +// X509Data represents the XMLSEC object of the same name +type X509Data struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"` + X509Certificates []X509Certificate `xml:"X509Certificate"` +} + +// X509Certificate represents the XMLSEC object of the same name +type X509Certificate struct { + XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"` + Data string `xml:",chardata"` } // Endpoint represents the SAML EndpointType object. diff --git a/metadata_test.go b/metadata_test.go index 8ac1091b..d9dcdbca 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -106,7 +106,10 @@ func TestCanProduceSPMetadata(t *testing.T) { { Use: "encryption", KeyInfo: KeyInfo{ - Certificate: `MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UE + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + { + Data: `MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UE CAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoX DTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28x EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308 @@ -115,12 +118,18 @@ SPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gf nqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90Dv TLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+ cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==`, + }, + }, + }, }, }, { Use: "signing", KeyInfo: KeyInfo{ - Certificate: `MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UE + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + { + Data: `MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UE CAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoX DTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28x EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308 @@ -129,6 +138,9 @@ SPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gf nqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90Dv TLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+ cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==`, + }, + }, + }, }, }, }, diff --git a/samlidp/testdata/http_metadata_response.html b/samlidp/testdata/http_metadata_response.html index e204fd3e..a6836ddd 100644 --- a/samlidp/testdata/http_metadata_response.html +++ b/samlidp/testdata/http_metadata_response.html @@ -2,15 +2,15 @@ - - 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 diff --git a/samlidp/testdata/sp_metadata.xml b/samlidp/testdata/sp_metadata.xml index f5cef423..6e7610ed 100644 --- a/samlidp/testdata/sp_metadata.xml +++ b/samlidp/testdata/sp_metadata.xml @@ -1 +1 @@ -MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== \ No newline at end of file +MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== \ No newline at end of file diff --git a/samlsp/testdata/expected_middleware_metadata.xml b/samlsp/testdata/expected_middleware_metadata.xml index dbcbb8dd..012ba0ed 100644 --- a/samlsp/testdata/expected_middleware_metadata.xml +++ b/samlsp/testdata/expected_middleware_metadata.xml @@ -2,8 +2,8 @@ - - MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== diff --git a/service_provider.go b/service_provider.go index 654d456d..adf16e8d 100644 --- a/service_provider.go +++ b/service_provider.go @@ -150,7 +150,11 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { { Use: "encryption", KeyInfo: KeyInfo{ - Certificate: base64.StdEncoding.EncodeToString(certBytes), + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: base64.StdEncoding.EncodeToString(certBytes)}, + }, + }, }, EncryptionMethods: []EncryptionMethod{ {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"}, @@ -164,7 +168,11 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { keyDescriptors = append(keyDescriptors, KeyDescriptor{ Use: "signing", KeyInfo: KeyInfo{ - Certificate: base64.StdEncoding.EncodeToString(certBytes), + X509Data: X509Data{ + X509Certificates: []X509Certificate{ + {Data: base64.StdEncoding.EncodeToString(certBytes)}, + }, + }, }, }) } @@ -314,10 +322,12 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) { // either set to "signing" or is missing for _, idpSSODescriptor := range sp.IDPMetadata.IDPSSODescriptors { for _, keyDescriptor := range idpSSODescriptor.KeyDescriptors { - if keyDescriptor.KeyInfo.Certificate != "" { + if len(keyDescriptor.KeyInfo.X509Data.X509Certificates) != 0 { switch keyDescriptor.Use { case "", "signing": - certStrs = append(certStrs, keyDescriptor.KeyInfo.Certificate) + for _, certificate := range keyDescriptor.KeyInfo.X509Data.X509Certificates { + certStrs = append(certStrs, certificate.Data) + } } } } diff --git a/service_provider_go116_test.go b/service_provider_go116_test.go index 6bb64607..77395e01 100644 --- a/service_provider_go116_test.go +++ b/service_provider_go116_test.go @@ -121,13 +121,13 @@ func TestSPInvalidResponses(t *testing.T) { "urn:oasis:names:tc:SAML:2.0:status:Success")) StatusSuccess = oldSpStatusSuccess - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "invalid" req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "aW52YWxpZA==" + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "aW52YWxpZA==" req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) diff --git a/service_provider_go117_test.go b/service_provider_go117_test.go index 1e2953b9..1f5a07d5 100644 --- a/service_provider_go117_test.go +++ b/service_provider_go117_test.go @@ -121,13 +121,13 @@ func TestSPInvalidResponses(t *testing.T) { "urn:oasis:names:tc:SAML:2.0:status:Success")) StatusSuccess = oldSpStatusSuccess - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "invalid" req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "aW52YWxpZA==" + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "aW52YWxpZA==" req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) diff --git a/service_provider_test.go b/service_provider_test.go index 434aa35d..32eb84a2 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -1046,7 +1046,7 @@ func TestSPInvalidAssertions(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.Certificate = "invalid" + s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "invalid" _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assertionBuf := []byte(err.(*InvalidResponseError).Response) diff --git a/testdata/TestCanProduceSPMetadata_expected b/testdata/TestCanProduceSPMetadata_expected index 9250ba1a..79158e1e 100644 --- a/testdata/TestCanProduceSPMetadata_expected +++ b/testdata/TestCanProduceSPMetadata_expected @@ -2,15 +2,15 @@ - - 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 diff --git a/testdata/TestSPCanHandleOneloginResponse_IDPMetadata b/testdata/TestSPCanHandleOneloginResponse_IDPMetadata index 6203a80a..3becbd96 100644 --- a/testdata/TestSPCanHandleOneloginResponse_IDPMetadata +++ b/testdata/TestSPCanHandleOneloginResponse_IDPMetadata @@ -38,4 +38,4 @@ uzZ1y9sNHH6kH8GFnvS2MqyHiNz0h0Sq/q6n+w== Support support@onelogin.com - + \ No newline at end of file diff --git a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata index 7eb50a45..78291e7b 100644 --- a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata +++ b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata @@ -2,8 +2,8 @@ - - MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== @@ -13,8 +13,8 @@ - - MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== diff --git a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata index 82637492..b94d83a3 100644 --- a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata +++ b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata @@ -2,8 +2,8 @@ - - MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== diff --git a/testdata/TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata b/testdata/TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata index 2bd8b35e..f2b298fc 100644 --- a/testdata/TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata +++ b/testdata/TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata @@ -17,4 +17,4 @@ Support support@onelogin.com - + \ No newline at end of file diff --git a/xmlenc/corpus/encrypt-content-aes192-cbc-dh-sha512.xml b/xmlenc/corpus/encrypt-content-aes192-cbc-dh-sha512.xml index d1242784..2c8069d3 100644 --- a/xmlenc/corpus/encrypt-content-aes192-cbc-dh-sha512.xml +++ b/xmlenc/corpus/encrypt-content-aes192-cbc-dh-sha512.xml @@ -44,7 +44,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN39MIMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB @@ -71,7 +71,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN3+EMMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB diff --git a/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml b/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml index c9c30e09..562b8f87 100644 --- a/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml +++ b/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p-sha256.xml @@ -10,8 +10,8 @@ - - + + MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYT AklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9s b2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVu diff --git a/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml b/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml index 29daa4ea..fef209eb 100644 --- a/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml +++ b/xmlenc/corpus/encrypt-data-tripledes-cbc-rsa-oaep-mgf1p.xml @@ -7,8 +7,8 @@ - - + + MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYT AklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9s b2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVu diff --git a/xmlenc/corpus/encrypt-element-aes128-cbc-rsa-1_5.xml b/xmlenc/corpus/encrypt-element-aes128-cbc-rsa-1_5.xml index 9d74e16c..eebd0b59 100644 --- a/xmlenc/corpus/encrypt-element-aes128-cbc-rsa-1_5.xml +++ b/xmlenc/corpus/encrypt-element-aes128-cbc-rsa-1_5.xml @@ -20,8 +20,8 @@ - - + + MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYT AklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9s b2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVu diff --git a/xmlenc/corpus/encrypt-element-aes256-cbc-kw-aes256-dh-ripemd160.xml b/xmlenc/corpus/encrypt-element-aes256-cbc-kw-aes256-dh-ripemd160.xml index 5fb336ac..71b77885 100644 --- a/xmlenc/corpus/encrypt-element-aes256-cbc-kw-aes256-dh-ripemd160.xml +++ b/xmlenc/corpus/encrypt-element-aes256-cbc-kw-aes256-dh-ripemd160.xml @@ -46,7 +46,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN39MIMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB @@ -73,7 +73,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN3+EMMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB diff --git a/xmlenc/corpus/encsig-hmac-sha256-dh.xml b/xmlenc/corpus/encsig-hmac-sha256-dh.xml index a69d9361..46af3fb2 100644 --- a/xmlenc/corpus/encsig-hmac-sha256-dh.xml +++ b/xmlenc/corpus/encsig-hmac-sha256-dh.xml @@ -41,7 +41,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN39MIMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB @@ -68,7 +68,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN3+EMMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB diff --git a/xmlenc/corpus/encsig-hmac-sha256-kw-tripledes-dh.xml b/xmlenc/corpus/encsig-hmac-sha256-kw-tripledes-dh.xml index 79ef3f12..f6cf2ac7 100644 --- a/xmlenc/corpus/encsig-hmac-sha256-kw-tripledes-dh.xml +++ b/xmlenc/corpus/encsig-hmac-sha256-kw-tripledes-dh.xml @@ -44,7 +44,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN39MIMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB @@ -71,7 +71,7 @@ - + MIIDvjCCA36gAwIBAgIGAOxN3+EMMAkGByqGSM44BAMwbjELMAkGA1UEBhMCSUUx DzANBgNVBAgTBkR1YmxpbjEkMCIGA1UEChMbQmFsdGltb3JlIFRlY2hub2xvZ2ll cyBMdGQuMREwDwYDVQQLEwhYL1NlY3VyZTEVMBMGA1UEAxMMVHJhbnNpZW50IENB diff --git a/xmlenc/corpus/encsig-hmac-sha256-rsa-1_5.xml b/xmlenc/corpus/encsig-hmac-sha256-rsa-1_5.xml index ecc29878..948cad30 100644 --- a/xmlenc/corpus/encsig-hmac-sha256-rsa-1_5.xml +++ b/xmlenc/corpus/encsig-hmac-sha256-rsa-1_5.xml @@ -15,8 +15,8 @@ - - + + MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYT AklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9s b2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVu diff --git a/xmlenc/corpus/encsig-hmac-sha256-rsa-oaep-mgf1p.xml b/xmlenc/corpus/encsig-hmac-sha256-rsa-oaep-mgf1p.xml index 1779093a..e088df0f 100644 --- a/xmlenc/corpus/encsig-hmac-sha256-rsa-oaep-mgf1p.xml +++ b/xmlenc/corpus/encsig-hmac-sha256-rsa-oaep-mgf1p.xml @@ -20,8 +20,8 @@ - - + + MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYT AklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9s b2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVu diff --git a/xmlenc/crashers/255c4741516851ba13e8151340541d1d247a1d5f b/xmlenc/crashers/255c4741516851ba13e8151340541d1d247a1d5f index 69762d9c..c32edc58 100644 --- a/xmlenc/crashers/255c4741516851ba13e8151340541d1d247a1d5f +++ b/xmlenc/crashers/255c4741516851ba13e8151340541d1d247a1d5f @@ -1 +1 @@ -0 \ No newline at end of file +0 diff --git a/xmlenc/crashers/4d7686c884eb4d4e41d69446018d7c3122c9bc11 b/xmlenc/crashers/4d7686c884eb4d4e41d69446018d7c3122c9bc11 index fd608dc1..99b5da31 100644 --- a/xmlenc/crashers/4d7686c884eb4d4e41d69446018d7c3122c9bc11 +++ b/xmlenc/crashers/4d7686c884eb4d4e41d69446018d7c3122c9bc11 @@ -1 +1 @@ -0 \ No newline at end of file +0 diff --git a/xmlenc/crashers/5837247e7ed40387b811a2a1be52cd91b873d349 b/xmlenc/crashers/5837247e7ed40387b811a2a1be52cd91b873d349 index bda37610..3b8110e6 100644 --- a/xmlenc/crashers/5837247e7ed40387b811a2a1be52cd91b873d349 +++ b/xmlenc/crashers/5837247e7ed40387b811a2a1be52cd91b873d349 @@ -1 +1 @@ -MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVudCBDQTAeFw0wMjAyMjgxNzUyNDZaFw0wMzAyMjgxNzUyNDBaMG8xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFjAUBgNVBAMTDU1lcmxpbiBIdWdoZXMwgZ8wDQYGKoZIhvcNAQEBBQADgY0AMIGJAoGBAORdNSxbNFWlQeNsOlYJ9gN9eZD+rguRqKhmhOm7i63VDd5ALm2APXhqAmGBPzLN5jlL9g2XALK5WSO4XKjJMcVfYg4+nPuOeHgqdD4HUgf19j/6SaTMcmDFJQMmx1Qw+Aakq3mGcSfvOJcBZctza50VucfCGL1NdfBEcaL3BnhjAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIFoDARBgNVHQ4ECgQIjFG0ZGNyvNswEwYDVR0jBAwwCoAIhJXVlhr6O4wwDQYJKoZIhvcNAQEFBQADgYEAXzG7x5aCJYRusTbmuZqhidGM5iiA9+RmZ4JTPDEgbeiTiJROxpr+ZjnATmsDKrCpqNUiHWjmsKEArYQp8R/KjdKl/pVe3jUvTxb0YZ+li/7k0GQ5LyRT/K4c2SgyLlyBPhpMq+z3g4P2egVRaZbxsLuKQILf7MIV/X5iAEBzu1w= \ No newline at end of file +MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVudCBDQTAeFw0wMjAyMjgxNzUyNDZaFw0wMzAyMjgxNzUyNDBaMG8xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFjAUBgNVBAMTDU1lcmxpbiBIdWdoZXMwgZ8wDQYGKoZIhvcNAQEBBQADgY0AMIGJAoGBAORdNSxbNFWlQeNsOlYJ9gN9eZD+rguRqKhmhOm7i63VDd5ALm2APXhqAmGBPzLN5jlL9g2XALK5WSO4XKjJMcVfYg4+nPuOeHgqdD4HUgf19j/6SaTMcmDFJQMmx1Qw+Aakq3mGcSfvOJcBZctza50VucfCGL1NdfBEcaL3BnhjAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIFoDARBgNVHQ4ECgQIjFG0ZGNyvNswEwYDVR0jBAwwCoAIhJXVlhr6O4wwDQYJKoZIhvcNAQEFBQADgYEAXzG7x5aCJYRusTbmuZqhidGM5iiA9+RmZ4JTPDEgbeiTiJROxpr+ZjnATmsDKrCpqNUiHWjmsKEArYQp8R/KjdKl/pVe3jUvTxb0YZ+li/7k0GQ5LyRT/K4c2SgyLlyBPhpMq+z3g4P2egVRaZbxsLuKQILf7MIV/X5iAEBzu1w= diff --git a/xmlenc/crashers/655d116d6e8b8d9c1de179459ee71293a6151792 b/xmlenc/crashers/655d116d6e8b8d9c1de179459ee71293a6151792 index 35ac2fc8..b290dff3 100644 --- a/xmlenc/crashers/655d116d6e8b8d9c1de179459ee71293a6151792 +++ b/xmlenc/crashers/655d116d6e8b8d9c1de179459ee71293a6151792 @@ -1 +1 @@ -MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGEIb3DQEBBQUAMG4xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVudCBDQTAeFw0wMjAyMjgxNzUyNDZaFw0wMzAyMjgxNzUyNDBaMG8xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFjAUBgNVBAMTDU1lcmxpbiBIdWdoZXMwgZ8wDQYJKoZIBvcNAQEBBQADgY0ACIGJAoGBAORdNSxbNFWlQeNsOlYJ9gN9eZD+rguRqKhmhOm7i63VDd5ALm2APXhqAmGBPzLN5jlL9g2XALK5WSO4XKjJMcVfYg4+nPuOeHgqdD4HUgf19j/6SaTMcmDFJQMmx1Qw+Aakq3mGcSfvOJcBZctza50VucfCGL1NdfBEcaL3BnhjAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIFoDARBgNVHQ4ECgQIjFG0ZGNyvNswEwYDVR0jBAwwCoAIhJXVlhr6O4wwDQYJKoZIhvcNAQEFBQADgYEAXzG7x5aCJYRusTbmuZqhidGM5iiA9+RmZ4JTPDEgbeiTiJROxpr+ZjnATmsDKrCpqNUiHWjmsKEArYQp8R/KjdKl/pVe3jUvTxb0YZ+li/7k0GQ5LyRT/K4c2SgyLlyhPhpMq+z3g4P2egVRaZbxsLuKQILf7MIV/X5iAEBzu1w= \ No newline at end of file +MIICkjCCAfugAwIBAgIGAOxN32E+MA0GCSqGEIb3DQEBBQUAMG4xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFTATBgNVBAMTDFRyYW5zaWVudCBDQTAeFw0wMjAyMjgxNzUyNDZaFw0wMzAyMjgxNzUyNDBaMG8xCzAJBgNVBAYTAklFMQ8wDQYDVQQIEwZEdWJsaW4xJDAiBgNVBAoTG0JhbHRpbW9yZSBUZWNobm9sb2dpZXMgTHRkLjERMA8GA1UECxMIWC9TZWN1cmUxFjAUBgNVBAMTDU1lcmxpbiBIdWdoZXMwgZ8wDQYJKoZIBvcNAQEBBQADgY0ACIGJAoGBAORdNSxbNFWlQeNsOlYJ9gN9eZD+rguRqKhmhOm7i63VDd5ALm2APXhqAmGBPzLN5jlL9g2XALK5WSO4XKjJMcVfYg4+nPuOeHgqdD4HUgf19j/6SaTMcmDFJQMmx1Qw+Aakq3mGcSfvOJcBZctza50VucfCGL1NdfBEcaL3BnhjAgMBAAGjOjA4MA4GA1UdDwEB/wQEAwIFoDARBgNVHQ4ECgQIjFG0ZGNyvNswEwYDVR0jBAwwCoAIhJXVlhr6O4wwDQYJKoZIhvcNAQEFBQADgYEAXzG7x5aCJYRusTbmuZqhidGM5iiA9+RmZ4JTPDEgbeiTiJROxpr+ZjnATmsDKrCpqNUiHWjmsKEArYQp8R/KjdKl/pVe3jUvTxb0YZ+li/7k0GQ5LyRT/K4c2SgyLlyhPhpMq+z3g4P2egVRaZbxsLuKQILf7MIV/X5iAEBzu1w= From de675fdc462d64a81f1c4d1cfcd678e0215f7606 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Dec 2021 07:15:48 -0500 Subject: [PATCH 12/58] Bump github.com/golang-jwt/jwt/v4 from 4.1.0 to 4.2.0 (#401) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.1.0...v4.2.0) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d9d423e..df30e1f1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 - github.com/golang-jwt/jwt/v4 v4.1.0 + github.com/golang-jwt/jwt/v4 v4.2.0 github.com/google/go-cmp v0.5.6 github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 diff --git a/go.sum b/go.sum index 9fd6eb8a..e4202f38 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ 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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -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.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= From c81ab81f1b2a39c99c77e46e7aeb7025f1933957 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Jan 2022 12:34:01 -0500 Subject: [PATCH 13/58] Update go.mod (#403) * upgrade golang.org/x/crypto from v0.0.0-20210322153248-0c34fe9e7dc2 to v0.0.0-20211215153901-e495a2d5b3d3 Co-authored-by: Github Actions --- go.mod | 2 +- go.sum | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index df30e1f1..0fbb5640 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index e4202f38..4603a9ec 100644 --- a/go.sum +++ b/go.sum @@ -41,12 +41,14 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= From e007e418a04dddb5adf9ecf9abfb25cfbf684e31 Mon Sep 17 00:00:00 2001 From: Andrew Svoboda Date: Wed, 5 Jan 2022 15:09:55 +0000 Subject: [PATCH 14/58] Move example packages to their own go module (#407) Resolves #406 --- .github/workflows/test.yml | 2 +- example/go.mod | 17 ++++++++++ example/go.sum | 66 ++++++++++++++++++++++++++++++++++++++ go.mod | 3 -- go.sum | 4 --- samlidp/go.mod | 14 ++++++++ samlidp/go.sum | 63 ++++++++++++++++++++++++++++++++++++ 7 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 example/go.mod create mode 100644 example/go.sum create mode 100644 samlidp/go.mod create mode 100644 samlidp/go.sum diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa51407b..2e18bb6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,4 +21,4 @@ jobs: with: go-version: ${{ matrix.go }} - name: Run Go tests - run: go test -v ./... + run: find . -name go.mod -execdir go test -v ./... \; diff --git a/example/go.mod b/example/go.mod new file mode 100644 index 00000000..69b7b2e4 --- /dev/null +++ b/example/go.mod @@ -0,0 +1,17 @@ +module github.com/crewjam/saml/example + +replace github.com/crewjam/saml => ../ + +replace github.com/crewjam/saml/samlidp => ../samlidp + +go 1.13 + +require ( + github.com/crewjam/saml v0.0.0-00010101000000-000000000000 + github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 + github.com/kr/pretty v0.3.0 + github.com/zenazn/goji v1.0.1 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 +) + +require github.com/crewjam/saml/samlidp v0.0.0-00010101000000-000000000000 diff --git a/example/go.sum b/example/go.sum new file mode 100644 index 00000000..4603a9ec --- /dev/null +++ b/example/go.sum @@ -0,0 +1,66 @@ +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/creack/pty v1.1.9/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= +github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/go.mod b/go.mod index 0fbb5640..6e58ae81 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,11 @@ require ( github.com/beevik/etree v1.1.0 github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 github.com/golang-jwt/jwt/v4 v4.2.0 github.com/google/go-cmp v0.5.6 - github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 - github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 4603a9ec..545010ef 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= -github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -39,8 +37,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= -github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/samlidp/go.mod b/samlidp/go.mod new file mode 100644 index 00000000..a53ba2cc --- /dev/null +++ b/samlidp/go.mod @@ -0,0 +1,14 @@ +module github.com/crewjam/saml/samlidp + +replace github.com/crewjam/saml => ../ + +go 1.13 + +require ( + github.com/crewjam/saml v0.0.0-00010101000000-000000000000 + github.com/golang-jwt/jwt/v4 v4.2.0 + github.com/mattermost/xml-roundtrip-validator v0.1.0 + github.com/zenazn/goji v1.0.1 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + gotest.tools v2.2.0+incompatible +) diff --git a/samlidp/go.sum b/samlidp/go.sum new file mode 100644 index 00000000..a653625b --- /dev/null +++ b/samlidp/go.sum @@ -0,0 +1,63 @@ +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From cdfe90f08ceb0c0c91891676fc49fcf86b64110f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:44:21 -0500 Subject: [PATCH 15/58] Bump github.com/google/go-cmp from 0.5.6 to 0.5.7 (#412) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.6 to 0.5.7. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.6...v0.5.7) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6e58ae81..7ab1f1d3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 - github.com/google/go-cmp v0.5.6 + github.com/google/go-cmp v0.5.7 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 diff --git a/go.sum b/go.sum index 545010ef..a22a10aa 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ 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/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= From 34d029892e58c333e86b36ddc4d77c3354daa28c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:44:35 -0500 Subject: [PATCH 16/58] Update go.mod (#409) * upgrade golang.org/x/crypto from v0.0.0-20211215153901-e495a2d5b3d3 to v0.0.0-20220128200615-198e4374d7ed Co-authored-by: Github Actions --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7ab1f1d3..23d4e4b0 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index a22a10aa..fc41a780 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From ebaef9ab8e269bf6145c2a5d694abd0d2c9c8b9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 08:39:13 -0400 Subject: [PATCH 17/58] Bump github.com/golang-jwt/jwt/v4 from 4.2.0 to 4.4.1 (#430) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.2.0 to 4.4.1. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.2.0...v4.4.1) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 23d4e4b0..c0c487f3 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/beevik/etree v1.1.0 github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 + github.com/golang-jwt/jwt/v4 v4.4.1 github.com/google/go-cmp v0.5.7 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index fc41a780..3f8bc96b 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= From 224026ca8d53d7ad0dd20073fbafde801ce103ee Mon Sep 17 00:00:00 2001 From: Arjen <4867268+arjentz@users.noreply.github.com> Date: Mon, 28 Mar 2022 14:40:00 +0200 Subject: [PATCH 18/58] Allow to specify HTTPClient for SAML artifact resolution (#416) * Allow to specify HTTPClient for SAML artifact resolution * go fmt --- samlsp/new.go | 2 ++ service_provider.go | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/samlsp/new.go b/samlsp/new.go index a1ab584e..f9898a02 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -19,6 +19,7 @@ type Options struct { Key *rsa.PrivateKey Certificate *x509.Certificate Intermediates []*x509.Certificate + HTTPClient *http.Client AllowIDPInitiated bool DefaultRedirectURI string IDPMetadata *saml.EntityDescriptor @@ -104,6 +105,7 @@ func DefaultServiceProvider(opts Options) saml.ServiceProvider { EntityID: opts.EntityID, Key: opts.Key, Certificate: opts.Certificate, + HTTPClient: opts.HTTPClient, Intermediates: opts.Intermediates, MetadataURL: *metadataURL, AcsURL: *acsURL, diff --git a/service_provider.go b/service_provider.go index adf16e8d..1888245f 100644 --- a/service_provider.go +++ b/service_provider.go @@ -72,6 +72,9 @@ type ServiceProvider struct { Certificate *x509.Certificate Intermediates []*x509.Certificate + // HTTPClient to use during SAML artifact resolution + HTTPClient *http.Client + // MetadataURL is the full URL to the metadata endpoint on this host, // i.e. https://example.com/saml/metadata MetadataURL url.URL @@ -629,7 +632,11 @@ func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs [ var requestBuffer bytes.Buffer doc.WriteTo(&requestBuffer) - response, err := http.Post(sp.GetArtifactBindingLocation(SOAPBinding), "text/xml", &requestBuffer) + client := sp.HTTPClient + if client == nil { + client = http.DefaultClient + } + response, err := client.Post(sp.GetArtifactBindingLocation(SOAPBinding), "text/xml", &requestBuffer) if err != nil { retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) return nil, retErr From d4ed82f19df6a5201af70c25608d1999313ae3d0 Mon Sep 17 00:00:00 2001 From: Arjen <4867268+arjentz@users.noreply.github.com> Date: Mon, 28 Mar 2022 14:40:37 +0200 Subject: [PATCH 19/58] Fix ArtifactResolve element and add tests for artifact binding (#415) * Fix ArtifactResolveElement and add tests * goimports --- schema.go | 2 +- schema_test.go | 95 +++++++++ service_provider_test.go | 200 +++++++++++++++++- ...TestGetArtifactBindingLocation_IDPMetadata | 122 +++++++++++ testdata/TestMakeArtifactResolveRequest | 1 + testdata/TestMakeSignedArtifactResolveRequest | 1 + .../TestParseXMLArtifactResponse_assertion | 1 + .../TestParseXMLArtifactResponse_response | 36 ++++ 8 files changed, 456 insertions(+), 2 deletions(-) create mode 100644 testdata/TestGetArtifactBindingLocation_IDPMetadata create mode 100644 testdata/TestMakeArtifactResolveRequest create mode 100644 testdata/TestMakeSignedArtifactResolveRequest create mode 100644 testdata/TestParseXMLArtifactResponse_assertion create mode 100644 testdata/TestParseXMLArtifactResponse_response diff --git a/schema.go b/schema.go index eefd2c66..e7a6117e 100644 --- a/schema.go +++ b/schema.go @@ -306,7 +306,7 @@ func (a *NameIDPolicy) Element() *etree.Element { // ArtifactResolve represents the SAML object of the same name. type ArtifactResolve struct { - XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol ArtifactResponse"` + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol ArtifactResolve"` ID string `xml:",attr"` Version string `xml:",attr"` IssueInstant time.Time `xml:",attr"` diff --git a/schema_test.go b/schema_test.go index 7bd27466..c5ccb20e 100644 --- a/schema_test.go +++ b/schema_test.go @@ -95,6 +95,101 @@ func TestAuthnStatementMarshalWithoutSessionNotOnOrAfter(t *testing.T) { assert.Check(t, is.DeepEqual(expected, actual)) } +func TestArtifactResolveElement(t *testing.T) { + issueInstant := time.Date(2020, 7, 21, 12, 30, 45, 0, time.UTC) + expected := ArtifactResolve{ + ID: "index", + Version: "version", + IssueInstant: issueInstant, + // Signature *etree.Element + } + + doc := etree.NewDocument() + doc.SetRoot(expected.Element()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) + + var actual ArtifactResolve + err = xml.Unmarshal(x, &actual) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(expected, actual)) + + x, err = xml.Marshal(expected) + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) +} + +func TestArtifactResolveSoapRequest(t *testing.T) { + issueInstant := time.Date(2020, 7, 21, 12, 30, 45, 0, time.UTC) + expected := ArtifactResolve{ + ID: "index", + Version: "version", + IssueInstant: issueInstant, + // Signature *etree.Element + } + + doc := etree.NewDocument() + doc.SetRoot(expected.SoapRequest()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) +} + +func TestArtifactResponseElement(t *testing.T) { + issueInstant := time.Date(2020, 7, 21, 12, 30, 45, 0, time.UTC) + status := Status{ + XMLName: xml.Name{ + Space: "urn:oasis:names:tc:SAML:2.0:protocol", + Local: "Status", + }, + StatusCode: StatusCode{ + XMLName: xml.Name{ + Space: "urn:oasis:names:tc:SAML:2.0:protocol", + Local: "StatusCode", + }, + Value: "value", + }, + } + expected := ArtifactResponse{ + ID: "index", + InResponseTo: "ID", + Version: "version", + IssueInstant: issueInstant, + Status: status, + Response: Response{ + ID: "index", + InResponseTo: "ID", + Version: "version", + Destination: "destination", + Consent: "consent", + Status: status, + IssueInstant: issueInstant, + }, + // Signature *etree.Element + } + + doc := etree.NewDocument() + doc.SetRoot(expected.Element()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) + + var actual ArtifactResponse + err = xml.Unmarshal(x, &actual) + assert.Check(t, err) + assert.Check(t, is.DeepEqual(expected, actual)) + + x, err = xml.Marshal(expected) + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) +} + func TestLogoutRequestXMLRoundTrip(t *testing.T) { issueInstant := time.Date(2021, 10, 8, 12, 30, 0, 0, time.UTC) notOnOrAfter := time.Date(2021, 10, 8, 12, 35, 0, 0, time.UTC) diff --git a/service_provider_test.go b/service_provider_test.go index 32eb84a2..5b0ca3cc 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -307,7 +307,7 @@ func TestSPFailToProduceSignedRequestWithBogusSignatureMethod(t *testing.T) { assert.Check(t, err) _, err = s.MakeRedirectAuthenticationRequest("relayState") - assert.Check(t, is.ErrorContains(err, ""), "invalid signing method bogus") + assert.Check(t, is.ErrorContains(err, "invalid signing method bogus")) } func TestSPCanProducePostLogoutRequest(t *testing.T) { @@ -1531,3 +1531,201 @@ func TestSPResponseWithNoIssuer(t *testing.T) { _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, err) } + +func TestGetArtifactBindingLocation(t *testing.T) { + test := NewServiceProviderTest(t) + test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata") + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + } + + location := sp.GetArtifactBindingLocation(SOAPBinding) + assert.Check(t, is.Equal(location, "")) + + err := xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata) + assert.Check(t, err) + + location = sp.GetArtifactBindingLocation(SOAPBinding) + assert.Check(t, is.Equal(location, "https://samltest.id/idp/profile/SAML2/SOAP/ArtifactResolution")) +} + +func TestMakeArtifactResolveRequest(t *testing.T) { + test := NewServiceProviderTest(t) + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + } + + req, err := sp.MakeArtifactResolveRequest("artifactId") + assert.Check(t, err) + + x, err := xml.Marshal(req) + assert.Check(t, err) + golden.Assert(t, string(x), t.Name()) +} + +func TestMakeSignedArtifactResolveRequest(t *testing.T) { + test := NewServiceProviderTest(t) + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + SignatureMethod: dsig.RSASHA1SignatureMethod, + } + + req, err := sp.MakeArtifactResolveRequest("artifactId") + assert.Check(t, err) + + x, err := xml.Marshal(req) + assert.Check(t, err) + golden.Assert(t, string(x), t.Name()) +} + +func TestMakeSignedArtifactResolveRequestWithBogusSignatureMethod(t *testing.T) { + test := NewServiceProviderTest(t) + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + SignatureMethod: "bogus", + } + + _, err := sp.MakeArtifactResolveRequest("artifactId") + assert.Check(t, is.ErrorContains(err, "invalid signing method bogus")) + +} + +func TestParseXMLArtifactResponse(t *testing.T) { + test := NewServiceProviderTest(t) + TimeNow = func() time.Time { + rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + + // an actual response from samltest.id + samlResponse := golden.Get(t, "TestParseXMLArtifactResponse_response") + test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata") + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"), + AcsURL: mustParseURL("http://localhost:8000/saml/acs"), + IDPMetadata: &EntityDescriptor{}, + } + + err := xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata) + assert.Check(t, err) + + possibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b94"} + reqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c7" + + assertion, err := sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, err) + + x, err := xml.Marshal(assertion) + assert.Check(t, err) + + golden.Assert(t, string(x), t.Name()+"_assertion") +} + +func TestParseBadXMLArtifactResponse(t *testing.T) { + test := NewServiceProviderTest(t) + TimeNow = func() time.Time { + rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + + // an actual response from samltest.id + samlResponse := golden.Get(t, "TestParseXMLArtifactResponse_response") + test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata") + + possibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b94"} + reqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c7" + + sp := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + } + + assertion, err := sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response Issuer does not match the IDP metadata (expected \"\")")) + assert.Check(t, is.Nil(assertion)) + + err = xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata) + assert.Check(t, err) + + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "`Destination` does not match AcsURL (expected \"https://example.com/saml2/acs\", actual \"http://localhost:8000/saml/acs\")")) + assert.Check(t, is.Nil(assertion)) + + sp.AcsURL = mustParseURL("http://localhost:8000/saml/acs") + + // TimeNow is used to verify the response time + TimeNow = func() time.Time { + rv, _ := time.Parse(timeFormat, "2022-08-17T10:26:57Z") + return rv + } + + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "response IssueInstant expired at 2021-08-17 10:28:50.146 +0000 UTC")) + assert.Check(t, is.Nil(assertion)) + + // Clock is used to verify the certificate + Clock = dsig.NewFakeClockAt(func() time.Time { + rv, _ := time.Parse(timeFormat, "2039-08-17T10:26:57Z") + return rv + }()) + TimeNow = func() time.Time { + rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z") + return rv + } + + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "cannot validate signature on Response: Cert is not valid at this time")) + assert.Check(t, is.Nil(assertion)) + Clock = dsig.NewFakeClockAt(TimeNow()) + + wrongReqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c8" + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, wrongReqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "`InResponseTo` does not match the artifact request ID (expected id-218eb155248f7db7c85fe4e2709a3f17a70d09c8)")) + assert.Check(t, is.Nil(assertion)) + + wrongPossibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b95"} + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, wrongPossibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "`InResponseTo` does not match any of the possible request IDs (expected [id-f3c7bc7d626a4ededa6028b718e5252c6e770b95])")) + assert.Check(t, is.Nil(assertion)) + + // random other key + sp.Key = mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey) + assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "failed to decrypt response: certificate does not match provided key")) + assert.Check(t, is.Nil(assertion)) +} diff --git a/testdata/TestGetArtifactBindingLocation_IDPMetadata b/testdata/TestGetArtifactBindingLocation_IDPMetadata new file mode 100644 index 00000000..f0f8b54c --- /dev/null +++ b/testdata/TestGetArtifactBindingLocation_IDPMetadata @@ -0,0 +1,122 @@ + + + + + + + + + samltest.id + + + + SAMLtest IdP + A free and basic IdP for testing SAML deployments + https://samltest.id/saml/logo.png + + + + + + + +MIIDETCCAfmgAwIBAgIUZRpDhkNKl5eWtJqk0Bu1BgTTargwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwHhcNMTgwODI0MjExNDEwWhcNMzgw +ODI0MjExNDEwWjAWMRQwEgYDVQQDDAtzYW1sdGVzdC5pZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJrh9/PcDsiv3UeL8Iv9rf4WfLPxuOm9W6aCntEA +8l6c1LQ1Zyrz+Xa/40ZgP29ENf3oKKbPCzDcc6zooHMji2fBmgXp6Li3fQUzu7yd ++nIC2teejijVtrNLjn1WUTwmqjLtuzrKC/ePoZyIRjpoUxyEMJopAd4dJmAcCq/K +k2eYX9GYRlqvIjLFoGNgy2R4dWwAKwljyh6pdnPUgyO/WjRDrqUBRFrLQJorR2kD +c4seZUbmpZZfp4MjmWMDgyGM1ZnR0XvNLtYeWAyt0KkSvFoOMjZUeVK/4xR74F8e +8ToPqLmZEg9ZUx+4z2KjVK00LpdRkH9Uxhh03RQ0FabHW6UCAwEAAaNXMFUwHQYD +VR0OBBYEFJDbe6uSmYQScxpVJhmt7PsCG4IeMDQGA1UdEQQtMCuCC3NhbWx0ZXN0 +LmlkhhxodHRwczovL3NhbWx0ZXN0LmlkL3NhbWwvaWRwMA0GCSqGSIb3DQEBCwUA +A4IBAQBNcF3zkw/g51q26uxgyuy4gQwnSr01Mhvix3Dj/Gak4tc4XwvxUdLQq+jC +cxr2Pie96klWhY/v/JiHDU2FJo9/VWxmc/YOk83whvNd7mWaNMUsX3xGv6AlZtCO +L3JhCpHjiN+kBcMgS5jrtGgV1Lz3/1zpGxykdvS0B4sPnFOcaCwHe2B9SOCWbDAN +JXpTjz1DmJO4ImyWPJpN1xsYKtm67Pefxmn0ax0uE2uuzq25h0xbTkqIQgJzyoE/ +DPkBFK1vDkMfAW11dQ0BXatEnW7Gtkc0lh2/PIbHWj4AzxYMyBf5Gy6HSVOftwjC +voQR2qr2xJBixsg+MIORKtmKHLfU + + + + + + + + + +MIIDEjCCAfqgAwIBAgIVAMECQ1tjghafm5OxWDh9hwZfxthWMA0GCSqGSIb3DQEB +CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4 +MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC0Z4QX1NFKs71ufbQwoQoW7qkNAJRIANGA4iM0 +ThYghul3pC+FwrGv37aTxWXfA1UG9njKbbDreiDAZKngCgyjxj0uJ4lArgkr4AOE +jj5zXA81uGHARfUBctvQcsZpBIxDOvUUImAl+3NqLgMGF2fktxMG7kX3GEVNc1kl +bN3dfYsaw5dUrw25DheL9np7G/+28GwHPvLb4aptOiONbCaVvh9UMHEA9F7c0zfF +/cL5fOpdVa54wTI0u12CsFKt78h6lEGG5jUs/qX9clZncJM7EFkN3imPPy+0HC8n +spXiH/MZW8o2cqWRkrw3MzBZW3Ojk5nQj40V6NUbjb7kfejzAgMBAAGjVzBVMB0G +A1UdDgQWBBQT6Y9J3Tw/hOGc8PNV7JEE4k2ZNTA0BgNVHREELTArggtzYW1sdGVz +dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF +AAOCAQEASk3guKfTkVhEaIVvxEPNR2w3vWt3fwmwJCccW98XXLWgNbu3YaMb2RSn +7Th4p3h+mfyk2don6au7Uyzc1Jd39RNv80TG5iQoxfCgphy1FYmmdaSfO8wvDtHT +TNiLArAxOYtzfYbzb5QrNNH/gQEN8RJaEf/g/1GTw9x/103dSMK0RXtl+fRs2nbl +D1JJKSQ3AdhxK/weP3aUPtLxVVJ9wMOQOfcy02l+hHMb6uAjsPOpOVKqi3M8XmcU +ZOpx4swtgGdeoSpeRyrtMvRwdcciNBp9UZome44qZAYH1iqrpmmjsfI9pJItsgWu +3kXPjhSfj1AJGR1l9JGvJrHki1iHTA== + + + + + + + + + +MIIDEjCCAfqgAwIBAgIVAPVbodo8Su7/BaHXUHykx0Pi5CFaMA0GCSqGSIb3DQEB +CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4 +MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCQb+1a7uDdTTBBFfwOUun3IQ9nEuKM98SmJDWa +MwM877elswKUTIBVh5gB2RIXAPZt7J/KGqypmgw9UNXFnoslpeZbA9fcAqqu28Z4 +sSb2YSajV1ZgEYPUKvXwQEmLWN6aDhkn8HnEZNrmeXihTFdyr7wjsLj0JpQ+VUlc +4/J+hNuU7rGYZ1rKY8AA34qDVd4DiJ+DXW2PESfOu8lJSOteEaNtbmnvH8KlwkDs +1NvPTsI0W/m4SK0UdXo6LLaV8saIpJfnkVC/FwpBolBrRC/Em64UlBsRZm2T89ca +uzDee2yPUvbBd5kLErw+sC7i4xXa2rGmsQLYcBPhsRwnmBmlAgMBAAGjVzBVMB0G +A1UdDgQWBBRZ3exEu6rCwRe5C7f5QrPcAKRPUjA0BgNVHREELTArggtzYW1sdGVz +dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF +AAOCAQEABZDFRNtcbvIRmblnZItoWCFhVUlq81ceSQddLYs8DqK340//hWNAbYdj +WcP85HhIZnrw6NGCO4bUipxZXhiqTA/A9d1BUll0vYB8qckYDEdPDduYCOYemKkD +dmnHMQWs9Y6zWiYuNKEJ9mf3+1N8knN/PK0TYVjVjXAf2CnOETDbLtlj6Nqb8La3 +sQkYmU+aUdopbjd5JFFwbZRaj6KiHXHtnIRgu8sUXNPrgipUgZUOVhP0C0N5OfE4 +JW8ZBrKgQC/6vJ2rSa9TlzI6JAa5Ww7gMXMP9M+cJUNQklcq+SBnTK8G+uBHgPKR +zBDsMIEzRtQZm4GIoHJae4zmnCekkQ== + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testdata/TestMakeArtifactResolveRequest b/testdata/TestMakeArtifactResolveRequest new file mode 100644 index 00000000..fcc54fcd --- /dev/null +++ b/testdata/TestMakeArtifactResolveRequest @@ -0,0 +1 @@ +https://example.com/saml2/metadataartifactId \ No newline at end of file diff --git a/testdata/TestMakeSignedArtifactResolveRequest b/testdata/TestMakeSignedArtifactResolveRequest new file mode 100644 index 00000000..8c1b614e --- /dev/null +++ b/testdata/TestMakeSignedArtifactResolveRequest @@ -0,0 +1 @@ +https://example.com/saml2/metadatadsSignaturexmlnsdshttp://www.w3.org/2000/09/xmldsig#dsSignedInfodsCanonicalizationMethodAlgorithmhttp://www.w3.org/2001/10/xml-exc-c14n#dsSignatureMethodAlgorithmhttp://www.w3.org/2000/09/xmldsig#rsa-sha1dsReferenceURI#id-00020406080a0c0e10121416181a1c1e20222426dsTransformsdsTransformAlgorithmhttp://www.w3.org/2000/09/xmldsig#enveloped-signaturedsTransformAlgorithmhttp://www.w3.org/2001/10/xml-exc-c14n#dsDigestMethodAlgorithmhttp://www.w3.org/2000/09/xmldsig#sha1dsDigestValueOZX5MUcmjNTL4/ULK2e2UgRiTxw=dsSignatureValuewWSt0RdNbeUfNXo6dWRO9Jdt4qyy2NXVxntlvyOi8mcgm8mHPqPC86cHCggx/DM/WKlTGOP3bvYgJYSj14GSrfAB3WIVsECOHI00juy5kRnMpFWRcsqMGij+gVX5a6WhAVoJWZox5N1avqJbw0T5bi+2rMG8pzHwfNtfSAQ8OkI=dsKeyInfodsX509DatadsX509CertificateMIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ==artifactId \ No newline at end of file diff --git a/testdata/TestParseXMLArtifactResponse_assertion b/testdata/TestParseXMLArtifactResponse_assertion new file mode 100644 index 00000000..f7b0ac21 --- /dev/null +++ b/testdata/TestParseXMLArtifactResponse_assertion @@ -0,0 +1 @@ +https://samltest.id/saml/idpAAdzZWNyZXQxmdGfdGiCl5GfFqdXr4fFg22uNwB1bPW0DpwXVsA8ZTM9Mm4WZbdwL2HGSb16cikyIjqUeddVshrsVM6DMD/iVagheXMVMIp8Y1JOQsPr6eLL4K7B9u5BIoE6FduV3W60g77uBwM2http://localhost:8000/saml/metadataurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransporturn:mace:dir:entitlement:common-lib-termsrickrsanchez@samltest.idmanager@samltest.id+1-555-555-5515rsanchez@samltest.idSanchezRick SanchezRick \ No newline at end of file diff --git a/testdata/TestParseXMLArtifactResponse_response b/testdata/TestParseXMLArtifactResponse_response new file mode 100644 index 00000000..71f01dca --- /dev/null +++ b/testdata/TestParseXMLArtifactResponse_response @@ -0,0 +1,36 @@ + +https://samltest.id/saml/idplFfEmKnbMD3hy2rIOa9KSlQWiRbNd5mn8MelKB2TPgo=jl6a7xNieATxehNBKJpKIWvjbp4LjYd2i/9nCk5pKKKyvdUchaz74nILrg6en0iR3HKZl0sGaXmIEzkmpFNRqiam5I0lX1OMzi8HQU99UcbyDtoArLM65uHIExrd5n/W0hnWXcqAjeucNdtWVygx3ptXl36ivh9i2QXM/xi/x4QwBcNZJcbguGYy/0pSmzVcDrmvYvLq2/Sg3M9pDXh1LR5hW5ui/heJFcnDbS2O6Iu0lTbFpVDoeRZz2PTURQqjP2WnsQOfxS0822H2ldFKdXVQdQ10MIGTpq6ckhMFahwbAYophLwLyZCsrl8SWjgovYk+LvumrG/TtQlqz51Iwg==MIIDEjCCAfqgAwIBAgIVAMECQ1tjghafm5OxWDh9hwZfxthWMA0GCSqGSIb3DQEBCwUAMBYxFDAS +BgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4MDgyNDIxMTQwOVowFjEUMBIG +A1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0Z4QX1NFK +s71ufbQwoQoW7qkNAJRIANGA4iM0ThYghul3pC+FwrGv37aTxWXfA1UG9njKbbDreiDAZKngCgyj +xj0uJ4lArgkr4AOEjj5zXA81uGHARfUBctvQcsZpBIxDOvUUImAl+3NqLgMGF2fktxMG7kX3GEVN +c1klbN3dfYsaw5dUrw25DheL9np7G/+28GwHPvLb4aptOiONbCaVvh9UMHEA9F7c0zfF/cL5fOpd +Va54wTI0u12CsFKt78h6lEGG5jUs/qX9clZncJM7EFkN3imPPy+0HC8nspXiH/MZW8o2cqWRkrw3 +MzBZW3Ojk5nQj40V6NUbjb7kfejzAgMBAAGjVzBVMB0GA1UdDgQWBBQT6Y9J3Tw/hOGc8PNV7JEE +4k2ZNTA0BgNVHREELTArggtzYW1sdGVzdC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lk +cDANBgkqhkiG9w0BAQsFAAOCAQEASk3guKfTkVhEaIVvxEPNR2w3vWt3fwmwJCccW98XXLWgNbu3 +YaMb2RSn7Th4p3h+mfyk2don6au7Uyzc1Jd39RNv80TG5iQoxfCgphy1FYmmdaSfO8wvDtHTTNiL +ArAxOYtzfYbzb5QrNNH/gQEN8RJaEf/g/1GTw9x/103dSMK0RXtl+fRs2nblD1JJKSQ3AdhxK/we +P3aUPtLxVVJ9wMOQOfcy02l+hHMb6uAjsPOpOVKqi3M8XmcUZOpx4swtgGdeoSpeRyrtMvRwdcci +NBp9UZome44qZAYH1iqrpmmjsfI9pJItsgWu3kXPjhSfj1AJGR1l9JGvJrHki1iHTA==https://samltest.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 \ No newline at end of file From 00a79cc1e7de8cc49f40a49d337bb77c36c8b46f Mon Sep 17 00:00:00 2001 From: Lulu Date: Thu, 14 Apr 2022 19:15:08 +0800 Subject: [PATCH 20/58] Fix: not enough arguments to call TransformExcC14n (#428) --- schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.go b/schema.go index e7a6117e..70178981 100644 --- a/schema.go +++ b/schema.go @@ -747,7 +747,7 @@ func (a *Assertion) Element() *etree.Element { for _, attributeStatement := range a.AttributeStatements { el.AddChild(attributeStatement.Element()) } - err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList) + err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList, false) if err != nil { panic(err) } From 7444b81da2996b175f637716e45c0457728b1416 Mon Sep 17 00:00:00 2001 From: Arjen <4867268+arjentz@users.noreply.github.com> Date: Mon, 23 May 2022 15:12:30 +0200 Subject: [PATCH 21/58] Add RequestedAuthnContext (#413) * Add support for RequestedAuthnContext Co-authored-by: David Venhoek --- samlsp/new.go | 56 +++++++++++++++++++++++---------------------- schema.go | 33 +++++++++++++++++++------- schema_test.go | 13 +++++++++++ service_provider.go | 7 +++++- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/samlsp/new.go b/samlsp/new.go index f9898a02..e060c2c7 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -14,20 +14,21 @@ import ( // Options represents the parameters for creating a new middleware type Options struct { - EntityID string - URL url.URL - Key *rsa.PrivateKey - Certificate *x509.Certificate - Intermediates []*x509.Certificate - HTTPClient *http.Client - AllowIDPInitiated bool - DefaultRedirectURI string - IDPMetadata *saml.EntityDescriptor - SignRequest bool - UseArtifactResponse bool - ForceAuthn bool // TODO(ross): this should be *bool - CookieSameSite http.SameSite - RelayStateFunc func(w http.ResponseWriter, r *http.Request) string + EntityID string + URL url.URL + Key *rsa.PrivateKey + Certificate *x509.Certificate + Intermediates []*x509.Certificate + HTTPClient *http.Client + AllowIDPInitiated bool + DefaultRedirectURI string + IDPMetadata *saml.EntityDescriptor + SignRequest bool + UseArtifactResponse bool + ForceAuthn bool // TODO(ross): this should be *bool + RequestedAuthnContext *saml.RequestedAuthnContext + CookieSameSite http.SameSite + RelayStateFunc func(w http.ResponseWriter, r *http.Request) string } // DefaultSessionCodec returns the default SessionCodec for the provided options, @@ -102,19 +103,20 @@ func DefaultServiceProvider(opts Options) saml.ServiceProvider { } return saml.ServiceProvider{ - EntityID: opts.EntityID, - Key: opts.Key, - Certificate: opts.Certificate, - HTTPClient: opts.HTTPClient, - Intermediates: opts.Intermediates, - MetadataURL: *metadataURL, - AcsURL: *acsURL, - SloURL: *sloURL, - IDPMetadata: opts.IDPMetadata, - ForceAuthn: forceAuthn, - SignatureMethod: signatureMethod, - AllowIDPInitiated: opts.AllowIDPInitiated, - DefaultRedirectURI: opts.DefaultRedirectURI, + EntityID: opts.EntityID, + Key: opts.Key, + Certificate: opts.Certificate, + HTTPClient: opts.HTTPClient, + Intermediates: opts.Intermediates, + MetadataURL: *metadataURL, + AcsURL: *acsURL, + SloURL: *sloURL, + IDPMetadata: opts.IDPMetadata, + ForceAuthn: forceAuthn, + RequestedAuthnContext: opts.RequestedAuthnContext, + SignatureMethod: signatureMethod, + AllowIDPInitiated: opts.AllowIDPInitiated, + DefaultRedirectURI: opts.DefaultRedirectURI, } } diff --git a/schema.go b/schema.go index 70178981..2b469bdb 100644 --- a/schema.go +++ b/schema.go @@ -11,6 +11,24 @@ import ( "github.com/russellhaering/goxmldsig/etreeutils" ) +// RequestedAuthnContext represents the SAML object of the same name, an indication of the +// requirements on the authentication process. +type RequestedAuthnContext struct { + XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol RequestedAuthnContext"` + Comparison string `xml:",attr"` + AuthnContextClassRef string `xml:"urn:oasis:names:tc:SAML:2.0:assertion AuthnContextClassRef"` +} + +// Element returns an etree.Element representing the object in XML form. +func (r *RequestedAuthnContext) Element() *etree.Element { + el := etree.NewElement("samlp:RequestedAuthnContext") + el.CreateAttr("Comparison", r.Comparison) + elContext := etree.NewElement("saml:AuthnContextClassRef") + elContext.SetText(r.AuthnContextClassRef) + el.AddChild(elContext) + return el +} + // AuthnRequest represents the SAML object of the same name, a request from a service provider // to authenticate a user. // @@ -26,10 +44,10 @@ type AuthnRequest struct { Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` Signature *etree.Element - Subject *Subject - NameIDPolicy *NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"` - Conditions *Conditions - //RequestedAuthnContext *RequestedAuthnContext // TODO + Subject *Subject + NameIDPolicy *NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"` + Conditions *Conditions + RequestedAuthnContext *RequestedAuthnContext //Scoping *Scoping // TODO ForceAuthn *bool `xml:",attr"` @@ -159,7 +177,6 @@ func (r *LogoutRequest) Deflate() ([]byte, error) { return b.Bytes(), nil } -// Element returns an etree.Element representing the object // Element returns an etree.Element representing the object in XML form. func (r *AuthnRequest) Element() *etree.Element { el := etree.NewElement("samlp:AuthnRequest") @@ -189,9 +206,9 @@ func (r *AuthnRequest) Element() *etree.Element { if r.Conditions != nil { el.AddChild(r.Conditions.Element()) } - //if r.RequestedAuthnContext != nil { - // el.AddChild(r.RequestedAuthnContext.Element()) - //} + if r.RequestedAuthnContext != nil { + el.AddChild(r.RequestedAuthnContext.Element()) + } //if r.Scoping != nil { // el.AddChild(r.Scoping.Element()) //} diff --git a/schema_test.go b/schema_test.go index c5ccb20e..3d9f793a 100644 --- a/schema_test.go +++ b/schema_test.go @@ -95,6 +95,19 @@ func TestAuthnStatementMarshalWithoutSessionNotOnOrAfter(t *testing.T) { assert.Check(t, is.DeepEqual(expected, actual)) } +func TestRequestedAuthnContext(t *testing.T) { + expected := RequestedAuthnContext{ + Comparison: "comparison", + } + + doc := etree.NewDocument() + doc.SetRoot(expected.Element()) + x, err := doc.WriteToBytes() + assert.Check(t, err) + assert.Check(t, is.Equal(``, + string(x))) +} + func TestArtifactResolveElement(t *testing.T) { issueInstant := time.Date(2020, 7, 21, 12, 30, 45, 0, time.UTC) expected := ArtifactResolve{ diff --git a/service_provider.go b/service_provider.go index 1888245f..9ebdcd1a 100644 --- a/service_provider.go +++ b/service_provider.go @@ -102,6 +102,10 @@ type ServiceProvider struct { // has a SSO session at the IdP. ForceAuthn *bool + // RequestedAuthnContext allow you to specify the requested authentication + // context in authentication requests + RequestedAuthnContext *RequestedAuthnContext + // AllowIdpInitiated AllowIDPInitiated bool @@ -407,7 +411,8 @@ func (sp *ServiceProvider) MakeAuthenticationRequest(idpURL string, binding stri // urn:oasis:names:tc:SAML:2.0:nameid-format:transient Format: &nameIDFormat, }, - ForceAuthn: sp.ForceAuthn, + ForceAuthn: sp.ForceAuthn, + RequestedAuthnContext: sp.RequestedAuthnContext, } // We don't need to sign the XML document if the IDP uses HTTP-Redirect binding if len(sp.SignatureMethod) > 0 && binding == HTTPPostBinding { From 5a22b43b85e09ead695da27c11cc3c09f404c059 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 25 Jun 2022 16:35:49 +0300 Subject: [PATCH 22/58] Fix LogoutRequest schema according to LogoutRequestType in SAML standard (#421) --- schema.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/schema.go b/schema.go index 2b469bdb..13ea2ef6 100644 --- a/schema.go +++ b/schema.go @@ -95,12 +95,12 @@ func (r *LogoutRequest) Element() *etree.Element { if r.Issuer != nil { el.AddChild(r.Issuer.Element()) } - if r.NameID != nil { - el.AddChild(r.NameID.Element()) - } if r.Signature != nil { el.AddChild(r.Signature) } + if r.NameID != nil { + el.AddChild(r.NameID.Element()) + } if r.SessionIndex != nil { el.AddChild(r.SessionIndex.Element()) } From 3573a32945bd509a469f38f2a5f686d73ab52601 Mon Sep 17 00:00:00 2001 From: Jguer Date: Sat, 25 Jun 2022 13:38:39 +0000 Subject: [PATCH 23/58] Add NameIDFormat to metadata (#443) * Add NameIDFormat to metadata * Fix metadata tests * Fix tests on rebase --- .../testdata/expected_middleware_metadata.xml | 1 + schema.go | 2 +- service_provider.go | 1 + service_provider_test.go | 22 ++++++++++--------- .../TestCanProduceMetadataEntityID_metadata | 1 + .../TestCanProduceMetadataNoCerts_metadata | 1 + ...SPCanProduceMetadataWithBothCerts_metadata | 1 + ...ProduceMetadataWithEncryptionCert_metadata | 1 + 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/samlsp/testdata/expected_middleware_metadata.xml b/samlsp/testdata/expected_middleware_metadata.xml index 012ba0ed..c317ca9c 100644 --- a/samlsp/testdata/expected_middleware_metadata.xml +++ b/samlsp/testdata/expected_middleware_metadata.xml @@ -12,6 +12,7 @@ + diff --git a/schema.go b/schema.go index 13ea2ef6..9181e6f4 100644 --- a/schema.go +++ b/schema.go @@ -764,7 +764,7 @@ func (a *Assertion) Element() *etree.Element { for _, attributeStatement := range a.AttributeStatements { el.AddChild(attributeStatement.Element()) } - err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList, false) + err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList) if err != nil { panic(err) } diff --git a/service_provider.go b/service_provider.go index 9ebdcd1a..5ebaf61d 100644 --- a/service_provider.go +++ b/service_provider.go @@ -204,6 +204,7 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { ResponseLocation: sp.SloURL.String(), }, }, + NameIDFormats: []NameIDFormat{sp.AuthnNameIDFormat}, }, AuthnRequestsSigned: &authnRequestsSigned, WantAssertionsSigned: &wantAssertionsSigned, diff --git a/service_provider_test.go b/service_provider_test.go index 5b0ca3cc..ae597619 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -125,13 +125,14 @@ func TestSPCanProduceMetadataWithEncryptionCert(t *testing.T) { func TestSPCanProduceMetadataWithBothCerts(t *testing.T) { test := NewServiceProviderTest(t) s := ServiceProvider{ - Key: test.Key, - Certificate: test.Certificate, - MetadataURL: mustParseURL("https://example.com/saml2/metadata"), - AcsURL: mustParseURL("https://example.com/saml2/acs"), - SloURL: mustParseURL("https://example.com/saml2/slo"), - IDPMetadata: &EntityDescriptor{}, - SignatureMethod: "not-empty", + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + SloURL: mustParseURL("https://example.com/saml2/slo"), + IDPMetadata: &EntityDescriptor{}, + AuthnNameIDFormat: TransientNameIDFormat, + SignatureMethod: "not-empty", } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) assert.Check(t, err) @@ -145,9 +146,10 @@ func TestSPCanProduceMetadataWithBothCerts(t *testing.T) { func TestCanProduceMetadataNoCerts(t *testing.T) { test := NewServiceProviderTest(t) s := ServiceProvider{ - MetadataURL: mustParseURL("https://example.com/saml2/metadata"), - AcsURL: mustParseURL("https://example.com/saml2/acs"), - IDPMetadata: &EntityDescriptor{}, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + AuthnNameIDFormat: TransientNameIDFormat, } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) assert.Check(t, err) diff --git a/testdata/TestCanProduceMetadataEntityID_metadata b/testdata/TestCanProduceMetadataEntityID_metadata index 68afb039..31122b25 100644 --- a/testdata/TestCanProduceMetadataEntityID_metadata +++ b/testdata/TestCanProduceMetadataEntityID_metadata @@ -1,6 +1,7 @@ + diff --git a/testdata/TestCanProduceMetadataNoCerts_metadata b/testdata/TestCanProduceMetadataNoCerts_metadata index f12f464a..9b475d98 100644 --- a/testdata/TestCanProduceMetadataNoCerts_metadata +++ b/testdata/TestCanProduceMetadataNoCerts_metadata @@ -1,6 +1,7 @@ + urn:oasis:names:tc:SAML:2.0:nameid-format:transient diff --git a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata index 78291e7b..3d4792ca 100644 --- a/testdata/TestSPCanProduceMetadataWithBothCerts_metadata +++ b/testdata/TestSPCanProduceMetadataWithBothCerts_metadata @@ -19,6 +19,7 @@ + urn:oasis:names:tc:SAML:2.0:nameid-format:transient diff --git a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata index b94d83a3..e0acc153 100644 --- a/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata +++ b/testdata/TestSPCanProduceMetadataWithEncryptionCert_metadata @@ -12,6 +12,7 @@ + From c9d22973df3cbd5754395923b3d030530a771047 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Jun 2022 09:39:23 -0400 Subject: [PATCH 24/58] Bump github.com/golang-jwt/jwt/v4 from 4.4.1 to 4.4.2 (#442) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.1 to 4.4.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.1...v4.4.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c0c487f3..7470f36a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/beevik/etree v1.1.0 github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang-jwt/jwt/v4 v4.4.1 + github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-cmp v0.5.7 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3f8bc96b..7408a099 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +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/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= From 1f7373b6cbc421ca84cf578afbb0d5d39a443818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Jun 2022 09:39:42 -0400 Subject: [PATCH 25/58] Bump github.com/russellhaering/goxmldsig from 1.1.1 to 1.2.0 (#426) Bumps [github.com/russellhaering/goxmldsig](https://github.com/russellhaering/goxmldsig) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/russellhaering/goxmldsig/releases) - [Commits](https://github.com/russellhaering/goxmldsig/compare/v1.1.1...v1.2.0) --- updated-dependencies: - dependency-name: github.com/russellhaering/goxmldsig dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7470f36a..ce78931a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/go-cmp v0.5.7 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect - github.com/russellhaering/goxmldsig v1.1.1 + github.com/russellhaering/goxmldsig v1.2.0 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 7408a099..4400e1bc 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= -github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= +github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= From a3bdfa1a94e77b64aa8cf3a6e079b16f69628b4f Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 09:42:52 -0400 Subject: [PATCH 26/58] bump golangci-lint in ci to 1.46.2 --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 69f6627a..b79baa9e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,4 +15,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.43.0 + version: v1.46.2 From 824f7933cc194b57f1a371691bfc561116ef43c3 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 09:43:03 -0400 Subject: [PATCH 27/58] remove maint.yaml from CI; use dependabot instead --- .github/workflows/maint.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/maint.yml diff --git a/.github/workflows/maint.yml b/.github/workflows/maint.yml deleted file mode 100644 index 1081e7a4..00000000 --- a/.github/workflows/maint.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Maintainer -on: - workflow_dispatch: - schedule: - - cron: "0 12 * * 0" -jobs: - upgrade_go: - name: Upgrade go.mod - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 - with: - go-version: "^1.15.6" - - name: Install goupdate - run: | - ( - cd $(mktemp -d) - go get github.com/crewjam/goupdate - ) - git config --global user.email noreply@github.com - git config --global user.name "Github Actions" - - name: Update go.mod - run: | - go version - go env - $(go env GOPATH)/bin/goupdate -test 'go test ./...' --commit -v - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 - with: - commit-message: "Update go.mod" - branch: auto/update-go - title: "Update go.mod" - body: "" From 702b90c6842e3beeac122011f37a1825962917e6 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 09:55:30 -0400 Subject: [PATCH 28/58] Revert "Bump github.com/russellhaering/goxmldsig from 1.1.1 to 1.2.0 (#426)" This reverts commit 1f7373b6cbc421ca84cf578afbb0d5d39a443818. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce78931a..7470f36a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/go-cmp v0.5.7 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect - github.com/russellhaering/goxmldsig v1.2.0 + github.com/russellhaering/goxmldsig v1.1.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 4400e1bc..7408a099 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= -github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= +github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= From e87e8389d0279cdeedaae5d533a8750f4e864f38 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 10:05:15 -0400 Subject: [PATCH 29/58] Support configuring binding for SP SLO endpoints (#444) Co-authored-by: korya --- samlsp/new.go | 6 ++ service_provider.go | 23 +++++--- service_provider_test.go | 55 ++++++++++++++++--- ...ceMetadataWithBothLougoutBindings_metadata | 20 +++++++ ...duceMetadataWithNoLougoutBindings_metadata | 18 ++++++ 5 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 testdata/TestSPCanProduceMetadataWithBothLougoutBindings_metadata create mode 100644 testdata/TestSPCanProduceMetadataWithNoLougoutBindings_metadata diff --git a/samlsp/new.go b/samlsp/new.go index e060c2c7..b68dad06 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -29,6 +29,7 @@ type Options struct { RequestedAuthnContext *saml.RequestedAuthnContext CookieSameSite http.SameSite RelayStateFunc func(w http.ResponseWriter, r *http.Request) string + LogoutBindings []string } // DefaultSessionCodec returns the default SessionCodec for the provided options, @@ -102,6 +103,10 @@ func DefaultServiceProvider(opts Options) saml.ServiceProvider { opts.DefaultRedirectURI = "/" } + if len(opts.LogoutBindings) == 0 { + opts.LogoutBindings = []string{saml.HTTPPostBinding} + } + return saml.ServiceProvider{ EntityID: opts.EntityID, Key: opts.Key, @@ -117,6 +122,7 @@ func DefaultServiceProvider(opts Options) saml.ServiceProvider { SignatureMethod: signatureMethod, AllowIDPInitiated: opts.AllowIDPInitiated, DefaultRedirectURI: opts.DefaultRedirectURI, + LogoutBindings: opts.LogoutBindings, } } diff --git a/service_provider.go b/service_provider.go index 5ebaf61d..4317e334 100644 --- a/service_provider.go +++ b/service_provider.go @@ -118,6 +118,10 @@ type ServiceProvider struct { // SignatureMethod, if non-empty, authentication requests will be signed SignatureMethod string + + // LogoutBindings specify the bindings available for SLO endpoint. If empty, + // HTTP-POST binding is used. + LogoutBindings []string } // MaxIssueDelay is the longest allowed time between when a SAML assertion is @@ -185,6 +189,15 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { } } + var sloEndpoints []Endpoint + for _, binding := range sp.LogoutBindings { + sloEndpoints = append(sloEndpoints, Endpoint{ + Binding: binding, + Location: sp.SloURL.String(), + ResponseLocation: sp.SloURL.String(), + }) + } + return &EntityDescriptor{ EntityID: firstSet(sp.EntityID, sp.MetadataURL.String()), ValidUntil: validUntil, @@ -197,14 +210,8 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { KeyDescriptors: keyDescriptors, ValidUntil: &validUntil, }, - SingleLogoutServices: []Endpoint{ - { - Binding: HTTPPostBinding, - Location: sp.SloURL.String(), - ResponseLocation: sp.SloURL.String(), - }, - }, - NameIDFormats: []NameIDFormat{sp.AuthnNameIDFormat}, + SingleLogoutServices: sloEndpoints, + NameIDFormats: []NameIDFormat{sp.AuthnNameIDFormat}, }, AuthnRequestsSigned: &authnRequestsSigned, WantAssertionsSigned: &wantAssertionsSigned, diff --git a/service_provider_test.go b/service_provider_test.go index ae597619..10f96818 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -107,12 +107,13 @@ func TestSPCanSetAuthenticationNameIDFormat(t *testing.T) { func TestSPCanProduceMetadataWithEncryptionCert(t *testing.T) { test := NewServiceProviderTest(t) s := ServiceProvider{ - Key: test.Key, - Certificate: test.Certificate, - MetadataURL: mustParseURL("https://example.com/saml2/metadata"), - AcsURL: mustParseURL("https://example.com/saml2/acs"), - SloURL: mustParseURL("https://example.com/saml2/slo"), - IDPMetadata: &EntityDescriptor{}, + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + SloURL: mustParseURL("https://example.com/saml2/slo"), + IDPMetadata: &EntityDescriptor{}, + LogoutBindings: []string{HTTPPostBinding}, } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) assert.Check(t, err) @@ -132,6 +133,7 @@ func TestSPCanProduceMetadataWithBothCerts(t *testing.T) { SloURL: mustParseURL("https://example.com/saml2/slo"), IDPMetadata: &EntityDescriptor{}, AuthnNameIDFormat: TransientNameIDFormat, + LogoutBindings: []string{HTTPPostBinding}, SignatureMethod: "not-empty", } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) @@ -150,6 +152,7 @@ func TestCanProduceMetadataNoCerts(t *testing.T) { AcsURL: mustParseURL("https://example.com/saml2/acs"), IDPMetadata: &EntityDescriptor{}, AuthnNameIDFormat: TransientNameIDFormat, + LogoutBindings: []string{HTTPPostBinding}, } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) assert.Check(t, err) @@ -162,9 +165,28 @@ func TestCanProduceMetadataNoCerts(t *testing.T) { func TestCanProduceMetadataEntityID(t *testing.T) { test := NewServiceProviderTest(t) s := ServiceProvider{ - EntityID: "spn:11111111-2222-3333-4444-555555555555", + EntityID: "spn:11111111-2222-3333-4444-555555555555", + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + LogoutBindings: []string{HTTPPostBinding}, + } + err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) + assert.Check(t, err) + + spMetadata, err := xml.MarshalIndent(s.Metadata(), "", " ") + assert.Check(t, err) + golden.Assert(t, string(spMetadata), t.Name()+"_metadata") +} + +func TestSPCanProduceMetadataWithNoLougoutBindings(t *testing.T) { + test := NewServiceProviderTest(t) + s := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, MetadataURL: mustParseURL("https://example.com/saml2/metadata"), AcsURL: mustParseURL("https://example.com/saml2/acs"), + SloURL: mustParseURL("https://example.com/saml2/slo"), IDPMetadata: &EntityDescriptor{}, } err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) @@ -175,6 +197,25 @@ func TestCanProduceMetadataEntityID(t *testing.T) { golden.Assert(t, string(spMetadata), t.Name()+"_metadata") } +func TestSPCanProduceMetadataWithBothLougoutBindings(t *testing.T) { + test := NewServiceProviderTest(t) + s := ServiceProvider{ + Key: test.Key, + Certificate: test.Certificate, + MetadataURL: mustParseURL("https://example.com/saml2/metadata"), + AcsURL: mustParseURL("https://example.com/saml2/acs"), + SloURL: mustParseURL("https://example.com/saml2/slo"), + IDPMetadata: &EntityDescriptor{}, + LogoutBindings: []string{HTTPPostBinding, HTTPRedirectBinding}, + } + err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) + assert.Check(t, err) + + spMetadata, err := xml.MarshalIndent(s.Metadata(), "", " ") + assert.Check(t, err) + golden.Assert(t, string(spMetadata), t.Name()+"_metadata") +} + func TestSPCanProduceRedirectRequest(t *testing.T) { test := NewServiceProviderTest(t) TimeNow = func() time.Time { diff --git a/testdata/TestSPCanProduceMetadataWithBothLougoutBindings_metadata b/testdata/TestSPCanProduceMetadataWithBothLougoutBindings_metadata new file mode 100644 index 00000000..4a1d293f --- /dev/null +++ b/testdata/TestSPCanProduceMetadataWithBothLougoutBindings_metadata @@ -0,0 +1,20 @@ + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testdata/TestSPCanProduceMetadataWithNoLougoutBindings_metadata b/testdata/TestSPCanProduceMetadataWithNoLougoutBindings_metadata new file mode 100644 index 00000000..71d91b4d --- /dev/null +++ b/testdata/TestSPCanProduceMetadataWithNoLougoutBindings_metadata @@ -0,0 +1,18 @@ + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + + + + + \ No newline at end of file From 1a0d2f4ba048791f57ca343ff1a5dc35dd38dc4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Jun 2022 10:05:32 -0400 Subject: [PATCH 30/58] Bump github.com/google/go-cmp from 0.5.7 to 0.5.8 (#435) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.7 to 0.5.8. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.7...v0.5.8) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7470f36a..63d2d8a6 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,10 @@ require ( github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/google/go-cmp v0.5.7 + github.com/google/go-cmp v0.5.8 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index 7408a099..bdad7a05 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ 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/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/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -46,9 +46,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From a54444557d39de0f05a41a4569f87752c3a4c78a Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 10:26:47 -0400 Subject: [PATCH 31/58] update go.{mod,sum} files --- example/go.mod | 2 +- example/go.sum | 5 +++++ samlidp/go.mod | 4 ++-- samlidp/go.sum | 6 ++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/example/go.mod b/example/go.mod index 69b7b2e4..a6faffb9 100644 --- a/example/go.mod +++ b/example/go.mod @@ -11,7 +11,7 @@ require ( github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 github.com/kr/pretty v0.3.0 github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed ) require github.com/crewjam/saml/samlidp v0.0.0-00010101000000-000000000000 diff --git a/example/go.sum b/example/go.sum index 4603a9ec..d17df130 100644 --- a/example/go.sum +++ b/example/go.sum @@ -10,8 +10,11 @@ github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0 github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.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/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -43,6 +46,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/samlidp/go.mod b/samlidp/go.mod index a53ba2cc..61e8a124 100644 --- a/samlidp/go.mod +++ b/samlidp/go.mod @@ -6,9 +6,9 @@ go 1.13 require ( github.com/crewjam/saml v0.0.0-00010101000000-000000000000 - github.com/golang-jwt/jwt/v4 v4.2.0 + github.com/golang-jwt/jwt/v4 v4.4.2 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed gotest.tools v2.2.0+incompatible ) diff --git a/samlidp/go.sum b/samlidp/go.sum index a653625b..d9df064d 100644 --- a/samlidp/go.sum +++ b/samlidp/go.sum @@ -7,8 +7,12 @@ 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/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.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/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -40,6 +44,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From d74fd352075ee3860fa0bd8daef192e5a3b25f1c Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 10:27:13 -0400 Subject: [PATCH 32/58] fix test matrixing in CI --- .github/workflows/test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e18bb6e..b3fa36e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,20 +5,21 @@ on: branches: [ 'main' ] pull_request: branches: [ 'main' ] - jobs: tests: name: Run tests runs-on: ubuntu-latest strategy: matrix: - go: [ '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x' ] + go: [ '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x', '1.18.x' ] steps: - - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v2 - name: Check out code into the Go module directory uses: actions/checkout@v2 + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} + - name: Go version + run: go version - name: Run Go tests run: find . -name go.mod -execdir go test -v ./... \; From f5193739b56955ab0d87a20ad09a2f2d584a401d Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 10:31:15 -0400 Subject: [PATCH 33/58] CI: invoke tests correctly (?) --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3fa36e7..0a2df33f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,4 +22,7 @@ jobs: - name: Go version run: go version - name: Run Go tests - run: find . -name go.mod -execdir go test -v ./... \; + run: | + go test -v ./... + (cd samlidp && go test -v ./...) + (cd example && go test -v ./...) From 5e0ffd290abf0be7dfd4f8279e03a963071544eb Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Sat, 25 Jun 2022 10:33:34 -0400 Subject: [PATCH 34/58] minimum go version is 1.16 --- .github/workflows/test.yml | 2 +- example/go.mod | 2 +- go.mod | 2 +- samlidp/go.mod | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0a2df33f..ca99f764 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x', '1.18.x' ] + go: [ '1.16.x', '1.17.x', '1.18.x' ] steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/example/go.mod b/example/go.mod index a6faffb9..94522e88 100644 --- a/example/go.mod +++ b/example/go.mod @@ -4,7 +4,7 @@ replace github.com/crewjam/saml => ../ replace github.com/crewjam/saml/samlidp => ../samlidp -go 1.13 +go 1.16 require ( github.com/crewjam/saml v0.0.0-00010101000000-000000000000 diff --git a/go.mod b/go.mod index 63d2d8a6..ad8db0d0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/crewjam/saml -go 1.13 +go 1.16 require ( github.com/beevik/etree v1.1.0 diff --git a/samlidp/go.mod b/samlidp/go.mod index 61e8a124..a0f1f7c1 100644 --- a/samlidp/go.mod +++ b/samlidp/go.mod @@ -2,7 +2,7 @@ module github.com/crewjam/saml/samlidp replace github.com/crewjam/saml => ../ -go 1.13 +go 1.16 require ( github.com/crewjam/saml v0.0.0-00010101000000-000000000000 From aee3fb1edeeaf1088fcb458727e0fd863d277f8b Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Mon, 28 Nov 2022 08:48:29 -0500 Subject: [PATCH 35/58] Merge pull request from GHSA-j2jp-wvqg-wc2g * add test case (courtesy of fwilhelm) * fix --- service_provider.go | 771 ++++++++++++++++-------------- service_provider_go117_test.go | 4 +- service_provider_test.go | 79 ++- testdata/TestSPMultipleAssertions | 8 + 4 files changed, 495 insertions(+), 367 deletions(-) create mode 100644 testdata/TestSPMultipleAssertions diff --git a/service_provider.go b/service_provider.go index 4317e334..e5695a79 100644 --- a/service_provider.go +++ b/service_provider.go @@ -3,6 +3,7 @@ package saml import ( "bytes" "compress/flate" + "context" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -17,9 +18,8 @@ import ( "regexp" "time" - xrv "github.com/mattermost/xml-roundtrip-validator" - "github.com/beevik/etree" + xrv "github.com/mattermost/xml-roundtrip-validator" dsig "github.com/russellhaering/goxmldsig" "github.com/russellhaering/goxmldsig/etreeutils" @@ -592,97 +592,79 @@ func (e ErrBadStatus) Error() string { return e.Status } -func responseIsSigned(response *etree.Element) (bool, error) { - signatureElement, err := findChild(response, "http://www.w3.org/2000/09/xmldsig#", "Signature") - if err != nil { - return false, err +// ParseResponse extracts the SAML IDP response received in req, resolves +// artifacts when necessary, validates it, and returns the verified assertion. +func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs []string) (*Assertion, error) { + if artifactID := req.Form.Get("SAMLart"); artifactID != "" { + return sp.handleArtifactRequest(req.Context(), artifactID, possibleRequestIDs) } - return signatureElement != nil, nil + return sp.parseResponseHTTP(req, possibleRequestIDs) } -// validateDestination validates the Destination attribute. -// If the response is signed, the Destination is required to be present. -func (sp *ServiceProvider) validateDestination(response *etree.Element, responseDom *Response) error { - signed, err := responseIsSigned(response) +func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID string, possibleRequestIDs []string) (*Assertion, error) { + retErr := &InvalidResponseError{Now: TimeNow()} + + artifactResolveRequest, err := sp.MakeArtifactResolveRequest(artifactID) if err != nil { - return err + retErr.PrivateErr = fmt.Errorf("Cannot generate artifact resolution request: %s", err) + return nil, retErr } - // Compare if the response is signed OR the Destination is provided. - // (Even if the response is not signed, if the Destination is set it must match.) - if signed || responseDom.Destination != "" { - if responseDom.Destination != sp.AcsURL.String() { - return fmt.Errorf("`Destination` does not match AcsURL (expected %q, actual %q)", sp.AcsURL.String(), responseDom.Destination) - } + requestBody, err := elementToBytes(artifactResolveRequest.SoapRequest()) + if err != nil { + retErr.PrivateErr = err + return nil, retErr } - return nil -} - -// ParseResponse extracts the SAML IDP response received in req, resolves -// artifacts when necessary, validates it, and returns the verified assertion. -func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs []string) (*Assertion, error) { - now := TimeNow() + req, err := http.NewRequestWithContext(ctx, "POST", sp.GetArtifactBindingLocation(SOAPBinding), + bytes.NewReader(requestBody)) + if err != nil { + retErr.PrivateErr = err + return nil, retErr + } - var assertion *Assertion + httpClient := sp.HTTPClient + if httpClient == nil { + httpClient = http.DefaultClient + } + response, err := httpClient.Do(req) + if err != nil { + retErr.PrivateErr = fmt.Errorf("cannot resolve artifact: %s", err) + return nil, retErr + } + defer response.Body.Close() + if response.StatusCode != 200 { + retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: HTTP status %d (%s)", response.StatusCode, response.Status) + return nil, retErr + } + responseBody, err := ioutil.ReadAll(response.Body) + if err != nil { + retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) + return nil, retErr + } + assertion, err := sp.ParseXMLArtifactResponse(responseBody, possibleRequestIDs, artifactResolveRequest.ID) + if err != nil { + return nil, err + } + return assertion, nil +} +func (sp *ServiceProvider) parseResponseHTTP(req *http.Request, possibleRequestIDs []string) (*Assertion, error) { retErr := &InvalidResponseError{ - Now: now, - Response: req.PostForm.Get("SAMLResponse"), + Now: TimeNow(), } - if req.Form.Get("SAMLart") != "" { - retErr.Response = req.Form.Get("SAMLart") - - req, err := sp.MakeArtifactResolveRequest(req.Form.Get("SAMLart")) - if err != nil { - retErr.PrivateErr = fmt.Errorf("Cannot generate artifact resolution request: %s", err) - return nil, retErr - } - - doc := etree.NewDocument() - doc.SetRoot(req.SoapRequest()) - - var requestBuffer bytes.Buffer - doc.WriteTo(&requestBuffer) - client := sp.HTTPClient - if client == nil { - client = http.DefaultClient - } - response, err := client.Post(sp.GetArtifactBindingLocation(SOAPBinding), "text/xml", &requestBuffer) - if err != nil { - retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) - return nil, retErr - } - defer response.Body.Close() - if response.StatusCode != 200 { - retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: HTTP status %d (%s)", response.StatusCode, response.Status) - return nil, retErr - } - rawResponseBuf, err := ioutil.ReadAll(response.Body) - if err != nil { - retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: %s", err) - return nil, retErr - } - assertion, err = sp.ParseXMLArtifactResponse(rawResponseBuf, possibleRequestIDs, req.ID) - if err != nil { - return nil, err - } - } else { - rawResponseBuf, err := base64.StdEncoding.DecodeString(req.PostForm.Get("SAMLResponse")) - if err != nil { - retErr.PrivateErr = fmt.Errorf("cannot parse base64: %s", err) - return nil, retErr - } - retErr.Response = string(rawResponseBuf) - assertion, err = sp.ParseXMLResponse(rawResponseBuf, possibleRequestIDs) - if err != nil { - return nil, err - } + rawResponseBuf, err := base64.StdEncoding.DecodeString(req.PostForm.Get("SAMLResponse")) + if err != nil { + retErr.PrivateErr = fmt.Errorf("cannot parse base64: %s", err) + return nil, retErr } + assertion, err := sp.ParseXMLResponse(rawResponseBuf, possibleRequestIDs) + if err != nil { + return nil, err + } return assertion, nil - } // ParseXMLArtifactResponse validates the SAML Artifact resolver response @@ -695,83 +677,95 @@ func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs [ // properties are useful in describing which part of the parsing process // failed. However, to discourage inadvertent disclosure the diagnostic // information, the Error() method returns a static string. -func (sp *ServiceProvider) ParseXMLArtifactResponse(decodedResponseXML []byte, possibleRequestIDs []string, artifactRequestID string) (*Assertion, error) { +func (sp *ServiceProvider) ParseXMLArtifactResponse(soapResponseXML []byte, possibleRequestIDs []string, artifactRequestID string) (*Assertion, error) { now := TimeNow() - //var err error retErr := &InvalidResponseError{ + Response: string(soapResponseXML), Now: now, - Response: string(decodedResponseXML), } - // ensure that the response XML is well formed before we parse it - if err := xrv.Validate(bytes.NewReader(decodedResponseXML)); err != nil { + // ensure that the response XML is well-formed before we parse it + if err := xrv.Validate(bytes.NewReader(soapResponseXML)); err != nil { retErr.PrivateErr = fmt.Errorf("invalid xml: %s", err) return nil, retErr } - envelope := &struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` - Body struct { - ArtifactResponse ArtifactResponse - } `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` - }{} - if err := xml.Unmarshal(decodedResponseXML, &envelope); err != nil { + doc := etree.NewDocument() + if err := doc.ReadFromBytes(soapResponseXML); err != nil { retErr.PrivateErr = fmt.Errorf("cannot unmarshal response: %s", err) return nil, retErr } - - resp := envelope.Body.ArtifactResponse - - // Validate ArtifactResponse - if resp.InResponseTo != artifactRequestID { - retErr.PrivateErr = fmt.Errorf("`InResponseTo` does not match the artifact request ID (expected %v)", artifactRequestID) + if doc.Root().NamespaceURI() != "http://schemas.xmlsoap.org/soap/envelope/" || + doc.Root().Tag != "Envelope" { + retErr.PrivateErr = fmt.Errorf("expected a SOAP Envelope") return nil, retErr } - if resp.IssueInstant.Add(MaxIssueDelay).Before(now) { - retErr.PrivateErr = fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)) - return nil, retErr - } - if resp.Issuer != nil && resp.Issuer.Value != sp.IDPMetadata.EntityID { - retErr.PrivateErr = fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) - return nil, retErr - } - if resp.Status.StatusCode.Value != StatusSuccess { - retErr.PrivateErr = ErrBadStatus{Status: resp.Status.StatusCode.Value} + + soapBodyEl, err := findOneChild(doc.Root(), "http://schemas.xmlsoap.org/soap/envelope/", "Body") + if err != nil { + retErr.PrivateErr = err return nil, retErr } - doc := etree.NewDocument() - if err := doc.ReadFromBytes(decodedResponseXML); err != nil { + artifactResponseEl, err := findOneChild(soapBodyEl, "urn:oasis:names:tc:SAML:2.0:protocol", "ArtifactResponse") + if err != nil { retErr.PrivateErr = err return nil, retErr } - artifactEl := doc.FindElement("Envelope/Body/ArtifactResponse") - if artifactEl == nil { - retErr.PrivateErr = fmt.Errorf("missing ArtifactResponse") - return nil, retErr + return sp.parseArtifactResponse(artifactResponseEl, possibleRequestIDs, artifactRequestID, now) +} + +func (sp *ServiceProvider) parseArtifactResponse(artifactResponseEl *etree.Element, possibleRequestIDs []string, artifactRequestID string, now time.Time) (*Assertion, error) { + retErr := &InvalidResponseError{ + Now: now, + Response: elementToString(artifactResponseEl), + } + + { + var artifactResponse ArtifactResponse + if err := unmarshalElement(artifactResponseEl, &artifactResponse); err != nil { + retErr.PrivateErr = err + return nil, retErr + } + if artifactResponse.InResponseTo != artifactRequestID { + retErr.PrivateErr = fmt.Errorf("`InResponseTo` does not match the artifact request ID (expected %s)", artifactRequestID) + return nil, retErr + } + if artifactResponse.IssueInstant.Add(MaxIssueDelay).Before(now) { + retErr.PrivateErr = fmt.Errorf("response IssueInstant expired at %s", artifactResponse.IssueInstant.Add(MaxIssueDelay)) + return nil, retErr + } + if artifactResponse.Issuer != nil && artifactResponse.Issuer.Value != sp.IDPMetadata.EntityID { + retErr.PrivateErr = fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) + return nil, retErr + } + if artifactResponse.Status.StatusCode.Value != StatusSuccess { + retErr.PrivateErr = ErrBadStatus{Status: artifactResponse.Status.StatusCode.Value} + return nil, retErr + } } - responseEl := doc.FindElement("Envelope/Body/ArtifactResponse/Response") - if responseEl == nil { - retErr.PrivateErr = fmt.Errorf("missing inner Response") + + var signatureRequirement signatureRequirement + sigErr := sp.validateSignature(artifactResponseEl) + if sigErr == nil { + signatureRequirement = signatureNotRequired + } else if sigErr == errSignatureElementNotPresent { + signatureRequirement = signatureRequired + } else { + retErr.PrivateErr = sigErr return nil, retErr } - haveSignature := false - var err error - if err = sp.validateArtifactSigned(artifactEl); err != nil && err.Error() != "either the Response or Assertion must be signed" { + responseEl, err := findOneChild(artifactResponseEl, "urn:oasis:names:tc:SAML:2.0:protocol", "Response") + if err != nil { retErr.PrivateErr = err return nil, retErr } - if err == nil { - haveSignature = true - } - assertion, updatedResponse, err := sp.validateXMLResponse(&resp.Response, responseEl, possibleRequestIDs, now, !haveSignature) + + assertion, err := sp.parseResponse(responseEl, possibleRequestIDs, now, signatureRequirement) if err != nil { retErr.PrivateErr = err - if updatedResponse != nil { - retErr.Response = *updatedResponse - } return nil, retErr } @@ -797,150 +791,213 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR Response: string(decodedResponseXML), } - // ensure that the response XML is well formed before we parse it + // ensure that the response XML is well-formed before we parse it if err := xrv.Validate(bytes.NewReader(decodedResponseXML)); err != nil { retErr.PrivateErr = fmt.Errorf("invalid xml: %s", err) return nil, retErr } - // do some validation first before we decrypt - resp := Response{} - if err := xml.Unmarshal(decodedResponseXML, &resp); err != nil { - retErr.PrivateErr = fmt.Errorf("cannot unmarshal response: %s", err) - return nil, retErr - } - doc := etree.NewDocument() if err := doc.ReadFromBytes(decodedResponseXML); err != nil { retErr.PrivateErr = err return nil, retErr } - assertion, updatedResponse, err := sp.validateXMLResponse(&resp, doc.Root(), possibleRequestIDs, now, true) + assertion, err := sp.parseResponse(doc.Root(), possibleRequestIDs, now, signatureRequired) if err != nil { retErr.PrivateErr = err - if updatedResponse != nil { - retErr.Response = *updatedResponse - } return nil, retErr } return assertion, nil } +type signatureRequirement int + +const ( + signatureRequired signatureRequirement = iota + signatureNotRequired +) + // validateXMLResponse validates the SAML IDP response and returns // the verified assertion. // // This function handles decrypting the message, verifying the digital // signature on the assertion, and verifying that the specified conditions // and properties are met. -func (sp *ServiceProvider) validateXMLResponse(resp *Response, responseEl *etree.Element, possibleRequestIDs []string, now time.Time, needSig bool) (*Assertion, *string, error) { - var err error - var updatedResponse *string - if err := sp.validateDestination(responseEl, resp); err != nil { - return nil, updatedResponse, err +func (sp *ServiceProvider) parseResponse(responseEl *etree.Element, possibleRequestIDs []string, now time.Time, signatureRequirement signatureRequirement) (*Assertion, error) { + var responseSignatureErr error + var responseHasSignature bool + if signatureRequirement == signatureRequired { + responseSignatureErr = sp.validateSignature(responseEl) + if responseSignatureErr != errSignatureElementNotPresent { + responseHasSignature = true + } + + // Note: we're deferring taking action on the signature validation until after we've + // processed the request attributes, because certain test cases seem to require this mis-feature. + // TODO(ross): adjust the test cases so that we can abort here if the Response signature is invalid. } - requestIDvalid := false + // validate request attributes + { + var response Response + if err := unmarshalElement(responseEl, &response); err != nil { + return nil, fmt.Errorf("cannot unmarshal response: %v", err) + } - if sp.AllowIDPInitiated { - requestIDvalid = true - } else { - for _, possibleRequestID := range possibleRequestIDs { - if resp.InResponseTo == possibleRequestID { - requestIDvalid = true + // If the response is *not* signed, the Destination may be omitted. + if responseHasSignature || response.Destination != "" { + if response.Destination != sp.AcsURL.String() { + return nil, fmt.Errorf("`Destination` does not match AcsURL (expected %q, actual %q)", sp.AcsURL.String(), response.Destination) } } - } - - if !requestIDvalid { - return nil, updatedResponse, fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs) - } - if resp.IssueInstant.Add(MaxIssueDelay).Before(now) { - return nil, updatedResponse, fmt.Errorf("response IssueInstant expired at %s", resp.IssueInstant.Add(MaxIssueDelay)) - } - if resp.Issuer != nil && resp.Issuer.Value != sp.IDPMetadata.EntityID { - return nil, updatedResponse, fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) - } - if resp.Status.StatusCode.Value != StatusSuccess { - return nil, updatedResponse, ErrBadStatus{Status: resp.Status.StatusCode.Value} - } - - var assertion *Assertion - if resp.EncryptedAssertion == nil { - // TODO(ross): verify that the namespace is urn:oasis:names:tc:SAML:2.0:protocol - if responseEl.Tag != "Response" { - return nil, updatedResponse, fmt.Errorf("expected to find a response object, not %s", responseEl.Tag) + requestIDvalid := false + if sp.AllowIDPInitiated { + requestIDvalid = true + } else { + for _, possibleRequestID := range possibleRequestIDs { + if response.InResponseTo == possibleRequestID { + requestIDvalid = true + } + } + } + if !requestIDvalid { + return nil, fmt.Errorf("`InResponseTo` does not match any of the possible request IDs (expected %v)", possibleRequestIDs) } - if err = sp.validateSigned(responseEl); err != nil && !(!needSig && err.Error() == "either the Response or Assertion must be signed") { - return nil, updatedResponse, err + if response.IssueInstant.Add(MaxIssueDelay).Before(now) { + return nil, fmt.Errorf("response IssueInstant expired at %s", response.IssueInstant.Add(MaxIssueDelay)) + } + if response.Issuer != nil && response.Issuer.Value != sp.IDPMetadata.EntityID { + return nil, fmt.Errorf("response Issuer does not match the IDP metadata (expected %q)", sp.IDPMetadata.EntityID) } + if response.Status.StatusCode.Value != StatusSuccess { + return nil, ErrBadStatus{Status: response.Status.StatusCode.Value} + } + } - assertion = resp.Assertion + if signatureRequirement == signatureRequired { + if responseSignatureErr == nil { + // since the request has a signature, none of the Assertions need one + signatureRequirement = signatureNotRequired + } else if responseSignatureErr == errSignatureElementNotPresent { + // the request has no signature, so assertions must be signed + signatureRequirement = signatureRequired // nop + } else { + return nil, responseSignatureErr + } } - // decrypt the response - if resp.EncryptedAssertion != nil { - // encrypted assertions are part of the signature - // before decrypting the response verify that - responseSigned, err := responseIsSigned(responseEl) + var errs []error + var assertions []Assertion + + // look for encrypted assertions + { + encryptedAssertionEls, err := findChildren(responseEl, "urn:oasis:names:tc:SAML:2.0:assertion", "EncryptedAssertion") if err != nil { - return nil, updatedResponse, err + return nil, err } - if responseSigned { - if err := sp.validateSigned(responseEl); err != nil { - return nil, updatedResponse, err + for _, encryptedAssertionEl := range encryptedAssertionEls { + assertion, err := sp.parseEncryptedAssertion(encryptedAssertionEl, possibleRequestIDs, now, signatureRequirement) + if err != nil { + errs = append(errs, err) + continue } + assertions = append(assertions, *assertion) } + } - var key interface{} = sp.Key - keyEl := responseEl.FindElement("//EncryptedAssertion/EncryptedKey") - if keyEl != nil { - key, err = xmlenc.Decrypt(sp.Key, keyEl) + // look for plaintext assertions + { + assertionEls, err := findChildren(responseEl, "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion") + if err != nil { + return nil, err + } + for _, assertionEl := range assertionEls { + assertion, err := sp.parseAssertion(assertionEl, possibleRequestIDs, now, signatureRequirement) if err != nil { - return nil, updatedResponse, fmt.Errorf("failed to decrypt key from response: %s", err) + errs = append(errs, err) + continue } + assertions = append(assertions, *assertion) } + } - el := responseEl.FindElement("//EncryptedAssertion/EncryptedData") - plaintextAssertion, err := xmlenc.Decrypt(key, el) - if err != nil { - return nil, updatedResponse, fmt.Errorf("failed to decrypt response: %s", err) + if len(assertions) == 0 { + if len(errs) > 0 { + return nil, errs[0] } - updatedResponse = new(string) - *updatedResponse = string(plaintextAssertion) + return nil, fmt.Errorf("expected at least one valid Assertion, none found") + } - // TODO(ross): add test case for this - if err := xrv.Validate(bytes.NewReader(plaintextAssertion)); err != nil { - return nil, updatedResponse, fmt.Errorf("plaintext response contains invalid XML: %s", err) - } + // if we have at least one assertion, return the first one. It is almost universally true that valid responses + // contain only one assertion. This is less that fully correct, but we didn't realize that there could be more + // than one assertion at the time of establishing the public interface of ParseXMLResponse(), so for compatability + // we return the first one. + return &assertions[0], nil +} - doc := etree.NewDocument() - if err := doc.ReadFromBytes(plaintextAssertion); err != nil { - return nil, updatedResponse, fmt.Errorf("cannot parse plaintext response %v", err) - } +func (sp *ServiceProvider) parseEncryptedAssertion(encryptedAssertionEl *etree.Element, possibleRequestIDs []string, now time.Time, signatureRequirement signatureRequirement) (*Assertion, error) { + assertionEl, err := sp.decryptElement(encryptedAssertionEl) + if err != nil { + return nil, fmt.Errorf("failed to decrypt EncryptedAssertion: %v", err) + } + return sp.parseAssertion(assertionEl, possibleRequestIDs, now, signatureRequirement) +} - // the decrypted assertion may be signed too - // otherwise, a signed response is sufficient - if err := sp.validateSigned(doc.Root()); err != nil && !((responseSigned || !needSig) && err.Error() == "either the Response or Assertion must be signed") { - return nil, updatedResponse, err +func (sp *ServiceProvider) decryptElement(encryptedEl *etree.Element) (*etree.Element, error) { + encryptedDataEl, err := findOneChild(encryptedEl, "http://www.w3.org/2001/04/xmlenc#", "EncryptedData") + if err != nil { + return nil, err + } + + var key interface{} = sp.Key + keyEl := encryptedEl.FindElement("./EncryptedKey") + if keyEl != nil { + var err error + key, err = xmlenc.Decrypt(sp.Key, keyEl) + if err != nil { + return nil, fmt.Errorf("failed to decrypt key from response: %s", err) } + } + + plaintextEl, err := xmlenc.Decrypt(key, encryptedDataEl) + if err != nil { + return nil, err + } - assertion = &Assertion{} - // Note: plaintextAssertion is known to be safe to parse because - // plaintextAssertion is unmodified from when xrv.Validate() was called above. - if err := xml.Unmarshal(plaintextAssertion, assertion); err != nil { - return nil, updatedResponse, err + if err := xrv.Validate(bytes.NewReader(plaintextEl)); err != nil { + return nil, fmt.Errorf("plaintext response contains invalid XML: %s", err) + } + + doc := etree.NewDocument() + if err := doc.ReadFromBytes(plaintextEl); err != nil { + return nil, fmt.Errorf("cannot parse plaintext response %v", err) + } + return doc.Root(), nil +} + +func (sp *ServiceProvider) parseAssertion(assertionEl *etree.Element, possibleRequestIDs []string, now time.Time, signatureRequirement signatureRequirement) (*Assertion, error) { + if signatureRequirement == signatureRequired { + sigErr := sp.validateSignature(assertionEl) + if sigErr != nil { + return nil, sigErr } } - if err := sp.validateAssertion(assertion, possibleRequestIDs, now); err != nil { - return nil, updatedResponse, fmt.Errorf("assertion invalid: %s", err) + // parse the assertion we just validated + var assertion Assertion + if err := unmarshalElement(assertionEl, &assertion); err != nil { + return nil, err } - return assertion, updatedResponse, nil + if err := sp.validateAssertion(&assertion, possibleRequestIDs, now); err != nil { + return nil, err + } + + return &assertion, nil } // validateAssertion checks that the conditions specified in assertion match @@ -1012,117 +1069,21 @@ func (sp *ServiceProvider) validateAssertion(assertion *Assertion, possibleReque return nil } -func findChild(parentEl *etree.Element, childNS string, childTag string) (*etree.Element, error) { - for _, childEl := range parentEl.ChildElements() { - if childEl.Tag != childTag { - continue - } - - ctx, err := etreeutils.NSBuildParentContext(childEl) - if err != nil { - return nil, err - } - ctx, err = ctx.SubContext(childEl) - if err != nil { - return nil, err - } - - ns, err := ctx.LookupPrefix(childEl.Space) - if err != nil { - return nil, fmt.Errorf("[%s]:%s cannot find prefix %s: %v", childNS, childTag, childEl.Space, err) - } - if ns != childNS { - continue - } - - return childEl, nil - } - return nil, nil -} - -// validateArtifactSigned returns a nil error iff each of the signatures on the ArtifactResponse, Response -// and Assertion elements are valid and there is at least one signature. -func (sp *ServiceProvider) validateArtifactSigned(artifactEl *etree.Element) error { - haveSignature := false +var errSignatureElementNotPresent = errors.New("Signature element not present") - sigEl, err := findChild(artifactEl, "http://www.w3.org/2000/09/xmldsig#", "Signature") - if err != nil { - return err - } - if sigEl != nil { - if err = sp.validateSignature(artifactEl); err != nil { - return fmt.Errorf("cannot validate signature on Response: %v", err) - } - haveSignature = true - } - - responseEl, err := findChild(artifactEl, "urn:oasis:names:tc:SAML:2.0:protocol", "Response") - if err != nil { - return err - } - if responseEl != nil { - err = sp.validateSigned(responseEl) - if err != nil && err.Error() != "either the Response or Assertion must be signed" { - return err - } - if err == nil { - haveSignature = true // guaranteed by validateSigned - } - } - - if !haveSignature { - return errors.New("either the ArtifactResponse, Response or Assertion must be signed") - } - return nil -} - -// validateSigned returns a nil error iff each of the signatures on the Response and Assertion elements -// are valid and there is at least one signature. -func (sp *ServiceProvider) validateSigned(responseEl *etree.Element) error { - haveSignature := false - - // Some SAML responses have the signature on the Response object, and some on the Assertion - // object, and some on both. We will require that at least one signature be present and that - // all signatures be valid - sigEl, err := findChild(responseEl, "http://www.w3.org/2000/09/xmldsig#", "Signature") - if err != nil { - return err - } - if sigEl != nil { - if err = sp.validateSignature(responseEl); err != nil { - return fmt.Errorf("cannot validate signature on Response: %v", err) - } - haveSignature = true - } - - assertionEl, err := findChild(responseEl, "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion") +// validateSignature returns nil iff the Signature embedded in the element is valid +func (sp *ServiceProvider) validateSignature(el *etree.Element) error { + sigEl, err := findChild(el, "http://www.w3.org/2000/09/xmldsig#", "Signature") if err != nil { return err } - if assertionEl != nil { - sigEl, err := findChild(assertionEl, "http://www.w3.org/2000/09/xmldsig#", "Signature") - if err != nil { - return err - } - if sigEl != nil { - if err = sp.validateSignature(assertionEl); err != nil { - return fmt.Errorf("cannot validate signature on Response: %v", err) - } - haveSignature = true - } - } - - if !haveSignature { - return errors.New("either the Response or Assertion must be signed") + if sigEl == nil { + return errSignatureElementNotPresent } - return nil -} -// validateSignature returns nill iff the Signature embedded in the element is valid -func (sp *ServiceProvider) validateSignature(el *etree.Element) error { certs, err := sp.getIDPSigningCerts() if err != nil { - return err + return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err) } certificateStore := dsig.MemoryX509CertificateStore{ @@ -1154,23 +1115,26 @@ func (sp *ServiceProvider) validateSignature(el *etree.Element) error { ctx, err := etreeutils.NSBuildParentContext(el) if err != nil { - return err + return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err) } ctx, err = ctx.SubContext(el) if err != nil { - return err + return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err) } el, err = etreeutils.NSDetatch(ctx, el) if err != nil { - return err + return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err) } if sp.SignatureVerifier != nil { return sp.SignatureVerifier.VerifySignature(validationContext, el) } - _, err = validationContext.Validate(el) - return err + if _, err := validationContext.Validate(el); err != nil { + return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err) + } + + return nil } // SignLogoutRequest adds the `Signature` element to the `LogoutRequest`. @@ -1497,32 +1461,42 @@ func (sp *ServiceProvider) ValidateLogoutResponseRequest(req *http.Request) erro // ValidateLogoutResponseForm returns a nil error if the logout response is valid. func (sp *ServiceProvider) ValidateLogoutResponseForm(postFormData string) error { + retErr := &InvalidResponseError{ + Now: TimeNow(), + } + rawResponseBuf, err := base64.StdEncoding.DecodeString(postFormData) if err != nil { - return fmt.Errorf("unable to parse base64: %s", err) + retErr.PrivateErr = fmt.Errorf("unable to parse base64: %s", err) + return retErr } + retErr.Response = string(rawResponseBuf) // TODO(ross): add test case for this (SLO does not have tests right now) if err := xrv.Validate(bytes.NewReader(rawResponseBuf)); err != nil { return fmt.Errorf("response contains invalid XML: %s", err) } - var resp LogoutResponse - if err := xml.Unmarshal(rawResponseBuf, &resp); err != nil { - return fmt.Errorf("cannot unmarshal response: %s", err) + doc := etree.NewDocument() + if err := doc.ReadFromBytes(rawResponseBuf); err != nil { + retErr.PrivateErr = err + return retErr } - if err := sp.validateLogoutResponse(&resp); err != nil { - return err + if err := sp.validateSignature(doc.Root()); err != nil { + retErr.PrivateErr = err + return retErr } - doc := etree.NewDocument() - if err := doc.ReadFromBytes(rawResponseBuf); err != nil { + var resp LogoutResponse + if err := unmarshalElement(doc.Root(), &resp); err != nil { + retErr.PrivateErr = err + return retErr + } + if err := sp.validateLogoutResponse(&resp); err != nil { return err } - - responseEl := doc.Root() - return sp.validateSigned(responseEl) + return nil } // ValidateLogoutResponseRedirect returns a nil error if the logout response is valid. @@ -1530,40 +1504,47 @@ func (sp *ServiceProvider) ValidateLogoutResponseForm(postFormData string) error // URL Binding appears to be gzip / flate encoded // See https://www.oasis-open.org/committees/download.php/20645/sstc-saml-tech-overview-2%200-draft-10.pdf 6.6 func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData string) error { + retErr := &InvalidResponseError{ + Now: TimeNow(), + } + rawResponseBuf, err := base64.StdEncoding.DecodeString(queryParameterData) if err != nil { - return fmt.Errorf("unable to parse base64: %s", err) + retErr.PrivateErr = fmt.Errorf("unable to parse base64: %s", err) + return retErr } + retErr.Response = string(rawResponseBuf) gr, err := ioutil.ReadAll(flate.NewReader(bytes.NewBuffer(rawResponseBuf))) if err != nil { - return err + retErr.PrivateErr = err + return retErr } if err := xrv.Validate(bytes.NewReader(gr)); err != nil { return err } - decoder := xml.NewDecoder(bytes.NewReader(gr)) - - var resp LogoutResponse - - err = decoder.Decode(&resp) - if err != nil { - return fmt.Errorf("unable to flate decode: %s", err) + doc := etree.NewDocument() + if err := doc.ReadFromBytes(rawResponseBuf); err != nil { + retErr.PrivateErr = err + return retErr } - if err := sp.validateLogoutResponse(&resp); err != nil { - return err + if err := sp.validateSignature(doc.Root()); err != nil { + retErr.PrivateErr = err + return retErr } - doc := etree.NewDocument() - if _, err := doc.ReadFrom(bytes.NewReader(gr)); err != nil { + var resp LogoutResponse + if err := unmarshalElement(doc.Root(), &resp); err != nil { + retErr.PrivateErr = err + return retErr + } + if err := sp.validateLogoutResponse(&resp); err != nil { return err } - - responseEl := doc.Root() - return sp.validateSigned(responseEl) + return nil } // validateLogoutResponse validates the LogoutResponse fields. Returns a nil error if the LogoutResponse is valid. @@ -1592,3 +1573,101 @@ func firstSet(a, b string) string { } return a } + +// findChildren returns all the elements matching childNS/childTag that are direct children of parentEl. +func findChildren(parentEl *etree.Element, childNS string, childTag string) ([]*etree.Element, error) { + var rv []*etree.Element + for _, childEl := range parentEl.ChildElements() { + if childEl.Tag != childTag { + continue + } + + ctx, err := etreeutils.NSBuildParentContext(childEl) + if err != nil { + return nil, err + } + ctx, err = ctx.SubContext(childEl) + if err != nil { + return nil, err + } + + ns, err := ctx.LookupPrefix(childEl.Space) + if err != nil { + return nil, fmt.Errorf("[%s]:%s cannot find prefix %s: %v", childNS, childTag, childEl.Space, err) + } + if ns != childNS { + continue + } + + rv = append(rv, childEl) + } + + return rv, nil +} + +// findOneChild finds the specified child element. Returns an error if the element doesn't exist. +func findOneChild(parentEl *etree.Element, childNS string, childTag string) (*etree.Element, error) { + children, err := findChildren(parentEl, childNS, childTag) + if err != nil { + return nil, err + } + switch len(children) { + case 0: + return nil, fmt.Errorf("cannot find %s:%s element", childNS, childTag) + case 1: + return children[0], nil + default: + return nil, fmt.Errorf("expected exactly one %s:%s element", childNS, childTag) + } +} + +// findChild finds the specified child element. Returns (nil, nil) of the element doesn't exist. +func findChild(parentEl *etree.Element, childNS string, childTag string) (*etree.Element, error) { + children, err := findChildren(parentEl, childNS, childTag) + if err != nil { + return nil, err + } + switch len(children) { + case 0: + return nil, nil + case 1: + return children[0], nil + default: + return nil, fmt.Errorf("expected at most one %s:%s element", childNS, childTag) + } +} + +func elementToBytes(el *etree.Element) ([]byte, error) { + namespaces := map[string]string{} + for _, childEl := range el.FindElements("//*") { + ns := childEl.NamespaceURI() + if ns != "" { + namespaces[childEl.Space] = ns + } + } + + doc := etree.NewDocument() + doc.SetRoot(el.Copy()) + for space, uri := range namespaces { + doc.Root().CreateAttr("xmlns:"+space, uri) + } + + return doc.WriteToBytes() +} + +// unmarshalElement serializes el into v by serializing el and then parsing it with xml.Unmarshal. +func unmarshalElement(el *etree.Element, v interface{}) error { + buf, err := elementToBytes(el) + if err != nil { + return err + } + return xml.Unmarshal(buf, v) +} + +func elementToString(el *etree.Element) string { + buf, err := elementToBytes(el) + if err != nil { + return "" + } + return string(buf) +} diff --git a/service_provider_go117_test.go b/service_provider_go117_test.go index 1f5a07d5..3d4a5835 100644 --- a/service_provider_go117_test.go +++ b/service_provider_go117_test.go @@ -125,12 +125,12 @@ func TestSPInvalidResponses(t *testing.T) { req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: cannot parse certificate: illegal base64 data at input byte 4")) + "cannot validate signature on Assertion: cannot parse certificate: illegal base64 data at input byte 4")) s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "aW52YWxpZA==" req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: x509: malformed certificate")) + "cannot validate signature on Assertion: x509: malformed certificate")) } diff --git a/service_provider_test.go b/service_provider_test.go index 10f96818..c5ad9dd2 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -985,7 +985,7 @@ func TestServiceProviderMismatchedDestinationsWithSignaturePresent(t *testing.T) req := http.Request{PostForm: url.Values{}} s.AcsURL = mustParseURL("https://wrong/saml2/acs") - bytes, _ := addSignatureToDocument(test.responseDom()).WriteToBytes() + bytes, _ := test.responseDom().WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1087,11 +1087,20 @@ func TestSPInvalidAssertions(t *testing.T) { err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata) assert.Check(t, err) - req := http.Request{PostForm: url.Values{}} - req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse)) - s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "invalid" - _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) - assertionBuf := []byte(err.(*InvalidResponseError).Response) + // HACK: decrypt response without verifying assertions + var assertionBuf []byte + { + doc := etree.NewDocument() + assert.Check(t, doc.ReadFromBytes(test.SamlResponse)) + encryptedEL := doc.Root().FindElement("//EncryptedAssertion") + assertionEl, err := s.decryptElement(encryptedEL) + assert.Check(t, err) + + doc = etree.NewDocument() + doc.SetRoot(assertionEl) + assertionBuf, err = doc.WriteToBytes() + assert.Check(t, err) + } assertion := Assertion{} err = xml.Unmarshal(assertionBuf, &assertion) @@ -1234,9 +1243,13 @@ func TestXswPermutationThreeIsRejected(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) - // Because this permutation contains an unsigned assertion as child of the response - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "either the Response or Assertion must be signed")) + + // This response contains two assertions. The first is missing a Signature element. The second is + // signed by a certificate that is not yet valid at the time of issue. + // + // When no assertions are valid, we return the first error encountered, which in this case is that + // there is no Signature on the element. + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "Signature element not present")) } func TestXswPermutationFourIsRejected(t *testing.T) { @@ -1262,9 +1275,11 @@ func TestXswPermutationFourIsRejected(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) - // Because this permutation contains an unsigned assertion as child of the response - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "either the Response or Assertion must be signed")) + + // This permutation contains a signed assertion embedded within an unsigned assertion. + // I'm pretty sure this is just not allowed, so we properly decide that there are no + // signed assertions at all. + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "Signature element not present")) } func TestXswPermutationFiveIsRejected(t *testing.T) { @@ -1291,7 +1306,7 @@ func TestXswPermutationFiveIsRejected(t *testing.T) { req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Missing signature referencing the top-level element")) + "cannot validate signature on Assertion: Missing signature referencing the top-level element")) } func TestXswPermutationSixIsRejected(t *testing.T) { @@ -1318,7 +1333,7 @@ func TestXswPermutationSixIsRejected(t *testing.T) { req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Missing signature referencing the top-level element")) + "cannot validate signature on Assertion: Missing signature referencing the top-level element")) } func TestXswPermutationSevenIsRejected(t *testing.T) { @@ -1349,7 +1364,7 @@ func TestXswPermutationSevenIsRejected(t *testing.T) { _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) //It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Signature could not be verified")) + "cannot validate signature on Assertion: Signature could not be verified")) } func TestXswPermutationEightIsRejected(t *testing.T) { @@ -1380,7 +1395,7 @@ func TestXswPermutationEightIsRejected(t *testing.T) { _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) //It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Signature could not be verified")) + "cannot validate signature on Assertion: Signature could not be verified")) } func TestXswPermutationNineIsRejected(t *testing.T) { @@ -1411,7 +1426,7 @@ func TestXswPermutationNineIsRejected(t *testing.T) { _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) //It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Missing signature referencing the top-level element")) + "cannot validate signature on Assertion: Missing signature referencing the top-level element")) } func TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert(t *testing.T) { @@ -1749,7 +1764,7 @@ func TestParseBadXMLArtifactResponse(t *testing.T) { assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "cannot validate signature on Response: Cert is not valid at this time")) + "cannot validate signature on ArtifactResponse: Cert is not valid at this time")) assert.Check(t, is.Nil(assertion)) Clock = dsig.NewFakeClockAt(TimeNow()) @@ -1769,6 +1784,32 @@ func TestParseBadXMLArtifactResponse(t *testing.T) { sp.Key = mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey) assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, - "failed to decrypt response: certificate does not match provided key")) + "failed to decrypt EncryptedAssertion: certificate does not match provided key")) assert.Check(t, is.Nil(assertion)) } + +func TestMultipleAssertions(t *testing.T) { + idpMetadata := golden.Get(t, "TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert_idp_metadata") + respStr := golden.Get(t, "TestSPMultipleAssertions") + TimeNow = func() time.Time { + rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Fri Apr 21 13:12:51 UTC 2017") + return rv + } + Clock = dsig.NewFakeClockAt(TimeNow()) + s := ServiceProvider{ + Key: mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey), + Certificate: mustParseCertificate(golden.Get(t, "cert_2017.pem")), + MetadataURL: mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/metadata"), + AcsURL: mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/acs"), + IDPMetadata: &EntityDescriptor{}, + } + err := xml.Unmarshal(idpMetadata, &s.IDPMetadata) + assert.Check(t, err) + + req := http.Request{PostForm: url.Values{}} + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(respStr)) + profile, err := s.ParseResponse(&req, []string{"id-3992f74e652d89c3cf1efd6c7e472abaac9bc917"}) + + assert.Check(t, err) + assert.Check(t, profile.Subject.NameID.Value != "admin@evil.com") +} diff --git a/testdata/TestSPMultipleAssertions b/testdata/TestSPMultipleAssertions new file mode 100644 index 00000000..bfa5cb34 --- /dev/null +++ b/testdata/TestSPMultipleAssertions @@ -0,0 +1,8 @@ +https://idp.secureworks.com/SAML2 +Authentication success.https://idp.secureworks.com/SAML2BMN0lUblP0gYGcw2PCyhwFZzkxY=F/2aaOQ3J/S6ULUd+gAuIclVueHEC2UfmtO2eR2oYb/YXub9E22yZe7eQgj2wdhYOvacVXN28QJJJG+K3Njwvi6b7mqf+T8N1YwaJW1fYAm28ayg4dEOTjHnjbRMZ6L+3cZPmPcFyE+edhCHEMnTLSqSvBnSyc1cwGdO9PmfWmt6PzUwf2nr2P5577Yc1FEQ9OtTx7ugWN3iPmjtLeTcpZfIDQX9+gSsh0KT+t61uWaYz+PJhtKnZQFeyr3uIxBTxv4wQ90FnmE4PiDvMksin5CDMfiMwd7pn7rNbk4EVHiDgSMkY6P4h8eWQwiqglOrQSZZr4BJgCoUbcNfZCq/7A==zZlTNJ+QcTp2yGH1ECXO3ry4GHhcs1CW3I6GPiPvtO+P6lyWxYdQd2RK/Hk9Kap6qpm/qom0rTwb +FU2I67Y2JdQ3T5QBJjGHbGHU1uMxVWkhJluoa0Lpm381zNCJTZp8PetoB8dnIGua9y1aL75v04CG +TzJ14I9/sW+apTkWj7xVQXutvVKETdn4kAy+L33HpriZjQNlcuAbqQj6OWsN4tGkLvNFZT40jQzp +/8/tOQE6n2+zn3I8hUePwjPQROUmCeK86CkF0yVCPQ/vOTsC00Uaeu/SPOUu5ot+/75NPyE8w5Ry +DgefdDXhYNmeuQtwGtcu/FI66atQMNTDoChXJQ==AQABrkinder@secureworks.comhttps://preview.docrocket-ross.test.octolabs.io/saml/metadataurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified +https://idp.secureworks.com/SAML2 +admin@evil.comhttps://preview.docrocket-ross.test.octolabs.io/saml/metadataurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified From 81735a50f6120900a1d1cb9c56ff92cb39c948b8 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Mon, 28 Nov 2022 08:56:08 -0500 Subject: [PATCH 36/58] fix lint errors, update github test configuration for current go versions --- .github/workflows/test.yml | 2 +- saml.go | 134 +++++++++++++++++++------------------ samlsp/middleware.go | 5 +- samlsp/request_tracker.go | 8 +-- service_provider.go | 2 +- 5 files changed, 78 insertions(+), 73 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca99f764..ee0c8f1e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.16.x', '1.17.x', '1.18.x' ] + go: [ '1.18.x', '1.19.x', '1.20.x' ] steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/saml.go b/saml.go index e559182e..b171e56d 100644 --- a/saml.go +++ b/saml.go @@ -1,13 +1,13 @@ // Package saml contains a partial implementation of the SAML standard in golang. // SAML is a standard for identity federation, i.e. either allowing a third party to authenticate your users or allowing third parties to rely on us to authenticate their users. // -// Introduction +// # Introduction // // In SAML parlance an Identity Provider (IDP) is a service that knows how to authenticate users. A Service Provider (SP) is a service that delegates authentication to an IDP. If you are building a service where users log in with someone else's credentials, then you are a Service Provider. This package supports implementing both service providers and identity providers. // // The core package contains the implementation of SAML. The package samlsp provides helper middleware suitable for use in Service Provider applications. The package samlidp provides a rudimentary IDP service that is useful for testing or as a starting point for other integrations. // -// Breaking Changes +// # Breaking Changes // // Version 0.4.0 introduces a few breaking changes to the _samlsp_ package in order to make the package more extensible, and to clean up the interfaces a bit. The default behavior remains the same, but you can now provide interface implementations of _RequestTracker_ (which tracks pending requests), _Session_ (which handles maintaining a session) and _OnError_ which handles reporting errors. // @@ -32,7 +32,7 @@ // - `CookieDomain` - Instead assign a custom CookieRequestTracker or CookieSessionProvider // - `CookieDomain` - Instead assign a custom CookieRequestTracker or CookieSessionProvider // -// Getting Started as a Service Provider +// # Getting Started as a Service Provider // // Let us assume we have a simple web application to protect. We'll modify this application so it uses SAML to authenticate users. // @@ -40,24 +40,27 @@ // package main // // import ( -// "fmt" -// "net/http" +// +// "fmt" +// "net/http" +// // ) // -// func hello(w http.ResponseWriter, r *http.Request) { -// fmt.Fprintf(w, "Hello, World!") -// } +// func hello(w http.ResponseWriter, r *http.Request) { +// fmt.Fprintf(w, "Hello, World!") +// } +// +// func main() { +// app := http.HandlerFunc(hello) +// http.Handle("/hello", app) +// http.ListenAndServe(":8000", nil) +// } // -// func main() { -// app := http.HandlerFunc(hello) -// http.Handle("/hello", app) -// http.ListenAndServe(":8000", nil) -// } // ``` // // Each service provider must have an self-signed X.509 key pair established. You can generate your own with something like this: // -// openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com" +// openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com" // // We will use `samlsp.Middleware` to wrap the endpoint we want to protect. Middleware provides both an `http.Handler` to serve the SAML specific URLs and a set of wrappers to require the user to be logged in. We also provide the URL where the service provider can fetch the metadata from the IDP at startup. In our case, we'll use [samltest.id](https://samltest.id/), an identity provider designed for testing. // @@ -65,57 +68,60 @@ // package main // // import ( -// "crypto/rsa" -// "crypto/tls" -// "crypto/x509" -// "fmt" -// "net/http" -// "net/url" -// -// "github.com/crewjam/saml/samlsp" +// +// "crypto/rsa" +// "crypto/tls" +// "crypto/x509" +// "fmt" +// "net/http" +// "net/url" +// +// "github.com/crewjam/saml/samlsp" +// // ) // -// func hello(w http.ResponseWriter, r *http.Request) { -// fmt.Fprintf(w, "Hello, %s!", samlsp.Token(r.Context()).Attributes.Get("cn")) -// } -// -// func main() { -// keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key") -// if err != nil { -// panic(err) // TODO handle error -// } -// keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) -// if err != nil { -// panic(err) // TODO handle error -// } -// -// idpMetadataURL, err := url.Parse("https://samltest.id/saml/idp") -// if err != nil { -// panic(err) // TODO handle error -// } -// -// rootURL, err := url.Parse("http://localhost:8000") -// if err != nil { -// panic(err) // TODO handle error -// } -// -// samlSP, _ := samlsp.New(samlsp.Options{ -// URL: *rootURL, -// Key: keyPair.PrivateKey.(*rsa.PrivateKey), -// Certificate: keyPair.Leaf, -// IDPMetadataURL: idpMetadataURL, -// }) -// app := http.HandlerFunc(hello) -// http.Handle("/hello", samlSP.RequireAccount(app)) -// http.Handle("/saml/", samlSP) -// http.ListenAndServe(":8000", nil) -// } +// func hello(w http.ResponseWriter, r *http.Request) { +// fmt.Fprintf(w, "Hello, %s!", samlsp.Token(r.Context()).Attributes.Get("cn")) +// } +// +// func main() { +// keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key") +// if err != nil { +// panic(err) // TODO handle error +// } +// keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) +// if err != nil { +// panic(err) // TODO handle error +// } +// +// idpMetadataURL, err := url.Parse("https://samltest.id/saml/idp") +// if err != nil { +// panic(err) // TODO handle error +// } +// +// rootURL, err := url.Parse("http://localhost:8000") +// if err != nil { +// panic(err) // TODO handle error +// } +// +// samlSP, _ := samlsp.New(samlsp.Options{ +// URL: *rootURL, +// Key: keyPair.PrivateKey.(*rsa.PrivateKey), +// Certificate: keyPair.Leaf, +// IDPMetadataURL: idpMetadataURL, +// }) +// app := http.HandlerFunc(hello) +// http.Handle("/hello", samlSP.RequireAccount(app)) +// http.Handle("/saml/", samlSP) +// http.ListenAndServe(":8000", nil) +// } +// // ``` // // Next we'll have to register our service provider with the identity provider to establish trust from the service provider to the IDP. For [samltest.id](https://samltest.id/), you can do something like: // -// mdpath=saml-test-$USER-$HOST.xml -// curl localhost:8000/saml/metadata > $mdpath +// mdpath=saml-test-$USER-$HOST.xml +// curl localhost:8000/saml/metadata > $mdpath // // Navigate to https://samltest.id/upload.php and upload the file you fetched. // @@ -133,11 +139,11 @@ // // 1. This time when `localhost:8000/hello` is requested there is a valid session and so the main content is served. // -// Getting Started as an Identity Provider +// # Getting Started as an Identity Provider // // Please see `example/idp/` for a substantially complete example of how to use the library and helpers to be an identity provider. // -// Support +// # Support // // The SAML standard is huge and complex with many dark corners and strange, unused features. This package implements the most commonly used subset of these features required to provide a single sign on experience. The package supports at least the subset of SAML known as [interoperable SAML](http://saml2int.org). // @@ -145,13 +151,13 @@ // // The package can produce signed SAML assertions, and can validate both signed and encrypted SAML assertions. It does not support signed or encrypted requests. // -// RelayState +// # RelayState // // The _RelayState_ parameter allows you to pass user state information across the authentication flow. The most common use for this is to allow a user to request a deep link into your site, be redirected through the SAML login flow, and upon successful completion, be directed to the originally requested link, rather than the root. // // Unfortunately, _RelayState_ is less useful than it could be. Firstly, it is not authenticated, so anything you supply must be signed to avoid XSS or CSRF. Secondly, it is limited to 80 bytes in length, which precludes signing. (See section 3.6.3.1 of SAMLProfiles.) // -// References +// # References // // The SAML specification is a collection of PDFs (sadly): // @@ -165,7 +171,7 @@ // // [SAMLtest](https://samltest.id/) is a testing ground for SAML service and identity providers. // -// Security Issues +// # Security Issues // // Please do not report security issues in the issue tracker. Rather, please contact me directly at ross@kndr.org ([PGP Key `78B6038B3B9DFB88`](https://keybase.io/crewjam)). package saml diff --git a/samlsp/middleware.go b/samlsp/middleware.go index 5ba75c62..557b0263 100644 --- a/samlsp/middleware.go +++ b/samlsp/middleware.go @@ -209,9 +209,8 @@ func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.R // // For example: // -// goji.Use(m.RequireAccount) -// goji.Use(RequireAttributeMiddleware("eduPersonAffiliation", "Staff")) -// +// goji.Use(m.RequireAccount) +// goji.Use(RequireAttributeMiddleware("eduPersonAffiliation", "Staff")) func RequireAttribute(name, value string) func(http.Handler) http.Handler { return func(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/samlsp/request_tracker.go b/samlsp/request_tracker.go index ea1a484d..f5477c8d 100644 --- a/samlsp/request_tracker.go +++ b/samlsp/request_tracker.go @@ -8,11 +8,11 @@ import ( // // There are two main reasons for this: // -// 1. When the middleware initiates an authentication request it must track the original URL -// in order to redirect the user to the right place after the authentication completes. +// 1. When the middleware initiates an authentication request it must track the original URL +// in order to redirect the user to the right place after the authentication completes. // -// 2. After the authentication completes, we want to ensure that the user presenting the -// assertion is actually the one the request it, to mitigate request forgeries. +// 2. After the authentication completes, we want to ensure that the user presenting the +// assertion is actually the one the request it, to mitigate request forgeries. type RequestTracker interface { // TrackRequest starts tracking the SAML request with the given ID. It returns an // `index` that should be used as the RelayState in the SAMl request flow. diff --git a/service_provider.go b/service_provider.go index e5695a79..db6a9413 100644 --- a/service_provider.go +++ b/service_provider.go @@ -934,7 +934,7 @@ func (sp *ServiceProvider) parseResponse(responseEl *etree.Element, possibleRequ // if we have at least one assertion, return the first one. It is almost universally true that valid responses // contain only one assertion. This is less that fully correct, but we didn't realize that there could be more - // than one assertion at the time of establishing the public interface of ParseXMLResponse(), so for compatability + // than one assertion at the time of establishing the public interface of ParseXMLResponse(), so for compatibility // we return the first one. return &assertions[0], nil } From b3297164df077d8920470e8e4ad8a721bdcf1e1e Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Mon, 28 Nov 2022 08:57:42 -0500 Subject: [PATCH 37/58] update github test configuration for current go versions --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee0c8f1e..a4ab9e6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.18.x', '1.19.x', '1.20.x' ] + go: [ '1.17.x', '1.18.x', '1.19.x'] steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 From d951aa2d145a90c26443e583ec011273ce97573a Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Sun, 11 Dec 2022 13:59:03 +0100 Subject: [PATCH 38/58] Send artifact requests with Content-Type text/xml (#472) --- service_provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/service_provider.go b/service_provider.go index db6a9413..3c7a91e0 100644 --- a/service_provider.go +++ b/service_provider.go @@ -622,6 +622,7 @@ func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID retErr.PrivateErr = err return nil, retErr } + req.Header.Set("Content-Type", "text/xml") httpClient := sp.HTTPClient if httpClient == nil { From 5aa5021169e9991cbd474309005efee7ba190617 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 31 Dec 2022 07:37:58 -0500 Subject: [PATCH 39/58] Add log.Fatal to trivial example (#465) --- example/trivial/trivial.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/trivial/trivial.go b/example/trivial/trivial.go index 0da34c85..7562b5dd 100644 --- a/example/trivial/trivial.go +++ b/example/trivial/trivial.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "log" "github.com/crewjam/saml/samlsp" ) @@ -71,5 +72,5 @@ func main() { http.Handle("/hello", samlMiddleware.RequireAccount(app)) http.Handle("/saml/", samlMiddleware) http.Handle("/logout", slo) - http.ListenAndServe(":8000", nil) + log.Fatal(http.ListenAndServe(":8000", nil)) } From 2d6a7927aae7e5af53e993d20ab4cedb6d722e13 Mon Sep 17 00:00:00 2001 From: Simon Schuetz Date: Sun, 1 Jan 2023 17:33:42 +0100 Subject: [PATCH 40/58] add input validation for xml with no root (#470) --- service_provider.go | 8 ++++++++ service_provider_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/service_provider.go b/service_provider.go index 3c7a91e0..3eac33f7 100644 --- a/service_provider.go +++ b/service_provider.go @@ -696,6 +696,10 @@ func (sp *ServiceProvider) ParseXMLArtifactResponse(soapResponseXML []byte, poss retErr.PrivateErr = fmt.Errorf("cannot unmarshal response: %s", err) return nil, retErr } + if doc.Root() == nil { + retErr.PrivateErr = errors.New("invalid xml: no root") + return nil, retErr + } if doc.Root().NamespaceURI() != "http://schemas.xmlsoap.org/soap/envelope/" || doc.Root().Tag != "Envelope" { retErr.PrivateErr = fmt.Errorf("expected a SOAP Envelope") @@ -803,6 +807,10 @@ func (sp *ServiceProvider) ParseXMLResponse(decodedResponseXML []byte, possibleR retErr.PrivateErr = err return nil, retErr } + if doc.Root() == nil { + retErr.PrivateErr = errors.New("invalid xml: no root") + return nil, retErr + } assertion, err := sp.parseResponse(doc.Root(), possibleRequestIDs, now, signatureRequired) if err != nil { diff --git a/service_provider_test.go b/service_provider_test.go index c5ad9dd2..a9b3c50a 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -1786,6 +1786,44 @@ func TestParseBadXMLArtifactResponse(t *testing.T) { assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "failed to decrypt EncryptedAssertion: certificate does not match provided key")) assert.Check(t, is.Nil(assertion)) + + // no input + assertion, err = sp.ParseXMLArtifactResponse([]byte(""), possibleReqIDs, reqID) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "invalid xml: no root")) + assert.Check(t, is.Nil(assertion)) + + assertion, err = sp.ParseXMLArtifactResponse([]byte(""), []string{}) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, + "invalid xml: no root")) + assert.Check(t, is.Nil(assertion)) + + assertion, err = sp.ParseXMLResponse([]byte(" Date: Wed, 4 Jan 2023 10:31:11 +0800 Subject: [PATCH 41/58] fix time format (#481) --- metadata_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata_test.go b/metadata_test.go index d9dcdbca..cf1a4659 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -88,7 +88,7 @@ func TestCanParseMetadata(t *testing.T) { } func TestCanProduceSPMetadata(t *testing.T) { - validUntil, _ := time.Parse("2006-02-01T15:04:05.000000", "2013-10-03T00:32:19.104000") + validUntil, _ := time.Parse("2006-01-02T15:04:05.000000", "2013-03-10T00:32:19.104000") AuthnRequestsSigned := true WantAssertionsSigned := true metadata := EntityDescriptor{ From f7b2804119759ab2dae69829de2595586a46fb79 Mon Sep 17 00:00:00 2001 From: David Sharnoff Date: Thu, 12 Jan 2023 12:12:10 -0800 Subject: [PATCH 42/58] get rid of extra go.mods (#475) --- .github/workflows/test.yml | 2 -- example/go.mod | 17 --------- example/go.sum | 71 -------------------------------------- go.mod | 3 ++ go.sum | 5 ++- samlidp/go.mod | 14 -------- samlidp/go.sum | 69 ------------------------------------ 7 files changed, 7 insertions(+), 174 deletions(-) delete mode 100644 example/go.mod delete mode 100644 example/go.sum delete mode 100644 samlidp/go.mod delete mode 100644 samlidp/go.sum diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4ab9e6f..60f3f8f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,5 +24,3 @@ jobs: - name: Run Go tests run: | go test -v ./... - (cd samlidp && go test -v ./...) - (cd example && go test -v ./...) diff --git a/example/go.mod b/example/go.mod deleted file mode 100644 index 94522e88..00000000 --- a/example/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -module github.com/crewjam/saml/example - -replace github.com/crewjam/saml => ../ - -replace github.com/crewjam/saml/samlidp => ../samlidp - -go 1.16 - -require ( - github.com/crewjam/saml v0.0.0-00010101000000-000000000000 - github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 - github.com/kr/pretty v0.3.0 - github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed -) - -require github.com/crewjam/saml/samlidp v0.0.0-00010101000000-000000000000 diff --git a/example/go.sum b/example/go.sum deleted file mode 100644 index d17df130..00000000 --- a/example/go.sum +++ /dev/null @@ -1,71 +0,0 @@ -github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/creack/pty v1.1.9/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= -github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.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/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= -github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= -github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= -github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= -golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/go.mod b/go.mod index ad8db0d0..a645e86b 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,14 @@ require ( github.com/beevik/etree v1.1.0 github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dchest/uniuri v1.2.0 github.com/golang-jwt/jwt/v4 v4.4.2 github.com/google/go-cmp v0.5.8 + github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.1.1 + github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index bdad7a05..82c9802d 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3p github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= +github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= 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/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= @@ -37,6 +39,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -48,7 +52,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/samlidp/go.mod b/samlidp/go.mod deleted file mode 100644 index a0f1f7c1..00000000 --- a/samlidp/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/crewjam/saml/samlidp - -replace github.com/crewjam/saml => ../ - -go 1.16 - -require ( - github.com/crewjam/saml v0.0.0-00010101000000-000000000000 - github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/mattermost/xml-roundtrip-validator v0.1.0 - github.com/zenazn/goji v1.0.1 - golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed - gotest.tools v2.2.0+incompatible -) diff --git a/samlidp/go.sum b/samlidp/go.sum deleted file mode 100644 index d9df064d..00000000 --- a/samlidp/go.sum +++ /dev/null @@ -1,69 +0,0 @@ -github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.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/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= -github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= -github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= -github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= -golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From 9eb285e9692fb145910a809fd52fde6b97f4e98f Mon Sep 17 00:00:00 2001 From: Chris Rollins <3865054+chrisrollins65@users.noreply.github.com> Date: Thu, 12 Jan 2023 21:13:48 +0100 Subject: [PATCH 43/58] Fix to allow IDP Initiated logins to not rely on inexistent tracked request cookie (#463) * Fix to allow IDP Initiated logins to not rely on inexistent tracked request cookie * Change module package name * Using replace to change module * Undo go.mod changes --- samlsp/middleware.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/samlsp/middleware.go b/samlsp/middleware.go index 557b0263..834a79c1 100644 --- a/samlsp/middleware.go +++ b/samlsp/middleware.go @@ -186,12 +186,19 @@ func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.R if trackedRequestIndex := r.Form.Get("RelayState"); trackedRequestIndex != "" { trackedRequest, err := m.RequestTracker.GetTrackedRequest(r, trackedRequestIndex) if err != nil { - m.OnError(w, r, err) - return - } - m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex) + if err == http.ErrNoCookie && m.ServiceProvider.AllowIDPInitiated { + if uri := r.Form.Get("RelayState"); uri != "" { + redirectURI = uri + } + } else { + m.OnError(w, r, err) + return + } + } else { + m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex) - redirectURI = trackedRequest.URI + redirectURI = trackedRequest.URI + } } if err := m.Session.CreateSession(w, r, assertion); err != nil { From c77d60c62bd6b0d4421156a43dd34dfb99bd586d Mon Sep 17 00:00:00 2001 From: Stephen Blackstone Date: Thu, 12 Jan 2023 15:14:28 -0500 Subject: [PATCH 44/58] This patch adds a new option to `Options` which allows the user to specify the name of the cookie that will be used when creating sessions via CookieSessionProvider. (#482) Improve test case for CookieName to include default --- samlsp/new.go | 7 ++++++- samlsp/new_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 samlsp/new_test.go diff --git a/samlsp/new.go b/samlsp/new.go index b68dad06..81fa75f6 100644 --- a/samlsp/new.go +++ b/samlsp/new.go @@ -28,6 +28,7 @@ type Options struct { ForceAuthn bool // TODO(ross): this should be *bool RequestedAuthnContext *saml.RequestedAuthnContext CookieSameSite http.SameSite + CookieName string RelayStateFunc func(w http.ResponseWriter, r *http.Request) string LogoutBindings []string } @@ -47,8 +48,12 @@ func DefaultSessionCodec(opts Options) JWTSessionCodec { // DefaultSessionProvider returns the default SessionProvider for the provided options, // a CookieSessionProvider configured to store sessions in a cookie. func DefaultSessionProvider(opts Options) CookieSessionProvider { + cookieName := opts.CookieName + if cookieName == "" { + cookieName = defaultSessionCookieName + } return CookieSessionProvider{ - Name: defaultSessionCookieName, + Name: cookieName, Domain: opts.URL.Host, MaxAge: defaultSessionMaxAge, HTTPOnly: true, diff --git a/samlsp/new_test.go b/samlsp/new_test.go new file mode 100644 index 00000000..7f0a760a --- /dev/null +++ b/samlsp/new_test.go @@ -0,0 +1,34 @@ +package samlsp + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gotest.tools/assert" +) + +func TestNewCanAcceptCookieName(t *testing.T) { + + testCases := []struct { + testName string + cookieName string + expected string + }{ + {"Works with alt name", "altCookie", "altCookie"}, + {"Works with default", "", "token"}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + opts := Options{ + CookieName: tc.cookieName, + } + sp, err := New(opts) + require.Nil(t, err) + cookieProvider := sp.Session.(CookieSessionProvider) + assert.Equal(t, tc.expected, cookieProvider.Name) + + }) + } + +} From f892c9f9ba1f81462974056af4030883d60105d2 Mon Sep 17 00:00:00 2001 From: Mathieu Deous <393165+mdeous@users.noreply.github.com> Date: Thu, 12 Jan 2023 21:15:36 +0100 Subject: [PATCH 45/58] fix(idp): use a regular expression to capture session id in url (#462) * capture session id with regexp * revert auto formatting * revert auto formatting --- samlidp/samlidp.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samlidp/samlidp.go b/samlidp/samlidp.go index f22ca27c..2e94385e 100644 --- a/samlidp/samlidp.go +++ b/samlidp/samlidp.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "net/http" "net/url" + "regexp" "sync" "github.com/zenazn/goji/web" @@ -110,9 +111,10 @@ func (s *Server) InitializeHTTP() { mux.Put("/users/:id", s.HandlePutUser) mux.Delete("/users/:id", s.HandleDeleteUser) + sessionPath := regexp.MustCompile("/sessions/(?P.*)") mux.Get("/sessions/", s.HandleListSessions) - mux.Get("/sessions/:id", s.HandleGetSession) - mux.Delete("/sessions/:id", s.HandleDeleteSession) + mux.Get(sessionPath, s.HandleGetSession) + mux.Delete(sessionPath, s.HandleDeleteSession) mux.Get("/shortcuts/", s.HandleListShortcuts) mux.Get("/shortcuts/:id", s.HandleGetShortcut) From 9e43d2e4020725181065f9b933b8820dc16253c8 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Thu, 12 Jan 2023 21:37:49 +0100 Subject: [PATCH 46/58] feat: Add PostBinding method on IDP authn. requests (#453) --- identity_provider.go | 90 +++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/identity_provider.go b/identity_provider.go index 4c7282ae..ffde2a0d 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -875,12 +875,23 @@ func (req *IdpAuthnRequest) MakeAssertionEl() error { return nil } -// WriteResponse writes the `Response` to the http.ResponseWriter. If -// `Response` is not already set, it calls MakeResponse to produce it. -func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error { +// IdpAuthnRequestForm contans HTML form information to be submitted to the +// SAML HTTP POST binding ACS. +type IdpAuthnRequestForm struct { + URL string + SAMLResponse string + RelayState string +} + +// PostBinding creates the HTTP POST form information for this +// `IdpAuthnRequest`. If `Response` is not already set, it calls MakeResponse +// to produce it. +func (req *IdpAuthnRequest) PostBinding() (IdpAuthnRequestForm, error) { + var form IdpAuthnRequestForm + if req.ResponseEl == nil { if err := req.MakeResponse(); err != nil { - return err + return form, err } } @@ -888,45 +899,48 @@ func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error { doc.SetRoot(req.ResponseEl) responseBuf, err := doc.WriteToBytes() if err != nil { - return err + return form, err } - // the only supported binding is the HTTP-POST binding - switch req.ACSEndpoint.Binding { - case HTTPPostBinding: - tmpl := template.Must(template.New("saml-post-form").Parse(`` + - `
` + - `` + - `` + - `` + - `
` + - `` + - `` + - ``)) - data := struct { - URL string - SAMLResponse string - RelayState string - }{ - URL: req.ACSEndpoint.Location, - SAMLResponse: base64.StdEncoding.EncodeToString(responseBuf), - RelayState: req.RelayState, - } - - buf := bytes.NewBuffer(nil) - if err := tmpl.Execute(buf, data); err != nil { - return err - } - if _, err := io.Copy(w, buf); err != nil { - return err - } - return nil - - default: - return fmt.Errorf("%s: unsupported binding %s", + if req.ACSEndpoint.Binding != HTTPPostBinding { + return form, fmt.Errorf("%s: unsupported binding %s", req.ServiceProviderMetadata.EntityID, req.ACSEndpoint.Binding) } + + form.URL = req.ACSEndpoint.Location + form.SAMLResponse = base64.StdEncoding.EncodeToString(responseBuf) + form.RelayState = req.RelayState + + return form, nil +} + +// WriteResponse writes the `Response` to the http.ResponseWriter. If +// `Response` is not already set, it calls MakeResponse to produce it. +func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error { + form, err := req.PostBinding() + if err != nil { + return err + } + + tmpl := template.Must(template.New("saml-post-form").Parse(`` + + `
` + + `` + + `` + + `` + + `
` + + `` + + `` + + ``)) + + buf := bytes.NewBuffer(nil) + if err := tmpl.Execute(buf, form); err != nil { + return err + } + if _, err := io.Copy(w, buf); err != nil { + return err + } + return nil } // getSPEncryptionCert returns the certificate which we can use to encrypt things From f98e7590b94634c0f65e2e435ad7d3c6f58840c6 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Thu, 12 Jan 2023 21:39:48 +0100 Subject: [PATCH 47/58] feat: Add Subject ID attribute support (#454) --- identity_provider.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/identity_provider.go b/identity_provider.go index ffde2a0d..47052916 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -36,7 +36,10 @@ type Session struct { ExpireTime time.Time Index string - NameID string + NameID string + NameIDFormat string + SubjectID string + Groups []string UserName string UserEmail string @@ -734,6 +737,19 @@ func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Sessio }) } + if session.SubjectID != "" { + attributes = append(attributes, Attribute{ + Name: "urn:oasis:names:tc:SAML:attribute:subject-id", + NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + Values: []AttributeValue{ + { + Type: "xs:string", + Value: session.SubjectID, + }, + }, + }) + } + // allow for some clock skew in the validity period using the // issuer's apparent clock. notBefore := req.Now.Add(-1 * MaxClockSkew) @@ -743,6 +759,12 @@ func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Sessio notOnOrAfterAfter = notBefore.Add(MaxIssueDelay) } + nameIDFormat := "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + + if session.NameIDFormat != "" { + nameIDFormat = session.NameIDFormat + } + req.Assertion = &Assertion{ ID: fmt.Sprintf("id-%x", randomBytes(20)), IssueInstant: TimeNow(), @@ -753,7 +775,7 @@ func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Sessio }, Subject: &Subject{ NameID: &NameID{ - Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + Format: nameIDFormat, NameQualifier: req.IDP.Metadata().EntityID, SPNameQualifier: req.ServiceProviderMetadata.EntityID, Value: session.NameID, From 8b7e45896ba1bfa853358a970b860a549e3237d8 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Thu, 12 Jan 2023 15:42:53 -0500 Subject: [PATCH 48/58] fmt --- example/trivial/trivial.go | 2 +- samlidp/samlidp.go | 16 ++++++++-------- xmlenc/fuzz_test.go | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/example/trivial/trivial.go b/example/trivial/trivial.go index 7562b5dd..1d96e63a 100644 --- a/example/trivial/trivial.go +++ b/example/trivial/trivial.go @@ -6,9 +6,9 @@ import ( "crypto/tls" "crypto/x509" "fmt" + "log" "net/http" "net/url" - "log" "github.com/crewjam/saml/samlsp" ) diff --git a/samlidp/samlidp.go b/samlidp/samlidp.go index 2e94385e..0ed28b2f 100644 --- a/samlidp/samlidp.go +++ b/samlidp/samlidp.go @@ -27,14 +27,14 @@ type Options struct { // Server represents an IDP server. The server provides the following URLs: // -// /metadata - the SAML metadata -// /sso - the SAML endpoint to initiate an authentication flow -// /login - prompt for a username and password if no session established -// /login/:shortcut - kick off an IDP-initiated authentication flow -// /services - RESTful interface to Service objects -// /users - RESTful interface to User objects -// /sessions - RESTful interface to Session objects -// /shortcuts - RESTful interface to Shortcut objects +// /metadata - the SAML metadata +// /sso - the SAML endpoint to initiate an authentication flow +// /login - prompt for a username and password if no session established +// /login/:shortcut - kick off an IDP-initiated authentication flow +// /services - RESTful interface to Service objects +// /users - RESTful interface to User objects +// /sessions - RESTful interface to Session objects +// /shortcuts - RESTful interface to Shortcut objects type Server struct { http.Handler idpConfigMu sync.RWMutex // protects calls into the IDP diff --git a/xmlenc/fuzz_test.go b/xmlenc/fuzz_test.go index 07a40443..2d83e49c 100644 --- a/xmlenc/fuzz_test.go +++ b/xmlenc/fuzz_test.go @@ -1,3 +1,4 @@ +//go:build gofuzz // +build gofuzz package xmlenc From 957d92a38d67c03c2ec81c605699571e1b0bf5c7 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Thu, 12 Jan 2023 15:47:41 -0500 Subject: [PATCH 49/58] update test expectations --- samlidp/session_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samlidp/session_test.go b/samlidp/session_test.go index d24684f3..7f5d1351 100644 --- a/samlidp/session_test.go +++ b/samlidp/session_test.go @@ -33,7 +33,7 @@ func TestSessionsCrud(t *testing.T) { assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("session=AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=; Path=/; Max-Age=3600; HttpOnly; Secure", w.Header().Get("Set-Cookie"))) - assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", + assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", string(w.Body.Bytes()))) w = httptest.NewRecorder() @@ -41,14 +41,14 @@ func TestSessionsCrud(t *testing.T) { r.Header.Set("Cookie", "session=AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=") test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", + assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", string(w.Body.Bytes()))) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "https://idp.example.com/sessions/AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", + assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", string(w.Body.Bytes()))) w = httptest.NewRecorder() From 5a6e8ccedf6505fddbeafcee773779f17bc227e3 Mon Sep 17 00:00:00 2001 From: Paul Greenberg Date: Thu, 12 Jan 2023 15:52:14 -0500 Subject: [PATCH 50/58] update etreeutils.TransformExcC14n signature (#478) * update etreeutils.TransformExcC14n signature The invocation does not match the current function signature: ``` github.com/crewjam/saml@v0.4.10/schema.go:767:41: not enough arguments in call to etreeutils.TransformExcC14n | have (*etree.Element, string) | want (*etree.Element, string, bool) ``` * Update go.mod * upgrade to github.com/russellhaering/goxmldsig v1.2.0 * update go.sum * update github.com/russellhaering/goxmldsig * upgrade to github.com/russellhaering/goxmldsig v1.2.0 --- go.mod | 3 ++- go.sum | 5 +++-- schema.go | 2 +- service_provider_test.go | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index a645e86b..cb67896d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,8 @@ require ( github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect - github.com/russellhaering/goxmldsig v1.1.1 + github.com/russellhaering/goxmldsig v1.2.0 + github.com/stretchr/testify v1.6.1 github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 82c9802d..657298fb 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= -github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= +github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= @@ -52,6 +52,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/schema.go b/schema.go index 9181e6f4..13ea2ef6 100644 --- a/schema.go +++ b/schema.go @@ -764,7 +764,7 @@ func (a *Assertion) Element() *etree.Element { for _, attributeStatement := range a.AttributeStatements { el.AddChild(attributeStatement.Element()) } - err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList) + err := etreeutils.TransformExcC14n(el, canonicalizerPrefixList, false) if err != nil { panic(err) } diff --git a/service_provider_test.go b/service_provider_test.go index a9b3c50a..cdf5c723 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -771,7 +771,7 @@ func TestSPRejectsInjectedComment(t *testing.T) { // it *MUST NOT* validate { x, _ := base64.StdEncoding.DecodeString(string(SamlResponse)) - y := strings.Replace(string(x), "ross@octolabs.io", "ross@octolabs.io.example.com", 1) + y := strings.Replace(string(x), "ross@octolabs.io", "ross@octolabs.io.example.com", 1) SamlResponse = []byte(base64.StdEncoding.EncodeToString([]byte(y))) req := http.Request{PostForm: url.Values{}} From b7ab93b36f67ebac391f8bf360834c7a54d7f17c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:01:29 -0500 Subject: [PATCH 51/58] Bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (#459) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.8 to 0.5.9. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.8...v0.5.9) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cb67896d..f61880c2 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v1.2.0 github.com/golang-jwt/jwt/v4 v4.4.2 - github.com/google/go-cmp v0.5.8 + github.com/google/go-cmp v0.5.9 github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 657298fb..94f8e937 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= 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/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= From cfc9c7538d2cca2fcaec82697726745b5b899a35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jan 2023 16:05:50 -0500 Subject: [PATCH 52/58] Bump github.com/golang-jwt/jwt/v4 from 4.4.2 to 4.4.3 (#467) Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.4.2 to 4.4.3. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v4.4.2...v4.4.3) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f61880c2..303bd33f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/crewjam/httperr v0.2.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v1.2.0 - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-cmp v0.5.9 github.com/kr/pretty v0.3.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 diff --git a/go.sum b/go.sum index 94f8e937..cd8311e7 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ 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/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= -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-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= From 73bf1ff77a4e30562e2897eacf01d5add3b468af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 08:32:09 -0500 Subject: [PATCH 53/58] Bump github.com/stretchr/testify from 1.6.1 to 1.8.1 (#483) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.1 to 1.8.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.6.1...v1.8.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 303bd33f..db70a131 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.16 require ( github.com/beevik/etree v1.1.0 github.com/crewjam/httperr v0.2.0 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/uniuri v1.2.0 github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-cmp v0.5.9 @@ -13,7 +12,7 @@ require ( github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.2.0 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.8.1 github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index cd8311e7..1c955ee7 100644 --- a/go.sum +++ b/go.sum @@ -36,9 +36,14 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= @@ -57,7 +62,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From 5049389b651c221c331aba4b07060159c11c401a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Jan 2023 08:32:20 -0500 Subject: [PATCH 54/58] Bump github.com/kr/pretty from 0.3.0 to 0.3.1 (#484) Bumps [github.com/kr/pretty](https://github.com/kr/pretty) from 0.3.0 to 0.3.1. - [Release notes](https://github.com/kr/pretty/releases) - [Commits](https://github.com/kr/pretty/compare/v0.3.0...v0.3.1) --- updated-dependencies: - dependency-name: github.com/kr/pretty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db70a131..5500d63a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/dchest/uniuri v1.2.0 github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/go-cmp v0.5.9 - github.com/kr/pretty v0.3.0 + github.com/kr/pretty v0.3.1 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect github.com/russellhaering/goxmldsig v1.2.0 diff --git a/go.sum b/go.sum index 1c955ee7..a2bb4b19 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,9 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -31,8 +32,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 2aeb2efd8c405fb7b40aa75b591b0828ef552670 Mon Sep 17 00:00:00 2001 From: mattb18 Date: Thu, 26 Jan 2023 12:00:33 +0000 Subject: [PATCH 55/58] Update README example to use displayName attribute (#486) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71f24786..c0b98058 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ import ( ) func hello(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "cn")) + fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName")) } func main() { From 8e9236867d176ad6338c870a84e2039aef8a5021 Mon Sep 17 00:00:00 2001 From: Ross Kinder Date: Wed, 22 Mar 2023 15:22:00 -0400 Subject: [PATCH 56/58] Merge pull request from GHSA-5mqj-xc49-246p https://github.com/crewjam/saml/security/advisories/GHSA-5mqj-xc49-246p --- flate.go | 31 +++++++++++++++++++++++++++++++ identity_provider.go | 3 +-- identity_provider_test.go | 28 ++++++++++++++++++++++++++++ service_provider.go | 2 +- 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 flate.go diff --git a/flate.go b/flate.go new file mode 100644 index 00000000..4d14e780 --- /dev/null +++ b/flate.go @@ -0,0 +1,31 @@ +package saml + +import ( + "compress/flate" + "fmt" + "io" +) + +const flateUncompressLimit = 10 * 1024 * 1024 // 10MB + +func newSaferFlateReader(r io.Reader) io.ReadCloser { + return &saferFlateReader{r: flate.NewReader(r)} +} + +type saferFlateReader struct { + r io.ReadCloser + count int +} + +func (r *saferFlateReader) Read(p []byte) (n int, err error) { + if r.count+len(p) > flateUncompressLimit { + return 0, fmt.Errorf("flate: uncompress limit exceeded (%d bytes)", flateUncompressLimit) + } + n, err = r.r.Read(p) + r.count += n + return n, err +} + +func (r *saferFlateReader) Close() error { + return r.r.Close() +} diff --git a/identity_provider.go b/identity_provider.go index 47052916..bcea5828 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -2,7 +2,6 @@ package saml import ( "bytes" - "compress/flate" "crypto" "crypto/tls" "crypto/x509" @@ -363,7 +362,7 @@ func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnReques if err != nil { return nil, fmt.Errorf("cannot decode request: %s", err) } - req.RequestBuffer, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(compressedRequest))) + req.RequestBuffer, err = ioutil.ReadAll(newSaferFlateReader(bytes.NewReader(compressedRequest))) if err != nil { return nil, fmt.Errorf("cannot decompress request: %s", err) } diff --git a/identity_provider_test.go b/identity_provider_test.go index 0c602b53..a88480ef 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -1,6 +1,8 @@ package saml import ( + "bytes" + "compress/flate" "crypto" "crypto/rsa" "crypto/x509" @@ -1013,3 +1015,29 @@ func TestIDPNoDestination(t *testing.T) { err = req.MakeResponse() assert.Check(t, err) } + +func TestIDPRejectDecompressionBomb(t *testing.T) { + test := NewIdentifyProviderTest(t) + test.IDP.SessionProvider = &mockSessionProvider{ + GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { + fmt.Fprintf(w, "RelayState: %s\nSAMLRequest: %s", + req.RelayState, req.RequestBuffer) + return nil + }, + } + + //w := httptest.NewRecorder() + + data := bytes.Repeat([]byte("a"), 768*1024*1024) + var compressed bytes.Buffer + w, _ := flate.NewWriter(&compressed, flate.BestCompression) + w.Write(data) + w.Close() + encoded := base64.StdEncoding.EncodeToString(compressed.Bytes()) + + r, _ := http.NewRequest("GET", "/dontcare?"+url.Values{ + "SAMLRequest": {encoded}, + }.Encode(), nil) + _, err := NewIdpAuthnRequest(&test.IDP, r) + assert.Error(t, err, "cannot decompress request: flate: uncompress limit exceeded (10485760 bytes)") +} diff --git a/service_provider.go b/service_provider.go index 3eac33f7..6f6e7f4f 100644 --- a/service_provider.go +++ b/service_provider.go @@ -1524,7 +1524,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str } retErr.Response = string(rawResponseBuf) - gr, err := ioutil.ReadAll(flate.NewReader(bytes.NewBuffer(rawResponseBuf))) + gr, err := ioutil.ReadAll(newSaferFlateReader(bytes.NewBuffer(rawResponseBuf))) if err != nil { retErr.PrivateErr = err return retErr From f9e67165dd9c33505409fc4ee2393528b6bdc52a Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Tue, 11 Apr 2023 15:28:45 -0400 Subject: [PATCH 57/58] Update golangci-lint version, linting fixes. (#511) --- .github/workflows/lint.yml | 2 +- .golangci.yml | 34 +++---- example/idp/idp.go | 1 + example/service.go | 22 +++-- example/trivial/trivial.go | 10 +- identity_provider.go | 11 ++- identity_provider_go117_test.go | 6 +- identity_provider_test.go | 16 ++-- logger/logger.go | 1 + metadata.go | 2 +- samlidp/samlidp.go | 4 +- samlidp/samlidp_test.go | 8 +- samlidp/service.go | 20 ++-- samlidp/service_test.go | 6 +- samlidp/session.go | 39 +++++--- samlidp/session_test.go | 10 +- samlidp/shortcut.go | 19 ++-- samlidp/shortcut_test.go | 10 +- samlidp/user.go | 19 +++- samlidp/user_test.go | 8 +- samlsp/error.go | 2 +- samlsp/fetch_metadata.go | 8 +- samlsp/fetch_metadata_go117_test.go | 7 +- samlsp/fetch_metadata_test.go | 3 +- samlsp/middleware.go | 33 +++++-- samlsp/middleware_test.go | 4 +- samlsp/request_tracker_jwt.go | 14 +-- samlsp/session_cookie_test.go | 8 +- samlsp/session_jwt.go | 2 +- schema.go | 26 +++--- service_provider.go | 138 +++++++++++++++------------- service_provider_test.go | 41 +++++---- testsaml/parse.go | 1 + util.go | 1 + xmlenc/cbc.go | 2 +- xmlenc/decrypt.go | 1 + xmlenc/decrypt_test.go | 1 - xmlenc/digest.go | 1 + 38 files changed, 316 insertions(+), 225 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b79baa9e..f1640505 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,4 +15,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.46.2 + version: v1.52.2 diff --git a/.golangci.yml b/.golangci.yml index 4fbc0405..f93ef23b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,41 +7,35 @@ linters: enable: - - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] - - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true] - - gosec # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false] - - misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] - - deadcode # Finds unused code [fast: true, auto-fix: false] - - revive # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] - - unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false] - - disable: - # TODO(ross): fix errors reported by these checkers and enable them - bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] - depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] - - dupl # Tool for code clone detection [fast: true, auto-fix: false] - errcheck # Inspects source code for security problems [fast: true, auto-fix: false] - - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] - - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] - - goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] - gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false] - gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true] + - gosec # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false] - gosimple # Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false] - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false] - ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] - - interfacer # Linter that suggests narrower interface types [fast: false, auto-fix: false] - - lll # Reports long lines [fast: true, auto-fix: false] - - maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false] + - misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] - nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] - prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false] - - scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false] + - revive # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false] - - structcheck # Finds unused struct fields [fast: true, auto-fix: false] - stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false] - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false] + - unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false] - unparam # Reports unused function parameters [fast: false, auto-fix: false] - unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] - - varcheck # Finds unused global variables and constants [fast: true, auto-fix: false] + + disable: + # TODO(ross): fix errors reported by these checkers and enable them + - dupl # Tool for code clone detection [fast: true, auto-fix: false] + - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] + - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] + - goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] + - lll # Reports long lines [fast: true, auto-fix: false] linters-settings: goimports: local-prefixes: github.com/crewjam/saml diff --git a/example/idp/idp.go b/example/idp/idp.go index 6069d379..4e47a56a 100644 --- a/example/idp/idp.go +++ b/example/idp/idp.go @@ -1,3 +1,4 @@ +// Package main contains an example identity provider implementation. package main import ( diff --git a/example/service.go b/example/service.go index c153b65f..5b6ddb27 100644 --- a/example/service.go +++ b/example/service.go @@ -32,7 +32,7 @@ type Link struct { } // CreateLink handles requests to create links -func CreateLink(c web.C, w http.ResponseWriter, r *http.Request) { +func CreateLink(_ web.C, w http.ResponseWriter, r *http.Request) { account := r.Header.Get("X-Remote-User") l := Link{ ShortLink: uniuri.New(), @@ -42,22 +42,20 @@ func CreateLink(c web.C, w http.ResponseWriter, r *http.Request) { links[l.ShortLink] = l fmt.Fprintf(w, "%s\n", l.ShortLink) - return } // ServeLink handles requests to redirect to a link -func ServeLink(c web.C, w http.ResponseWriter, r *http.Request) { +func ServeLink(_ web.C, w http.ResponseWriter, r *http.Request) { l, ok := links[strings.TrimPrefix(r.URL.Path, "/")] if !ok { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } http.Redirect(w, r, l.Target, http.StatusFound) - return } // ListLinks returns a list of the current user's links -func ListLinks(c web.C, w http.ResponseWriter, r *http.Request) { +func ListLinks(_ web.C, w http.ResponseWriter, r *http.Request) { account := r.Header.Get("X-Remote-User") for _, l := range links { if l.Owner == account { @@ -145,14 +143,24 @@ func main() { spURL := *idpMetadataURL spURL.Path = "/services/sp" - http.Post(spURL.String(), "text/xml", bytes.NewReader(spMetadataBuf)) + resp, err := http.Post(spURL.String(), "text/xml", bytes.NewReader(spMetadataBuf)) + + if err != nil { + panic(err) + } + + if err := resp.Body.Close(); err != nil { + panic(err) + } goji.Handle("/saml/*", samlSP) authMux := web.New() authMux.Use(samlSP.RequireAccount) authMux.Get("/whoami", func(w http.ResponseWriter, r *http.Request) { - pretty.Fprintf(w, "%# v", r) + if _, err := pretty.Fprintf(w, "%# v", r); err != nil { + panic(err) + } }) authMux.Post("/", CreateLink) authMux.Get("/", ListLinks) diff --git a/example/trivial/trivial.go b/example/trivial/trivial.go index 1d96e63a..45f46080 100644 --- a/example/trivial/trivial.go +++ b/example/trivial/trivial.go @@ -1,3 +1,4 @@ +// Package main contains an example service provider implementation. package main import ( @@ -9,6 +10,7 @@ import ( "log" "net/http" "net/url" + "time" "github.com/crewjam/saml/samlsp" ) @@ -69,8 +71,14 @@ func main() { }) app := http.HandlerFunc(hello) slo := http.HandlerFunc(logout) + http.Handle("/hello", samlMiddleware.RequireAccount(app)) http.Handle("/saml/", samlMiddleware) http.Handle("/logout", slo) - log.Fatal(http.ListenAndServe(":8000", nil)) + + server := &http.Server{ + Addr: ":8080", + ReadHeaderTimeout: 5 * time.Second, + } + log.Fatal(server.ListenAndServe()) } diff --git a/identity_provider.go b/identity_provider.go index bcea5828..83f02991 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -196,10 +196,13 @@ func (idp *IdentityProvider) Handler() http.Handler { } // ServeMetadata is an http.HandlerFunc that serves the IDP metadata -func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, r *http.Request) { +func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, _ *http.Request) { buf, _ := xml.MarshalIndent(idp.Metadata(), "", " ") w.Header().Set("Content-Type", "application/samlmetadata+xml") - w.Write(buf) + if _, err := w.Write(buf); err != nil { + idp.Logger.Printf("ERROR: %s", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } } // ServeSSO handles SAML auth requests. @@ -716,9 +719,7 @@ func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Sessio }) } - for _, ca := range session.CustomAttributes { - attributes = append(attributes, ca) - } + attributes = append(attributes, session.CustomAttributes...) if len(session.Groups) != 0 { groupMemberAttributeValues := []AttributeValue{} diff --git a/identity_provider_go117_test.go b/identity_provider_go117_test.go index 536587d6..0ce6a1a7 100644 --- a/identity_provider_go117_test.go +++ b/identity_provider_go117_test.go @@ -43,8 +43,10 @@ func TestIDPHTTPCanHandleSSORequest(t *testing.T) { d := bytes.Replace(c, []byte("]]"), 1) f := bytes.Buffer{} e, _ := flate.NewWriter(&f, flate.DefaultCompression) - e.Write(d) - e.Close() + _, err := e.Write(d) + assert.Check(t, err) + err = e.Close() + assert.Check(t, err) g := base64.StdEncoding.EncodeToString(f.Bytes()) invalidRequest := url.QueryEscape(g) diff --git a/identity_provider_test.go b/identity_provider_test.go index a88480ef..2beaf83b 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -207,8 +207,8 @@ func TestIDPHTTPCanHandleMetadataRequest(t *testing.T) { test.IDP.Handler().ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("application/samlmetadata+xml", w.Header().Get("Content-type"))) - assert.Check(t, strings.HasPrefix(string(w.Body.Bytes()), "

"), - string(w.Body.Bytes())) + strings.HasPrefix(w.Body.String(), "

"), + w.Body.String()) golden.Assert(t, w.Body.String(), "http_sso_response.html") } diff --git a/samlidp/service.go b/samlidp/service.go index 5c2cc659..0b62cd3b 100644 --- a/samlidp/service.go +++ b/samlidp/service.go @@ -25,7 +25,7 @@ type Service struct { // service provider ID, which is typically the service provider's // metadata URL. If an appropriate service provider cannot be found then // the returned error must be os.ErrNotExist. -func (s *Server) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { +func (s *Server) GetServiceProvider(_ *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) { s.idpConfigMu.RLock() defer s.idpConfigMu.RUnlock() rv, ok := s.serviceProviders[serviceProviderID] @@ -37,7 +37,7 @@ func (s *Server) GetServiceProvider(r *http.Request, serviceProviderID string) ( // HandleListServices handles the `GET /services/` request and responds with a JSON formatted list // of service names. -func (s *Server) HandleListServices(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleListServices(_ web.C, w http.ResponseWriter, _ *http.Request) { services, err := s.Store.List("/services/") if err != nil { s.logger.Printf("ERROR: %s", err) @@ -45,14 +45,18 @@ func (s *Server) HandleListServices(c web.C, w http.ResponseWriter, r *http.Requ return } - json.NewEncoder(w).Encode(struct { + err = json.NewEncoder(w).Encode(struct { Services []string `json:"services"` }{Services: services}) + if err != nil { + s.logger.Printf("ERROR: %s", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } } // HandleGetService handles the `GET /services/:id` request and responds with the service // metadata in XML format. -func (s *Server) HandleGetService(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleGetService(c web.C, w http.ResponseWriter, _ *http.Request) { service := Service{} err := s.Store.Get(fmt.Sprintf("/services/%s", c.URLParams["id"]), &service) if err != nil { @@ -60,7 +64,11 @@ func (s *Server) HandleGetService(c web.C, w http.ResponseWriter, r *http.Reques http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - xml.NewEncoder(w).Encode(service.Metadata) + err = xml.NewEncoder(w).Encode(service.Metadata) + if err != nil { + s.logger.Printf("ERROR: %s", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } } // HandlePutService handles the `PUT /shortcuts/:id` request. It accepts the XML-formatted @@ -92,7 +100,7 @@ func (s *Server) HandlePutService(c web.C, w http.ResponseWriter, r *http.Reques } // HandleDeleteService handles the `DELETE /services/:id` request. -func (s *Server) HandleDeleteService(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleDeleteService(c web.C, w http.ResponseWriter, _ *http.Request) { service := Service{} err := s.Store.Get(fmt.Sprintf("/services/%s", c.URLParams["id"]), &service) if err != nil { diff --git a/samlidp/service_test.go b/samlidp/service_test.go index 57e7c4d4..7ee4df83 100644 --- a/samlidp/service_test.go +++ b/samlidp/service_test.go @@ -18,7 +18,7 @@ func TestServicesCrud(t *testing.T) { r, _ := http.NewRequest("GET", "https://idp.example.com/services/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"services\":[]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"services\":[]}\n", w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("PUT", "https://idp.example.com/services/sp", @@ -36,7 +36,7 @@ func TestServicesCrud(t *testing.T) { r, _ = http.NewRequest("GET", "https://idp.example.com/services/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"services\":[\"sp\"]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"services\":[\"sp\"]}\n", w.Body.String())) assert.Check(t, is.Len(test.Server.serviceProviders, 2)) @@ -49,6 +49,6 @@ func TestServicesCrud(t *testing.T) { r, _ = http.NewRequest("GET", "https://idp.example.com/services/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"services\":[]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"services\":[]}\n", w.Body.String())) assert.Check(t, is.Len(test.Server.serviceProviders, 1)) } diff --git a/samlidp/session.go b/samlidp/session.go index ba3bd65b..8ffae2ba 100644 --- a/samlidp/session.go +++ b/samlidp/session.go @@ -48,12 +48,13 @@ func (s *Server) GetSession(w http.ResponseWriter, r *http.Request, req *saml.Id } session := &saml.Session{ - ID: base64.StdEncoding.EncodeToString(randomBytes(32)), - NameID: user.Email, - CreateTime: saml.TimeNow(), - ExpireTime: saml.TimeNow().Add(sessionMaxAge), - Index: hex.EncodeToString(randomBytes(32)), - UserName: user.Name, + ID: base64.StdEncoding.EncodeToString(randomBytes(32)), + NameID: user.Email, + CreateTime: saml.TimeNow(), + ExpireTime: saml.TimeNow().Add(sessionMaxAge), + Index: hex.EncodeToString(randomBytes(32)), + UserName: user.Name, + // nolint:gocritic // Groups should be a slice here. Groups: user.Groups[:], UserEmail: user.Email, UserCommonName: user.CommonName, @@ -102,7 +103,7 @@ func (s *Server) GetSession(w http.ResponseWriter, r *http.Request, req *saml.Id // sendLoginForm produces a form which requests a username and password and directs the user // back to the IDP authorize URL to restart the SAML login flow, this time establishing a // session based on the credentials that were provided. -func (s *Server) sendLoginForm(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest, toast string) { +func (s *Server) sendLoginForm(w http.ResponseWriter, _ *http.Request, req *saml.IdpAuthnRequest, toast string) { tmpl := template.Must(template.New("saml-post-form").Parse(`` + `` + `

{{.Toast}}

` + @@ -135,7 +136,7 @@ func (s *Server) sendLoginForm(w http.ResponseWriter, r *http.Request, req *saml // in the request body, then they are validated. For valid credentials, the response is a // 200 OK and the JSON session object. For invalid credentials, the HTML login prompt form // is sent. -func (s *Server) HandleLogin(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleLogin(_ web.C, w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return @@ -144,38 +145,48 @@ func (s *Server) HandleLogin(c web.C, w http.ResponseWriter, r *http.Request) { if session == nil { return } - json.NewEncoder(w).Encode(session) + if err := json.NewEncoder(w).Encode(session); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandleListSessions handles the `GET /sessions/` request and responds with a JSON formatted list // of session names. -func (s *Server) HandleListSessions(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleListSessions(_ web.C, w http.ResponseWriter, _ *http.Request) { sessions, err := s.Store.List("/sessions/") if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - json.NewEncoder(w).Encode(struct { + err = json.NewEncoder(w).Encode(struct { Sessions []string `json:"sessions"` }{Sessions: sessions}) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandleGetSession handles the `GET /sessions/:id` request and responds with the session // object in JSON format. -func (s *Server) HandleGetSession(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleGetSession(c web.C, w http.ResponseWriter, _ *http.Request) { session := saml.Session{} err := s.Store.Get(fmt.Sprintf("/sessions/%s", c.URLParams["id"]), &session) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - json.NewEncoder(w).Encode(session) + if err := json.NewEncoder(w).Encode(session); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandleDeleteSession handles the `DELETE /sessions/:id` request. It invalidates the // specified session. -func (s *Server) HandleDeleteSession(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleDeleteSession(c web.C, w http.ResponseWriter, _ *http.Request) { err := s.Store.Delete(fmt.Sprintf("/sessions/%s", c.URLParams["id"])) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/samlidp/session_test.go b/samlidp/session_test.go index 7f5d1351..cb3d5c3d 100644 --- a/samlidp/session_test.go +++ b/samlidp/session_test.go @@ -17,7 +17,7 @@ func TestSessionsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"sessions\":[]}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("PUT", "https://idp.example.com/users/alice", @@ -34,7 +34,7 @@ func TestSessionsCrud(t *testing.T) { assert.Check(t, is.Equal("session=AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=; Path=/; Max-Age=3600; HttpOnly; Secure", w.Header().Get("Set-Cookie"))) assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "https://idp.example.com/login", nil) @@ -42,14 +42,14 @@ func TestSessionsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "https://idp.example.com/sessions/AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"ID\":\"AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=\",\"CreateTime\":\"2015-12-01T01:57:09Z\",\"ExpireTime\":\"2015-12-01T02:57:09Z\",\"Index\":\"40424446484a4c4e50525456585a5c5e60626466686a6c6e70727476787a7c7e\",\"NameID\":\"\",\"NameIDFormat\":\"\",\"SubjectID\":\"\",\"Groups\":null,\"UserName\":\"alice\",\"UserEmail\":\"\",\"UserCommonName\":\"\",\"UserSurname\":\"\",\"UserGivenName\":\"\",\"UserScopedAffiliation\":\"\",\"CustomAttributes\":null}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("DELETE", "https://idp.example.com/sessions/AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=", nil) @@ -61,6 +61,6 @@ func TestSessionsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"sessions\":[]}\n", - string(w.Body.Bytes()))) + w.Body.String())) } diff --git a/samlidp/shortcut.go b/samlidp/shortcut.go index 151a84ea..4c5b8650 100644 --- a/samlidp/shortcut.go +++ b/samlidp/shortcut.go @@ -31,28 +31,35 @@ type Shortcut struct { // HandleListShortcuts handles the `GET /shortcuts/` request and responds with a JSON formatted list // of shortcut names. -func (s *Server) HandleListShortcuts(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleListShortcuts(_ web.C, w http.ResponseWriter, _ *http.Request) { shortcuts, err := s.Store.List("/shortcuts/") if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - json.NewEncoder(w).Encode(struct { + err = json.NewEncoder(w).Encode(struct { Shortcuts []string `json:"shortcuts"` }{Shortcuts: shortcuts}) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandleGetShortcut handles the `GET /shortcuts/:id` request and responds with the shortcut // object in JSON format. -func (s *Server) HandleGetShortcut(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleGetShortcut(c web.C, w http.ResponseWriter, _ *http.Request) { shortcut := Shortcut{} err := s.Store.Get(fmt.Sprintf("/shortcuts/%s", c.URLParams["id"]), &shortcut) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - json.NewEncoder(w).Encode(shortcut) + if err := json.NewEncoder(w).Encode(shortcut); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandlePutShortcut handles the `PUT /shortcuts/:id` request. It accepts a JSON formatted @@ -74,7 +81,7 @@ func (s *Server) HandlePutShortcut(c web.C, w http.ResponseWriter, r *http.Reque } // HandleDeleteShortcut handles the `DELETE /shortcuts/:id` request. -func (s *Server) HandleDeleteShortcut(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleDeleteShortcut(c web.C, w http.ResponseWriter, _ *http.Request) { err := s.Store.Delete(fmt.Sprintf("/shortcuts/%s", c.URLParams["id"])) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -100,7 +107,7 @@ func (s *Server) HandleIDPInitiated(c web.C, w http.ResponseWriter, r *http.Requ case shortcut.RelayState != nil: relayState = *shortcut.RelayState case shortcut.URISuffixAsRelayState: - relayState, _ = c.URLParams["*"] + relayState = c.URLParams["*"] } s.idpConfigMu.RLock() diff --git a/samlidp/shortcut_test.go b/samlidp/shortcut_test.go index 7a15f148..e74f34bf 100644 --- a/samlidp/shortcut_test.go +++ b/samlidp/shortcut_test.go @@ -17,7 +17,7 @@ func TestShortcutsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"shortcuts\":[]}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("PUT", "https://idp.example.com/shortcuts/bob", @@ -30,14 +30,14 @@ func TestShortcutsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"name\":\"bob\",\"service_provider\":\"https://example.com/saml2/metadata\",\"url_suffix_as_relay_state\":true}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "https://idp.example.com/shortcuts/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"shortcuts\":[\"bob\"]}\n", - string(w.Body.Bytes()))) + w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("DELETE", "https://idp.example.com/shortcuts/bob", nil) @@ -49,7 +49,7 @@ func TestShortcutsCrud(t *testing.T) { test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) assert.Check(t, is.Equal("{\"shortcuts\":[]}\n", - string(w.Body.Bytes()))) + w.Body.String())) } func TestShortcut(t *testing.T) { @@ -78,7 +78,7 @@ func TestShortcut(t *testing.T) { r.Header.Set("Cookie", "session=AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD4=") test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - body := string(w.Body.Bytes()) + body := w.Body.String() assert.Check(t, strings.Contains(body, ""), diff --git a/samlidp/user.go b/samlidp/user.go index 46d1a964..c8c412cb 100644 --- a/samlidp/user.go +++ b/samlidp/user.go @@ -25,7 +25,7 @@ type User struct { // HandleListUsers handles the `GET /users/` request and responds with a JSON formatted list // of user names. -func (s *Server) HandleListUsers(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleListUsers(_ web.C, w http.ResponseWriter, _ *http.Request) { users, err := s.Store.List("/users/") if err != nil { s.logger.Printf("ERROR: %s", err) @@ -33,14 +33,19 @@ func (s *Server) HandleListUsers(c web.C, w http.ResponseWriter, r *http.Request return } - json.NewEncoder(w).Encode(struct { + err = json.NewEncoder(w).Encode(struct { Users []string `json:"users"` }{Users: users}) + if err != nil { + s.logger.Printf("ERROR: %s", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandleGetUser handles the `GET /users/:id` request and responds with the user object in JSON // format. The HashedPassword field is excluded. -func (s *Server) HandleGetUser(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleGetUser(c web.C, w http.ResponseWriter, _ *http.Request) { user := User{} err := s.Store.Get(fmt.Sprintf("/users/%s", c.URLParams["id"]), &user) if err != nil { @@ -49,7 +54,11 @@ func (s *Server) HandleGetUser(c web.C, w http.ResponseWriter, r *http.Request) return } user.HashedPassword = nil - json.NewEncoder(w).Encode(user) + if err := json.NewEncoder(w).Encode(user); err != nil { + s.logger.Printf("ERROR: %s", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } } // HandlePutUser handles the `PUT /users/:id` request. It accepts a JSON formatted user object in @@ -99,7 +108,7 @@ func (s *Server) HandlePutUser(c web.C, w http.ResponseWriter, r *http.Request) } // HandleDeleteUser handles the `DELETE /users/:id` request. -func (s *Server) HandleDeleteUser(c web.C, w http.ResponseWriter, r *http.Request) { +func (s *Server) HandleDeleteUser(c web.C, w http.ResponseWriter, _ *http.Request) { err := s.Store.Delete(fmt.Sprintf("/users/%s", c.URLParams["id"])) if err != nil { s.logger.Printf("ERROR: %s", err) diff --git a/samlidp/user_test.go b/samlidp/user_test.go index ecac3459..30a98a6b 100644 --- a/samlidp/user_test.go +++ b/samlidp/user_test.go @@ -16,7 +16,7 @@ func TestUsersCrud(t *testing.T) { r, _ := http.NewRequest("GET", "https://idp.example.com/users/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"users\":[]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"users\":[]}\n", w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("PUT", "https://idp.example.com/users/alice", @@ -28,13 +28,13 @@ func TestUsersCrud(t *testing.T) { r, _ = http.NewRequest("GET", "https://idp.example.com/users/alice", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"name\":\"alice\"}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"name\":\"alice\"}\n", w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("GET", "https://idp.example.com/users/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"users\":[\"alice\"]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"users\":[\"alice\"]}\n", w.Body.String())) w = httptest.NewRecorder() r, _ = http.NewRequest("DELETE", "https://idp.example.com/users/alice", nil) @@ -45,5 +45,5 @@ func TestUsersCrud(t *testing.T) { r, _ = http.NewRequest("GET", "https://idp.example.com/users/", nil) test.Server.ServeHTTP(w, r) assert.Check(t, is.Equal(http.StatusOK, w.Code)) - assert.Check(t, is.Equal("{\"users\":[]}\n", string(w.Body.Bytes()))) + assert.Check(t, is.Equal("{\"users\":[]}\n", w.Body.String())) } diff --git a/samlsp/error.go b/samlsp/error.go index 662bce74..496faccf 100644 --- a/samlsp/error.go +++ b/samlsp/error.go @@ -14,7 +14,7 @@ type ErrorFunction func(w http.ResponseWriter, r *http.Request, err error) // DefaultOnError is the default ErrorFunction implementation. It prints // an message via the standard log package and returns a simple text // "Forbidden" message to the user. -func DefaultOnError(w http.ResponseWriter, r *http.Request, err error) { +func DefaultOnError(w http.ResponseWriter, _ *http.Request, err error) { if parseErr, ok := err.(*saml.InvalidResponseError); ok { log.Printf("WARNING: received invalid saml response: %s (now: %s) %s", parseErr.Response, parseErr.Now, parseErr.PrivateErr) diff --git a/samlsp/fetch_metadata.go b/samlsp/fetch_metadata.go index 1ef521ac..4d92503e 100644 --- a/samlsp/fetch_metadata.go +++ b/samlsp/fetch_metadata.go @@ -12,6 +12,8 @@ import ( "github.com/crewjam/httperr" xrv "github.com/mattermost/xml-roundtrip-validator" + "github.com/crewjam/saml/logger" + "github.com/crewjam/saml" ) @@ -61,7 +63,11 @@ func FetchMetadata(ctx context.Context, httpClient *http.Client, metadataURL url if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logger.DefaultLogger.Printf("Error while closing response body during fetch metadata: %v", err) + } + }() if resp.StatusCode >= 400 { return nil, httperr.Response(*resp) } diff --git a/samlsp/fetch_metadata_go117_test.go b/samlsp/fetch_metadata_go117_test.go index be855c1a..c4edc61c 100644 --- a/samlsp/fetch_metadata_go117_test.go +++ b/samlsp/fetch_metadata_go117_test.go @@ -18,12 +18,13 @@ import ( func TestFetchMetadataRejectsInvalid(t *testing.T) { test := NewMiddlewareTest(t) - test.IDPMetadata = bytes.Replace(test.IDPMetadata, - []byte("]]"), -1) + test.IDPMetadata = bytes.ReplaceAll(test.IDPMetadata, + []byte("]]")) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Check(t, is.Equal("/metadata", r.URL.String())) - w.Write(test.IDPMetadata) + _, err := w.Write(test.IDPMetadata) + assert.Check(t, err) })) fmt.Println(testServer.URL + "/metadata") diff --git a/samlsp/fetch_metadata_test.go b/samlsp/fetch_metadata_test.go index f1da1320..bd90dd8a 100644 --- a/samlsp/fetch_metadata_test.go +++ b/samlsp/fetch_metadata_test.go @@ -17,7 +17,8 @@ func TestFetchMetadata(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Check(t, is.Equal("/metadata", r.URL.String())) - w.Write(test.IDPMetadata) + _, err := w.Write(test.IDPMetadata) + assert.Check(t, err) })) fmt.Println(testServer.URL + "/metadata") diff --git a/samlsp/middleware.go b/samlsp/middleware.go index 834a79c1..f5eabb16 100644 --- a/samlsp/middleware.go +++ b/samlsp/middleware.go @@ -1,6 +1,7 @@ package samlsp import ( + "bytes" "encoding/xml" "net/http" @@ -65,16 +66,22 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // ServeMetadata handles requests for the SAML metadata endpoint. -func (m *Middleware) ServeMetadata(w http.ResponseWriter, r *http.Request) { +func (m *Middleware) ServeMetadata(w http.ResponseWriter, _ *http.Request) { buf, _ := xml.MarshalIndent(m.ServiceProvider.Metadata(), "", " ") w.Header().Set("Content-Type", "application/samlmetadata+xml") - w.Write(buf) - return + if _, err := w.Write(buf); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } // ServeACS handles requests for the SAML ACS endpoint. func (m *Middleware) ServeACS(w http.ResponseWriter, r *http.Request) { - r.ParseForm() + err := r.ParseForm() + if err != nil { + m.OnError(w, r, err) + return + } possibleRequestIDs := []string{} if m.ServiceProvider.AllowIDPInitiated { @@ -93,7 +100,6 @@ func (m *Middleware) ServeACS(w http.ResponseWriter, r *http.Request) { } m.CreateSessionFromAssertion(w, r, assertion, m.ServiceProvider.DefaultRedirectURI) - return } // RequireAccount is HTTP middleware that requires that each request be @@ -114,7 +120,6 @@ func (m *Middleware) RequireAccount(handler http.Handler) http.Handler { } m.OnError(w, r, err) - return }) } @@ -173,9 +178,14 @@ func (m *Middleware) HandleStartAuthFlow(w http.ResponseWriter, r *http.Request) "script-src 'sha256-AjPdJSbZmeWHnEc5ykvJFay8FTWeTeRbs9dutfZ0HqE='; "+ "reflected-xss block; referrer no-referrer;") w.Header().Add("Content-type", "text/html") - w.Write([]byte(``)) - w.Write(authReq.Post(relayState)) - w.Write([]byte(``)) + var buf bytes.Buffer + buf.WriteString(``) + buf.Write(authReq.Post(relayState)) + buf.WriteString(``) + if _, err := w.Write(buf.Bytes()); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } return } panic("not reached") @@ -195,7 +205,10 @@ func (m *Middleware) CreateSessionFromAssertion(w http.ResponseWriter, r *http.R return } } else { - m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex) + if err := m.RequestTracker.StopTrackingRequest(w, r, trackedRequestIndex); err != nil { + m.OnError(w, r, err) + return + } redirectURI = trackedRequest.URI } diff --git a/samlsp/middleware_test.go b/samlsp/middleware_test.go index 8b8863b0..801aad08 100644 --- a/samlsp/middleware_test.go +++ b/samlsp/middleware_test.go @@ -426,7 +426,7 @@ func TestMiddlewareDefaultCookieDomainIPv4(t *testing.T) { req, _ := http.NewRequest("GET", "/", nil) resp := httptest.NewRecorder() - sp.CreateSession(resp, req, &saml.Assertion{}) + assert.Check(t, sp.CreateSession(resp, req, &saml.Assertion{})) assert.Check(t, strings.Contains(resp.Header().Get("Set-Cookie"), "Domain=127.0.0.1;"), @@ -445,7 +445,7 @@ func TestMiddlewareDefaultCookieDomainIPv6(t *testing.T) { req, _ := http.NewRequest("GET", "/", nil) resp := httptest.NewRecorder() - sp.CreateSession(resp, req, &saml.Assertion{}) + assert.Check(t, sp.CreateSession(resp, req, &saml.Assertion{})) assert.Check(t, strings.Contains(resp.Header().Get("Set-Cookie"), "Domain=::1;"), diff --git a/samlsp/request_tracker_jwt.go b/samlsp/request_tracker_jwt.go index 625a66e9..0ca47258 100644 --- a/samlsp/request_tracker_jwt.go +++ b/samlsp/request_tracker_jwt.go @@ -25,7 +25,7 @@ var _ TrackedRequestCodec = JWTTrackedRequestCodec{} // JWTTrackedRequestClaims represents the JWT claims for a tracked request. type JWTTrackedRequestClaims struct { - jwt.StandardClaims + jwt.RegisteredClaims TrackedRequest SAMLAuthnRequest bool `json:"saml-authn-request"` } @@ -34,12 +34,12 @@ type JWTTrackedRequestClaims struct { func (s JWTTrackedRequestCodec) Encode(value TrackedRequest) (string, error) { now := saml.TimeNow() claims := JWTTrackedRequestClaims{ - StandardClaims: jwt.StandardClaims{ - Audience: s.Audience, - ExpiresAt: now.Add(s.MaxAge).Unix(), - IssuedAt: now.Unix(), + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{s.Audience}, + ExpiresAt: jwt.NewNumericDate(now.Add(s.MaxAge)), + IssuedAt: jwt.NewNumericDate(now), Issuer: s.Issuer, - NotBefore: now.Unix(), // TODO(ross): correct for clock skew + NotBefore: jwt.NewNumericDate(now), // TODO(ross): correct for clock skew Subject: value.Index, }, TrackedRequest: value, @@ -67,7 +67,7 @@ func (s JWTTrackedRequestCodec) Decode(signed string) (*TrackedRequest, error) { if !claims.VerifyIssuer(s.Issuer, true) { return nil, fmt.Errorf("expected issuer %q, got %q", s.Issuer, claims.Issuer) } - if claims.SAMLAuthnRequest != true { + if !claims.SAMLAuthnRequest { return nil, fmt.Errorf("expected saml-authn-request") } claims.TrackedRequest.Index = claims.Subject diff --git a/samlsp/session_cookie_test.go b/samlsp/session_cookie_test.go index ef594ee2..74fcf2cb 100644 --- a/samlsp/session_cookie_test.go +++ b/samlsp/session_cookie_test.go @@ -26,10 +26,12 @@ func TestCookieSameSite(t *testing.T) { resp := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) err := csp.CreateSession(resp, req, &saml.Assertion{}) - assert.Check(t, err) + assert.Check(tb, err) - cookies := resp.Result().Cookies() - assert.Check(t, is.Len(cookies, 1), "Expected to have a cookie set") + result := resp.Result() + cookies := result.Cookies() + assert.Check(tb, is.Len(cookies, 1), "Expected to have a cookie set") + assert.Check(tb, result.Body.Close()) return cookies[0] } diff --git a/samlsp/session_jwt.go b/samlsp/session_jwt.go index c4531fb9..8d801e47 100644 --- a/samlsp/session_jwt.go +++ b/samlsp/session_jwt.go @@ -106,7 +106,7 @@ func (c JWTSessionCodec) Decode(signed string) (Session, error) { if !claims.VerifyIssuer(c.Issuer, true) { return nil, fmt.Errorf("expected issuer %q, got %q", c.Issuer, claims.Issuer) } - if claims.SAMLSession != true { + if !claims.SAMLSession { return nil, errors.New("expected saml-session") } return claims, nil diff --git a/schema.go b/schema.go index 13ea2ef6..e281505d 100644 --- a/schema.go +++ b/schema.go @@ -48,7 +48,7 @@ type AuthnRequest struct { NameIDPolicy *NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"` Conditions *Conditions RequestedAuthnContext *RequestedAuthnContext - //Scoping *Scoping // TODO + // Scoping *Scoping // TODO ForceAuthn *bool `xml:",attr"` IsPassive *bool `xml:",attr"` @@ -108,7 +108,7 @@ func (r *LogoutRequest) Element() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *LogoutRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *LogoutRequest) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias LogoutRequest aux := &struct { IssueInstant RelaxedTime `xml:",attr"` @@ -209,9 +209,9 @@ func (r *AuthnRequest) Element() *etree.Element { if r.RequestedAuthnContext != nil { el.AddChild(r.RequestedAuthnContext.Element()) } - //if r.Scoping != nil { - // el.AddChild(r.Scoping.Element()) - //} + // if r.Scoping != nil { + // el.AddChild(r.Scoping.Element()) + // } if r.ForceAuthn != nil { el.CreateAttr("ForceAuthn", strconv.FormatBool(*r.ForceAuthn)) } @@ -237,7 +237,7 @@ func (r *AuthnRequest) Element() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *AuthnRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *AuthnRequest) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias AuthnRequest aux := &struct { IssueInstant RelaxedTime `xml:",attr"` @@ -374,7 +374,7 @@ func (r *ArtifactResolve) SoapRequest() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *ArtifactResolve) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *ArtifactResolve) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias ArtifactResolve aux := &struct { IssueInstant RelaxedTime `xml:",attr"` @@ -448,7 +448,7 @@ func (r *ArtifactResponse) Element() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *ArtifactResponse) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *ArtifactResponse) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias ArtifactResponse aux := &struct { IssueInstant RelaxedTime `xml:",attr"` @@ -542,7 +542,7 @@ func (r *Response) Element() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *Response) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *Response) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias Response aux := &struct { IssueInstant RelaxedTime `xml:",attr"` @@ -1153,9 +1153,9 @@ func (a *SubjectLocality) Element() *etree.Element { // See http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf ยง2.7.2.2 type AuthnContext struct { AuthnContextClassRef *AuthnContextClassRef - //AuthnContextDecl *AuthnContextDecl ... TODO - //AuthnContextDeclRef *AuthnContextDeclRef ... TODO - //AuthenticatingAuthorities []AuthenticatingAuthority... TODO + // AuthnContextDecl *AuthnContextDecl ... TODO + // AuthnContextDeclRef *AuthnContextDeclRef ... TODO + // AuthenticatingAuthorities []AuthenticatingAuthority... TODO } // Element returns an etree.Element representing the object in XML form. @@ -1292,7 +1292,7 @@ func (r *LogoutResponse) Element() *etree.Element { } // MarshalXML implements xml.Marshaler -func (r *LogoutResponse) MarshalXML(e *xml.Encoder, start xml.StartElement) error { +func (r *LogoutResponse) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { type Alias LogoutResponse aux := &struct { IssueInstant RelaxedTime `xml:",attr"` diff --git a/service_provider.go b/service_provider.go index 6f6e7f4f..927ad6e4 100644 --- a/service_provider.go +++ b/service_provider.go @@ -23,6 +23,7 @@ import ( dsig "github.com/russellhaering/goxmldsig" "github.com/russellhaering/goxmldsig/etreeutils" + "github.com/crewjam/saml/logger" "github.com/crewjam/saml/xmlenc" ) @@ -189,13 +190,13 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { } } - var sloEndpoints []Endpoint - for _, binding := range sp.LogoutBindings { - sloEndpoints = append(sloEndpoints, Endpoint{ + sloEndpoints := make([]Endpoint, len(sp.LogoutBindings)) + for i, binding := range sp.LogoutBindings { + sloEndpoints[i] = Endpoint{ Binding: binding, Location: sp.SloURL.String(), ResponseLocation: sp.SloURL.String(), - }) + } } return &EntityDescriptor{ @@ -245,25 +246,29 @@ func (sp *ServiceProvider) MakeRedirectAuthenticationRequest(relayState string) } // Redirect returns a URL suitable for using the redirect binding with the request -func (req *AuthnRequest) Redirect(relayState string, sp *ServiceProvider) (*url.URL, error) { +func (r *AuthnRequest) Redirect(relayState string, sp *ServiceProvider) (*url.URL, error) { w := &bytes.Buffer{} w1 := base64.NewEncoder(base64.StdEncoding, w) w2, _ := flate.NewWriter(w1, 9) doc := etree.NewDocument() - doc.SetRoot(req.Element()) + doc.SetRoot(r.Element()) if _, err := doc.WriteTo(w2); err != nil { panic(err) } - w2.Close() - w1.Close() + if err := w2.Close(); err != nil { + panic(err) + } + if err := w1.Close(); err != nil { + panic(err) + } - rv, _ := url.Parse(req.Destination) + rv, _ := url.Parse(r.Destination) // We can't depend on Query().set() as order matters for signing query := rv.RawQuery if len(query) > 0 { - query += "&SAMLRequest=" + url.QueryEscape(string(w.Bytes())) + query += "&SAMLRequest=" + url.QueryEscape(w.String()) } else { - query += "SAMLRequest=" + url.QueryEscape(string(w.Bytes())) + query += "SAMLRequest=" + url.QueryEscape(w.String()) } if relayState != "" { @@ -352,11 +357,11 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) { return nil, errors.New("cannot find any signing certificate in the IDP SSO descriptor") } - var certs []*x509.Certificate + certs := make([]*x509.Certificate, len(certStrs)) // cleanup whitespace regex := regexp.MustCompile(`\s+`) - for _, certStr := range certStrs { + for i, certStr := range certStrs { certStr = regex.ReplaceAllString(certStr, "") certBytes, err := base64.StdEncoding.DecodeString(certStr) if err != nil { @@ -367,7 +372,7 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) { if err != nil { return nil, err } - certs = append(certs, parsedCert) + certs[i] = parsedCert } return certs, nil @@ -439,9 +444,9 @@ func GetSigningContext(sp *ServiceProvider) (*dsig.SigningContext, error) { Leaf: sp.Certificate, } // TODO: add intermediates for SP - //for _, cert := range sp.Intermediates { - // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) - //} + // for _, cert := range sp.Intermediates { + // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) + // } keyStore := dsig.TLSCertKeyStore(keyPair) if sp.SignatureMethod != dsig.RSASHA1SignatureMethod && @@ -508,9 +513,9 @@ func (sp *ServiceProvider) MakePostAuthenticationRequest(relayState string) ([]b } // Post returns an HTML form suitable for using the HTTP-POST binding with the request -func (req *AuthnRequest) Post(relayState string) []byte { +func (r *AuthnRequest) Post(relayState string) []byte { doc := etree.NewDocument() - doc.SetRoot(req.Element()) + doc.SetRoot(r.Element()) reqBuf, err := doc.WriteToBytes() if err != nil { panic(err) @@ -530,7 +535,7 @@ func (req *AuthnRequest) Post(relayState string) []byte { SAMLRequest string RelayState string }{ - URL: req.Destination, + URL: r.Destination, SAMLRequest: encodedReqBuf, RelayState: relayState, } @@ -579,7 +584,7 @@ type InvalidResponseError struct { } func (ivr *InvalidResponseError) Error() string { - return fmt.Sprintf("Authentication failed") + return "Authentication failed" } // ErrBadStatus is returned when the assertion provided is valid but the @@ -606,7 +611,7 @@ func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID artifactResolveRequest, err := sp.MakeArtifactResolveRequest(artifactID) if err != nil { - retErr.PrivateErr = fmt.Errorf("Cannot generate artifact resolution request: %s", err) + retErr.PrivateErr = fmt.Errorf("cannot generate artifact resolution request: %s", err) return nil, retErr } @@ -633,7 +638,11 @@ func (sp *ServiceProvider) handleArtifactRequest(ctx context.Context, artifactID retErr.PrivateErr = fmt.Errorf("cannot resolve artifact: %s", err) return nil, retErr } - defer response.Body.Close() + defer func() { + if err := response.Body.Close(); err != nil { + logger.DefaultLogger.Printf("Error while closing response body during artifact resolution: %v", err) + } + }() if response.StatusCode != 200 { retErr.PrivateErr = fmt.Errorf("Error during artifact resolution: HTTP status %d (%s)", response.StatusCode, response.Status) return nil, retErr @@ -753,11 +762,12 @@ func (sp *ServiceProvider) parseArtifactResponse(artifactResponseEl *etree.Eleme var signatureRequirement signatureRequirement sigErr := sp.validateSignature(artifactResponseEl) - if sigErr == nil { + switch sigErr { + case nil: signatureRequirement = signatureNotRequired - } else if sigErr == errSignatureElementNotPresent { + case errSignatureElementNotPresent: signatureRequirement = signatureRequired - } else { + default: retErr.PrivateErr = sigErr return nil, retErr } @@ -888,13 +898,14 @@ func (sp *ServiceProvider) parseResponse(responseEl *etree.Element, possibleRequ } if signatureRequirement == signatureRequired { - if responseSignatureErr == nil { + switch responseSignatureErr { + case nil: // since the request has a signature, none of the Assertions need one signatureRequirement = signatureNotRequired - } else if responseSignatureErr == errSignatureElementNotPresent { + case errSignatureElementNotPresent: // the request has no signature, so assertions must be signed signatureRequirement = signatureRequired // nop - } else { + default: return nil, responseSignatureErr } } @@ -1078,7 +1089,7 @@ func (sp *ServiceProvider) validateAssertion(assertion *Assertion, possibleReque return nil } -var errSignatureElementNotPresent = errors.New("Signature element not present") +var errSignatureElementNotPresent = errors.New("signature element not present") // validateSignature returns nil iff the Signature embedded in the element is valid func (sp *ServiceProvider) validateSignature(el *etree.Element) error { @@ -1154,9 +1165,9 @@ func (sp *ServiceProvider) SignLogoutRequest(req *LogoutRequest) error { Leaf: sp.Certificate, } // TODO: add intermediates for SP - //for _, cert := range sp.Intermediates { - // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) - //} + // for _, cert := range sp.Intermediates { + // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) + // } keyStore := dsig.TLSCertKeyStore(keyPair) if sp.SignatureMethod != dsig.RSASHA1SignatureMethod && @@ -1222,22 +1233,26 @@ func (sp *ServiceProvider) MakeRedirectLogoutRequest(nameID, relayState string) } // Redirect returns a URL suitable for using the redirect binding with the request -func (req *LogoutRequest) Redirect(relayState string) *url.URL { +func (r *LogoutRequest) Redirect(relayState string) *url.URL { w := &bytes.Buffer{} w1 := base64.NewEncoder(base64.StdEncoding, w) w2, _ := flate.NewWriter(w1, 9) doc := etree.NewDocument() - doc.SetRoot(req.Element()) + doc.SetRoot(r.Element()) if _, err := doc.WriteTo(w2); err != nil { panic(err) } - w2.Close() - w1.Close() + if err := w2.Close(); err != nil { + panic(err) + } + if err := w1.Close(); err != nil { + panic(err) + } - rv, _ := url.Parse(req.Destination) + rv, _ := url.Parse(r.Destination) query := rv.Query() - query.Set("SAMLRequest", string(w.Bytes())) + query.Set("SAMLRequest", w.String()) if relayState != "" { query.Set("RelayState", relayState) } @@ -1258,9 +1273,9 @@ func (sp *ServiceProvider) MakePostLogoutRequest(nameID, relayState string) ([]b } // Post returns an HTML form suitable for using the HTTP-POST binding with the request -func (req *LogoutRequest) Post(relayState string) []byte { +func (r *LogoutRequest) Post(relayState string) []byte { doc := etree.NewDocument() - doc.SetRoot(req.Element()) + doc.SetRoot(r.Element()) reqBuf, err := doc.WriteToBytes() if err != nil { panic(err) @@ -1280,7 +1295,7 @@ func (req *LogoutRequest) Post(relayState string) []byte { SAMLRequest string RelayState string }{ - URL: req.Destination, + URL: r.Destination, SAMLRequest: encodedReqBuf, RelayState: relayState, } @@ -1332,22 +1347,26 @@ func (sp *ServiceProvider) MakeRedirectLogoutResponse(logoutRequestID, relayStat } // Redirect returns a URL suitable for using the redirect binding with the LogoutResponse. -func (resp *LogoutResponse) Redirect(relayState string) *url.URL { +func (r *LogoutResponse) Redirect(relayState string) *url.URL { w := &bytes.Buffer{} w1 := base64.NewEncoder(base64.StdEncoding, w) w2, _ := flate.NewWriter(w1, 9) doc := etree.NewDocument() - doc.SetRoot(resp.Element()) + doc.SetRoot(r.Element()) if _, err := doc.WriteTo(w2); err != nil { panic(err) } - w2.Close() - w1.Close() + if err := w2.Close(); err != nil { + panic(err) + } + if err := w1.Close(); err != nil { + panic(err) + } - rv, _ := url.Parse(resp.Destination) + rv, _ := url.Parse(r.Destination) query := rv.Query() - query.Set("SAMLResponse", string(w.Bytes())) + query.Set("SAMLResponse", w.String()) if relayState != "" { query.Set("RelayState", relayState) } @@ -1368,9 +1387,9 @@ func (sp *ServiceProvider) MakePostLogoutResponse(logoutRequestID, relayState st } // Post returns an HTML form suitable for using the HTTP-POST binding with the LogoutResponse. -func (resp *LogoutResponse) Post(relayState string) []byte { +func (r *LogoutResponse) Post(relayState string) []byte { doc := etree.NewDocument() - doc.SetRoot(resp.Element()) + doc.SetRoot(r.Element()) reqBuf, err := doc.WriteToBytes() if err != nil { panic(err) @@ -1390,7 +1409,7 @@ func (resp *LogoutResponse) Post(relayState string) []byte { SAMLResponse string RelayState string }{ - URL: resp.Destination, + URL: r.Destination, SAMLResponse: encodedReqBuf, RelayState: relayState, } @@ -1411,9 +1430,9 @@ func (sp *ServiceProvider) SignLogoutResponse(resp *LogoutResponse) error { Leaf: sp.Certificate, } // TODO: add intermediates for SP - //for _, cert := range sp.Intermediates { - // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) - //} + // for _, cert := range sp.Intermediates { + // keyPair.Certificate = append(keyPair.Certificate, cert.Raw) + // } keyStore := dsig.TLSCertKeyStore(keyPair) if sp.SignatureMethod != dsig.RSASHA1SignatureMethod && @@ -1502,10 +1521,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseForm(postFormData string) error retErr.PrivateErr = err return retErr } - if err := sp.validateLogoutResponse(&resp); err != nil { - return err - } - return nil + return sp.validateLogoutResponse(&resp) } // ValidateLogoutResponseRedirect returns a nil error if the logout response is valid. @@ -1550,10 +1566,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str retErr.PrivateErr = err return retErr } - if err := sp.validateLogoutResponse(&resp); err != nil { - return err - } - return nil + return sp.validateLogoutResponse(&resp) } // validateLogoutResponse validates the LogoutResponse fields. Returns a nil error if the LogoutResponse is valid. @@ -1585,6 +1598,7 @@ func firstSet(a, b string) string { // findChildren returns all the elements matching childNS/childTag that are direct children of parentEl. func findChildren(parentEl *etree.Element, childNS string, childTag string) ([]*etree.Element, error) { + //nolint:prealloc // We don't know how many child elements we'll actually put into this array. var rv []*etree.Element for _, childEl := range parentEl.ChildElements() { if childEl.Tag != childTag { diff --git a/service_provider_test.go b/service_provider_test.go index cdf5c723..67c2fbd9 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -951,9 +951,10 @@ func TestSPCanProcessResponseWithoutDestination(t *testing.T) { assert.Check(t, err) } -func (test *ServiceProviderTest) responseDom() (doc *etree.Document) { +func (test *ServiceProviderTest) responseDom(t *testing.T) (doc *etree.Document) { doc = etree.NewDocument() - doc.ReadFromBytes(test.SamlResponse) + err := doc.ReadFromBytes(test.SamlResponse) + assert.Check(t, err) return doc } @@ -985,7 +986,7 @@ func TestServiceProviderMismatchedDestinationsWithSignaturePresent(t *testing.T) req := http.Request{PostForm: url.Values{}} s.AcsURL = mustParseURL("https://wrong/saml2/acs") - bytes, _ := test.responseDom().WriteToBytes() + bytes, _ := test.responseDom(t).WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1005,7 +1006,7 @@ func TestServiceProviderMissingDestinationWithSignaturePresent(t *testing.T) { assert.Check(t, err) req := http.Request{PostForm: url.Values{}} - bytes, _ := removeDestinationFromDocument(addSignatureToDocument(test.responseDom())).WriteToBytes() + bytes, _ := removeDestinationFromDocument(addSignatureToDocument(test.responseDom(t))).WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1026,7 +1027,7 @@ func TestSPMismatchedDestinationsWithSignaturePresent(t *testing.T) { req := http.Request{PostForm: url.Values{}} test.replaceDestination("https://wrong/saml2/acs") - bytes, _ := addSignatureToDocument(test.responseDom()).WriteToBytes() + bytes, _ := addSignatureToDocument(test.responseDom(t)).WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1047,7 +1048,7 @@ func TestSPMismatchedDestinationsWithNoSignaturePresent(t *testing.T) { req := http.Request{PostForm: url.Values{}} test.replaceDestination("https://wrong/saml2/acs") - bytes, _ := test.responseDom().WriteToBytes() + bytes, _ := test.responseDom(t).WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1068,7 +1069,7 @@ func TestSPMissingDestinationWithSignaturePresent(t *testing.T) { req := http.Request{PostForm: url.Values{}} test.replaceDestination("") - bytes, _ := addSignatureToDocument(test.responseDom()).WriteToBytes() + bytes, _ := addSignatureToDocument(test.responseDom(t)).WriteToBytes() req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes)) _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"}) assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, @@ -1113,19 +1114,19 @@ func TestSPInvalidAssertions(t *testing.T) { err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "issuer is not \"https://idp.testshib.org/idp/shibboleth\"")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Subject.NameID.NameQualifier = "bob" err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, err) // not verified assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Subject.NameID.SPNameQualifier = "bob" err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, err) // not verified assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) err = s.validateAssertion(&assertion, []string{"any request id"}, TimeNow()) assert.Check(t, is.Error(err, "assertion SubjectConfirmation one of the possible request IDs ([any request id])")) @@ -1134,31 +1135,31 @@ func TestSPInvalidAssertions(t *testing.T) { err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "assertion SubjectConfirmation Recipient is not https://15661444.ngrok.io/saml2/acs")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter = TimeNow().Add(-1 * time.Hour) err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "assertion SubjectConfirmationData is expired")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Conditions.NotBefore = TimeNow().Add(time.Hour) err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "assertion Conditions is not yet valid")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Conditions.NotOnOrAfter = TimeNow().Add(-1 * time.Hour) err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "assertion Conditions is expired")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) assertion.Conditions.AudienceRestrictions[0].Audience.Value = "not/our/metadata/url" err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow()) assert.Check(t, is.Error(err, "assertion Conditions AudienceRestriction does not contain \"https://15661444.ngrok.io/saml2/metadata\"")) assertion = Assertion{} - xml.Unmarshal(assertionBuf, &assertion) + assert.Check(t, xml.Unmarshal(assertionBuf, &assertion)) // Not having an audience is not an error assertion.Conditions.AudienceRestrictions = []AudienceRestriction{} @@ -1249,7 +1250,7 @@ func TestXswPermutationThreeIsRejected(t *testing.T) { // // When no assertions are valid, we return the first error encountered, which in this case is that // there is no Signature on the element. - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "Signature element not present")) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "signature element not present")) } func TestXswPermutationFourIsRejected(t *testing.T) { @@ -1279,7 +1280,7 @@ func TestXswPermutationFourIsRejected(t *testing.T) { // This permutation contains a signed assertion embedded within an unsigned assertion. // I'm pretty sure this is just not allowed, so we properly decide that there are no // signed assertions at all. - assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "Signature element not present")) + assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "signature element not present")) } func TestXswPermutationFiveIsRejected(t *testing.T) { @@ -1362,7 +1363,7 @@ func TestXswPermutationSevenIsRejected(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) - //It's the assertion signature that can't be verified. The error message is generic and always mentions Response + // It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "cannot validate signature on Assertion: Signature could not be verified")) } @@ -1393,7 +1394,7 @@ func TestXswPermutationEightIsRejected(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) - //It's the assertion signature that can't be verified. The error message is generic and always mentions Response + // It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "cannot validate signature on Assertion: Signature could not be verified")) } @@ -1424,7 +1425,7 @@ func TestXswPermutationNineIsRejected(t *testing.T) { req := http.Request{PostForm: url.Values{}} req.PostForm.Set("SAMLResponse", string(respStr)) _, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"}) - //It's the assertion signature that can't be verified. The error message is generic and always mentions Response + // It's the assertion signature that can't be verified. The error message is generic and always mentions Response assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "cannot validate signature on Assertion: Missing signature referencing the top-level element")) } diff --git a/testsaml/parse.go b/testsaml/parse.go index 6c64398a..63e3dbc5 100644 --- a/testsaml/parse.go +++ b/testsaml/parse.go @@ -1,3 +1,4 @@ +// Package testsaml contains functions for use in testing SAML requests and responses. package testsaml import ( diff --git a/util.go b/util.go index c9731b1b..eda053ee 100644 --- a/util.go +++ b/util.go @@ -21,6 +21,7 @@ var Clock *dsig.Clock // rand.Reader, but it can be replaced for testing. var RandReader = rand.Reader +//nolint:unparam // This always receives 20, but we want the option to do more or less if needed. func randomBytes(n int) []byte { rv := make([]byte, n) diff --git a/xmlenc/cbc.go b/xmlenc/cbc.go index 77ddb3b2..991ba1eb 100644 --- a/xmlenc/cbc.go +++ b/xmlenc/cbc.go @@ -31,7 +31,7 @@ func (e CBC) Algorithm() string { // Encrypt encrypts plaintext with key, which should be a []byte of length KeySize(). // It returns an xenc:EncryptedData element. -func (e CBC) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) { +func (e CBC) Encrypt(key interface{}, plaintext []byte, _ []byte) (*etree.Element, error) { keyBuf, ok := key.([]byte) if !ok { return nil, ErrIncorrectKeyType("[]byte") diff --git a/xmlenc/decrypt.go b/xmlenc/decrypt.go index 93991f9f..98a575da 100644 --- a/xmlenc/decrypt.go +++ b/xmlenc/decrypt.go @@ -90,6 +90,7 @@ func validateRSAKeyIfPresent(key interface{}, encryptedKey *etree.Element) (*rsa // if the key will work, or let the service provider know which key // to use to decrypt the message. Either way, verification is not // security-critical. + //nolint:revive,staticcheck // Keep the later empty branch so that we know to address this at a later date. if el := encryptedKey.FindElement("./KeyInfo/X509Data/X509Certificate"); el != nil { certPEMbuf := el.Text() certPEMbuf = "-----BEGIN CERTIFICATE-----\n" + certPEMbuf + "\n-----END CERTIFICATE-----\n" diff --git a/xmlenc/decrypt_test.go b/xmlenc/decrypt_test.go index 2e23f8f6..a8872f22 100644 --- a/xmlenc/decrypt_test.go +++ b/xmlenc/decrypt_test.go @@ -100,6 +100,5 @@ func TestCanDecryptWithoutCertificate(t *testing.T) { el = doc.Root().FindElement("//EncryptedData") _, err = Decrypt(key, el) assert.Check(t, err) - //assertion.NotNil(t, plaintext) }) } diff --git a/xmlenc/digest.go b/xmlenc/digest.go index 801347f2..3eaaf7bc 100644 --- a/xmlenc/digest.go +++ b/xmlenc/digest.go @@ -6,6 +6,7 @@ import ( "crypto/sha512" "hash" + //nolint:staticcheck // We should support this for legacy reasons. "golang.org/x/crypto/ripemd160" ) From 34930b26d33bfb2b3e0216c868754979e8fae7c2 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Thu, 20 Apr 2023 07:16:43 -0400 Subject: [PATCH 58/58] Add support for hardware security module (HSM) signing. (#503) --- go.mod | 2 +- go.sum | 4 +- identity_provider.go | 82 ++++++++++++++++------------ identity_provider_go116_test.go | 2 +- identity_provider_go117_test.go | 2 +- identity_provider_test.go | 97 ++++++++++++++++++++++++--------- samlidp/samlidp.go | 2 + 7 files changed, 125 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 5500d63a..745c5c2c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/pkg/errors v0.9.1 // indirect - github.com/russellhaering/goxmldsig v1.2.0 + github.com/russellhaering/goxmldsig v1.3.0 github.com/stretchr/testify v1.8.1 github.com/zenazn/goji v1.0.1 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed diff --git a/go.sum b/go.sum index a2bb4b19..7ab71ea2 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= -github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= +github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/identity_provider.go b/identity_provider.go index 83f02991..b03dc89b 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -96,6 +96,7 @@ type AssertionMaker interface { // and password). type IdentityProvider struct { Key crypto.PrivateKey + Signer crypto.Signer Logger logger.Interface Certificate *x509.Certificate Intermediates []*x509.Certificate @@ -831,24 +832,8 @@ const canonicalizerPrefixList = "" // MakeAssertionEl sets `AssertionEl` to a signed, possibly encrypted, version of `Assertion`. func (req *IdpAuthnRequest) MakeAssertionEl() error { - keyPair := tls.Certificate{ - Certificate: [][]byte{req.IDP.Certificate.Raw}, - PrivateKey: req.IDP.Key, - Leaf: req.IDP.Certificate, - } - for _, cert := range req.IDP.Intermediates { - keyPair.Certificate = append(keyPair.Certificate, cert.Raw) - } - keyStore := dsig.TLSCertKeyStore(keyPair) - - signatureMethod := req.IDP.SignatureMethod - if signatureMethod == "" { - signatureMethod = dsig.RSASHA1SignatureMethod - } - - signingContext := dsig.NewDefaultSigningContext(keyStore) - signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList) - if err := signingContext.SetSignatureMethod(signatureMethod); err != nil { + signingContext, err := req.signingContext() + if err != nil { return err } @@ -1049,24 +1034,8 @@ func (req *IdpAuthnRequest) MakeResponse() error { // Sign the response element (we've already signed the Assertion element) { - keyPair := tls.Certificate{ - Certificate: [][]byte{req.IDP.Certificate.Raw}, - PrivateKey: req.IDP.Key, - Leaf: req.IDP.Certificate, - } - for _, cert := range req.IDP.Intermediates { - keyPair.Certificate = append(keyPair.Certificate, cert.Raw) - } - keyStore := dsig.TLSCertKeyStore(keyPair) - - signatureMethod := req.IDP.SignatureMethod - if signatureMethod == "" { - signatureMethod = dsig.RSASHA1SignatureMethod - } - - signingContext := dsig.NewDefaultSigningContext(keyStore) - signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList) - if err := signingContext.SetSignatureMethod(signatureMethod); err != nil { + signingContext, err := req.signingContext() + if err != nil { return err } @@ -1084,3 +1053,44 @@ func (req *IdpAuthnRequest) MakeResponse() error { req.ResponseEl = responseEl return nil } + +// signingContext will create a signing context for the request. +func (req *IdpAuthnRequest) signingContext() (*dsig.SigningContext, error) { + // Create a cert chain based off of the IDP cert and its intermediates. + certificates := [][]byte{req.IDP.Certificate.Raw} + for _, cert := range req.IDP.Intermediates { + certificates = append(certificates, cert.Raw) + } + + var signingContext *dsig.SigningContext + var err error + // If signer is set, use it instead of the private key. + if req.IDP.Signer != nil { + signingContext, err = dsig.NewSigningContext(req.IDP.Signer, certificates) + if err != nil { + return nil, err + } + } else { + keyPair := tls.Certificate{ + Certificate: certificates, + PrivateKey: req.IDP.Key, + Leaf: req.IDP.Certificate, + } + keyStore := dsig.TLSCertKeyStore(keyPair) + + signingContext = dsig.NewDefaultSigningContext(keyStore) + } + + // Default to using SHA1 if the signature method isn't set. + signatureMethod := req.IDP.SignatureMethod + if signatureMethod == "" { + signatureMethod = dsig.RSASHA1SignatureMethod + } + + signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList) + if err := signingContext.SetSignatureMethod(signatureMethod); err != nil { + return nil, err + } + + return signingContext, nil +} diff --git a/identity_provider_go116_test.go b/identity_provider_go116_test.go index ead0a780..6d4a0a53 100644 --- a/identity_provider_go116_test.go +++ b/identity_provider_go116_test.go @@ -18,7 +18,7 @@ import ( ) func TestIDPHTTPCanHandleSSORequest(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) w := httptest.NewRecorder() const validRequest = `lJJBayoxFIX%2FypC9JhnU5wszAz7lgWCLaNtFd5fMbQ1MkmnunVb%2FfUfbUqEgdhs%2BTr5zkmLW8S5s8KVD4mzvm0Cl6FIwEciRCeCRDFuznd2sTD5Upk2Ro42NyGZEmNjFMI%2BBOo9pi%2BnVWbzfrEqxY27JSEntEPfg2waHNnpJ4JtcgiWRLfoLXYBjwDfu6p%2B8JIoiWy5K4eqBUipXIzVRUwXKKtRK53qkJ3qqQVuNPUjU4TIQQ%2BBS5EqPBzofKH2ntBn%2FMervo8jWnyX%2BuVC78FwKkT1gopNKX1JUxSklXTMIfM0gsv8xeeDL%2BPGk7%2FF0Qg0GdnwQ1cW5PDLUwFDID6uquO1Dlot1bJw9%2FPLRmia%2BzRMCYyk4dSiq6205QSDXOxfy3KAq5Pkvqt4DAAD%2F%2Fw%3D%3D` diff --git a/identity_provider_go117_test.go b/identity_provider_go117_test.go index 0ce6a1a7..68624518 100644 --- a/identity_provider_go117_test.go +++ b/identity_provider_go117_test.go @@ -18,7 +18,7 @@ import ( ) func TestIDPHTTPCanHandleSSORequest(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) w := httptest.NewRecorder() const validRequest = `lJJBayoxFIX%2FypC9JhnU5wszAz7lgWCLaNtFd5fMbQ1MkmnunVb%2FfUfbUqEgdhs%2BTr5zkmLW8S5s8KVD4mzvm0Cl6FIwEciRCeCRDFuznd2sTD5Upk2Ro42NyGZEmNjFMI%2BBOo9pi%2BnVWbzfrEqxY27JSEntEPfg2waHNnpJ4JtcgiWRLfoLXYBjwDfu6p%2B8JIoiWy5K4eqBUipXIzVRUwXKKtRK53qkJ3qqQVuNPUjU4TIQQ%2BBS5EqPBzofKH2ntBn%2FMervo8jWnyX%2BuVC78FwKkT1gopNKX1JUxSklXTMIfM0gsv8xeeDL%2BPGk7%2FF0Qg0GdnwQ1cW5PDLUwFDID6uquO1Dlot1bJw9%2FPLRmia%2BzRMCYyk4dSiq6205QSDXOxfy3KAq5Pkvqt4DAAD%2F%2Fw%3D%3D` diff --git a/identity_provider_test.go b/identity_provider_test.go index 2beaf83b..6ad81e2c 100644 --- a/identity_provider_test.go +++ b/identity_provider_test.go @@ -25,6 +25,7 @@ import ( "github.com/beevik/etree" "github.com/golang-jwt/jwt/v4" + dsig "github.com/russellhaering/goxmldsig" "github.com/crewjam/saml/logger" "github.com/crewjam/saml/testsaml" @@ -37,6 +38,7 @@ type IdentityProviderTest struct { SP ServiceProvider Key crypto.PrivateKey + Signer crypto.Signer Certificate *x509.Certificate SessionProvider SessionProvider IDP IdentityProvider @@ -50,7 +52,7 @@ func mustParseURL(s string) url.URL { return *rv } -func mustParsePrivateKey(pemStr []byte) crypto.PrivateKey { +func mustParsePrivateKey(pemStr []byte) crypto.Signer { b, _ := pem.Decode(pemStr) if b == nil { panic("cannot parse PEM") @@ -74,7 +76,28 @@ func mustParseCertificate(pemStr []byte) *x509.Certificate { return cert } -func NewIdentifyProviderTest(t *testing.T) *IdentityProviderTest { +// idpTestOpts are options that can be applied to the identity provider. +type idpTestOpts struct { + apply func(*testing.T, *IdentityProviderTest) +} + +// applyKey will set the private key for the identity provider. +var applyKey = idpTestOpts{ + apply: func(t *testing.T, test *IdentityProviderTest) { + test.Key = mustParsePrivateKey(golden.Get(t, "idp_key.pem")) + (&test.IDP).Key = test.Key + }, +} + +// applySigner will set the signer for the identity provider. +var applySigner = idpTestOpts{ + apply: func(t *testing.T, test *IdentityProviderTest) { + test.Signer = mustParsePrivateKey(golden.Get(t, "idp_key.pem")) + (&test.IDP).Signer = test.Signer + }, +} + +func NewIdentityProviderTest(t *testing.T, opts ...idpTestOpts) *IdentityProviderTest { test := IdentityProviderTest{} TimeNow = func() time.Time { rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015") @@ -94,11 +117,9 @@ func NewIdentifyProviderTest(t *testing.T) *IdentityProviderTest { IDPMetadata: &EntityDescriptor{}, } - test.Key = mustParsePrivateKey(golden.Get(t, "idp_key.pem")) test.Certificate = mustParseCertificate(golden.Get(t, "idp_cert.pem")) test.IDP = IdentityProvider{ - Key: test.Key, Certificate: test.Certificate, Logger: logger.DefaultLogger, MetadataURL: mustParseURL("https://idp.example.com/saml/metadata"), @@ -118,6 +139,11 @@ func NewIdentifyProviderTest(t *testing.T) *IdentityProviderTest { }, } + // apply the test options + for _, opt := range opts { + opt.apply(t, &test) + } + // bind the service provider and the IDP test.SP.IDPMetadata = test.IDP.Metadata() return &test @@ -140,7 +166,7 @@ func (mspp *mockServiceProviderProvider) GetServiceProvider(r *http.Request, ser } func TestIDPCanProduceMetadata(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) expected := &EntityDescriptor{ ValidUntil: TimeNow().Add(DefaultValidDuration), CacheDuration: DefaultValidDuration, @@ -201,7 +227,7 @@ func TestIDPCanProduceMetadata(t *testing.T) { } func TestIDPHTTPCanHandleMetadataRequest(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "https://idp.example.com/saml/metadata", nil) test.IDP.Handler().ServeHTTP(w, r) @@ -212,7 +238,7 @@ func TestIDPHTTPCanHandleMetadataRequest(t *testing.T) { } func TestIDPCanHandleRequestWithNewSession(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { fmt.Fprintf(w, "RelayState: %s\nSAMLRequest: %s", @@ -238,7 +264,7 @@ func TestIDPCanHandleRequestWithNewSession(t *testing.T) { } func TestIDPCanHandleRequestWithExistingSession(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ @@ -263,7 +289,7 @@ func TestIDPCanHandleRequestWithExistingSession(t *testing.T) { } func TestIDPCanHandlePostRequestWithExistingSession(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ @@ -292,7 +318,7 @@ func TestIDPCanHandlePostRequestWithExistingSession(t *testing.T) { } func TestIDPRejectsInvalidRequest(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { panic("not reached") @@ -313,7 +339,7 @@ func TestIDPRejectsInvalidRequest(t *testing.T) { } func TestIDPCanParse(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) r, _ := http.NewRequest("GET", "https://idp.example.com/saml/sso?RelayState=ThisIsTheRelayState&SAMLRequest=lJJBayoxFIX%2FypC9JhnU5wszAz7lgWCLaNtFd5fMbQ1MkmnunVb%2FfUfbUqEgdhs%2BTr5zkmLW8S5s8KVD4mzvm0Cl6FIwEciRCeCRDFuznd2sTD5Upk2Ro42NyGZEmNjFMI%2BBOo9pi%2BnVWbzfrEqxY27JSEntEPfg2waHNnpJ4JtcgiWRLfoLXYBjwDfu6p%2B8JIoiWy5K4eqBUipXIzVRUwXKKtRK53qkJ3qqQVuNPUjU4TIQQ%2BBS5EqPBzofKH2ntBn%2FMervo8jWnyX%2BuVC78FwKkT1gopNKX1JUxSklXTMIfM0gsv8xeeDL%2BPGk7%2FF0Qg0GdnwQ1cW5PDLUwFDID6uquO1Dlot1bJw9%2FPLRmia%2BzRMCYyk4dSiq6205QSDXOxfy3KAq5Pkvqt4DAAD%2F%2Fw%3D%3D", nil) req, err := NewIdpAuthnRequest(&test.IDP, r) assert.Check(t, err) @@ -337,7 +363,7 @@ func TestIDPCanParse(t *testing.T) { } func TestIDPCanValidate(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) req := IdpAuthnRequest{ Now: TimeNow(), IDP: &test.IDP, @@ -461,7 +487,7 @@ func TestIDPCanValidate(t *testing.T) { } func TestIDPMakeAssertion(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) req := IdpAuthnRequest{ Now: TimeNow(), IDP: &test.IDP, @@ -645,7 +671,7 @@ func TestIDPMakeAssertion(t *testing.T) { } func TestIDPMarshalAssertion(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) req := IdpAuthnRequest{ Now: TimeNow(), IDP: &test.IDP, @@ -693,8 +719,19 @@ func TestIDPMarshalAssertion(t *testing.T) { golden.Assert(t, string(assertionBuffer), t.Name()+"_encrypted_assertion") } -func TestIDPMakeResponse(t *testing.T) { - test := NewIdentifyProviderTest(t) +func TestIDPMakeResponsePrivateKey(t *testing.T) { + test := NewIdentityProviderTest(t, applyKey) + + testMakeResponse(t, test) +} + +func TestIDPMakeResponseSigner(t *testing.T) { + test := NewIdentityProviderTest(t, applySigner) + + testMakeResponse(t, test) +} + +func testMakeResponse(t *testing.T, test *IdentityProviderTest) { req := IdpAuthnRequest{ Now: TimeNow(), IDP: &test.IDP, @@ -715,6 +752,16 @@ func TestIDPMakeResponse(t *testing.T) { err = req.MakeResponse() assert.Check(t, err) + certificateStore := &dsig.MemoryX509CertificateStore{ + Roots: []*x509.Certificate{ + req.IDP.Certificate, + }, + } + validationCtx := dsig.NewDefaultValidationContext(certificateStore) + validationCtx.Clock = dsig.NewFakeClockAt(req.IDP.Certificate.NotBefore) + _, err = validationCtx.Validate(req.ResponseEl) + assert.Check(t, err) + response := Response{} err = unmarshalEtreeHack(req.ResponseEl, &response) assert.Check(t, err) @@ -724,11 +771,11 @@ func TestIDPMakeResponse(t *testing.T) { doc.Indent(2) responseStr, err := doc.WriteToString() assert.Check(t, err) - golden.Assert(t, responseStr, t.Name()+"_response.xml") + golden.Assert(t, responseStr, "TestIDPMakeResponse_response.xml") } func TestIDPWriteResponse(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) req := IdpAuthnRequest{ Now: TimeNow(), IDP: &test.IDP, @@ -748,7 +795,7 @@ func TestIDPWriteResponse(t *testing.T) { } func TestIDPIDPInitiatedNewSession(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { fmt.Fprintf(w, "RelayState: %s", req.RelayState) @@ -764,7 +811,7 @@ func TestIDPIDPInitiatedNewSession(t *testing.T) { } func TestIDPIDPInitiatedExistingSession(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ @@ -782,7 +829,7 @@ func TestIDPIDPInitiatedExistingSession(t *testing.T) { } func TestIDPIDPInitiatedBadServiceProvider(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ @@ -799,7 +846,7 @@ func TestIDPIDPInitiatedBadServiceProvider(t *testing.T) { } func TestIDPCanHandleUnencryptedResponse(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ID: "f00df00df00d", UserName: "alice"} @@ -847,7 +894,7 @@ func TestIDPCanHandleUnencryptedResponse(t *testing.T) { } func TestIDPRequestedAttributes(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) metadata := EntityDescriptor{} err := xml.Unmarshal(golden.Get(t, "TestIDPRequestedAttributes_idp_metadata.xml"), &metadata) assert.Check(t, err) @@ -977,7 +1024,7 @@ func TestIDPRequestedAttributes(t *testing.T) { } func TestIDPNoDestination(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t, applyKey) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { return &Session{ID: "f00df00df00d", UserName: "alice"} @@ -1017,7 +1064,7 @@ func TestIDPNoDestination(t *testing.T) { } func TestIDPRejectDecompressionBomb(t *testing.T) { - test := NewIdentifyProviderTest(t) + test := NewIdentityProviderTest(t) test.IDP.SessionProvider = &mockSessionProvider{ GetSessionFunc: func(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session { fmt.Fprintf(w, "RelayState: %s\nSAMLRequest: %s", diff --git a/samlidp/samlidp.go b/samlidp/samlidp.go index 2141ca89..13ca10b9 100644 --- a/samlidp/samlidp.go +++ b/samlidp/samlidp.go @@ -20,6 +20,7 @@ import ( type Options struct { URL url.URL Key crypto.PrivateKey + Signer crypto.Signer Logger logger.Interface Certificate *x509.Certificate Store Store @@ -59,6 +60,7 @@ func New(opts Options) (*Server, error) { serviceProviders: map[string]*saml.EntityDescriptor{}, IDP: saml.IdentityProvider{ Key: opts.Key, + Signer: opts.Signer, Logger: logr, Certificate: opts.Certificate, MetadataURL: metadataURL,