diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b79baa9e..391d7d14 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.54.2 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() { diff --git a/example/trivial/trivial.go b/example/trivial/trivial.go index 1d96e63a..81ae051b 100644 --- a/example/trivial/trivial.go +++ b/example/trivial/trivial.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "net/url" + "time" "github.com/crewjam/saml/samlsp" ) @@ -72,5 +73,9 @@ func main() { http.Handle("/hello", samlMiddleware.RequireAccount(app)) http.Handle("/saml/", samlMiddleware) http.Handle("/logout", slo) - log.Fatal(http.ListenAndServe(":8000", nil)) + server := &http.Server{ + Addr: ":8000", + ReadHeaderTimeout: 3 * time.Second, + } + log.Fatal(server.ListenAndServe()) } 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..1427fafe 100644 --- a/identity_provider.go +++ b/identity_provider.go @@ -2,7 +2,6 @@ package saml import ( "bytes" - "compress/flate" "crypto" "crypto/tls" "crypto/x509" @@ -130,6 +129,8 @@ func (idp *IdentityProvider) Metadata() *EntityDescriptor { SSODescriptor: SSODescriptor{ RoleDescriptor: RoleDescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", + CacheDuration: validDuration, + ValidUntil: TimeNow().Add(validDuration), KeyDescriptors: []KeyDescriptor{ { Use: "signing", @@ -363,7 +364,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..d79cf8a5 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" @@ -147,6 +149,8 @@ func TestIDPCanProduceMetadata(t *testing.T) { { SSODescriptor: SSODescriptor{ RoleDescriptor: RoleDescriptor{ + ValidUntil: TimeNow().Add(DefaultValidDuration), + CacheDuration: DefaultValidDuration, ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", KeyDescriptors: []KeyDescriptor{ { @@ -205,7 +209,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()), " - + diff --git a/samlsp/testdata/expected_middleware_metadata.xml b/samlsp/testdata/expected_middleware_metadata.xml index c317ca9c..05a019a0 100644 --- a/samlsp/testdata/expected_middleware_metadata.xml +++ b/samlsp/testdata/expected_middleware_metadata.xml @@ -1,5 +1,5 @@ - + diff --git a/samlsp/testdata/idp_metadata.xml b/samlsp/testdata/idp_metadata.xml index bb812694..85bba161 100644 --- a/samlsp/testdata/idp_metadata.xml +++ b/samlsp/testdata/idp_metadata.xml @@ -10,7 +10,7 @@ - + testshib.org diff --git a/schema.go b/schema.go index 13ea2ef6..f81133a2 100644 --- a/schema.go +++ b/schema.go @@ -649,7 +649,7 @@ const ( StatusNoAvailableIDP = "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP" // StatusNoPassive means Indicates the responding provider cannot authenticate the principal passively, as has been requested. - StatusNoPassive = "urn:oasis:names:tc:SAML:2.0:status:NoPassive" //nolint:gosec + StatusNoPassive = "urn:oasis:names:tc:SAML:2.0:status:NoPassive" // #nosec G101 // StatusNoSupportedIDP is used by an intermediary to indicate that none of the identity providers in an are supported by the intermediary. StatusNoSupportedIDP = "urn:oasis:names:tc:SAML:2.0:status:NoSupportedIDP" @@ -667,7 +667,7 @@ const ( StatusRequestUnsupported = "urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported" // StatusRequestVersionDeprecated means the SAML responder cannot process any requests with the protocol version specified in the request. - StatusRequestVersionDeprecated = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated" + StatusRequestVersionDeprecated = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated" // #nosec G101 // StatusRequestVersionTooHigh means the SAML responder cannot process the request because the protocol version specified in the request message is a major upgrade from the highest protocol version supported by the responder. StatusRequestVersionTooHigh = "urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooHigh" diff --git a/service_provider.go b/service_provider.go index 3eac33f7..f827e611 100644 --- a/service_provider.go +++ b/service_provider.go @@ -208,7 +208,7 @@ func (sp *ServiceProvider) Metadata() *EntityDescriptor { RoleDescriptor: RoleDescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", KeyDescriptors: keyDescriptors, - ValidUntil: &validUntil, + ValidUntil: validUntil, }, SingleLogoutServices: sloEndpoints, NameIDFormats: []NameIDFormat{sp.AuthnNameIDFormat}, @@ -259,29 +259,24 @@ func (req *AuthnRequest) Redirect(relayState string, sp *ServiceProvider) (*url. rv, _ := url.Parse(req.Destination) // We can't depend on Query().set() as order matters for signing + reqString := string(w.Bytes()) query := rv.RawQuery if len(query) > 0 { - query += "&SAMLRequest=" + url.QueryEscape(string(w.Bytes())) + query += "&" + string(samlRequest) + "=" + url.QueryEscape(reqString) } else { - query += "SAMLRequest=" + url.QueryEscape(string(w.Bytes())) + query += string(samlRequest) + "=" + url.QueryEscape(reqString) } if relayState != "" { query += "&RelayState=" + relayState } if len(sp.SignatureMethod) > 0 { - query += "&SigAlg=" + url.QueryEscape(sp.SignatureMethod) - signingContext, err := GetSigningContext(sp) - - if err != nil { - return nil, err + var errSig error + query, errSig = sp.signQuery(samlRequest, query, reqString, relayState) + if errSig != nil { + return nil, errSig } - sig, err := signingContext.SignString(query) - if err != nil { - return nil, err - } - query += "&Signature=" + url.QueryEscape(base64.StdEncoding.EncodeToString(sig)) } rv.RawQuery = query @@ -1456,8 +1451,9 @@ func (sp *ServiceProvider) nameIDFormat() string { // ValidateLogoutResponseRequest validates the LogoutResponse content from the request func (sp *ServiceProvider) ValidateLogoutResponseRequest(req *http.Request) error { - if data := req.URL.Query().Get("SAMLResponse"); data != "" { - return sp.ValidateLogoutResponseRedirect(data) + query := req.URL.Query() + if data := query.Get("SAMLResponse"); data != "" { + return sp.ValidateLogoutResponseRedirect(query) } err := req.ParseForm() @@ -1512,7 +1508,8 @@ 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 { +func (sp *ServiceProvider) ValidateLogoutResponseRedirect(query url.Values) error { + queryParameterData := query.Get("SAMLResponse") retErr := &InvalidResponseError{ Now: TimeNow(), } @@ -1524,7 +1521,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 @@ -1534,15 +1531,26 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str return err } + hasValidSignature := false + if query.Get("Signature") != "" && query.Get("SigAlg") != "" { + if err := sp.validateQuerySig(query); err != nil { + retErr.PrivateErr = err + return retErr + } + hasValidSignature = true + } + doc := etree.NewDocument() - if err := doc.ReadFromBytes(rawResponseBuf); err != nil { + if err := doc.ReadFromBytes(gr); err != nil { retErr.PrivateErr = err return retErr } if err := sp.validateSignature(doc.Root()); err != nil { - retErr.PrivateErr = err - return retErr + if err != errSignatureElementNotPresent || !hasValidSignature { + retErr.PrivateErr = err + return retErr + } } var resp LogoutResponse @@ -1553,6 +1561,7 @@ func (sp *ServiceProvider) ValidateLogoutResponseRedirect(queryParameterData str if err := sp.validateLogoutResponse(&resp); err != nil { return err } + return nil } @@ -1648,17 +1657,26 @@ func findChild(parentEl *etree.Element, childNS string, childTag string) (*etree 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 + currentElement := el + // Retrieve namespaces from the element itself and its parents + for currentElement != nil { + // Iterate over the attributes of the element, if an attribute is a namespace declaration, add it to the list of namespaces + for _, attr := range currentElement.Attr { + // "xmlns" is either the space or the key of the attribute, depending on whether it is a default namespace declaration or not + if attr.Space == "xmlns" || attr.Key == "xmlns" { + // If the namespace is already preset in the list, it means that a child element has overridden it, so skip it + if _, prefixExists := namespaces[attr.FullKey()]; !prefixExists { + namespaces[attr.FullKey()] = attr.Value + } + } } + currentElement = currentElement.Parent() } doc := etree.NewDocument() doc.SetRoot(el.Copy()) - for space, uri := range namespaces { - doc.Root().CreateAttr("xmlns:"+space, uri) + for prefix, uri := range namespaces { + doc.Root().CreateAttr(prefix, uri) } return doc.WriteToBytes() diff --git a/service_provider_signed.go b/service_provider_signed.go new file mode 100644 index 00000000..d8b02f06 --- /dev/null +++ b/service_provider_signed.go @@ -0,0 +1,141 @@ +package saml + +import ( + "crypto" + "crypto/rsa" + "crypto/sha1" // #nosec G505 + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "net/url" + + dsig "github.com/russellhaering/goxmldsig" +) + +type reqType string + +const ( + samlRequest reqType = "SAMLRequest" + samlResponse reqType = "SAMLResponse" +) + +var ( + // ErrInvalidQuerySignature is returned when the query signature is invalid + ErrInvalidQuerySignature = errors.New("invalid query signature") + // ErrNoQuerySignature is returned when the query does not contain a signature + ErrNoQuerySignature = errors.New("query Signature or SigAlg not found") +) + +// Sign Query with the SP private key. +// Returns provided query with the SigAlg and Signature parameters added. +func (sp *ServiceProvider) signQuery(reqT reqType, query, body, relayState string) (string, error) { + signingContext, err := GetSigningContext(sp) + + // Encode Query as standard demands. query.Encode() is not standard compliant + toHash := string(reqT) + "=" + url.QueryEscape(body) + if relayState != "" { + toHash += "&RelayState=" + url.QueryEscape(relayState) + } + + toHash += "&SigAlg=" + url.QueryEscape(sp.SignatureMethod) + + if err != nil { + return "", err + } + + sig, err := signingContext.SignString(toHash) + if err != nil { + return "", err + } + + query += "&SigAlg=" + url.QueryEscape(sp.SignatureMethod) + query += "&Signature=" + url.QueryEscape(base64.StdEncoding.EncodeToString(sig)) + + return query, nil +} + +// validateSig validation of the signature of the Redirect Binding in query values +// Query is valid if return is nil +func (sp *ServiceProvider) validateQuerySig(query url.Values) error { + sig := query.Get("Signature") + alg := query.Get("SigAlg") + if sig == "" || alg == "" { + return ErrNoQuerySignature + } + + certs, err := sp.getIDPSigningCerts() + if err != nil { + return err + } + + respType := "" + if query.Get("SAMLResponse") != "" { + respType = "SAMLResponse" + } else if query.Get("SAMLRequest") != "" { + respType = "SAMLRequest" + } else { + return fmt.Errorf("No SAMLResponse or SAMLRequest found in query") + } + + // Encode Query as standard demands. + // query.Encode() is not standard compliant + // as query encoding order matters + res := respType + "=" + url.QueryEscape(query.Get(respType)) + + relayState := query.Get("RelayState") + if relayState != "" { + res += "&RelayState=" + url.QueryEscape(relayState) + } + + res += "&SigAlg=" + url.QueryEscape(alg) + + // Signature is base64 encoded + sigBytes, err := base64.StdEncoding.DecodeString(sig) + if err != nil { + return fmt.Errorf("failed to decode signature: %w", err) + } + + var ( + hashed []byte + hashAlg crypto.Hash + sigAlg x509.SignatureAlgorithm + ) + + // Hashed Query + switch alg { + case dsig.RSASHA256SignatureMethod: + hashed256 := sha256.Sum256([]byte(res)) + hashed = hashed256[:] + hashAlg = crypto.SHA256 + sigAlg = x509.SHA256WithRSA + case dsig.RSASHA512SignatureMethod: + hashed512 := sha512.Sum512([]byte(res)) + hashed = hashed512[:] + hashAlg = crypto.SHA512 + sigAlg = x509.SHA512WithRSA + case dsig.RSASHA1SignatureMethod: + hashed1 := sha1.Sum([]byte(res)) // #nosec G401 + hashed = hashed1[:] + hashAlg = crypto.SHA1 + sigAlg = x509.SHA1WithRSA + default: + return fmt.Errorf("unsupported signature algorithm: %s", alg) + } + + // validate signature + for _, cert := range certs { + // verify cert is RSA + if cert.SignatureAlgorithm != sigAlg { + continue + } + + if err := rsa.VerifyPKCS1v15(cert.PublicKey.(*rsa.PublicKey), hashAlg, hashed, sigBytes); err == nil { + return nil + } + } + + return ErrInvalidQuerySignature +} diff --git a/service_provider_signed_test.go b/service_provider_signed_test.go new file mode 100644 index 00000000..a45b8cbd --- /dev/null +++ b/service_provider_signed_test.go @@ -0,0 +1,117 @@ +package saml + +import ( + "crypto/rsa" + "encoding/base64" + "encoding/xml" + "net/url" + "testing" + + dsig "github.com/russellhaering/goxmldsig" + "gotest.tools/assert" + "gotest.tools/golden" +) + +// Given a SAMLRequest query string, sign the query and validate signature +// Using same Cert for SP and IdP in order to test +func TestSigningAndValidation(t *testing.T) { + type testCase struct { + desc string + relayState string + requestType reqType + wantErr bool + wantRawQuery string + } + + testCases := []testCase{ + { + desc: "validate signature of SAMLRequest with relayState", + relayState: "AAAAAAAAAAAA", + requestType: samlRequest, + wantRawQuery: "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iaWQtMDAwMjA0MDYwODBhMGMwZTEwMTIxNDE2MTgxYTFjMWUyMDIyMjQyNiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMTItMDFUMDE6NTc6MDlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20vc2FtbC9zc28iIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly9zcC5leGFtcGxlLmNvbS9zYW1sMi9hY3MiIFByb3RvY29sQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCI%2BPHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL3NwLmV4YW1wbGUuY29tL3NhbWwyL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48c2FtbHA6TmFtZUlEUG9saWN5IEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50IiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D&RelayState=AAAAAAAAAAAA&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=zWAF4S%2FIs7tfmEriOsT5Fm8EFOGS3iCq6OxP5i7hM%2BMPwAoXwdDz6fKH8euS1gQ3sGOZBdHD588FZLvnO1OeCxLaEsxHMVKsAZSZFLBmPPwqB6e%2B84cCwX2szOeoMROaR%2B36mdoBDRQz36JIvyBBG%2FND9x41k%2FGQuAuwk%2B9fkuE%3D", + }, + { + desc: "validate signature of SAML request without relay state", + relayState: "", + requestType: samlRequest, + wantRawQuery: "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iaWQtMDAwMjA0MDYwODBhMGMwZTEwMTIxNDE2MTgxYTFjMWUyMDIyMjQyNiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMTItMDFUMDE6NTc6MDlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20vc2FtbC9zc28iIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly9zcC5leGFtcGxlLmNvbS9zYW1sMi9hY3MiIFByb3RvY29sQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCI%2BPHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL3NwLmV4YW1wbGUuY29tL3NhbWwyL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48c2FtbHA6TmFtZUlEUG9saWN5IEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50IiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=HDdoHJSdkYh9%2BmE7RZ1LXcsAWIMJ6LuzKJgwLxH%2BQ4sKFlh8b5moFuQ%2B7rPEwoTcg9SjgCGV5rW9v8PrSU7WGKcLfAbeVwXWyU94ghjFZHEj%2BFCDpsfTD750ZPAPVnhVr0GogFZZ7c%2BEWX4NAqL4CYxDvsg56o%2BpOjw62G%2FyPDc%3D", + }, + { + desc: "validate signature of SAML response with relay state", + relayState: "AAAAAAAAAAAA", + requestType: samlResponse, + wantRawQuery: "SAMLResponse=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iaWQtMDAwMjA0MDYwODBhMGMwZTEwMTIxNDE2MTgxYTFjMWUyMDIyMjQyNiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMTItMDFUMDE6NTc6MDlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20vc2FtbC9zc28iIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly9zcC5leGFtcGxlLmNvbS9zYW1sMi9hY3MiIFByb3RvY29sQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCI%2BPHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL3NwLmV4YW1wbGUuY29tL3NhbWwyL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48c2FtbHA6TmFtZUlEUG9saWN5IEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50IiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D&RelayState=AAAAAAAAAAAA&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=JDeiWfLgV7SZqgqU64wgtAHS%2FqtF2c3c%2B9g1vdfRHn03tm5jrgsvJtIYg1BD8HoejCoyruH3xgDz1i2qqecVcUiAdaVgVvhn0JWJ%2BzeN9YpUFTEQ4Ah1pwezlSArzuz5esgYzSkemViox313HePWZ%2Fd0FAmtdXuGHA8O0Lp%2F4Ws%3D", + }, + } + + idpMetadata := golden.Get(t, "SP_IDPMetadata_signing") + s := ServiceProvider{ + Key: mustParsePrivateKey(golden.Get(t, "idp_key.pem")).(*rsa.PrivateKey), + Certificate: mustParseCertificate(golden.Get(t, "idp_cert.pem")), + MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"), + AcsURL: mustParseURL("https://15661444.ngrok.io/saml2/acs"), + SignatureMethod: dsig.RSASHA1SignatureMethod, + } + + err := xml.Unmarshal(idpMetadata, &s.IDPMetadata) + idpCert, err := s.getIDPSigningCerts() + + assert.Check(t, err == nil) + assert.Check(t, + s.Certificate.Issuer.CommonName == idpCert[0].Issuer.CommonName, "expected %s, got %s", + s.Certificate.Issuer.CommonName, idpCert[0].Issuer.CommonName) + + req := golden.Get(t, "idp_authn_request.xml") + reqString := base64.StdEncoding.EncodeToString(req) + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + relayState := tc.relayState + + rawQuery := string(tc.requestType) + "=" + url.QueryEscape(reqString) + + if relayState != "" { + rawQuery += "&RelayState=" + relayState + } + + rawQuery, err = s.signQuery(tc.requestType, rawQuery, reqString, relayState) + assert.NilError(t, err, "error signing query: %s", err) + + assert.Equal(t, tc.wantRawQuery, rawQuery) + + query, err := url.ParseQuery(rawQuery) + assert.NilError(t, err, "error parsing query: %s", err) + + err = s.validateQuerySig(query) + assert.NilError(t, err, "error validating query: %s", err) + }) + } +} + +// Given a raw query with an unsupported signature method, the signature should be rejected. +func TestInvalidSignatureAlgorithm(t *testing.T) { + rawQuery := "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iaWQtMDAwMjA0MDYwODBhMGMwZTEwMTIxNDE2MTgxYTFjMWUyMDIyMjQyNiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMTItMDFUMDE6NTc6MDlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20vc2FtbC9zc28iIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly9zcC5leGFtcGxlLmNvbS9zYW1sMi9hY3MiIFByb3RvY29sQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCI%2BPHNhbWw6SXNzdWVyIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL3NwLmV4YW1wbGUuY29tL3NhbWwyL21ldGFkYXRhPC9zYW1sOklzc3Vlcj48c2FtbHA6TmFtZUlEUG9saWN5IEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50IiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D&RelayState=AAAAAAAAAAAA&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha384&Signature=zWAF4S%2FIs7tfmEriOsT5Fm8EFOGS3iCq6OxP5i7hM%2BMPwAoXwdDz6fKH8euS1gQ3sGOZBdHD588FZLvnO1OeCxLaEsxHMVKsAZSZFLBmPPwqB6e%2B84cCwX2szOeoMROaR%2B36mdoBDRQz36JIvyBBG%2FND9x41k%2FGQuAuwk%2B9fkuE%3D" + + idpMetadata := golden.Get(t, "SP_IDPMetadata_signing") + s := ServiceProvider{ + Key: mustParsePrivateKey(golden.Get(t, "idp_key.pem")).(*rsa.PrivateKey), + Certificate: mustParseCertificate(golden.Get(t, "idp_cert.pem")), + MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"), + AcsURL: mustParseURL("https://15661444.ngrok.io/saml2/acs"), + SignatureMethod: dsig.RSASHA1SignatureMethod, + } + + err := xml.Unmarshal(idpMetadata, &s.IDPMetadata) + idpCert, err := s.getIDPSigningCerts() + + assert.Check(t, err == nil) + assert.Check(t, + s.Certificate.Issuer.CommonName == idpCert[0].Issuer.CommonName, "expected %s, got %s", + s.Certificate.Issuer.CommonName, idpCert[0].Issuer.CommonName) + + query, err := url.ParseQuery(rawQuery) + assert.NilError(t, err, "error parsing query: %s", err) + + err = s.validateQuerySig(query) + assert.Error(t, err, "unsupported signature algorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha384") +} diff --git a/service_provider_test.go b/service_provider_test.go index cdf5c723..1389c290 100644 --- a/service_provider_test.go +++ b/service_provider_test.go @@ -1851,3 +1851,28 @@ func TestMultipleAssertions(t *testing.T) { assert.Check(t, err) assert.Check(t, profile.Subject.NameID.Value != "admin@evil.com") } + +func TestResponseWithDefaultNamespace(t *testing.T) { + idpMetadata := golden.Get(t, "TestSPWithDefaultNamespace_idp_metadata") + respStr := golden.Get(t, "TestSPWithDefaultNamespace") + 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://sp.example.com/saml2/metadata"), + AcsURL: mustParseURL("https://sp.example.com/saml2/acs"), + IDPMetadata: &EntityDescriptor{}, + } + err := xml.Unmarshal(idpMetadata, &s.IDPMetadata) + assert.NilError(t, err) + + req := http.Request{PostForm: url.Values{}} + req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(respStr)) + _, err = s.ParseResponse(&req, []string{"id-00020406080a0c0e10121416181a1c1e"}) + + assert.NilError(t, err) +} diff --git a/testdata/SP_IDPMetadata_signing b/testdata/SP_IDPMetadata_signing new file mode 100644 index 00000000..58793ea9 --- /dev/null +++ b/testdata/SP_IDPMetadata_signing @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + testshib.org + + TestShib Test IdP + TestShib IdP. Use this as a source of attributes + for your test SP. + https://www.testshib.org/testshibtwo.jpg + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJV + UzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0 + MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMx + CzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCB + nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9 + ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmH + O8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKv + Rsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgk + akpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeT + QLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvn + OwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + + + + + urn:mace:shibboleth:1.0:nameIdentifier + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJV + UzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0 + MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMx + CzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCB + nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9 + ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmH + O8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKv + Rsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgk + akpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeT + QLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvn + OwJlNCASPZRH/JmF8tX0hoHuAQ== + + + + + + + + + + + + urn:mace:shibboleth:1.0:nameIdentifier + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + TestShib Two Identity Provider + TestShib Two + http://www.testshib.org/testshib-two/ + + + Nate + Klingenstein + ndk@internet2.edu + + \ No newline at end of file diff --git a/testdata/TestCanParseMetadata_metadata.xml b/testdata/TestCanParseMetadata_metadata.xml index aacba808..c53e4aba 100644 --- a/testdata/TestCanParseMetadata_metadata.xml +++ b/testdata/TestCanParseMetadata_metadata.xml @@ -1 +1,23 @@ -Required attributes \ No newline at end of file + + + + + + Required attributes + + + + + + + \ No newline at end of file diff --git a/testdata/TestCanProduceSPMetadata_expected b/testdata/TestCanProduceSPMetadata_expected index 79158e1e..f419b4a0 100644 --- a/testdata/TestCanProduceSPMetadata_expected +++ b/testdata/TestCanProduceSPMetadata_expected @@ -1,5 +1,5 @@ - + diff --git a/testdata/TestMetadataValidatesUrlSchemeForProtocolBinding_metadata.xml b/testdata/TestMetadataValidatesUrlSchemeForProtocolBinding_metadata.xml new file mode 100644 index 00000000..8273c6b0 --- /dev/null +++ b/testdata/TestMetadataValidatesUrlSchemeForProtocolBinding_metadata.xml @@ -0,0 +1 @@ +Required attributes \ No newline at end of file diff --git a/testdata/TestSPWithDefaultNamespace b/testdata/TestSPWithDefaultNamespace new file mode 100644 index 00000000..ef5512a6 --- /dev/null +++ b/testdata/TestSPWithDefaultNamespace @@ -0,0 +1 @@ +https://idp.example.com/saml/metadatak/YYE7FN38le10KY8AOrpSN8HQc=KhB4vVN3JxVfjJnrIV12kzKawGgp7V9EkUPp4Pqak1R++rN7GluYy+okQHUKhmD71qokmhpxKbVTCt/IAS4pumd3yRZg/kT0xeG/U9qoNIzzqJECYev7mOjyKRxfJEahcTEu0QDuge0uNf5Uj21oQMqdcV4C4jCLwW2zWLYFLpf+IJPVjQjDL7cogffq7c5AD8fpS6ad7lt7tbWxjOLt1xyDoJRcEYbZrV2QrWpJ64V8R+Y0pzsMJ85r6gPtZpZRuqPeOtP3NOrR3XdT2w435SngGR0mqwvcGGOvgXuHz7xePrhKDxZCBc6Bx7VUOgLlMTXDlmmQf50W3aypMGEoNg==MIIDRTCCAi2gAwIBAgIJANke+OUVRk19MA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTAeFw0xNzA0MTkxOTU2MTNaFw0xODA0MTkxOTU2MTNaMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7tsAJqJ4rUZFT/5SjvoA1mpQBC+KidcehMsTFW/9l73cPnYNhtnNe+9S8n76eF2ASuEwAKN5xoXDP297y4ZOORnkouP8jiYZjqNHTckE9LevM8YOpTPVojhq+rq7hiQyMF6pJkBPgduolcsnBg188jn/oNmdmzIaS33QiNOZ6FGXLKt3/Fapscea0gq8a46wKhFFQmW8i+MGqdSSi255c05ZZL5H04plDNoGEvES5OsxSjbZ4+294K83nOXoGSEPfsO4XUTORlryis0p/f3aNUgooueeTphD9Go2pGHYvahjusx9ONbZ7egc07dJoIAVjgaMSv+UwqfpxjAU7018sCAwEAAaOBgTB/MB0GA1UdDgQWBBSYE9Nwp/eUqfRQ11rqwoowNFHNyTBQBgNVHSMESTBHgBSYE9Nwp/eUqfRQ11rqwoowNFHNyaEkpCIwIDEeMBwGA1UEAxMVbXlzZXJ2aWNlLmV4YW1wbGUuY29tggkA2R745RVGTX0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAVJmpMg1ZpDGweoCU4k66RVDpPzSuPJ+9H9L2jcaA38itDtXmG9IzdbOLpNF9fDbU60P421SgS0nF/s7zkxkYJWOoZaced/vUO6H9TdWEZay+uywAjvoZGwkZ9HxYMqKMVld4EwW/OwT67UVBdtgkSfI1O7ojqDOFx7U4+HJWxUEwGOc0pOPzNyLSYCsAkQt2CZU7dN72L96Ka8xxklNaVcUaUH+zOWF1JBamV9s6M2umcdBot8MO3m1zQTkXzBKM3f+Yvk+dRjO4TSW90h2oQqot8xrkPhy+DgOqJj3/lKmZXjqE5mAEhpQB0uVPekPvKN89hCnkPo2EvXKPf7VZgg==https://idp.example.com/saml/metadataunqkaATYuvJT2koFrke8ArX8K9s=Qzb+/IyJgHq2WLR4PyCXNX2pR87GYm0pDm0yBcKjcISo3i8S9Xm04yxqm+zxYb+4HuReB6ZJHId2Jhaz9Ny0aDmj0WMa2QDpeCugffhDdgEEhNiqCpZJVLfACK6QpgqLnsKccalM2VPexU96Q73INxz/zNQKw0Dltf/GBRCbEbJKcHNIX/Xg7CPJdV2sgAGPVlXbZ5vahW3Oy+7HUcGVajZTc6P5DrLEIQRSsZgHZLv4RSvbKBxQc/CvIzVagDWJDD5kJniqf9JGYVZ8j4MQeb8u0VtDlijvrq008Ia566OGom3qj7trdxYdz+Co0ZPJra4c9sKyDa3udXW428fqog==MIIDRTCCAi2gAwIBAgIJANke+OUVRk19MA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTAeFw0xNzA0MTkxOTU2MTNaFw0xODA0MTkxOTU2MTNaMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7tsAJqJ4rUZFT/5SjvoA1mpQBC+KidcehMsTFW/9l73cPnYNhtnNe+9S8n76eF2ASuEwAKN5xoXDP297y4ZOORnkouP8jiYZjqNHTckE9LevM8YOpTPVojhq+rq7hiQyMF6pJkBPgduolcsnBg188jn/oNmdmzIaS33QiNOZ6FGXLKt3/Fapscea0gq8a46wKhFFQmW8i+MGqdSSi255c05ZZL5H04plDNoGEvES5OsxSjbZ4+294K83nOXoGSEPfsO4XUTORlryis0p/f3aNUgooueeTphD9Go2pGHYvahjusx9ONbZ7egc07dJoIAVjgaMSv+UwqfpxjAU7018sCAwEAAaOBgTB/MB0GA1UdDgQWBBSYE9Nwp/eUqfRQ11rqwoowNFHNyTBQBgNVHSMESTBHgBSYE9Nwp/eUqfRQ11rqwoowNFHNyaEkpCIwIDEeMBwGA1UEAxMVbXlzZXJ2aWNlLmV4YW1wbGUuY29tggkA2R745RVGTX0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAVJmpMg1ZpDGweoCU4k66RVDpPzSuPJ+9H9L2jcaA38itDtXmG9IzdbOLpNF9fDbU60P421SgS0nF/s7zkxkYJWOoZaced/vUO6H9TdWEZay+uywAjvoZGwkZ9HxYMqKMVld4EwW/OwT67UVBdtgkSfI1O7ojqDOFx7U4+HJWxUEwGOc0pOPzNyLSYCsAkQt2CZU7dN72L96Ka8xxklNaVcUaUH+zOWF1JBamV9s6M2umcdBot8MO3m1zQTkXzBKM3f+Yvk+dRjO4TSW90h2oQqot8xrkPhy+DgOqJj3/lKmZXjqE5mAEhpQB0uVPekPvKN89hCnkPo2EvXKPf7VZgg==https://sp.example.com/saml2/metadataurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportalice diff --git a/testdata/TestSPWithDefaultNamespace_idp_metadata b/testdata/TestSPWithDefaultNamespace_idp_metadata new file mode 100644 index 00000000..faa6c89f --- /dev/null +++ b/testdata/TestSPWithDefaultNamespace_idp_metadata @@ -0,0 +1 @@ +MIIDRTCCAi2gAwIBAgIJANke+OUVRk19MA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTAeFw0xNzA0MTkxOTU2MTNaFw0xODA0MTkxOTU2MTNaMCAxHjAcBgNVBAMTFW15c2VydmljZS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7tsAJqJ4rUZFT/5SjvoA1mpQBC+KidcehMsTFW/9l73cPnYNhtnNe+9S8n76eF2ASuEwAKN5xoXDP297y4ZOORnkouP8jiYZjqNHTckE9LevM8YOpTPVojhq+rq7hiQyMF6pJkBPgduolcsnBg188jn/oNmdmzIaS33QiNOZ6FGXLKt3/Fapscea0gq8a46wKhFFQmW8i+MGqdSSi255c05ZZL5H04plDNoGEvES5OsxSjbZ4+294K83nOXoGSEPfsO4XUTORlryis0p/f3aNUgooueeTphD9Go2pGHYvahjusx9ONbZ7egc07dJoIAVjgaMSv+UwqfpxjAU7018sCAwEAAaOBgTB/MB0GA1UdDgQWBBSYE9Nwp/eUqfRQ11rqwoowNFHNyTBQBgNVHSMESTBHgBSYE9Nwp/eUqfRQ11rqwoowNFHNyaEkpCIwIDEeMBwGA1UEAxMVbXlzZXJ2aWNlLmV4YW1wbGUuY29tggkA2R745RVGTX0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAVJmpMg1ZpDGweoCU4k66RVDpPzSuPJ+9H9L2jcaA38itDtXmG9IzdbOLpNF9fDbU60P421SgS0nF/s7zkxkYJWOoZaced/vUO6H9TdWEZay+uywAjvoZGwkZ9HxYMqKMVld4EwW/OwT67UVBdtgkSfI1O7ojqDOFx7U4+HJWxUEwGOc0pOPzNyLSYCsAkQt2CZU7dN72L96Ka8xxklNaVcUaUH+zOWF1JBamV9s6M2umcdBot8MO3m1zQTkXzBKM3f+Yvk+dRjO4TSW90h2oQqot8xrkPhy+DgOqJj3/lKmZXjqE5mAEhpQB0uVPekPvKN89hCnkPo2EvXKPf7VZgg==urn:oasis:names:tc:SAML:2.0:nameid-format:transient diff --git a/testdata/escalation_test_metadata.xml b/testdata/escalation_test_metadata.xml new file mode 100644 index 00000000..2a3a5d55 --- /dev/null +++ b/testdata/escalation_test_metadata.xml @@ -0,0 +1,81 @@ + + + + + + + MIICxDCCAaygAwIBAQIQOwSPtsjAS6O4dntXuFH8lzANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDExNsb2dpbi5mdXNpb25hdXRoLmlvMB4XDTIyMDgxNzIxNDc1OFoXDTMyMDgxNzIxNDc1OFowHjEcMBoGA1UEAxMTbG9naW4uZnVzaW9uYXV0aC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANADptGr+yPTRxo+uSt+gffFjRukFrFZpONSlBJtYdJafPTB61ZXEIYBmGU5nicSp67nSUYc3fFWOnK4ZsT5ATQ9ctWEyyC53w2QDQr6+XeLUtoQ+xzsFCw4j2eaqBlQGiaGBzHk+jKsb+5c4/Ep07MI9r5o9/et8k489UOc6puTpTg0Zn1Xgo2l7GVv9FOY4yjRRLXg9m47IZbIrshOEl7zz2ZXYxtSruwWw8kUWed42yfZbI5M/iaoNi2fnzh4S1asf56eFNXvVVYnW/p8wd++87umiAZQAaoW54TeDL8WctQuY64Nd0smVIaczw3by5GidTHyoDPGL+0jaNIxFIUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEApH37IfLZ7Hsfruma8FqhGwRE+M9P0qtcBt6z4lok5uZMFoQjBWN7/zN3tLeMgmZfRoTFhSpQ3Fv5HNquRJvj15MOom/+SHCVsqH+E7oucoDeoRyYnWm+zK0FNARQXbvx1UXumBJZ+HXgHpvkzOCSgYOTxMe1HYVrThY1F2EcK7qh5os1yZXLlcr3cYc388+1dYHxhIu0YrevzKNGuYhTwcqY0Z87n5mXmH8Dt1VsK5ldfSAp9EqaqKvu4KnzoEGUHql3nVWNNMJLMkEc173qZ4HRxOJ7qAShtud0j2asra6SCLd5jssBvkMJJqFgWf/S6PI5q9wglGYJTViQ9KvDrg== + + + + + + + + MIICxDCCAaygAwIBAQIQRBGI3tdcSRe4Mkr9m/ZvbDANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDExNsb2dpbi5mdXNpb25hdXRoLmlvMB4XDTIzMDIwNDAxMzc0N1oXDTMzMDIwNDAxMzc0N1owHjEcMBoGA1UEAxMTbG9naW4uZnVzaW9uYXV0aC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALUNRhP5pNVWoI3PVuQChADl9IOLjAEQTQHWwth2B5Olo57cnUmQ9BKjCpcJNKQpBVKzt5Vu72UtmfQ4OnhLsEfDIq0MuS6KRhhbhhVcYYa43Hzt/ngPnR093UBmI36/mpsTuqQCrfeEqKJpg7/449KNCzUJQS6sQO/d9XxUAyYH3CK/47y+HLKUEMYDnY2sZmWKKNauvG5igeoUtawYDMDRx1PU3huFR5sULPO1rTJCNtkcAg5YXWMq4ashWT77rtnMmQj0c9FWh6niyzRTVYo1ANOgyCTxJd0ecIke2PvyVbsCHFzw3Fl+U3dMGfr5xmarF/CbXNTJ6oylyNCZZ/kCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAgxR+6s4QOW/n5wnBEUVr60xJQtEPwyL6jCFURBWkBsJdPMGcUv3glt1lwxAkczOLi+DL272+derVWnHVwIWXzgIky6FGL9n++xaYuXQfT/+ITNZ3OYhcfiv5hDRUnWMSAL9XoVIHt1fvynkb2gUy6ykmAAWEZ41I2GMy4QrVSBLOiTJjVY87uu5ShSMvlSD/Kirpoylygc6IYiGNbv3TCNjds56HV+rSPAcDUuGj6Gq9JQOAQA2dpy6nNtUDL+ndd+NxcOcGuhukE5dsrfiQIwwgjKQ48CBLZVyCFlslRfoRLmluckHg1cXfS7S8y2c0fsP13WLaztQ3poG21vwoLg== + + + + + + + + MIICxTCCAa2gAwIBAQIRAMSTo6bqo0pEk0mu25dIa6owDQYJKoZIhvcNAQELBQAwHjEcMBoGA1UEAxMTbG9naW4uZnVzaW9uYXV0aC5pbzAeFw0yMjA1MTcxNzU1MDVaFw0zMjA1MTcxNzU1MDVaMB4xHDAaBgNVBAMTE2xvZ2luLmZ1c2lvbmF1dGguaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClW8D4lYIcVJvXnO6t3QdS1YDZWNI/37PH0gfh9cIl2qeqlQgPdZDRIx4J3RCipYdSdhth77EVmWk4CiShUQe9JoqDalguM7cumMfbhaQ5Q0OJKEG+oaHRW9je+IW0uWTRF2TxvNDF7WzXCyyzL0YGAg6hswAJrkzBIaaV+TOqTyM1n1fsKKKD0/KeGPrV47J3xpNYVGla5K2K6VU1ccf/2/BYaZlNzcv/4T7Jed/xWWHdi0C0RrlQv50NUJ2q6+APzgBp2X60WCAcIsds3U7PI8l50GG5cTZ+aqp0xlOeM/LktlFhHzMhpY7Nkds3w9JObsnlOqC92Lmf6Tu6TXlPAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAB+xo6QXibqXxlvYO9J1fbYwedLqBvwr9jXHlCKp+cqPfp4RfJBbPz2KPZcxf8TgCQF6OPQ3RWThlHCnVf9DlQihm5+ScKpLyGRtkuUMe2Q6lvqdSbQTpFBCNFJ+1HhryUnXhL1aLbUK1dSsT7yFvx5JENklpXgNgQ+eb2TYDL4f06QU6+7Yg6rU2/JjQBgKZ5rWsv8spCyqs1OHYFK9efOupgbOwW2tGP/jJDzUA5LoN3r9KItYs1KXCFTBHeGy73dRqy0Pt7ztnHv2l8tdR1RRfxqKwyHTBttcpBGlJiHgPHCV7gZIcAUY2Y+8rIMsJIONvsIXrtfkgQgv1HW7W88= + + + + + + + + MIICxDCCAaygAwIBAQIQUzabsBYoRhmD1jWkcnjGYDANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDExNsb2dpbi5mdXNpb25hdXRoLmlvMB4XDTIzMDQyNzIxMTY1MloXDTMzMDQyNzIxMTY1MlowHjEcMBoGA1UEAxMTbG9naW4uZnVzaW9uYXV0aC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJMcyW3tf7wsAC19yGpkfP6LVb/wf+u4oexo86KTtFpeDoLwq5tPEHCgT5xj6JotTduGIlt1PtKZmK/eoMvTzf1ygfRkfRDbpfvIZOfq3m2B50IYBPNj7oEmtRTbPJwZzb/HSPqEYegZ0+at80OEvMHacJCvO3dfG2ZY7X9CF7lKwPZ7D96rrOUlk0yOywRmgTU0g7wpcxJC9NYW8C1vvaLsF/HZspV+saXP84FXqmaaefpJzTumzPcg6Rk1GXJFR1S3YxIq6txiR32euY0F5k+EBt7dH1yXqqwZHkCGauaDjuCicZNUl0MalCcRxdbqlexHh8KPFI6RGc2iO4mQVXECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEADUlR+klbbz+IwTjyzKxyOPSfloI+Wwah3Kbs1BCtlNm4MRkP0+8iUPLmPYuYNNHYP5DDPNgciK9qqmru+I0odkscjm6wn9YcotuSEjFD459N/nbpRO10COvGrMy3khiPt3f2T1MU7+QOyyOnMhniXQegA+5aEXW8yswLFJvoyOXIKdWTIEB4in5QN/izbIjaoBc7h9wCX7x0z+oCrK1iw03F7QE6m8HnXueUU9HBVoAUiJY82+t0+FTfIRblMwElP1P6ScML0XwKhPJxDxut8MMHnb8QwTfoZ6Wy0Nj9oLHu6zHhyi6PB9IMXArRer0gOywyed/V8vTWCgELwbUTBQ== + + + + + + + + MIICwjCCAaqgAwIBAQIQYpzZmYQGSCmhK/gn/XnyqTANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJsb2dpbi5pbnZlcnNvZnQuaW8wHhcNMjAwMTA3MjEyNDA2WhcNMzAwMTA3MjEyNDA2WjAdMRswGQYDVQQDExJsb2dpbi5pbnZlcnNvZnQuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCI3+zSFl3R7G6qhJSTg3exV0/XRwCjzTYQ19RJrClyVcJ2hRPddN9NWiWcTExmKeCqcz1tyOLKMVj3ZbnsYUJATiBccctiyhQ0t+ojDGPyWrRJVflbWZar6tj7O1EXbgWNyAHtbdEoQ4/VDmC6jGqNTC7PiVSFWyOmiID2PsFB8yBZx+kT27CKGEQSNpyMncf+ds9iSIlZe5LpSxtptGW3WUis9iwtcFzaV+sQZ3Y/agN0Nzut1dR4DiODL0OImeAaQyu7og4d+Ypu2oH1jp2Tke1SK6yxwYDeuRunbsreIsmcm4AEz1bh0/+Wvn05thSvglFm/oSKTD+3Hs7Vj0XPAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADt8SR/tljjczCZJjjZFOuYfn2bJHcniI/6nZbc6QUhE2IkWVtOIrnz1U7MSKOLTowsw6ZQlgGOqvxgf1cpbyIQNI7tLB0pyWmh17LZ+y3dqKnNqgRBPw3Zt1ESwocoS8POzASDR8j27RG+CHA1nxffU9yhl37o2icDqsjhB8JNB6cHEKCudRLzsyGpuIMa9tiZw7tl6aqfxv4E5wU/xt0j9nkiqxPzZQMuGM/H1tavONtkN1jC0HGCi8VbkLM6VkbEZtXxqBBQ0vOS4/TxRzV7Nceg+NxIK+pz324HZlVI9mF+IV4fbu1V/L8fpbRKhbtt1wbXWzmy/CmuBUqcwPPs= + + + + + + + + MIICwzCCAaugAwIBAQIRALQxckS5FkxviI5bQogqEGwwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAxMSbG9naW4uaW52ZXJzb2Z0LmlvMB4XDTIwMDEwNzIxMDQzN1oXDTMwMDEwNzIxMDQzN1owHTEbMBkGA1UEAxMSbG9naW4uaW52ZXJzb2Z0LmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/lS0D6wW11Wzpk2OBEpeyJHIjQimfBw1BLCxPjQPSCSeuIQugPsky6DvFaxcSVbSxydyT2RhXm5+258da7ls6pylq0Ht6+vSJRXS2NZlVDI4LdDyNXlk6eK+b3VqYu0yP4Xl0ElDxcCqMNXnk8ybv3A6mPxkW0jEBr9GRWEUWQ/2jzqmoj9snBhDXBImNSIVSCtfeVxBNLrg0aQzao4t2C9T2AaNG+tS5U05wU8t76XF2CgHZsl1DloILQORno1DdNpRaN+lT/ioj+9fljo2JRKysbW+rcgVBd8yqMk2UP6VnWItmhi66eYGmcinN78mSFl4tEzd7dm6Ettu4J3+wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfU73acU0l/BDFB8DTU4NLabyw5Up1/V+aB7eQx3y3J+Jvr4KVADcsISPsvL6T1xpgjM05EFxqPVlUtPN0+QiItYrPMl0bhvBKSv8mh6zjEZvXPC3DvCdjcbc0swDiWwTJGqatRowwC1hOsVzyV+c4qSjag8UrQwtq0yStXY2jPCWamj0koypEz6xjKfUZ/6xVHDhxoZFxaOZWA6e3Caoo2IJ7z6N+u0IfNrmxFlryiBsNlZcom2TsFn5I9BMqsLZ9EtzpBkz6HzyPJpxrObaSSF3PaS8PFBYincU9Qt/LXQcfHJrhRJF7lM4b4upyttpIGF4KiupfuYo9T9Iojnxk + + + + + + + + MIICxTCCAa2gAwIBAQIRAPtRbLD9ykgblVzQSkelGQ8wDQYJKoZIhvcNAQELBQAwHjEcMBoGA1UEAxMTbG9naW4uZnVzaW9uYXV0aC5pbzAeFw0yMzA2MDIxODIzMjJaFw0zMzA2MDIxODIzMjJaMB4xHDAaBgNVBAMTE2xvZ2luLmZ1c2lvbmF1dGguaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQHAVgYpB104PKyH1N7oPdYPar6jo+JPOjuEc5nUV74l2mw/WrzWs9edFdtHKmfNDdL5lLxhB4Q/vYdsesitaOszmhK313iU1J2ya1kQpwJcuMIUX/Ak2xHOcZTyli9qtTAYHTT8xPIwRcn73Sz1Ekpahp5sHFeY0U32eiXX9aEwkLMhF4vR6Xyst1kDHpwDSYaLAI2xQaSBQoWz6RQA3MLFyjxFx4BHk5wmEDb1hjaVL6WsaROOHGHxONg3Ddw9G+wRzH4aAFc0izcC0VHQ/x9t0s91NGisRWX+VsZDyAbvmyqIcNtwAbMiAnw87fLcfnRjstEoEiuoZcHtekU/dXAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACtWidmd+nRhEHT5hi6NmDXTGvmLZJaSfnFwUQGLpMe9Fv59ezbI9yp5afaqyrVU6xN1KuZehmHy35+KCOQV3dzoQWiUSvFJJyazpiaLt9wtiIgJUCQ8t4BwTA9EEyr0qhNCvjcZ6H9gAe5Q3d0z+ZbubBu3OWUF/QL48I5jnIHWJ344+Ia4NBKaJqWdz4rCnp/9HlopjnPaXexOrbAGRX1Y6rSwe9UVEjIr331zV0kmw+Z5g1a9OaBj/P7Am7g0xcTLMjbXuhUET2mqSIEG0H/stB4b89wBGmdnJLHgHGIVHLIgM/tn6i713YRpnnuG9h3eEgzxTAY45oj5IQZK024= + + + + + + + + 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 + + + + + + + + + \ No newline at end of file diff --git a/testdata/escalation_test_response.xml b/testdata/escalation_test_response.xml new file mode 100644 index 00000000..f0a1dd3b --- /dev/null +++ b/testdata/escalation_test_response.xml @@ -0,0 +1,71 @@ + + https://login.fusionauth.io/samlv2/f32a81a4-e5be-93f2-cb34-ec605ed7f708 + + + + + https://login.fusionauth.io/samlv2/f32a81a4-e5be-93f2-cb34-ec605ed7f708 + + + + + + + + + + + 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 + + + + + 00000000-0000-0000-0000-00000012cbce + + + + + + + https://fusionauthprod.grafana.net/saml/metadata + + + + + daniel@fusionauth.io + + + Daniel + + + daniel@fusionauth.io + + + Daniel + + + DeGroff + + + 00000000-0000-0000-0000-00000012cbce + + + DeGroff + + + admin + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + \ No newline at end of file