github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_hmac_test.go (about) 1 package gateway 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha1" 6 "crypto/sha512" 7 "encoding/base64" 8 "fmt" 9 "hash" 10 "net/http" 11 "net/http/httptest" 12 "net/url" 13 "regexp" 14 "strings" 15 "sync" 16 "testing" 17 "time" 18 19 "github.com/justinas/alice" 20 "github.com/lonelycode/go-uuid/uuid" 21 22 "github.com/TykTechnologies/tyk/apidef" 23 "github.com/TykTechnologies/tyk/config" 24 "github.com/TykTechnologies/tyk/user" 25 ) 26 27 const hmacAuthDef = `{ 28 "api_id": "1", 29 "org_id": "default", 30 "enable_signature_checking": true, 31 "hmac_allowed_clock_skew": 5000, 32 "auth": {"auth_header_name": "authorization"}, 33 "version_data": { 34 "not_versioned": true, 35 "versions": { 36 "v1": {"name": "v1"} 37 } 38 }, 39 "proxy": { 40 "listen_path": "/v1", 41 "target_url": "` + testHttpAny + `" 42 } 43 }` 44 45 func createHMACAuthSession() *user.SessionState { 46 session := new(user.SessionState) 47 session.Rate = 8.0 48 session.Allowance = session.Rate 49 session.LastCheck = time.Now().Unix() 50 session.Per = 1.0 51 session.QuotaRenewalRate = 300 // 5 minutes 52 session.QuotaRenews = time.Now().Unix() + 20 53 session.QuotaRemaining = 1 54 session.QuotaMax = -1 55 session.HMACEnabled = true 56 session.HmacSecret = "9879879878787878" 57 return session 58 } 59 60 func getHMACAuthChain(spec *APISpec) http.Handler { 61 remote, _ := url.Parse(testHttpAny) 62 proxy := TykNewSingleHostReverseProxy(remote, spec) 63 proxyHandler := ProxyHandler(proxy, spec) 64 baseMid := BaseMiddleware{Spec: spec, Proxy: proxy} 65 chain := alice.New(mwList( 66 &IPWhiteListMiddleware{baseMid}, 67 &IPBlackListMiddleware{BaseMiddleware: baseMid}, 68 &HMACMiddleware{BaseMiddleware: baseMid}, 69 &VersionCheck{BaseMiddleware: baseMid}, 70 &KeyExpired{baseMid}, 71 &AccessRightsCheck{baseMid}, 72 &RateLimitAndQuotaCheck{baseMid}, 73 )...).Then(proxyHandler) 74 return chain 75 } 76 77 type testAuthFailEventHandler struct { 78 cb func(config.EventMessage) 79 } 80 81 func (w *testAuthFailEventHandler) Init(handlerConf interface{}) error { 82 return nil 83 } 84 85 func (w *testAuthFailEventHandler) HandleEvent(em config.EventMessage) { 86 w.cb(em) 87 } 88 89 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { 90 c := make(chan struct{}) 91 go func() { 92 defer close(c) 93 wg.Wait() 94 }() 95 select { 96 case <-c: 97 return false // completed normally 98 case <-time.After(timeout): 99 return true // timed out 100 } 101 } 102 103 func testPrepareHMACAuthSessionPass(tb testing.TB, hashFn func() hash.Hash, eventWG *sync.WaitGroup, withHeader bool, isBench bool) (string, *APISpec, *http.Request, string) { 104 spec := CreateSpecTest(tb, hmacAuthDef) 105 session := createHMACAuthSession() 106 107 // Should not receive an AuthFailure event 108 cb := func(em config.EventMessage) { 109 eventWG.Done() 110 } 111 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 112 "AuthFailure": {&testAuthFailEventHandler{cb}}, 113 } 114 115 sessionKey := "" 116 if isBench { 117 sessionKey = uuid.New() 118 } else { 119 sessionKey = "9876" 120 } 121 122 spec.SessionManager.UpdateSession(sessionKey, session, 60, false) 123 124 req := TestReq(tb, "GET", "/", nil) 125 126 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 127 128 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 129 130 // Prep the signature string 131 tim := time.Now().Format(refDate) 132 req.Header.Set("Date", tim) 133 signatureString := "" 134 if withHeader { 135 req.Header.Set("X-Test-1", "hello") 136 req.Header.Set("X-Test-2", "world") 137 signatureString = strings.ToLower("(request-target): ") + "get /\n" 138 signatureString += strings.ToLower("Date") + ": " + tim + "\n" 139 signatureString += strings.ToLower("X-Test-1") + ": " + "hello" + "\n" 140 signatureString += strings.ToLower("X-Test-2") + ": " + "world" 141 } else { 142 signatureString = strings.ToLower("Date") + ": " + tim 143 } 144 145 // Encode it 146 key := []byte(session.HmacSecret) 147 h := hmac.New(hashFn, key) 148 h.Write([]byte(signatureString)) 149 150 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 151 encodedString := url.QueryEscape(sigString) 152 153 return encodedString, spec, req, sessionKey 154 } 155 156 func TestHMACAuthSessionPass(t *testing.T) { 157 // Should not receive an AuthFailure event 158 var eventWG sync.WaitGroup 159 eventWG.Add(1) 160 encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha1.New, &eventWG, false, false) 161 162 recorder := httptest.NewRecorder() 163 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",signature=\"%s\"", sessionKey, encodedString)) 164 165 chain := getHMACAuthChain(spec) 166 chain.ServeHTTP(recorder, req) 167 168 if recorder.Code != 200 { 169 t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String()) 170 } 171 172 // Check we did not get our AuthFailure event 173 if !waitTimeout(&eventWG, 20*time.Millisecond) { 174 t.Error("Request should not have generated an AuthFailure event!: \n") 175 } 176 } 177 178 func TestHMACAuthSessionSHA512Pass(t *testing.T) { 179 // Should not receive an AuthFailure event 180 var eventWG sync.WaitGroup 181 eventWG.Add(1) 182 encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha512.New, &eventWG, false, false) 183 184 recorder := httptest.NewRecorder() 185 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha512\",signature=\"%s\"", sessionKey, encodedString)) 186 187 spec.HmacAllowedAlgorithms = []string{"hmac-sha512"} 188 chain := getHMACAuthChain(spec) 189 chain.ServeHTTP(recorder, req) 190 191 if recorder.Code != 200 { 192 t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String()) 193 } 194 195 // Check we did not get our AuthFailure event 196 if !waitTimeout(&eventWG, 20*time.Millisecond) { 197 t.Error("Request should not have generated an AuthFailure event!: \n") 198 } 199 } 200 201 func BenchmarkHMACAuthSessionPass(b *testing.B) { 202 b.ReportAllocs() 203 204 var eventWG sync.WaitGroup 205 eventWG.Add(b.N) 206 encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(b, sha1.New, &eventWG, false, true) 207 208 recorder := httptest.NewRecorder() 209 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",signature=\"%s\"", sessionKey, encodedString)) 210 211 chain := getHMACAuthChain(spec) 212 213 for i := 0; i < b.N; i++ { 214 chain.ServeHTTP(recorder, req) 215 if recorder.Code != 200 { 216 b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code, recorder.Body.String()) 217 } 218 } 219 } 220 221 func TestHMACAuthSessionAuxDateHeader(t *testing.T) { 222 spec := CreateSpecTest(t, hmacAuthDef) 223 session := createHMACAuthSession() 224 225 // Should not receive an AuthFailure event 226 var eventWG sync.WaitGroup 227 eventWG.Add(1) 228 cb := func(em config.EventMessage) { 229 eventWG.Done() 230 } 231 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 232 "AuthFailure": {&testAuthFailEventHandler{cb}}, 233 } 234 235 // Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session. 236 spec.SessionManager.UpdateSession("9876", session, 60, false) 237 238 recorder := httptest.NewRecorder() 239 req := TestReq(t, "GET", "/", nil) 240 241 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 242 243 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 244 245 // Prep the signature string 246 tim := time.Now().Format(refDate) 247 req.Header.Set("x-aux-date", tim) 248 signatureString := strings.ToLower("x-aux-date") + ": " + tim 249 250 // Encode it 251 key := []byte(session.HmacSecret) 252 h := hmac.New(sha1.New, key) 253 h.Write([]byte(signatureString)) 254 255 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 256 encodedString := url.QueryEscape(sigString) 257 258 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString)) 259 260 chain := getHMACAuthChain(spec) 261 chain.ServeHTTP(recorder, req) 262 263 if recorder.Code != 200 { 264 t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code) 265 } 266 267 // Check we did not get our AuthFailure event 268 if !waitTimeout(&eventWG, 20*time.Millisecond) { 269 t.Error("Request should not have generated an AuthFailure event!: \n") 270 } 271 } 272 273 func TestHMACAuthSessionFailureDateExpired(t *testing.T) { 274 spec := CreateSpecTest(t, hmacAuthDef) 275 session := createHMACAuthSession() 276 277 // Should receive an AuthFailure event 278 var eventWG sync.WaitGroup 279 eventWG.Add(1) 280 cb := func(em config.EventMessage) { 281 eventWG.Done() 282 } 283 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 284 "AuthFailure": {&testAuthFailEventHandler{cb}}, 285 } 286 287 // Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session. 288 spec.SessionManager.UpdateSession("9876", session, 60, false) 289 290 recorder := httptest.NewRecorder() 291 req := TestReq(t, "GET", "/", nil) 292 293 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 294 295 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 296 297 // Prep the signature string 298 tim := time.Now().Format(refDate) 299 req.Header.Set("Date", tim) 300 signatureString := strings.ToLower("Date") + ":" + tim 301 302 // Encode it 303 key := []byte(session.HmacSecret) 304 h := hmac.New(sha1.New, key) 305 h.Write([]byte(signatureString)) 306 307 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 308 encodedString := url.QueryEscape(sigString) 309 310 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString)) 311 312 chain := getHMACAuthChain(spec) 313 chain.ServeHTTP(recorder, req) 314 315 if recorder.Code != 400 { 316 t.Error("Request should have failed with out of date error!: \n", recorder.Code) 317 } 318 319 // Check we did get our AuthFailure event 320 if waitTimeout(&eventWG, 20*time.Millisecond) { 321 t.Error("Request should have generated an AuthFailure event!: \n") 322 } 323 } 324 325 func TestHMACAuthSessionKeyMissing(t *testing.T) { 326 spec := CreateSpecTest(t, hmacAuthDef) 327 session := createHMACAuthSession() 328 329 // Should receive an AuthFailure event 330 var eventWG sync.WaitGroup 331 eventWG.Add(1) 332 cb := func(em config.EventMessage) { 333 eventWG.Done() 334 } 335 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 336 "AuthFailure": {&testAuthFailEventHandler{cb}}, 337 } 338 339 // Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session. 340 spec.SessionManager.UpdateSession("9876", session, 60, false) 341 342 recorder := httptest.NewRecorder() 343 req := TestReq(t, "GET", "/", nil) 344 345 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 346 347 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 348 349 // Prep the signature string 350 tim := time.Now().Format(refDate) 351 req.Header.Set("Date", tim) 352 signatureString := strings.ToLower("Date") + ":" + tim 353 354 // Encode it 355 key := []byte(session.HmacSecret) 356 h := hmac.New(sha1.New, key) 357 h.Write([]byte(signatureString)) 358 359 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 360 encodedString := url.QueryEscape(sigString) 361 362 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"98765\",algorithm=\"hmac-sha1\",signature=\"%s\"", encodedString)) 363 364 chain := getHMACAuthChain(spec) 365 chain.ServeHTTP(recorder, req) 366 367 if recorder.Code != 400 { 368 t.Error("Request should have failed with key not found error!: \n", recorder.Code) 369 } 370 371 // Check we did get our AuthFailure event 372 if waitTimeout(&eventWG, 20*time.Millisecond) { 373 t.Error("Request should have generated an AuthFailure event!: \n") 374 } 375 } 376 377 func TestHMACAuthSessionMalformedHeader(t *testing.T) { 378 spec := CreateSpecTest(t, hmacAuthDef) 379 session := createHMACAuthSession() 380 381 // Should receive an AuthFailure event 382 var eventWG sync.WaitGroup 383 eventWG.Add(1) 384 cb := func(em config.EventMessage) { 385 eventWG.Done() 386 } 387 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 388 "AuthFailure": {&testAuthFailEventHandler{cb}}, 389 } 390 391 // Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session. 392 spec.SessionManager.UpdateSession("9876", session, 60, false) 393 394 recorder := httptest.NewRecorder() 395 req := TestReq(t, "GET", "/", nil) 396 397 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 398 399 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 400 401 // Prep the signature string 402 tim := time.Now().Format(refDate) 403 req.Header.Set("Date", tim) 404 signatureString := strings.ToLower("Date") + ":" + tim 405 406 // Encode it 407 key := []byte(session.HmacSecret) 408 h := hmac.New(sha1.New, key) 409 h.Write([]byte(signatureString)) 410 411 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 412 encodedString := url.QueryEscape(sigString) 413 414 req.Header.Set("Authorization", fmt.Sprintf("Signature keyID=\"98765\", algorithm=\"hmac-sha256\", signature=\"%s\"", encodedString)) 415 416 chain := getHMACAuthChain(spec) 417 chain.ServeHTTP(recorder, req) 418 419 if recorder.Code != 400 { 420 t.Error("Request should have failed with key not found error!: \n", recorder.Code) 421 } 422 423 // Check we did get our AuthFailure event 424 if waitTimeout(&eventWG, 20*time.Millisecond) { 425 t.Error("Request should have generated an AuthFailure event!: \n") 426 } 427 } 428 429 func TestHMACAuthSessionPassWithHeaderField(t *testing.T) { 430 // Should not receive an AuthFailure event 431 var eventWG sync.WaitGroup 432 eventWG.Add(1) 433 encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(t, sha1.New, &eventWG, true, false) 434 435 recorder := httptest.NewRecorder() 436 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", sessionKey, encodedString)) 437 438 chain := getHMACAuthChain(spec) 439 chain.ServeHTTP(recorder, req) 440 441 if recorder.Code != 200 { 442 t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code) 443 } 444 445 // Check we did not get our AuthFailure event 446 if !waitTimeout(&eventWG, 20*time.Millisecond) { 447 t.Error("Request should not have generated an AuthFailure event!: \n") 448 } 449 } 450 451 func BenchmarkHMACAuthSessionPassWithHeaderField(b *testing.B) { 452 b.ReportAllocs() 453 454 var eventWG sync.WaitGroup 455 eventWG.Add(b.N) 456 encodedString, spec, req, sessionKey := testPrepareHMACAuthSessionPass(b, sha1.New, &eventWG, true, true) 457 458 recorder := httptest.NewRecorder() 459 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"%s\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", sessionKey, encodedString)) 460 461 chain := getHMACAuthChain(spec) 462 463 for i := 0; i < b.N; i++ { 464 chain.ServeHTTP(recorder, req) 465 if recorder.Code != 200 { 466 b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code) 467 } 468 } 469 } 470 471 func getUpperCaseEscaped(signature string) []string { 472 r := regexp.MustCompile(`%[A-F0-9][A-F0-9]`) 473 foundList := r.FindAllString(signature, -1) 474 return foundList 475 } 476 477 func replaceUpperCase(originalSignature string, lowercaseList []string) string { 478 newSignature := originalSignature 479 for _, lStr := range lowercaseList { 480 asUpper := strings.ToLower(lStr) 481 newSignature = strings.Replace(newSignature, lStr, asUpper, -1) 482 } 483 484 return newSignature 485 } 486 487 func TestHMACAuthSessionPassWithHeaderFieldLowerCase(t *testing.T) { 488 spec := CreateSpecTest(t, hmacAuthDef) 489 session := createHMACAuthSession() 490 491 // Should not receive an AuthFailure event 492 var eventWG sync.WaitGroup 493 eventWG.Add(1) 494 cb := func(em config.EventMessage) { 495 eventWG.Done() 496 } 497 spec.EventPaths = map[apidef.TykEvent][]config.TykEventHandler{ 498 "AuthFailure": {&testAuthFailEventHandler{cb}}, 499 } 500 501 // Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session. 502 spec.SessionManager.UpdateSession("9876", session, 60, false) 503 504 recorder := httptest.NewRecorder() 505 req := TestReq(t, "GET", "/", nil) 506 507 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 508 509 // Signature needs to be: Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 510 511 // Prep the signature string 512 tim := time.Now().Format(refDate) 513 req.Header.Set("Date", tim) 514 req.Header.Set("X-Test-1", "hello?") 515 req.Header.Set("X-Test-2", "world£") 516 signatureString := strings.ToLower("(request-target): ") + "get /\n" 517 signatureString += strings.ToLower("Date") + ": " + tim + "\n" 518 signatureString += strings.ToLower("X-Test-1") + ": " + "hello?" + "\n" 519 signatureString += strings.ToLower("X-Test-2") + ": " + "world£" 520 521 // Encode it 522 key := []byte(session.HmacSecret) 523 h := hmac.New(sha1.New, key) 524 h.Write([]byte(signatureString)) 525 526 sigString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 527 encodedString := url.QueryEscape(sigString) 528 529 upperCaseList := getUpperCaseEscaped(encodedString) 530 newEncodedSignature := replaceUpperCase(encodedString, upperCaseList) 531 532 req.Header.Set("Authorization", fmt.Sprintf("Signature keyId=\"9876\",algorithm=\"hmac-sha1\",headers=\"(request-target) date x-test-1 x-test-2\",signature=\"%s\"", newEncodedSignature)) 533 534 chain := getHMACAuthChain(spec) 535 chain.ServeHTTP(recorder, req) 536 537 if recorder.Code != 200 { 538 t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code) 539 } 540 541 // Check we did not get our AuthFailure event 542 if !waitTimeout(&eventWG, 20*time.Millisecond) { 543 t.Error("Request should not have generated an AuthFailure event!: \n") 544 } 545 }