The io/ioutil package has been deprecated as of Go 1.16, see https://golang.org/doc/go1.16#ioutil. This commit replaces the existing io/ioutil functions with their new definitions in io and os packages. Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
		
			
				
	
	
		
			593 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			593 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package saml
 | |
| 
 | |
| import (
 | |
| 	"crypto/x509"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/pem"
 | |
| 	"errors"
 | |
| 	"os"
 | |
| 	"sort"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/kylelemons/godebug/pretty"
 | |
| 	dsig "github.com/russellhaering/goxmldsig"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 
 | |
| 	"github.com/dexidp/dex/connector"
 | |
| )
 | |
| 
 | |
| // responseTest maps a SAML 2.0 response object to a set of expected values.
 | |
| //
 | |
| // Tests are defined in the "testdata" directory and are self-signed using xmlsec1.
 | |
| //
 | |
| // To add a new test, define a new, unsigned SAML 2.0 response that exercises some
 | |
| // case, then sign it using the "testdata/gen.sh" script.
 | |
| //
 | |
| //     cp testdata/good-resp.tmpl testdata/( testname ).tmpl
 | |
| //     vim ( testname ).tmpl # Modify your template for your test case.
 | |
| //     vim testdata/gen.sh   # Add a xmlsec1 command to the generation script.
 | |
| //     ./testdata/gen.sh     # Sign your template.
 | |
| //
 | |
| // To install xmlsec1 on Fedora run:
 | |
| //
 | |
| //     sudo dnf install xmlsec1 xmlsec1-openssl
 | |
| //
 | |
| // On mac:
 | |
| //
 | |
| //     brew install Libxmlsec1
 | |
| //
 | |
| type responseTest struct {
 | |
| 	// CA file and XML file of the response.
 | |
| 	caFile   string
 | |
| 	respFile string
 | |
| 
 | |
| 	// Values that should be used to validate the signature.
 | |
| 	now          string
 | |
| 	inResponseTo string
 | |
| 	redirectURI  string
 | |
| 	entityIssuer string
 | |
| 
 | |
| 	// Attribute customization.
 | |
| 	usernameAttr  string
 | |
| 	emailAttr     string
 | |
| 	groupsAttr    string
 | |
| 	allowedGroups []string
 | |
| 	filterGroups  bool
 | |
| 
 | |
| 	// Expected outcome of the test.
 | |
| 	wantErr   bool
 | |
| 	wantIdent connector.Identity
 | |
| }
 | |
| 
 | |
| func TestGoodResponse(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/good-resp.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroups(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/good-resp.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		groupsAttr:   "groups",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{"Admins", "Everyone"},
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroupsWhitelist(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:        "testdata/ca.crt",
 | |
| 		respFile:      "testdata/good-resp.xml",
 | |
| 		now:           "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr:  "Name",
 | |
| 		emailAttr:     "email",
 | |
| 		groupsAttr:    "groups",
 | |
| 		allowedGroups: []string{"Admins"},
 | |
| 		inResponseTo:  "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:   "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{"Admins", "Everyone"},
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroupsWhitelistWithFiltering(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:        "testdata/ca.crt",
 | |
| 		respFile:      "testdata/good-resp.xml",
 | |
| 		now:           "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr:  "Name",
 | |
| 		emailAttr:     "email",
 | |
| 		groupsAttr:    "groups",
 | |
| 		allowedGroups: []string{"Admins"},
 | |
| 		filterGroups:  true,
 | |
| 		inResponseTo:  "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:   "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{"Admins"}, // "Everyone" is filtered
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroupsWhitelistEmpty(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:        "testdata/ca.crt",
 | |
| 		respFile:      "testdata/good-resp.xml",
 | |
| 		now:           "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr:  "Name",
 | |
| 		emailAttr:     "email",
 | |
| 		groupsAttr:    "groups",
 | |
| 		allowedGroups: []string{},
 | |
| 		inResponseTo:  "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:   "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{"Admins", "Everyone"},
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroupsWhitelistDisallowed(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		wantErr:       true,
 | |
| 		caFile:        "testdata/ca.crt",
 | |
| 		respFile:      "testdata/good-resp.xml",
 | |
| 		now:           "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr:  "Name",
 | |
| 		emailAttr:     "email",
 | |
| 		groupsAttr:    "groups",
 | |
| 		allowedGroups: []string{"Nope"},
 | |
| 		inResponseTo:  "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:   "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{"Admins", "Everyone"},
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestGroupsWhitelistDisallowedNoGroupsOnIdent(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		wantErr:       true,
 | |
| 		caFile:        "testdata/ca.crt",
 | |
| 		respFile:      "testdata/good-resp.xml",
 | |
| 		now:           "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr:  "Name",
 | |
| 		emailAttr:     "email",
 | |
| 		groupsAttr:    "groups",
 | |
| 		allowedGroups: []string{"Nope"},
 | |
| 		inResponseTo:  "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:   "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 			Groups:        []string{},
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| // TestOkta tests against an actual response from Okta.
 | |
| func TestOkta(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/okta-ca.pem",
 | |
| 		respFile:     "testdata/okta-resp.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestBadStatus(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/bad-status.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestInvalidCA(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/bad-ca.crt", // Not the CA that signed this response.
 | |
| 		respFile:     "testdata/good-resp.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestUnsignedResponse(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/good-resp.tmpl", // Use the unsigned template, not the signed document.
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestExpiredAssertion(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/assertion-signed.xml",
 | |
| 		now:          "2020-04-04T04:34:59.330Z", // Assertion has expired.
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| // TestAssertionSignedNotResponse ensures the connector validates SAML 2.0
 | |
| // responses where the assertion is signed but the root element, the
 | |
| // response, isn't.
 | |
| func TestAssertionSignedNotResponse(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/assertion-signed.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestInvalidSubjectInResponseTo(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/assertion-signed.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "invalid-id", // Bad InResponseTo value.
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestInvalidSubjectRecipient(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/assertion-signed.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://bad.com/dex/callback", // Doesn't match Recipient value.
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestInvalidAssertionAudience(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/assertion-signed.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		// EntityIssuer overrides RedirectURI when determining the expected
 | |
| 		// audience. In this case, ensure the audience is invalid.
 | |
| 		entityIssuer: "http://localhost:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| // TestTwoAssertionFirstSigned tries to catch an edge case where an attacker
 | |
| // provides a second assertion that's not signed.
 | |
| func TestTwoAssertionFirstSigned(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/two-assertions-first-signed.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantIdent: connector.Identity{
 | |
| 			UserID:        "eric.chiang+okta@coreos.com",
 | |
| 			Username:      "Eric",
 | |
| 			Email:         "eric.chiang+okta@coreos.com",
 | |
| 			EmailVerified: true,
 | |
| 		},
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func TestTamperedResponseNameID(t *testing.T) {
 | |
| 	test := responseTest{
 | |
| 		caFile:       "testdata/ca.crt",
 | |
| 		respFile:     "testdata/tampered-resp.xml",
 | |
| 		now:          "2017-04-04T04:34:59.330Z",
 | |
| 		usernameAttr: "Name",
 | |
| 		emailAttr:    "email",
 | |
| 		inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m",
 | |
| 		redirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 		wantErr:      true,
 | |
| 	}
 | |
| 	test.run(t)
 | |
| }
 | |
| 
 | |
| func loadCert(ca string) (*x509.Certificate, error) {
 | |
| 	data, err := os.ReadFile(ca)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	block, _ := pem.Decode(data)
 | |
| 	if block == nil {
 | |
| 		return nil, errors.New("ca file didn't contain any PEM data")
 | |
| 	}
 | |
| 	return x509.ParseCertificate(block.Bytes)
 | |
| }
 | |
| 
 | |
| func (r responseTest) run(t *testing.T) {
 | |
| 	c := Config{
 | |
| 		CA:            r.caFile,
 | |
| 		UsernameAttr:  r.usernameAttr,
 | |
| 		EmailAttr:     r.emailAttr,
 | |
| 		GroupsAttr:    r.groupsAttr,
 | |
| 		RedirectURI:   r.redirectURI,
 | |
| 		EntityIssuer:  r.entityIssuer,
 | |
| 		AllowedGroups: r.allowedGroups,
 | |
| 		FilterGroups:  r.filterGroups,
 | |
| 		// Never logging in, don't need this.
 | |
| 		SSOURL: "http://foo.bar/",
 | |
| 	}
 | |
| 	now, err := time.Parse(timeFormat, r.now)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("parse test time: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	conn, err := c.openConnector(logrus.New())
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	conn.now = func() time.Time { return now }
 | |
| 	resp, err := os.ReadFile(r.respFile)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	samlResp := base64.StdEncoding.EncodeToString(resp)
 | |
| 
 | |
| 	scopes := connector.Scopes{
 | |
| 		OfflineAccess: false,
 | |
| 		Groups:        true,
 | |
| 	}
 | |
| 	ident, err := conn.HandlePOST(scopes, samlResp, r.inResponseTo)
 | |
| 	if err != nil {
 | |
| 		if !r.wantErr {
 | |
| 			t.Fatalf("handle response: %v", err)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if r.wantErr {
 | |
| 		t.Fatalf("wanted error")
 | |
| 	}
 | |
| 	sort.Strings(ident.Groups)
 | |
| 	sort.Strings(r.wantIdent.Groups)
 | |
| 	if diff := pretty.Compare(ident, r.wantIdent); diff != "" {
 | |
| 		t.Error(diff)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestConfigCAData(t *testing.T) {
 | |
| 	logger := logrus.New()
 | |
| 	validPEM, err := os.ReadFile("testdata/ca.crt")
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	valid2ndPEM, err := os.ReadFile("testdata/okta-ca.pem")
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	// copy helper, avoid messing with the byte slice among different cases
 | |
| 	c := func(bs []byte) []byte {
 | |
| 		return append([]byte(nil), bs...)
 | |
| 	}
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name    string
 | |
| 		caData  []byte
 | |
| 		wantErr bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:   "one valid PEM entry",
 | |
| 			caData: c(validPEM),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "one valid PEM entry with trailing newline",
 | |
| 			caData: append(c(validPEM), []byte("\n")...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "one valid PEM entry with trailing spaces",
 | |
| 			caData: append(c(validPEM), []byte("   ")...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "one valid PEM entry with two trailing newlines",
 | |
| 			caData: append(c(validPEM), []byte("\n\n")...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "two valid PEM entries",
 | |
| 			caData: append(c(validPEM), c(valid2ndPEM)...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "two valid PEM entries with newline in between",
 | |
| 			caData: append(append(c(validPEM), []byte("\n")...), c(valid2ndPEM)...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:   "two valid PEM entries with trailing newline",
 | |
| 			caData: append(c(valid2ndPEM), append(c(validPEM), []byte("\n")...)...),
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "empty",
 | |
| 			caData:  []byte{},
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "one valid PEM entry with trailing data",
 | |
| 			caData:  append(c(validPEM), []byte("yaddayadda")...),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "one valid PEM entry with bad data before",
 | |
| 			caData:  append([]byte("yaddayadda"), c(validPEM)...),
 | |
| 			wantErr: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			c := Config{
 | |
| 				CAData:       tc.caData,
 | |
| 				UsernameAttr: "user",
 | |
| 				EmailAttr:    "email",
 | |
| 				RedirectURI:  "http://127.0.0.1:5556/dex/callback",
 | |
| 				SSOURL:       "http://foo.bar/",
 | |
| 			}
 | |
| 			_, err := (&c).Open("samltest", logger)
 | |
| 			if tc.wantErr {
 | |
| 				if err == nil {
 | |
| 					t.Error("expected error, got nil")
 | |
| 				}
 | |
| 			} else if err != nil {
 | |
| 				t.Errorf("expected no error, got %v", err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Deprecated: Use testing framework established above.
 | |
| func runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) {
 | |
| 	cert, err := loadCert(ca)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	s := certStore{[]*x509.Certificate{cert}}
 | |
| 
 | |
| 	validator := dsig.NewDefaultValidationContext(s)
 | |
| 
 | |
| 	data, err := os.ReadFile(resp)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	if _, _, err := verifyResponseSig(validator, data); err != nil {
 | |
| 		if shouldSucceed {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		if !shouldSucceed {
 | |
| 			t.Fatalf("expected an invalid signature but verification has been successful")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVerify(t *testing.T) {
 | |
| 	runVerify(t, "testdata/okta-ca.pem", "testdata/okta-resp.xml", true)
 | |
| }
 | |
| 
 | |
| func TestVerifyUnsignedMessageAndSignedAssertionWithRootXmlNs(t *testing.T) {
 | |
| 	runVerify(t, "testdata/oam-ca.pem", "testdata/oam-resp.xml", true)
 | |
| }
 | |
| 
 | |
| func TestVerifySignedMessageAndUnsignedAssertion(t *testing.T) {
 | |
| 	runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message.xml", true)
 | |
| }
 | |
| 
 | |
| func TestVerifyUnsignedMessageAndSignedAssertion(t *testing.T) {
 | |
| 	runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-assertion.xml", true)
 | |
| }
 | |
| 
 | |
| func TestVerifySignedMessageAndSignedAssertion(t *testing.T) {
 | |
| 	runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message-and-assertion.xml", true)
 | |
| }
 | |
| 
 | |
| func TestVerifyUnsignedMessageAndUnsignedAssertion(t *testing.T) {
 | |
| 	runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp.xml", false)
 | |
| }
 |