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 }