github.com/crewjam/saml@v0.4.14/service_provider_test.go (about)

     1  package saml
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rsa"
     6  	"crypto/x509"
     7  	"encoding/base64"
     8  	"encoding/xml"
     9  	"html"
    10  	"net/http"
    11  	"net/url"
    12  	"regexp"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"gotest.tools/assert"
    18  	is "gotest.tools/assert/cmp"
    19  	"gotest.tools/golden"
    20  
    21  	"github.com/beevik/etree"
    22  	dsig "github.com/russellhaering/goxmldsig"
    23  
    24  	"github.com/crewjam/saml/testsaml"
    25  )
    26  
    27  type ServiceProviderTest struct {
    28  	AuthnRequest []byte
    29  	SamlResponse []byte
    30  	Key          *rsa.PrivateKey
    31  	Certificate  *x509.Certificate
    32  	IDPMetadata  []byte
    33  }
    34  
    35  // Helper to decode SAML redirect binding requests
    36  // http://play.golang.org/p/sTlV0pCS2y
    37  //     x1 := "lJJBj9MwEIX%2FSuR7Y4%2FJRisriVS2Qqq0QNUAB27GmbYWiV08E6D%2FHqeA6AnKdfz85nvPbtYzn8Iev8xIXHyfxkCtmFMw0ZInE%2ByEZNiZfv362ehSmXOKHF0cRbEmwsQ%2BhqcYaJ4w9Zi%2Beofv98%2BtODGfyUgJD3UNVVWV4Zji59JHSXYatbSORLHJO32wi8efG344l5wP6OQ%2FlTEdl4HMWw9%2BRLlgaLnHwSd0LPv%2BrSi2m1b4YaWU0qpStXpUVjmFoEBDBTU8ggUHmIVEM24DsQ3cCq3gYQV6peCdAvMCjIaPotj9ivfSh8GHYytE8QETXQlzfNE1V5d0T1X2d0GieBXTZPnv8mWScxyuUoOBPV9E968iJ2Q7WLaN%2FAnWNW%2Byz3azi6N3l%2F980XGM354SWsZWcJpRdPcDc7KBfMZu5C1B18jbL9b9CAAA%2F%2F8%3D"
    38  //     x2, _ := url.QueryUnescape(x1)
    39  //     x3, _ := base64.StdEncoding.DecodeString(x2)
    40  //     x4, _ := io.ReadAll(flate.NewReader(bytes.NewReader(x3)))
    41  //     fmt.Printf("%s\n", x4)
    42  
    43  type testRandomReader struct {
    44  	Next byte
    45  }
    46  
    47  func (tr *testRandomReader) Read(p []byte) (n int, err error) {
    48  	for i := 0; i < len(p); i++ {
    49  		p[i] = tr.Next
    50  		tr.Next += 2
    51  	}
    52  	return len(p), nil
    53  }
    54  
    55  func NewServiceProviderTest(t *testing.T) *ServiceProviderTest {
    56  	TimeNow = func() time.Time {
    57  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015")
    58  		return rv
    59  	}
    60  	Clock = dsig.NewFakeClockAt(TimeNow())
    61  
    62  	RandReader = &testRandomReader{}
    63  
    64  	test := ServiceProviderTest{}
    65  	test.AuthnRequest = golden.Get(t, "SP_AuthnRequest")
    66  	test.SamlResponse = golden.Get(t, "SP_SamlResponse")
    67  	test.Key = mustParsePrivateKey(golden.Get(t, "sp_key.pem")).(*rsa.PrivateKey)
    68  	test.Certificate = mustParseCertificate(golden.Get(t, "sp_cert.pem"))
    69  	test.IDPMetadata = golden.Get(t, "SP_IDPMetadata")
    70  	return &test
    71  }
    72  
    73  func TestSPCanSetAuthenticationNameIDFormat(t *testing.T) {
    74  	test := NewServiceProviderTest(t)
    75  
    76  	s := ServiceProvider{
    77  		Key:         test.Key,
    78  		Certificate: test.Certificate,
    79  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
    80  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
    81  	}
    82  
    83  	// defaults to "transient"
    84  	req, err := s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding)
    85  	assert.Check(t, err)
    86  	assert.Check(t, is.Equal(string(TransientNameIDFormat), *req.NameIDPolicy.Format))
    87  
    88  	// explicitly set to "transient"
    89  	s.AuthnNameIDFormat = TransientNameIDFormat
    90  	req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding)
    91  	assert.Check(t, err)
    92  	assert.Check(t, is.Equal(string(TransientNameIDFormat), *req.NameIDPolicy.Format))
    93  
    94  	// explicitly set to "unspecified"
    95  	s.AuthnNameIDFormat = UnspecifiedNameIDFormat
    96  	req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding)
    97  	assert.Check(t, err)
    98  	assert.Check(t, is.Equal("", *req.NameIDPolicy.Format))
    99  
   100  	// explicitly set to "emailAddress"
   101  	s.AuthnNameIDFormat = EmailAddressNameIDFormat
   102  	req, err = s.MakeAuthenticationRequest("", HTTPRedirectBinding, HTTPPostBinding)
   103  	assert.Check(t, err)
   104  	assert.Check(t, is.Equal(string(EmailAddressNameIDFormat), *req.NameIDPolicy.Format))
   105  }
   106  
   107  func TestSPCanProduceMetadataWithEncryptionCert(t *testing.T) {
   108  	test := NewServiceProviderTest(t)
   109  	s := ServiceProvider{
   110  		Key:            test.Key,
   111  		Certificate:    test.Certificate,
   112  		MetadataURL:    mustParseURL("https://example.com/saml2/metadata"),
   113  		AcsURL:         mustParseURL("https://example.com/saml2/acs"),
   114  		SloURL:         mustParseURL("https://example.com/saml2/slo"),
   115  		IDPMetadata:    &EntityDescriptor{},
   116  		LogoutBindings: []string{HTTPPostBinding},
   117  	}
   118  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   119  	assert.Check(t, err)
   120  
   121  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   122  	assert.Check(t, err)
   123  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   124  }
   125  
   126  func TestSPCanProduceMetadataWithBothCerts(t *testing.T) {
   127  	test := NewServiceProviderTest(t)
   128  	s := ServiceProvider{
   129  		Key:               test.Key,
   130  		Certificate:       test.Certificate,
   131  		MetadataURL:       mustParseURL("https://example.com/saml2/metadata"),
   132  		AcsURL:            mustParseURL("https://example.com/saml2/acs"),
   133  		SloURL:            mustParseURL("https://example.com/saml2/slo"),
   134  		IDPMetadata:       &EntityDescriptor{},
   135  		AuthnNameIDFormat: TransientNameIDFormat,
   136  		LogoutBindings:    []string{HTTPPostBinding},
   137  		SignatureMethod:   "not-empty",
   138  	}
   139  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   140  	assert.Check(t, err)
   141  
   142  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   143  	assert.Check(t, err)
   144  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   145  
   146  }
   147  
   148  func TestCanProduceMetadataNoCerts(t *testing.T) {
   149  	test := NewServiceProviderTest(t)
   150  	s := ServiceProvider{
   151  		MetadataURL:       mustParseURL("https://example.com/saml2/metadata"),
   152  		AcsURL:            mustParseURL("https://example.com/saml2/acs"),
   153  		IDPMetadata:       &EntityDescriptor{},
   154  		AuthnNameIDFormat: TransientNameIDFormat,
   155  		LogoutBindings:    []string{HTTPPostBinding},
   156  	}
   157  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   158  	assert.Check(t, err)
   159  
   160  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   161  	assert.Check(t, err)
   162  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   163  }
   164  
   165  func TestCanProduceMetadataEntityID(t *testing.T) {
   166  	test := NewServiceProviderTest(t)
   167  	s := ServiceProvider{
   168  		EntityID:       "spn:11111111-2222-3333-4444-555555555555",
   169  		MetadataURL:    mustParseURL("https://example.com/saml2/metadata"),
   170  		AcsURL:         mustParseURL("https://example.com/saml2/acs"),
   171  		IDPMetadata:    &EntityDescriptor{},
   172  		LogoutBindings: []string{HTTPPostBinding},
   173  	}
   174  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   175  	assert.Check(t, err)
   176  
   177  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   178  	assert.Check(t, err)
   179  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   180  }
   181  
   182  func TestSPCanProduceMetadataWithNoLougoutBindings(t *testing.T) {
   183  	test := NewServiceProviderTest(t)
   184  	s := ServiceProvider{
   185  		Key:         test.Key,
   186  		Certificate: test.Certificate,
   187  		MetadataURL: mustParseURL("https://example.com/saml2/metadata"),
   188  		AcsURL:      mustParseURL("https://example.com/saml2/acs"),
   189  		SloURL:      mustParseURL("https://example.com/saml2/slo"),
   190  		IDPMetadata: &EntityDescriptor{},
   191  	}
   192  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   193  	assert.Check(t, err)
   194  
   195  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   196  	assert.Check(t, err)
   197  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   198  }
   199  
   200  func TestSPCanProduceMetadataWithBothLougoutBindings(t *testing.T) {
   201  	test := NewServiceProviderTest(t)
   202  	s := ServiceProvider{
   203  		Key:            test.Key,
   204  		Certificate:    test.Certificate,
   205  		MetadataURL:    mustParseURL("https://example.com/saml2/metadata"),
   206  		AcsURL:         mustParseURL("https://example.com/saml2/acs"),
   207  		SloURL:         mustParseURL("https://example.com/saml2/slo"),
   208  		IDPMetadata:    &EntityDescriptor{},
   209  		LogoutBindings: []string{HTTPPostBinding, HTTPRedirectBinding},
   210  	}
   211  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   212  	assert.Check(t, err)
   213  
   214  	spMetadata, err := xml.MarshalIndent(s.Metadata(), "", "  ")
   215  	assert.Check(t, err)
   216  	golden.Assert(t, string(spMetadata), t.Name()+"_metadata")
   217  }
   218  
   219  func TestSPCanProduceRedirectRequest(t *testing.T) {
   220  	test := NewServiceProviderTest(t)
   221  	TimeNow = func() time.Time {
   222  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   223  		return rv
   224  	}
   225  	Clock = dsig.NewFakeClockAt(TimeNow())
   226  	s := ServiceProvider{
   227  		Key:         test.Key,
   228  		Certificate: test.Certificate,
   229  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   230  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   231  		IDPMetadata: &EntityDescriptor{},
   232  	}
   233  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   234  	assert.Check(t, err)
   235  
   236  	redirectURL, err := s.MakeRedirectAuthenticationRequest("relayState")
   237  	assert.Check(t, err)
   238  
   239  	decodedRequest, err := testsaml.ParseRedirectRequest(redirectURL)
   240  	assert.Check(t, err)
   241  	assert.Check(t, is.Equal("idp.testshib.org",
   242  		redirectURL.Host))
   243  	assert.Check(t, is.Equal("/idp/profile/SAML2/Redirect/SSO",
   244  		redirectURL.Path))
   245  	golden.Assert(t, string(decodedRequest), t.Name()+"_decoded_request")
   246  }
   247  
   248  func TestSPCanProducePostRequest(t *testing.T) {
   249  	test := NewServiceProviderTest(t)
   250  	TimeNow = func() time.Time {
   251  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Mon Dec 1 01:31:21 UTC 2015")
   252  		return rv
   253  	}
   254  	s := ServiceProvider{
   255  		Key:         test.Key,
   256  		Certificate: test.Certificate,
   257  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   258  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   259  		IDPMetadata: &EntityDescriptor{},
   260  	}
   261  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   262  	assert.Check(t, err)
   263  
   264  	form, err := s.MakePostAuthenticationRequest("relayState")
   265  	assert.Check(t, err)
   266  	golden.Assert(t, string(form), t.Name()+"_form")
   267  }
   268  
   269  func TestSPCanProduceSignedRequestRedirectBinding(t *testing.T) {
   270  	test := NewServiceProviderTest(t)
   271  	TimeNow = func() time.Time {
   272  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   273  		return rv
   274  	}
   275  	Clock = dsig.NewFakeClockAt(TimeNow())
   276  	s := ServiceProvider{
   277  		Key:             test.Key,
   278  		Certificate:     test.Certificate,
   279  		MetadataURL:     mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   280  		AcsURL:          mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   281  		IDPMetadata:     &EntityDescriptor{},
   282  		SignatureMethod: dsig.RSASHA1SignatureMethod,
   283  	}
   284  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   285  	assert.Check(t, err)
   286  
   287  	redirectURL, err := s.MakeRedirectAuthenticationRequest("relayState")
   288  	assert.Check(t, err)
   289  	// Signature we check against in the query string was validated with
   290  	// https://www.samltool.com/validate_authn_req.php . Once we add
   291  	// support for validating signed AuthN requests in the IDP implementation
   292  	// we can switch to testing using that.
   293  	golden.Assert(t, redirectURL.RawQuery, t.Name()+"_queryString")
   294  
   295  	decodedRequest, err := testsaml.ParseRedirectRequest(redirectURL)
   296  	assert.Check(t, err)
   297  	assert.Check(t, is.Equal("idp.testshib.org",
   298  		redirectURL.Host))
   299  	assert.Check(t, is.Equal("/idp/profile/SAML2/Redirect/SSO",
   300  		redirectURL.Path))
   301  	// Contains no enveloped signature
   302  	golden.Assert(t, string(decodedRequest), t.Name()+"_decodedRequest")
   303  }
   304  
   305  func TestSPCanProduceSignedRequestPostBinding(t *testing.T) {
   306  	test := NewServiceProviderTest(t)
   307  	TimeNow = func() time.Time {
   308  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   309  		return rv
   310  	}
   311  	Clock = dsig.NewFakeClockAt(TimeNow())
   312  	s := ServiceProvider{
   313  		Key:             test.Key,
   314  		Certificate:     test.Certificate,
   315  		MetadataURL:     mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   316  		AcsURL:          mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   317  		IDPMetadata:     &EntityDescriptor{},
   318  		SignatureMethod: dsig.RSASHA1SignatureMethod,
   319  	}
   320  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   321  	assert.Check(t, err)
   322  
   323  	htmlForm, err := s.MakePostAuthenticationRequest("relayState")
   324  	assert.Check(t, err)
   325  	rgx := regexp.MustCompile(`\"SAMLRequest\" value=\"(.*?)\" /><input`)
   326  	rs := rgx.FindStringSubmatch(string(htmlForm))
   327  	assert.Check(t, len(rs) == 2)
   328  
   329  	decodedRequest, err := base64.StdEncoding.DecodeString(html.UnescapeString(rs[1]))
   330  	assert.Check(t, err)
   331  	golden.Assert(t, string(decodedRequest), t.Name()+"_decodedRequest")
   332  }
   333  
   334  func TestSPFailToProduceSignedRequestWithBogusSignatureMethod(t *testing.T) {
   335  	test := NewServiceProviderTest(t)
   336  	TimeNow = func() time.Time {
   337  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   338  		return rv
   339  	}
   340  	Clock = dsig.NewFakeClockAt(TimeNow())
   341  	s := ServiceProvider{
   342  		Key:             test.Key,
   343  		Certificate:     test.Certificate,
   344  		MetadataURL:     mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   345  		AcsURL:          mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   346  		IDPMetadata:     &EntityDescriptor{},
   347  		SignatureMethod: "bogus",
   348  	}
   349  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   350  	assert.Check(t, err)
   351  
   352  	_, err = s.MakeRedirectAuthenticationRequest("relayState")
   353  	assert.Check(t, is.ErrorContains(err, "invalid signing method bogus"))
   354  }
   355  
   356  func TestSPCanProducePostLogoutRequest(t *testing.T) {
   357  	test := NewServiceProviderTest(t)
   358  	TimeNow = func() time.Time {
   359  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Mon Dec 1 01:31:21 UTC 2015")
   360  		return rv
   361  	}
   362  	s := ServiceProvider{
   363  		Key:         test.Key,
   364  		Certificate: test.Certificate,
   365  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   366  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   367  		IDPMetadata: &EntityDescriptor{},
   368  	}
   369  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   370  	assert.Check(t, err)
   371  
   372  	form, err := s.MakePostLogoutRequest("ros@octolabs.io", "relayState")
   373  	assert.Check(t, err)
   374  	golden.Assert(t, string(form), t.Name()+"_form")
   375  }
   376  
   377  func TestSPCanProduceRedirectLogoutRequest(t *testing.T) {
   378  	test := NewServiceProviderTest(t)
   379  	TimeNow = func() time.Time {
   380  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   381  		return rv
   382  	}
   383  	Clock = dsig.NewFakeClockAt(TimeNow())
   384  	s := ServiceProvider{
   385  		Key:         test.Key,
   386  		Certificate: test.Certificate,
   387  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   388  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   389  		IDPMetadata: &EntityDescriptor{},
   390  	}
   391  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   392  	assert.Check(t, err)
   393  
   394  	redirectURL, err := s.MakeRedirectLogoutRequest("ross@octolabs.io", "relayState")
   395  	assert.Check(t, err)
   396  
   397  	decodedRequest, err := testsaml.ParseRedirectRequest(redirectURL)
   398  	assert.Check(t, err)
   399  	assert.Check(t, is.Equal("idp.testshib.org",
   400  		redirectURL.Host))
   401  	assert.Check(t, is.Equal("/idp/profile/SAML2/Redirect/SLO",
   402  		redirectURL.Path))
   403  	golden.Assert(t, string(decodedRequest), t.Name()+"_decodedRequest")
   404  }
   405  
   406  func TestSPCanProducePostLogoutResponse(t *testing.T) {
   407  	test := NewServiceProviderTest(t)
   408  	TimeNow = func() time.Time {
   409  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Mon Dec 1 01:31:21 UTC 2015")
   410  		return rv
   411  	}
   412  	s := ServiceProvider{
   413  		Key:         test.Key,
   414  		Certificate: test.Certificate,
   415  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   416  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   417  		IDPMetadata: &EntityDescriptor{},
   418  	}
   419  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   420  	assert.Check(t, err)
   421  
   422  	form, err := s.MakePostLogoutResponse("id-d40c15c104b52691eccf0a2a5c8a15595be75423", "relayState")
   423  	assert.Check(t, err)
   424  	golden.Assert(t, string(form), t.Name()+"_form")
   425  }
   426  
   427  func TestSPCanProduceRedirectLogoutResponse(t *testing.T) {
   428  	test := NewServiceProviderTest(t)
   429  	TimeNow = func() time.Time {
   430  		rv, _ := time.Parse("Mon Jan 2 15:04:05.999999999 UTC 2006", "Mon Dec 1 01:31:21.123456789 UTC 2015")
   431  		return rv
   432  	}
   433  	Clock = dsig.NewFakeClockAt(TimeNow())
   434  	s := ServiceProvider{
   435  		Key:         test.Key,
   436  		Certificate: test.Certificate,
   437  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   438  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   439  		IDPMetadata: &EntityDescriptor{},
   440  	}
   441  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   442  	assert.Check(t, err)
   443  
   444  	redirectURL, err := s.MakeRedirectLogoutResponse("id-d40c15c104b52691eccf0a2a5c8a15595be75423", "relayState")
   445  	assert.Check(t, err)
   446  
   447  	decodedResponse, err := testsaml.ParseRedirectResponse(redirectURL)
   448  	assert.Check(t, err)
   449  	golden.Assert(t, string(decodedResponse), t.Name()+"_decodedResponse")
   450  }
   451  
   452  func TestSPCanHandleOneloginResponse(t *testing.T) {
   453  	test := NewServiceProviderTest(t)
   454  	// An actual response from onelogin
   455  	TimeNow = func() time.Time {
   456  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 17:53:12 UTC 2016")
   457  		return rv
   458  	}
   459  	Clock = dsig.NewFakeClockAt(TimeNow())
   460  
   461  	SamlResponse := golden.Get(t, "TestSPCanHandleOneloginResponse_response")
   462  	test.IDPMetadata = golden.Get(t, "TestSPCanHandleOneloginResponse_IDPMetadata")
   463  
   464  	s := ServiceProvider{
   465  		Key:         test.Key,
   466  		Certificate: test.Certificate,
   467  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
   468  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
   469  		IDPMetadata: &EntityDescriptor{},
   470  	}
   471  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   472  	assert.Check(t, err)
   473  
   474  	req := http.Request{PostForm: url.Values{}}
   475  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
   476  	assertion, err := s.ParseResponse(&req, []string{"id-d40c15c104b52691eccf0a2a5c8a15595be75423"})
   477  	assert.Check(t, err)
   478  
   479  	assert.Check(t, is.Equal("ross@kndr.org", assertion.Subject.NameID.Value))
   480  	assert.Check(t, is.DeepEqual([]Attribute{
   481  		{
   482  			Name:       "User.email",
   483  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
   484  			Values: []AttributeValue{
   485  				{
   486  					Type:  "xs:string",
   487  					Value: "ross@kndr.org",
   488  				},
   489  			},
   490  		},
   491  		{
   492  			Name:       "memberOf",
   493  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
   494  			Values: []AttributeValue{
   495  				{
   496  					Type:  "xs:string",
   497  					Value: "",
   498  				},
   499  			},
   500  		},
   501  		{
   502  			Name:       "User.LastName",
   503  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
   504  			Values: []AttributeValue{
   505  				{
   506  					Type:  "xs:string",
   507  					Value: "Kinder",
   508  				},
   509  			},
   510  		},
   511  		{
   512  			Name:       "PersonImmutableID",
   513  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
   514  			Values: []AttributeValue{
   515  				{
   516  					Type:  "xs:string",
   517  					Value: "",
   518  				},
   519  			},
   520  		},
   521  		{
   522  			Name:       "User.FirstName",
   523  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
   524  			Values: []AttributeValue{
   525  				{
   526  					Type:  "xs:string",
   527  					Value: "Ross",
   528  				},
   529  			},
   530  		},
   531  	},
   532  		assertion.AttributeStatements[0].Attributes))
   533  }
   534  
   535  func TestSPCanHandleOktaSignedResponseEncryptedAssertion(t *testing.T) {
   536  	test := NewServiceProviderTest(t)
   537  	// An actual response from okta - captured with trivial.go + test.Key/test.Certificate
   538  	TimeNow = func() time.Time {
   539  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Mar 3 19:24:28 UTC 2020")
   540  		return rv
   541  	}
   542  	Clock = dsig.NewFakeClockAt(TimeNow())
   543  	SamlResponse := golden.Get(t, "TestSPCanHandleOktaSignedResponseEncryptedAssertion_response")
   544  	test.IDPMetadata = golden.Get(t, "TestSPCanHandleOktaSignedResponseEncryptedAssertion_IDPMetadata")
   545  	s := ServiceProvider{
   546  		Key:         test.Key,
   547  		Certificate: test.Certificate,
   548  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
   549  		AcsURL:      mustParseURL("http://localhost:8000/saml/acs"),
   550  		IDPMetadata: &EntityDescriptor{},
   551  	}
   552  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   553  	assert.Check(t, err)
   554  
   555  	req := http.Request{PostForm: url.Values{}}
   556  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
   557  	assertion, err := s.ParseResponse(&req, []string{"id-a7364d1e4432aa9085a7a8bd824ea2fa8fa8f684"})
   558  	assert.Check(t, err)
   559  
   560  	assert.Check(t, is.Equal("testuser@testrsc.com", assertion.Subject.NameID.Value))
   561  	assert.Check(t, is.DeepEqual([]Attribute{
   562  		{
   563  			Name:       "Username",
   564  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
   565  			Values: []AttributeValue{
   566  				{
   567  					Type:  "xs:string",
   568  					Value: "FixedValue",
   569  				},
   570  			},
   571  		},
   572  	}, assertion.AttributeStatements[0].Attributes))
   573  }
   574  
   575  func TestSPCanHandleOktaResponseEncryptedSignedAssertion(t *testing.T) {
   576  	test := NewServiceProviderTest(t)
   577  	// An actual response from okta - captured with trivial.go + test.Key/test.Certificate
   578  	TimeNow = func() time.Time {
   579  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Mar 3 19:31:55 UTC 2020")
   580  		return rv
   581  	}
   582  	Clock = dsig.NewFakeClockAt(TimeNow())
   583  	SamlResponse := golden.Get(t, "TestSPCanHandleOktaResponseEncryptedSignedAssertion_response")
   584  	test.IDPMetadata = golden.Get(t, "TestSPCanHandleOktaResponseEncryptedSignedAssertion_IDPMetadata")
   585  
   586  	s := ServiceProvider{
   587  		Key:         test.Key,
   588  		Certificate: test.Certificate,
   589  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
   590  		AcsURL:      mustParseURL("http://localhost:8000/saml/acs"),
   591  		IDPMetadata: &EntityDescriptor{},
   592  	}
   593  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   594  	assert.Check(t, err)
   595  
   596  	req := http.Request{PostForm: url.Values{}}
   597  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
   598  	assertion, err := s.ParseResponse(&req, []string{"id-6d976cdde8e76df5df0a8ff58148fc0b7ec6796d"})
   599  	assert.Check(t, err)
   600  
   601  	assert.Check(t, is.Equal("testuser@testrsc.com", assertion.Subject.NameID.Value))
   602  	assert.Check(t, is.DeepEqual([]Attribute{
   603  		{
   604  			Name:       "Username",
   605  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
   606  			Values: []AttributeValue{
   607  				{
   608  					Type:  "xs:string",
   609  					Value: "FixedValue",
   610  				},
   611  			},
   612  		},
   613  	}, assertion.AttributeStatements[0].Attributes))
   614  }
   615  
   616  func TestSPCanHandleOktaResponseEncryptedAssertionBothSigned(t *testing.T) {
   617  	test := NewServiceProviderTest(t)
   618  	// An actual response from okta - captured with trivial.go + test.Key/test.Certificate
   619  	TimeNow = func() time.Time {
   620  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Mar 3 19:40:54 UTC 2020")
   621  		return rv
   622  	}
   623  	Clock = dsig.NewFakeClockAt(TimeNow())
   624  	SamlResponse := golden.Get(t, "TestSPCanHandleOktaResponseEncryptedAssertionBothSigned_response")
   625  	test.IDPMetadata = golden.Get(t, "TestSPCanHandleOktaResponseEncryptedAssertionBothSigned_IDPMetadata")
   626  
   627  	s := ServiceProvider{
   628  		Key:         test.Key,
   629  		Certificate: test.Certificate,
   630  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
   631  		AcsURL:      mustParseURL("http://localhost:8000/saml/acs"),
   632  		IDPMetadata: &EntityDescriptor{},
   633  	}
   634  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   635  	assert.Check(t, err)
   636  
   637  	req := http.Request{PostForm: url.Values{}}
   638  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
   639  	assertion, err := s.ParseResponse(&req, []string{"id-953d4cab69ff475c5901d12e585b0bb15a7b85fe"})
   640  	assert.Check(t, err)
   641  
   642  	assert.Check(t, is.Equal("testuser@testrsc.com", assertion.Subject.NameID.Value))
   643  	assert.Check(t, is.DeepEqual([]Attribute{
   644  		{
   645  			Name:       "Username",
   646  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
   647  			Values: []AttributeValue{
   648  				{
   649  					Type:  "xs:string",
   650  					Value: "FixedValue",
   651  				},
   652  			},
   653  		},
   654  	}, assertion.AttributeStatements[0].Attributes))
   655  }
   656  
   657  func TestSPCanHandlePlaintextResponse(t *testing.T) {
   658  	test := NewServiceProviderTest(t)
   659  	// An actual response from google
   660  	TimeNow = func() time.Time {
   661  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016")
   662  		return rv
   663  	}
   664  	Clock = dsig.NewFakeClockAt(TimeNow())
   665  	SamlResponse := golden.Get(t, "TestSPCanHandlePlaintextResponse_response")
   666  	test.IDPMetadata = golden.Get(t, "TestSPCanHandlePlaintextResponse_IDPMetadata")
   667  
   668  	s := ServiceProvider{
   669  		Key:         test.Key,
   670  		Certificate: test.Certificate,
   671  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
   672  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
   673  		IDPMetadata: &EntityDescriptor{},
   674  	}
   675  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   676  	assert.Check(t, err)
   677  
   678  	req := http.Request{PostForm: url.Values{}}
   679  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
   680  	assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
   681  	assert.Check(t, err)
   682  
   683  	assert.Check(t, is.Equal("ross@octolabs.io", assertion.Subject.NameID.Value))
   684  	assert.Check(t, is.DeepEqual([]Attribute{
   685  		{
   686  			Name:   "phone",
   687  			Values: nil,
   688  		},
   689  		{
   690  			Name:   "address",
   691  			Values: nil,
   692  		},
   693  		{
   694  			Name:   "jobTitle",
   695  			Values: nil,
   696  		},
   697  		{
   698  			Name: "firstName",
   699  			Values: []AttributeValue{
   700  				{
   701  					Type:  "xs:anyType",
   702  					Value: "Ross",
   703  				},
   704  			},
   705  		},
   706  		{
   707  			Name: "lastName",
   708  			Values: []AttributeValue{
   709  				{
   710  					Type:  "xs:anyType",
   711  					Value: "Kinder",
   712  				},
   713  			},
   714  		},
   715  	}, assertion.AttributeStatements[0].Attributes))
   716  }
   717  
   718  func TestSPRejectsInjectedComment(t *testing.T) {
   719  	test := NewServiceProviderTest(t)
   720  	// An actual response from google
   721  	TimeNow = func() time.Time {
   722  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016")
   723  		return rv
   724  	}
   725  	Clock = dsig.NewFakeClockAt(TimeNow())
   726  
   727  	SamlResponse := golden.Get(t, "TestSPRejectsInjectedComment_response")
   728  	test.IDPMetadata = golden.Get(t, "TestSPRejectsInjectedComment_IDPMetadata")
   729  
   730  	s := ServiceProvider{
   731  		Key:         test.Key,
   732  		Certificate: test.Certificate,
   733  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
   734  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
   735  		IDPMetadata: &EntityDescriptor{},
   736  	}
   737  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   738  	assert.Check(t, err)
   739  
   740  	// this is a valid response
   741  	{
   742  		req := http.Request{PostForm: url.Values{}}
   743  		req.PostForm.Set("SAMLResponse", string(SamlResponse))
   744  		assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
   745  		assert.Check(t, err)
   746  		assert.Check(t, is.Equal("ross@octolabs.io", assertion.Subject.NameID.Value))
   747  	}
   748  
   749  	// this is a valid response but with a comment injected
   750  	{
   751  		x, _ := base64.StdEncoding.DecodeString(string(SamlResponse))
   752  		y := strings.Replace(string(x), "ross@octolabs.io", "ross@<!-- and a comment -->octolabs.io", 1)
   753  		SamlResponse = []byte(base64.StdEncoding.EncodeToString([]byte(y)))
   754  
   755  		req := http.Request{PostForm: url.Values{}}
   756  		req.PostForm.Set("SAMLResponse", string(SamlResponse))
   757  		assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
   758  
   759  		// Note: I would expect the injected comment to be stripped and for the signature
   760  		// to validate. Less ideal, but not insecure is the case where the comment breaks
   761  		// the signature, perhaps because xml-c18n isn't being implemented correctly by
   762  		// dsig.
   763  		if err == nil {
   764  			assert.Check(t, is.Equal("ross@octolabs.io",
   765  				assertion.Subject.NameID.Value))
   766  		}
   767  	}
   768  
   769  	// this is an invalid response with a commend injected per CVE-2018-7340
   770  	// ref: https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations
   771  	// it *MUST NOT* validate
   772  	{
   773  		x, _ := base64.StdEncoding.DecodeString(string(SamlResponse))
   774  		y := strings.Replace(string(x), "ross@<!-- and a comment -->octolabs.io", "ross@octolabs.io<!-- and a comment -->.example.com", 1)
   775  		SamlResponse = []byte(base64.StdEncoding.EncodeToString([]byte(y)))
   776  
   777  		req := http.Request{PostForm: url.Values{}}
   778  		req.PostForm.Set("SAMLResponse", string(SamlResponse))
   779  		_, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
   780  		assert.Check(t, err != nil)
   781  
   782  		realErr := err.(*InvalidResponseError).PrivateErr
   783  		assert.Check(t, is.Error(realErr,
   784  			"cannot validate signature on Response: Signature could not be verified"))
   785  	}
   786  }
   787  
   788  func TestSPCanParseResponse(t *testing.T) {
   789  	test := NewServiceProviderTest(t)
   790  	s := ServiceProvider{
   791  		Key:         test.Key,
   792  		Certificate: test.Certificate,
   793  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   794  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   795  		IDPMetadata: &EntityDescriptor{},
   796  	}
   797  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   798  	assert.Check(t, err)
   799  
   800  	req := http.Request{PostForm: url.Values{}}
   801  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
   802  	assertion, err := s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
   803  	assert.Check(t, err)
   804  
   805  	assert.Check(t, is.DeepEqual([]Attribute{
   806  		{
   807  			FriendlyName: "uid",
   808  			Name:         "urn:oid:0.9.2342.19200300.100.1.1",
   809  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   810  			Values: []AttributeValue{
   811  				{
   812  					Type:  "xs:string",
   813  					Value: "myself",
   814  				},
   815  			},
   816  		},
   817  		{
   818  			FriendlyName: "eduPersonAffiliation",
   819  			Name:         "urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
   820  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   821  			Values: []AttributeValue{
   822  				{
   823  					Type:  "xs:string",
   824  					Value: "Member",
   825  				},
   826  				{
   827  					Type:  "xs:string",
   828  					Value: "Staff",
   829  				},
   830  			},
   831  		},
   832  		{
   833  			FriendlyName: "eduPersonPrincipalName",
   834  			Name:         "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
   835  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   836  			Values: []AttributeValue{
   837  				{
   838  					Type:  "xs:string",
   839  					Value: "myself@testshib.org",
   840  				},
   841  			},
   842  		},
   843  		{
   844  			FriendlyName: "sn",
   845  			Name:         "urn:oid:2.5.4.4",
   846  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   847  			Values: []AttributeValue{
   848  				{
   849  					Type:  "xs:string",
   850  					Value: "And I",
   851  				},
   852  			},
   853  		},
   854  		{
   855  			FriendlyName: "eduPersonScopedAffiliation",
   856  			Name:         "urn:oid:1.3.6.1.4.1.5923.1.1.1.9",
   857  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   858  			Values: []AttributeValue{
   859  				{
   860  					Type:  "xs:string",
   861  					Value: "Member@testshib.org",
   862  				},
   863  				{
   864  					Type:  "xs:string",
   865  					Value: "Staff@testshib.org",
   866  				},
   867  			},
   868  		},
   869  		{
   870  			FriendlyName: "givenName",
   871  			Name:         "urn:oid:2.5.4.42",
   872  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   873  			Values: []AttributeValue{
   874  				{
   875  					Type:  "xs:string",
   876  					Value: "Me Myself",
   877  				},
   878  			},
   879  		},
   880  		{
   881  			FriendlyName: "eduPersonEntitlement",
   882  			Name:         "urn:oid:1.3.6.1.4.1.5923.1.1.1.7",
   883  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   884  			Values: []AttributeValue{
   885  				{
   886  					Type:  "xs:string",
   887  					Value: "urn:mace:dir:entitlement:common-lib-terms",
   888  				},
   889  			},
   890  		},
   891  		{
   892  			FriendlyName: "cn",
   893  			Name:         "urn:oid:2.5.4.3",
   894  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   895  			Values: []AttributeValue{
   896  				{
   897  					Type:  "xs:string",
   898  					Value: "Me Myself And I",
   899  				},
   900  			},
   901  		},
   902  		{
   903  			FriendlyName: "eduPersonTargetedID",
   904  			Name:         "urn:oid:1.3.6.1.4.1.5923.1.1.1.10",
   905  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   906  			Values: []AttributeValue{
   907  				{
   908  					NameID: &NameID{Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", NameQualifier: "https://idp.testshib.org/idp/shibboleth", SPNameQualifier: "https://15661444.ngrok.io/saml2/metadata", Value: "8F+M9ovyaYNwCId0pVkVsnZYRDo="},
   909  				},
   910  			},
   911  		},
   912  		{
   913  			FriendlyName: "telephoneNumber",
   914  			Name:         "urn:oid:2.5.4.20",
   915  			NameFormat:   "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
   916  			Values: []AttributeValue{
   917  				{
   918  					Type:  "xs:string",
   919  					Value: "555-5555",
   920  				},
   921  			},
   922  		},
   923  	}, assertion.AttributeStatements[0].Attributes))
   924  }
   925  
   926  func (test *ServiceProviderTest) replaceDestination(newDestination string) {
   927  	newStr := ""
   928  	if newDestination != "" {
   929  		newStr = `Destination="` + newDestination + `"`
   930  	}
   931  	test.SamlResponse = bytes.Replace(test.SamlResponse,
   932  		[]byte(`Destination="https://15661444.ngrok.io/saml2/acs"`), []byte(newStr), 1)
   933  }
   934  
   935  func TestSPCanProcessResponseWithoutDestination(t *testing.T) {
   936  	test := NewServiceProviderTest(t)
   937  	s := ServiceProvider{
   938  		Key:         test.Key,
   939  		Certificate: test.Certificate,
   940  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   941  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   942  		IDPMetadata: &EntityDescriptor{},
   943  	}
   944  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   945  	assert.Check(t, err)
   946  
   947  	req := http.Request{PostForm: url.Values{}}
   948  	test.replaceDestination("")
   949  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
   950  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
   951  	assert.Check(t, err)
   952  }
   953  
   954  func (test *ServiceProviderTest) responseDom(t *testing.T) (doc *etree.Document) {
   955  	doc = etree.NewDocument()
   956  	err := doc.ReadFromBytes(test.SamlResponse)
   957  	assert.Check(t, err)
   958  	return doc
   959  }
   960  
   961  func addSignatureToDocument(doc *etree.Document) *etree.Document {
   962  	responseEl := doc.FindElement("//Response")
   963  	signatureEl := doc.CreateElement("xmldsig:Signature")
   964  	signatureEl.CreateAttr("xmlns:xmldsig", "http://www.w3.org/2000/09/xmldsig#")
   965  	responseEl.AddChild(signatureEl)
   966  	return doc
   967  }
   968  
   969  func removeDestinationFromDocument(doc *etree.Document) *etree.Document {
   970  	responseEl := doc.FindElement("//Response")
   971  	responseEl.RemoveAttr("Destination")
   972  	return doc
   973  }
   974  
   975  func TestServiceProviderMismatchedDestinationsWithSignaturePresent(t *testing.T) {
   976  	test := NewServiceProviderTest(t)
   977  	s := ServiceProvider{
   978  		Key:         test.Key,
   979  		Certificate: test.Certificate,
   980  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
   981  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
   982  		IDPMetadata: &EntityDescriptor{},
   983  	}
   984  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
   985  	assert.Check(t, err)
   986  
   987  	req := http.Request{PostForm: url.Values{}}
   988  	s.AcsURL = mustParseURL("https://wrong/saml2/acs")
   989  	bytes, _ := test.responseDom(t).WriteToBytes()
   990  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes))
   991  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
   992  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
   993  		"`Destination` does not match AcsURL (expected \"https://wrong/saml2/acs\", actual \"https://15661444.ngrok.io/saml2/acs\")"))
   994  }
   995  
   996  func TestServiceProviderMissingDestinationWithSignaturePresent(t *testing.T) {
   997  	test := NewServiceProviderTest(t)
   998  	s := ServiceProvider{
   999  		Key:         test.Key,
  1000  		Certificate: test.Certificate,
  1001  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1002  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1003  		IDPMetadata: &EntityDescriptor{},
  1004  	}
  1005  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1006  	assert.Check(t, err)
  1007  
  1008  	req := http.Request{PostForm: url.Values{}}
  1009  	bytes, _ := removeDestinationFromDocument(addSignatureToDocument(test.responseDom(t))).WriteToBytes()
  1010  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes))
  1011  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1012  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1013  		"`Destination` does not match AcsURL (expected \"https://15661444.ngrok.io/saml2/acs\", actual \"\")"))
  1014  }
  1015  
  1016  func TestSPMismatchedDestinationsWithSignaturePresent(t *testing.T) {
  1017  	test := NewServiceProviderTest(t)
  1018  	s := ServiceProvider{
  1019  		Key:         test.Key,
  1020  		Certificate: test.Certificate,
  1021  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1022  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1023  		IDPMetadata: &EntityDescriptor{},
  1024  	}
  1025  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1026  	assert.Check(t, err)
  1027  
  1028  	req := http.Request{PostForm: url.Values{}}
  1029  	test.replaceDestination("https://wrong/saml2/acs")
  1030  	bytes, _ := addSignatureToDocument(test.responseDom(t)).WriteToBytes()
  1031  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes))
  1032  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1033  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1034  		"`Destination` does not match AcsURL (expected \"https://15661444.ngrok.io/saml2/acs\", actual \"https://wrong/saml2/acs\")"))
  1035  }
  1036  
  1037  func TestSPMismatchedDestinationsWithNoSignaturePresent(t *testing.T) {
  1038  	test := NewServiceProviderTest(t)
  1039  	s := ServiceProvider{
  1040  		Key:         test.Key,
  1041  		Certificate: test.Certificate,
  1042  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1043  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1044  		IDPMetadata: &EntityDescriptor{},
  1045  	}
  1046  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1047  	assert.Check(t, err)
  1048  
  1049  	req := http.Request{PostForm: url.Values{}}
  1050  	test.replaceDestination("https://wrong/saml2/acs")
  1051  	bytes, _ := test.responseDom(t).WriteToBytes()
  1052  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes))
  1053  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1054  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1055  		"`Destination` does not match AcsURL (expected \"https://15661444.ngrok.io/saml2/acs\", actual \"https://wrong/saml2/acs\")"))
  1056  }
  1057  
  1058  func TestSPMissingDestinationWithSignaturePresent(t *testing.T) {
  1059  	test := NewServiceProviderTest(t)
  1060  	s := ServiceProvider{
  1061  		Key:         test.Key,
  1062  		Certificate: test.Certificate,
  1063  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1064  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1065  		IDPMetadata: &EntityDescriptor{},
  1066  	}
  1067  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1068  	assert.Check(t, err)
  1069  
  1070  	req := http.Request{PostForm: url.Values{}}
  1071  	test.replaceDestination("")
  1072  	bytes, _ := addSignatureToDocument(test.responseDom(t)).WriteToBytes()
  1073  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(bytes))
  1074  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1075  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1076  		"`Destination` does not match AcsURL (expected \"https://15661444.ngrok.io/saml2/acs\", actual \"\")"))
  1077  }
  1078  
  1079  func TestSPInvalidAssertions(t *testing.T) {
  1080  	test := NewServiceProviderTest(t)
  1081  	s := ServiceProvider{
  1082  		Key:         test.Key,
  1083  		Certificate: test.Certificate,
  1084  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1085  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1086  		IDPMetadata: &EntityDescriptor{},
  1087  	}
  1088  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1089  	assert.Check(t, err)
  1090  
  1091  	// HACK: decrypt response without verifying assertions
  1092  	var assertionBuf []byte
  1093  	{
  1094  		doc := etree.NewDocument()
  1095  		assert.Check(t, doc.ReadFromBytes(test.SamlResponse))
  1096  		encryptedEL := doc.Root().FindElement("//EncryptedAssertion")
  1097  		assertionEl, err := s.decryptElement(encryptedEL)
  1098  		assert.Check(t, err)
  1099  
  1100  		doc = etree.NewDocument()
  1101  		doc.SetRoot(assertionEl)
  1102  		assertionBuf, err = doc.WriteToBytes()
  1103  		assert.Check(t, err)
  1104  	}
  1105  
  1106  	assertion := Assertion{}
  1107  	err = xml.Unmarshal(assertionBuf, &assertion)
  1108  	assert.Check(t, err)
  1109  
  1110  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow().Add(time.Hour))
  1111  	assert.Check(t, is.Error(err, "expired on 2015-12-01 01:57:51.375 +0000 UTC"))
  1112  
  1113  	assertion.Issuer.Value = "bob"
  1114  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1115  	assert.Check(t, is.Error(err, "issuer is not \"https://idp.testshib.org/idp/shibboleth\""))
  1116  	assertion = Assertion{}
  1117  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1118  
  1119  	assertion.Subject.NameID.NameQualifier = "bob"
  1120  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1121  	assert.Check(t, err) // not verified
  1122  	assertion = Assertion{}
  1123  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1124  
  1125  	assertion.Subject.NameID.SPNameQualifier = "bob"
  1126  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1127  	assert.Check(t, err) // not verified
  1128  	assertion = Assertion{}
  1129  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1130  
  1131  	err = s.validateAssertion(&assertion, []string{"any request id"}, TimeNow())
  1132  	assert.Check(t, is.Error(err, "assertion SubjectConfirmation one of the possible request IDs ([any request id])"))
  1133  
  1134  	assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.Recipient = "wrong/acs/url"
  1135  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1136  	assert.Check(t, is.Error(err, "assertion SubjectConfirmation Recipient is not https://15661444.ngrok.io/saml2/acs"))
  1137  	assertion = Assertion{}
  1138  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1139  
  1140  	assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter = TimeNow().Add(-1 * time.Hour)
  1141  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1142  	assert.Check(t, is.Error(err, "assertion SubjectConfirmationData is expired"))
  1143  	assertion = Assertion{}
  1144  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1145  
  1146  	assertion.Conditions.NotBefore = TimeNow().Add(time.Hour)
  1147  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1148  	assert.Check(t, is.Error(err, "assertion Conditions is not yet valid"))
  1149  	assertion = Assertion{}
  1150  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1151  
  1152  	assertion.Conditions.NotOnOrAfter = TimeNow().Add(-1 * time.Hour)
  1153  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1154  	assert.Check(t, is.Error(err, "assertion Conditions is expired"))
  1155  	assertion = Assertion{}
  1156  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1157  
  1158  	assertion.Conditions.AudienceRestrictions[0].Audience.Value = "not/our/metadata/url"
  1159  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1160  	assert.Check(t, is.Error(err, "assertion Conditions AudienceRestriction does not contain \"https://15661444.ngrok.io/saml2/metadata\""))
  1161  	assertion = Assertion{}
  1162  	assert.Check(t, xml.Unmarshal(assertionBuf, &assertion))
  1163  
  1164  	// Not having an audience is not an error
  1165  	assertion.Conditions.AudienceRestrictions = []AudienceRestriction{}
  1166  	err = s.validateAssertion(&assertion, []string{"id-9e61753d64e928af5a7a341a97f420c9"}, TimeNow())
  1167  	assert.Check(t, err)
  1168  }
  1169  
  1170  func TestXswPermutationOneIsRejected(t *testing.T) {
  1171  	test := NewServiceProviderTest(t)
  1172  	idpMetadata := golden.Get(t, "TestSPCanHandleOneloginResponse_IDPMetadata")
  1173  	respStr := golden.Get(t, "TestXswPermutationOneIsRejected_response")
  1174  	TimeNow = func() time.Time {
  1175  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 17:53:12 UTC 2016")
  1176  		return rv
  1177  	}
  1178  	Clock = dsig.NewFakeClockAt(TimeNow())
  1179  
  1180  	s := ServiceProvider{
  1181  		Key:         test.Key,
  1182  		Certificate: test.Certificate,
  1183  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
  1184  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
  1185  		IDPMetadata: &EntityDescriptor{},
  1186  	}
  1187  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1188  	assert.Check(t, err)
  1189  
  1190  	req := http.Request{PostForm: url.Values{}}
  1191  	req.PostForm.Set("SAMLResponse", string(respStr))
  1192  	_, err = s.ParseResponse(&req, []string{"id-d40c15c104b52691eccf0a2a5c8a15595be75423"})
  1193  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1194  		"cannot validate signature on Response: Missing signature referencing the top-level element"))
  1195  }
  1196  
  1197  func TestXswPermutationTwoIsRejected(t *testing.T) {
  1198  	test := NewServiceProviderTest(t)
  1199  	idpMetadata := golden.Get(t, "TestSPCanHandleOneloginResponse_IDPMetadata")
  1200  	respStr := golden.Get(t, "TestXswPermutationTwoIsRejected_response")
  1201  	TimeNow = func() time.Time {
  1202  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 17:53:12 UTC 2016")
  1203  		return rv
  1204  	}
  1205  	Clock = dsig.NewFakeClockAt(TimeNow())
  1206  
  1207  	s := ServiceProvider{
  1208  		Key:         test.Key,
  1209  		Certificate: test.Certificate,
  1210  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
  1211  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
  1212  		IDPMetadata: &EntityDescriptor{},
  1213  	}
  1214  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1215  	assert.Check(t, err)
  1216  
  1217  	req := http.Request{PostForm: url.Values{}}
  1218  	req.PostForm.Set("SAMLResponse", string(respStr))
  1219  	_, err = s.ParseResponse(&req, []string{"id-d40c15c104b52691eccf0a2a5c8a15595be75423"})
  1220  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1221  		"cannot validate signature on Response: Missing signature referencing the top-level element"))
  1222  }
  1223  
  1224  func TestXswPermutationThreeIsRejected(t *testing.T) {
  1225  	test := NewServiceProviderTest(t)
  1226  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1227  	respStr := golden.Get(t, "TestXswPermutationThreeIsRejected_response")
  1228  	TimeNow = func() time.Time {
  1229  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1230  		return rv
  1231  	}
  1232  	Clock = dsig.NewFakeClockAt(TimeNow())
  1233  
  1234  	s := ServiceProvider{
  1235  		Key:         test.Key,
  1236  		Certificate: test.Certificate,
  1237  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1238  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1239  		IDPMetadata: &EntityDescriptor{},
  1240  	}
  1241  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1242  	assert.Check(t, err)
  1243  
  1244  	req := http.Request{PostForm: url.Values{}}
  1245  	req.PostForm.Set("SAMLResponse", string(respStr))
  1246  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1247  
  1248  	// This response contains two assertions. The first is missing a Signature element. The second is
  1249  	// signed by a certificate that is not yet valid at the time of issue.
  1250  	//
  1251  	// When no assertions are valid, we return the first error encountered, which in this case is that
  1252  	// there is no Signature on the element.
  1253  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "signature element not present"))
  1254  }
  1255  
  1256  func TestXswPermutationFourIsRejected(t *testing.T) {
  1257  	test := NewServiceProviderTest(t)
  1258  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1259  	respStr := golden.Get(t, "TestXswPermutationFourIsRejected_response")
  1260  	TimeNow = func() time.Time {
  1261  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1262  		return rv
  1263  	}
  1264  	Clock = dsig.NewFakeClockAt(TimeNow())
  1265  
  1266  	s := ServiceProvider{
  1267  		Key:         test.Key,
  1268  		Certificate: test.Certificate,
  1269  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1270  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1271  		IDPMetadata: &EntityDescriptor{},
  1272  	}
  1273  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1274  	assert.Check(t, err)
  1275  
  1276  	req := http.Request{PostForm: url.Values{}}
  1277  	req.PostForm.Set("SAMLResponse", string(respStr))
  1278  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1279  
  1280  	// This permutation contains a signed assertion embedded within an unsigned assertion.
  1281  	// I'm pretty sure this is just not allowed, so we properly decide that there are no
  1282  	// signed assertions at all.
  1283  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr, "signature element not present"))
  1284  }
  1285  
  1286  func TestXswPermutationFiveIsRejected(t *testing.T) {
  1287  	test := NewServiceProviderTest(t)
  1288  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1289  	respStr := golden.Get(t, "TestXswPermutationFiveIsRejected_response")
  1290  	TimeNow = func() time.Time {
  1291  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1292  		return rv
  1293  	}
  1294  	Clock = dsig.NewFakeClockAt(TimeNow())
  1295  
  1296  	s := ServiceProvider{
  1297  		Key:         test.Key,
  1298  		Certificate: test.Certificate,
  1299  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1300  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1301  		IDPMetadata: &EntityDescriptor{},
  1302  	}
  1303  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1304  	assert.Check(t, err)
  1305  
  1306  	req := http.Request{PostForm: url.Values{}}
  1307  	req.PostForm.Set("SAMLResponse", string(respStr))
  1308  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1309  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1310  		"cannot validate signature on Assertion: Missing signature referencing the top-level element"))
  1311  }
  1312  
  1313  func TestXswPermutationSixIsRejected(t *testing.T) {
  1314  	test := NewServiceProviderTest(t)
  1315  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1316  	respStr := golden.Get(t, "TestXswPermutationSixIsRejected_response")
  1317  	TimeNow = func() time.Time {
  1318  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1319  		return rv
  1320  	}
  1321  	Clock = dsig.NewFakeClockAt(TimeNow())
  1322  
  1323  	s := ServiceProvider{
  1324  		Key:         test.Key,
  1325  		Certificate: test.Certificate,
  1326  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1327  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1328  		IDPMetadata: &EntityDescriptor{},
  1329  	}
  1330  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1331  	assert.Check(t, err)
  1332  
  1333  	req := http.Request{PostForm: url.Values{}}
  1334  	req.PostForm.Set("SAMLResponse", string(respStr))
  1335  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1336  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1337  		"cannot validate signature on Assertion: Missing signature referencing the top-level element"))
  1338  }
  1339  
  1340  func TestXswPermutationSevenIsRejected(t *testing.T) {
  1341  	test := NewServiceProviderTest(t)
  1342  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1343  	respStr := golden.Get(t, "TestXswPermutationSevenIsRejected_response")
  1344  	TimeNow = func() time.Time {
  1345  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1346  		return rv
  1347  	}
  1348  	Clock = dsig.NewFakeClockAt(func() time.Time {
  1349  		rv, _ := time.Parse(timeFormat, "2014-07-17T14:12:57Z")
  1350  		return rv
  1351  	}())
  1352  
  1353  	s := ServiceProvider{
  1354  		Key:         test.Key,
  1355  		Certificate: test.Certificate,
  1356  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1357  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1358  		IDPMetadata: &EntityDescriptor{},
  1359  	}
  1360  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1361  	assert.Check(t, err)
  1362  
  1363  	req := http.Request{PostForm: url.Values{}}
  1364  	req.PostForm.Set("SAMLResponse", string(respStr))
  1365  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1366  	// It's the assertion signature that can't be verified. The error message is generic and always mentions Response
  1367  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1368  		"cannot validate signature on Assertion: Signature could not be verified"))
  1369  }
  1370  
  1371  func TestXswPermutationEightIsRejected(t *testing.T) {
  1372  	test := NewServiceProviderTest(t)
  1373  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1374  	respStr := golden.Get(t, "TestXswPermutationEightIsRejected_response")
  1375  	TimeNow = func() time.Time {
  1376  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1377  		return rv
  1378  	}
  1379  	Clock = dsig.NewFakeClockAt(func() time.Time {
  1380  		rv, _ := time.Parse(timeFormat, "2014-07-17T14:12:57Z")
  1381  		return rv
  1382  	}())
  1383  
  1384  	s := ServiceProvider{
  1385  		Key:         test.Key,
  1386  		Certificate: test.Certificate,
  1387  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1388  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1389  		IDPMetadata: &EntityDescriptor{},
  1390  	}
  1391  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1392  	assert.Check(t, err)
  1393  
  1394  	req := http.Request{PostForm: url.Values{}}
  1395  	req.PostForm.Set("SAMLResponse", string(respStr))
  1396  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1397  	// It's the assertion signature that can't be verified. The error message is generic and always mentions Response
  1398  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1399  		"cannot validate signature on Assertion: Signature could not be verified"))
  1400  }
  1401  
  1402  func TestXswPermutationNineIsRejected(t *testing.T) {
  1403  	test := NewServiceProviderTest(t)
  1404  	idpMetadata := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1405  	respStr := golden.Get(t, "TestXswPermutationNineIsRejected_response")
  1406  	TimeNow = func() time.Time {
  1407  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1408  		return rv
  1409  	}
  1410  	Clock = dsig.NewFakeClockAt(func() time.Time {
  1411  		rv, _ := time.Parse(timeFormat, "2014-07-17T14:12:57Z")
  1412  		return rv
  1413  	}())
  1414  
  1415  	s := ServiceProvider{
  1416  		Key:         test.Key,
  1417  		Certificate: test.Certificate,
  1418  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1419  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1420  		IDPMetadata: &EntityDescriptor{},
  1421  	}
  1422  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1423  	assert.Check(t, err)
  1424  
  1425  	req := http.Request{PostForm: url.Values{}}
  1426  	req.PostForm.Set("SAMLResponse", string(respStr))
  1427  	_, err = s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1428  	// It's the assertion signature that can't be verified. The error message is generic and always mentions Response
  1429  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1430  		"cannot validate signature on Assertion: Missing signature referencing the top-level element"))
  1431  }
  1432  
  1433  func TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert(t *testing.T) {
  1434  	// This is a real world SAML response that we observed. It contains <ds:RSAKeyValue> elements
  1435  	idpMetadata := golden.Get(t, "TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert_idp_metadata")
  1436  	respStr := golden.Get(t, "TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert_response")
  1437  	TimeNow = func() time.Time {
  1438  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Fri Apr 21 13:12:51 UTC 2017")
  1439  		return rv
  1440  	}
  1441  	Clock = dsig.NewFakeClockAt(TimeNow())
  1442  	s := ServiceProvider{
  1443  		Key:         mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey),
  1444  		Certificate: mustParseCertificate(golden.Get(t, "cert_2017.pem")),
  1445  		MetadataURL: mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/metadata"),
  1446  		AcsURL:      mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/acs"),
  1447  		IDPMetadata: &EntityDescriptor{},
  1448  	}
  1449  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1450  	assert.Check(t, err)
  1451  
  1452  	req := http.Request{PostForm: url.Values{}}
  1453  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(respStr))
  1454  	_, err = s.ParseResponse(&req, []string{"id-3992f74e652d89c3cf1efd6c7e472abaac9bc917"})
  1455  	if err != nil {
  1456  		assert.Check(t, err.(*InvalidResponseError).PrivateErr)
  1457  	}
  1458  	assert.Check(t, err)
  1459  }
  1460  
  1461  func TestSPRealWorldAssertionSignedNotResponse(t *testing.T) {
  1462  	// This is a real world SAML response that we observed. It contains <ds:RSAKeyValue> elements rather than
  1463  	// a certificate in the response.
  1464  	idpMetadata := golden.Get(t, "TestSPRealWorldAssertionSignedNotResponse_idp_metadata")
  1465  	respStr := golden.Get(t, "TestSPRealWorldAssertionSignedNotResponse_response")
  1466  
  1467  	TimeNow = func() time.Time {
  1468  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Fri Apr 21 13:12:51 UTC 2017")
  1469  		return rv
  1470  	}
  1471  	Clock = dsig.NewFakeClockAt(TimeNow())
  1472  
  1473  	s := ServiceProvider{
  1474  		Key:         mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey),
  1475  		Certificate: mustParseCertificate(golden.Get(t, "cert_2017.pem")),
  1476  		MetadataURL: mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/metadata"),
  1477  		AcsURL:      mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/acs"),
  1478  		IDPMetadata: &EntityDescriptor{},
  1479  	}
  1480  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1481  	assert.Check(t, err)
  1482  
  1483  	req := http.Request{PostForm: url.Values{}}
  1484  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(respStr))
  1485  	_, err = s.ParseResponse(&req, []string{"id-3992f74e652d89c3cf1efd6c7e472abaac9bc917"})
  1486  	if err != nil {
  1487  		assert.Check(t, err.(*InvalidResponseError).PrivateErr)
  1488  	}
  1489  	assert.Check(t, err)
  1490  }
  1491  
  1492  func TestServiceProviderCanHandleSignedAssertionsResponse(t *testing.T) {
  1493  	test := NewServiceProviderTest(t)
  1494  
  1495  	// Note: This test uses an actual response from onelogin, submitted by a user.
  1496  	// However, the test data below isn't actually valid -- the issue instant is
  1497  	// before the certificate's issued time. In order to preserve this test data and
  1498  	// signatures, we assign a different time to Clock, used by xmldsig than to
  1499  	// TimeNow which is used to verify the issue time of the SAML assertion.
  1500  
  1501  	Clock = dsig.NewFakeClockAt(func() time.Time {
  1502  		rv, _ := time.Parse(timeFormat, "2014-07-17T14:12:57Z")
  1503  		return rv
  1504  	}())
  1505  	TimeNow = func() time.Time {
  1506  		rv, _ := time.Parse(timeFormat, "2014-07-17T01:02:59Z")
  1507  		return rv
  1508  	}
  1509  
  1510  	SamlResponse := golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_response")
  1511  	test.IDPMetadata = golden.Get(t, "TestServiceProviderCanHandleSignedAssertionsResponse_IDPMetadata")
  1512  	s := ServiceProvider{
  1513  		Key:         test.Key,
  1514  		Certificate: test.Certificate,
  1515  		MetadataURL: mustParseURL("http://sp.example.com/demo1/metadata.php"),
  1516  		AcsURL:      mustParseURL("http://sp.example.com/demo1/index.php?acs"),
  1517  		IDPMetadata: &EntityDescriptor{},
  1518  	}
  1519  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1520  	assert.Check(t, err)
  1521  
  1522  	req := http.Request{PostForm: url.Values{}}
  1523  	req.PostForm.Set("SAMLResponse", string(SamlResponse))
  1524  	assertion, err := s.ParseResponse(&req, []string{"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"})
  1525  	if err != nil {
  1526  		t.Logf("%s", err.(*InvalidResponseError).PrivateErr)
  1527  	}
  1528  	assert.Check(t, err)
  1529  
  1530  	assert.Check(t, is.Equal("_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", assertion.Subject.NameID.Value))
  1531  	assert.Check(t, is.DeepEqual([]Attribute{
  1532  		{
  1533  			Name:       "uid",
  1534  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
  1535  			Values: []AttributeValue{
  1536  				{
  1537  					Type:  "xs:string",
  1538  					Value: "test",
  1539  				},
  1540  			},
  1541  		},
  1542  		{
  1543  			Name:       "mail",
  1544  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
  1545  			Values: []AttributeValue{
  1546  				{
  1547  					Type:  "xs:string",
  1548  					Value: "test@example.com",
  1549  				},
  1550  			},
  1551  		},
  1552  		{
  1553  			Name:       "eduPersonAffiliation",
  1554  			NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
  1555  			Values: []AttributeValue{
  1556  				{
  1557  					Type:  "xs:string",
  1558  					Value: "users",
  1559  				},
  1560  				{
  1561  					Type:  "xs:string",
  1562  					Value: "examplerole1",
  1563  				},
  1564  			},
  1565  		},
  1566  	}, assertion.AttributeStatements[0].Attributes))
  1567  }
  1568  
  1569  func TestSPResponseWithNoIssuer(t *testing.T) {
  1570  	test := NewServiceProviderTest(t)
  1571  
  1572  	// This test case for the IdP response with no <Issuer> element. SAML standard says
  1573  	// that the <Issuer> element MAY be omitted in the <Response> (but MUST present in the <Assertion>).
  1574  
  1575  	s := ServiceProvider{
  1576  		Key:         test.Key,
  1577  		Certificate: test.Certificate,
  1578  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1579  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1580  		IDPMetadata: &EntityDescriptor{},
  1581  	}
  1582  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1583  	assert.Check(t, err)
  1584  
  1585  	req := http.Request{PostForm: url.Values{}}
  1586  
  1587  	// Response with no <Issuer> (modified ServiceProviderTest.SamlResponse)
  1588  	samlResponse := golden.Get(t, "TestSPResponseWithNoIssuer_response")
  1589  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(samlResponse))
  1590  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1591  	assert.Check(t, err)
  1592  }
  1593  
  1594  func TestGetArtifactBindingLocation(t *testing.T) {
  1595  	test := NewServiceProviderTest(t)
  1596  	test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata")
  1597  
  1598  	sp := ServiceProvider{
  1599  		Key:         test.Key,
  1600  		Certificate: test.Certificate,
  1601  		MetadataURL: mustParseURL("https://example.com/saml2/metadata"),
  1602  		AcsURL:      mustParseURL("https://example.com/saml2/acs"),
  1603  		IDPMetadata: &EntityDescriptor{},
  1604  	}
  1605  
  1606  	location := sp.GetArtifactBindingLocation(SOAPBinding)
  1607  	assert.Check(t, is.Equal(location, ""))
  1608  
  1609  	err := xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata)
  1610  	assert.Check(t, err)
  1611  
  1612  	location = sp.GetArtifactBindingLocation(SOAPBinding)
  1613  	assert.Check(t, is.Equal(location, "https://samltest.id/idp/profile/SAML2/SOAP/ArtifactResolution"))
  1614  }
  1615  
  1616  func TestMakeArtifactResolveRequest(t *testing.T) {
  1617  	test := NewServiceProviderTest(t)
  1618  
  1619  	sp := ServiceProvider{
  1620  		Key:         test.Key,
  1621  		Certificate: test.Certificate,
  1622  		MetadataURL: mustParseURL("https://example.com/saml2/metadata"),
  1623  		AcsURL:      mustParseURL("https://example.com/saml2/acs"),
  1624  		IDPMetadata: &EntityDescriptor{},
  1625  	}
  1626  
  1627  	req, err := sp.MakeArtifactResolveRequest("artifactId")
  1628  	assert.Check(t, err)
  1629  
  1630  	x, err := xml.Marshal(req)
  1631  	assert.Check(t, err)
  1632  	golden.Assert(t, string(x), t.Name())
  1633  }
  1634  
  1635  func TestMakeSignedArtifactResolveRequest(t *testing.T) {
  1636  	test := NewServiceProviderTest(t)
  1637  
  1638  	sp := ServiceProvider{
  1639  		Key:             test.Key,
  1640  		Certificate:     test.Certificate,
  1641  		MetadataURL:     mustParseURL("https://example.com/saml2/metadata"),
  1642  		AcsURL:          mustParseURL("https://example.com/saml2/acs"),
  1643  		IDPMetadata:     &EntityDescriptor{},
  1644  		SignatureMethod: dsig.RSASHA1SignatureMethod,
  1645  	}
  1646  
  1647  	req, err := sp.MakeArtifactResolveRequest("artifactId")
  1648  	assert.Check(t, err)
  1649  
  1650  	x, err := xml.Marshal(req)
  1651  	assert.Check(t, err)
  1652  	golden.Assert(t, string(x), t.Name())
  1653  }
  1654  
  1655  func TestMakeSignedArtifactResolveRequestWithBogusSignatureMethod(t *testing.T) {
  1656  	test := NewServiceProviderTest(t)
  1657  
  1658  	sp := ServiceProvider{
  1659  		Key:             test.Key,
  1660  		Certificate:     test.Certificate,
  1661  		MetadataURL:     mustParseURL("https://example.com/saml2/metadata"),
  1662  		AcsURL:          mustParseURL("https://example.com/saml2/acs"),
  1663  		IDPMetadata:     &EntityDescriptor{},
  1664  		SignatureMethod: "bogus",
  1665  	}
  1666  
  1667  	_, err := sp.MakeArtifactResolveRequest("artifactId")
  1668  	assert.Check(t, is.ErrorContains(err, "invalid signing method bogus"))
  1669  
  1670  }
  1671  
  1672  func TestParseXMLArtifactResponse(t *testing.T) {
  1673  	test := NewServiceProviderTest(t)
  1674  	TimeNow = func() time.Time {
  1675  		rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z")
  1676  		return rv
  1677  	}
  1678  	Clock = dsig.NewFakeClockAt(TimeNow())
  1679  
  1680  	// an actual response from samltest.id
  1681  	samlResponse := golden.Get(t, "TestParseXMLArtifactResponse_response")
  1682  	test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata")
  1683  
  1684  	sp := ServiceProvider{
  1685  		Key:         test.Key,
  1686  		Certificate: test.Certificate,
  1687  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
  1688  		AcsURL:      mustParseURL("http://localhost:8000/saml/acs"),
  1689  		IDPMetadata: &EntityDescriptor{},
  1690  	}
  1691  
  1692  	err := xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata)
  1693  	assert.Check(t, err)
  1694  
  1695  	possibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b94"}
  1696  	reqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c7"
  1697  
  1698  	assertion, err := sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1699  	assert.Check(t, err)
  1700  
  1701  	x, err := xml.Marshal(assertion)
  1702  	assert.Check(t, err)
  1703  
  1704  	golden.Assert(t, string(x), t.Name()+"_assertion")
  1705  }
  1706  
  1707  func TestParseBadXMLArtifactResponse(t *testing.T) {
  1708  	test := NewServiceProviderTest(t)
  1709  	TimeNow = func() time.Time {
  1710  		rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z")
  1711  		return rv
  1712  	}
  1713  	Clock = dsig.NewFakeClockAt(TimeNow())
  1714  
  1715  	// an actual response from samltest.id
  1716  	samlResponse := golden.Get(t, "TestParseXMLArtifactResponse_response")
  1717  	test.IDPMetadata = golden.Get(t, "TestGetArtifactBindingLocation_IDPMetadata")
  1718  
  1719  	possibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b94"}
  1720  	reqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c7"
  1721  
  1722  	sp := ServiceProvider{
  1723  		Key:         test.Key,
  1724  		Certificate: test.Certificate,
  1725  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
  1726  		AcsURL:      mustParseURL("https://example.com/saml2/acs"),
  1727  		IDPMetadata: &EntityDescriptor{},
  1728  	}
  1729  
  1730  	assertion, err := sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1731  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1732  		"response Issuer does not match the IDP metadata (expected \"\")"))
  1733  	assert.Check(t, is.Nil(assertion))
  1734  
  1735  	err = xml.Unmarshal(test.IDPMetadata, &sp.IDPMetadata)
  1736  	assert.Check(t, err)
  1737  
  1738  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1739  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1740  		"`Destination` does not match AcsURL (expected \"https://example.com/saml2/acs\", actual \"http://localhost:8000/saml/acs\")"))
  1741  	assert.Check(t, is.Nil(assertion))
  1742  
  1743  	sp.AcsURL = mustParseURL("http://localhost:8000/saml/acs")
  1744  
  1745  	// TimeNow is used to verify the response time
  1746  	TimeNow = func() time.Time {
  1747  		rv, _ := time.Parse(timeFormat, "2022-08-17T10:26:57Z")
  1748  		return rv
  1749  	}
  1750  
  1751  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1752  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1753  		"response IssueInstant expired at 2021-08-17 10:28:50.146 +0000 UTC"))
  1754  	assert.Check(t, is.Nil(assertion))
  1755  
  1756  	// Clock is used to verify the certificate
  1757  	Clock = dsig.NewFakeClockAt(func() time.Time {
  1758  		rv, _ := time.Parse(timeFormat, "2039-08-17T10:26:57Z")
  1759  		return rv
  1760  	}())
  1761  	TimeNow = func() time.Time {
  1762  		rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z")
  1763  		return rv
  1764  	}
  1765  
  1766  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1767  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1768  		"cannot validate signature on ArtifactResponse: Cert is not valid at this time"))
  1769  	assert.Check(t, is.Nil(assertion))
  1770  	Clock = dsig.NewFakeClockAt(TimeNow())
  1771  
  1772  	wrongReqID := "id-218eb155248f7db7c85fe4e2709a3f17a70d09c8"
  1773  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, wrongReqID)
  1774  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1775  		"`InResponseTo` does not match the artifact request ID (expected id-218eb155248f7db7c85fe4e2709a3f17a70d09c8)"))
  1776  	assert.Check(t, is.Nil(assertion))
  1777  
  1778  	wrongPossibleReqIDs := []string{"id-f3c7bc7d626a4ededa6028b718e5252c6e770b95"}
  1779  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, wrongPossibleReqIDs, reqID)
  1780  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1781  		"`InResponseTo` does not match any of the possible request IDs (expected [id-f3c7bc7d626a4ededa6028b718e5252c6e770b95])"))
  1782  	assert.Check(t, is.Nil(assertion))
  1783  
  1784  	// random other key
  1785  	sp.Key = mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey)
  1786  	assertion, err = sp.ParseXMLArtifactResponse(samlResponse, possibleReqIDs, reqID)
  1787  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1788  		"failed to decrypt EncryptedAssertion: certificate does not match provided key"))
  1789  	assert.Check(t, is.Nil(assertion))
  1790  
  1791  	// no input
  1792  	assertion, err = sp.ParseXMLArtifactResponse([]byte("<!-- no xml root -->"), possibleReqIDs, reqID)
  1793  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1794  		"invalid xml: no root"))
  1795  	assert.Check(t, is.Nil(assertion))
  1796  
  1797  	assertion, err = sp.ParseXMLArtifactResponse([]byte("<invalid xml"), possibleReqIDs, reqID)
  1798  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1799  		"invalid xml: XML syntax error on line 1: unexpected EOF"))
  1800  	assert.Check(t, is.Nil(assertion))
  1801  }
  1802  
  1803  func TestParseBadXMLResponse(t *testing.T) {
  1804  	test := NewServiceProviderTest(t)
  1805  	TimeNow = func() time.Time {
  1806  		rv, _ := time.Parse(timeFormat, "2021-08-17T10:26:57Z")
  1807  		return rv
  1808  	}
  1809  	Clock = dsig.NewFakeClockAt(TimeNow())
  1810  
  1811  	sp := ServiceProvider{
  1812  		Key:         test.Key,
  1813  		Certificate: test.Certificate,
  1814  		MetadataURL: mustParseURL("http://localhost:8000/saml/metadata"),
  1815  		AcsURL:      mustParseURL("https://example.com/saml2/acs"),
  1816  		IDPMetadata: &EntityDescriptor{},
  1817  	}
  1818  
  1819  	assertion, err := sp.ParseXMLResponse([]byte("<!-- no xml root -->"), []string{})
  1820  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1821  		"invalid xml: no root"))
  1822  	assert.Check(t, is.Nil(assertion))
  1823  
  1824  	assertion, err = sp.ParseXMLResponse([]byte("<invalid xml"), []string{})
  1825  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1826  		"invalid xml: XML syntax error on line 1: unexpected EOF"))
  1827  	assert.Check(t, is.Nil(assertion))
  1828  }
  1829  
  1830  func TestMultipleAssertions(t *testing.T) {
  1831  	idpMetadata := golden.Get(t, "TestSPRealWorldKeyInfoHasRSAPublicKeyNotX509Cert_idp_metadata")
  1832  	respStr := golden.Get(t, "TestSPMultipleAssertions")
  1833  	TimeNow = func() time.Time {
  1834  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Fri Apr 21 13:12:51 UTC 2017")
  1835  		return rv
  1836  	}
  1837  	Clock = dsig.NewFakeClockAt(TimeNow())
  1838  	s := ServiceProvider{
  1839  		Key:         mustParsePrivateKey(golden.Get(t, "key_2017.pem")).(*rsa.PrivateKey),
  1840  		Certificate: mustParseCertificate(golden.Get(t, "cert_2017.pem")),
  1841  		MetadataURL: mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/metadata"),
  1842  		AcsURL:      mustParseURL("https://preview.docrocket-ross.test.octolabs.io/saml/acs"),
  1843  		IDPMetadata: &EntityDescriptor{},
  1844  	}
  1845  	err := xml.Unmarshal(idpMetadata, &s.IDPMetadata)
  1846  	assert.Check(t, err)
  1847  
  1848  	req := http.Request{PostForm: url.Values{}}
  1849  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(respStr))
  1850  	profile, err := s.ParseResponse(&req, []string{"id-3992f74e652d89c3cf1efd6c7e472abaac9bc917"})
  1851  
  1852  	assert.Check(t, err)
  1853  	assert.Check(t, profile.Subject.NameID.Value != "admin@evil.com")
  1854  }
  1855  
  1856  func TestSPRejectsMalformedResponse(t *testing.T) {
  1857  	test := NewServiceProviderTest(t)
  1858  	// An actual response from google
  1859  	TimeNow = func() time.Time {
  1860  		rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016")
  1861  		return rv
  1862  	}
  1863  	Clock = dsig.NewFakeClockAt(TimeNow())
  1864  	SamlResponse := golden.Get(t, "TestSPRejectsMalformedResponse_response")
  1865  	test.IDPMetadata = golden.Get(t, "TestSPRejectsMalformedResponse_IDPMetadata")
  1866  
  1867  	s := ServiceProvider{
  1868  		Key:         test.Key,
  1869  		Certificate: test.Certificate,
  1870  		MetadataURL: mustParseURL("https://29ee6d2e.ngrok.io/saml/metadata"),
  1871  		AcsURL:      mustParseURL("https://29ee6d2e.ngrok.io/saml/acs"),
  1872  		IDPMetadata: &EntityDescriptor{},
  1873  	}
  1874  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1875  	assert.Check(t, err)
  1876  
  1877  	// this is a valid response
  1878  	{
  1879  		req := http.Request{PostForm: url.Values{}}
  1880  		req.PostForm.Set("SAMLResponse", string(SamlResponse))
  1881  		assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
  1882  		assert.Check(t, err)
  1883  		assert.Check(t, is.Equal("ross@octolabs.io", assertion.Subject.NameID.Value))
  1884  	}
  1885  
  1886  	// this is a valid response but with a comment injected
  1887  	{
  1888  		x, _ := base64.StdEncoding.DecodeString(string(SamlResponse))
  1889  		y := strings.Replace(string(x), "<saml2p:Response", "<saml2p:Response ::foo=\"bar\"", 1)
  1890  		SamlResponse = []byte(base64.StdEncoding.EncodeToString([]byte(y)))
  1891  
  1892  		req := http.Request{PostForm: url.Values{}}
  1893  		req.PostForm.Set("SAMLResponse", string(SamlResponse))
  1894  		assertion, err := s.ParseResponse(&req, []string{"id-fd419a5ab0472645427f8e07d87a3a5dd0b2e9a6"})
  1895  		assert.Check(t, err != nil)
  1896  		assert.Check(t, is.Nil(assertion))
  1897  	}
  1898  }
  1899  
  1900  func TestSPInvalidResponses(t *testing.T) {
  1901  	test := NewServiceProviderTest(t)
  1902  	s := ServiceProvider{
  1903  		Key:         test.Key,
  1904  		Certificate: test.Certificate,
  1905  		MetadataURL: mustParseURL("https://15661444.ngrok.io/saml2/metadata"),
  1906  		AcsURL:      mustParseURL("https://15661444.ngrok.io/saml2/acs"),
  1907  		IDPMetadata: &EntityDescriptor{},
  1908  	}
  1909  	err := xml.Unmarshal(test.IDPMetadata, &s.IDPMetadata)
  1910  	assert.Check(t, err)
  1911  
  1912  	req := http.Request{PostForm: url.Values{}}
  1913  	req.PostForm.Set("SAMLResponse", "???")
  1914  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1915  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1916  		"cannot parse base64: illegal base64 data at input byte 0"))
  1917  
  1918  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString([]byte("<hello>World!</hello>")))
  1919  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1920  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1921  		"cannot unmarshal response: expected element type <Response> but have <hello>"))
  1922  
  1923  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1924  	_, err = s.ParseResponse(&req, []string{"wrongRequestID"})
  1925  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1926  		"`InResponseTo` does not match any of the possible request IDs (expected [wrongRequestID])"))
  1927  
  1928  	TimeNow = func() time.Time {
  1929  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Nov 30 20:57:09 UTC 2016")
  1930  		return rv
  1931  	}
  1932  	Clock = dsig.NewFakeClockAt(TimeNow())
  1933  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1934  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1935  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1936  		"response IssueInstant expired at 2015-12-01 01:57:51.375 +0000 UTC"))
  1937  	TimeNow = func() time.Time {
  1938  		rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015")
  1939  		return rv
  1940  	}
  1941  	Clock = dsig.NewFakeClockAt(TimeNow())
  1942  
  1943  	s.IDPMetadata.EntityID = "http://snakeoil.com"
  1944  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1945  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1946  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1947  		"response Issuer does not match the IDP metadata (expected \"http://snakeoil.com\")"))
  1948  	s.IDPMetadata.EntityID = "https://idp.testshib.org/idp/shibboleth"
  1949  
  1950  	oldSpStatusSuccess := StatusSuccess
  1951  	StatusSuccess = "not:the:success:value"
  1952  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1953  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1954  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1955  		"urn:oasis:names:tc:SAML:2.0:status:Success"))
  1956  	StatusSuccess = oldSpStatusSuccess
  1957  
  1958  	s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "invalid"
  1959  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1960  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1961  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1962  		"cannot validate signature on Assertion: cannot parse certificate: illegal base64 data at input byte 4"))
  1963  
  1964  	s.IDPMetadata.IDPSSODescriptors[0].KeyDescriptors[0].KeyInfo.X509Data.X509Certificates[0].Data = "aW52YWxpZA=="
  1965  	req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString(test.SamlResponse))
  1966  	_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
  1967  
  1968  	assert.Check(t, is.Error(err.(*InvalidResponseError).PrivateErr,
  1969  		"cannot validate signature on Assertion: x509: malformed certificate"))
  1970  }