github.com/mailgun/holster/v4@v4.20.0/httpsign/signer_test.go (about) 1 package httpsign 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/mailgun/holster/v4/clock" 14 "github.com/stretchr/testify/require" 15 ) 16 17 var ( 18 testKey = []byte("042DAD12E0BE4625AC0B2C3F7172DBA8") 19 ) 20 21 func TestSignRequest(t *testing.T) { 22 ctx := context.Background() 23 clock.Freeze(clock.Unix(1330837567, 0)) 24 defer clock.Unfreeze() 25 randomPrv = &fakeRandom{} 26 27 var signtests = []struct { 28 inHeadersToSign map[string]string 29 inSignVerbAndURI bool 30 inHTTPVerb string 31 inRequestURI string 32 inRequestBody string 33 outNonce string 34 outTimestamp string 35 outSignature string 36 outSignatureVersion string 37 }{ 38 {nil, false, "POST", "", `{"hello": "world"}`, 39 "000102030405060708090a0b0c0d0e0f", "1330837567", "5a42c21371e8b3a2b50ca1ad72869dc7882aa83a6a2fb13db1bf108d92c6f05f", "2"}, 40 {map[string]string{"X-Mailgun-Foo": "bar"}, false, "POST", "", `{"hello": "world"}`, 41 "000102030405060708090a0b0c0d0e0f", "1330837567", "d3bee620f172eb16a3bb30fb6b44b7193fdf04391d44c392d080efe71250753d", "2"}, 42 {nil, true, "POST", "/path?key=value&key=value#fragment", `{"hello": "world"}`, 43 "000102030405060708090a0b0c0d0e0f", "1330837567", "6341720191526856d8940d01611394bfc72a04bc6b8fe90f976ff4eb976ec016", "2"}, 44 } 45 46 for i, tt := range signtests { 47 headerNames := make([]string, 0, len(tt.inHeadersToSign)) 48 for k := range tt.inHeadersToSign { 49 headerNames = append(headerNames, k) 50 } 51 // setup 52 s, err := New( 53 &Config{ 54 KeyBytes: testKey, 55 HeadersToSign: headerNames, 56 SignVerbAndURI: tt.inSignVerbAndURI, 57 NonceCacheCapacity: defaultCacheCapacity, 58 NonceCacheTimeout: defaultCacheTimeout, 59 }, 60 ) 61 if err != nil { 62 t.Errorf("[%v] Got unexpected error from NewWithHeadersAndProviders: %v", i, err) 63 } 64 65 body := strings.NewReader(tt.inRequestBody) 66 request, err := http.NewRequestWithContext(ctx, tt.inHTTPVerb, tt.inRequestURI, body) 67 if err != nil { 68 t.Errorf("[%v] Got unexpected error from http.NewRequest: %v", i, err) 69 } 70 if len(tt.inHeadersToSign) > 0 { 71 for k, v := range tt.inHeadersToSign { 72 request.Header.Set(k, v) 73 } 74 } 75 76 // test signing a request 77 err = s.SignRequest(request) 78 if err != nil { 79 t.Errorf("[%v] Got unexpected error from SignRequest: %v", i, err) 80 } 81 82 // check nonce 83 if g, w := request.Header.Get(XMailgunNonce), tt.outNonce; g != w { 84 t.Errorf("[%v] Nonce from SignRequest: Got %s, Want %s", i, g, w) 85 } 86 87 // check timestamp 88 if g, w := request.Header.Get(XMailgunTimestamp), tt.outTimestamp; g != w { 89 t.Errorf("[%v] Timestamp from SignRequest: Got %s, Want %s", i, g, w) 90 } 91 92 // check signature 93 if g, w := request.Header.Get(XMailgunSignature), tt.outSignature; g != w { 94 t.Errorf("[%v] Signature from SignRequest: Got %s, Want %s", i, g, w) 95 } 96 97 // check signature version 98 if g, w := request.Header.Get(XMailgunSignatureVersion), tt.outSignatureVersion; g != w { 99 t.Errorf("[%v] SignatureVersion from SignRequest: Got %s, Want %s", i, g, w) 100 } 101 } 102 } 103 104 func TestAuthenticateRequest(t *testing.T) { 105 ctx := context.Background() 106 clock.Freeze(clock.Unix(1330837567, 0)) 107 defer clock.Unfreeze() 108 randomPrv = &fakeRandom{} 109 110 s, err := New( 111 &Config{ 112 KeyBytes: testKey, 113 HeadersToSign: []string{}, 114 SignVerbAndURI: false, 115 NonceCacheCapacity: defaultCacheCapacity, 116 NonceCacheTimeout: defaultCacheTimeout, 117 }, 118 ) 119 if err != nil { 120 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 121 } 122 123 // http server 124 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 125 // test 126 err := s.VerifyRequest(r) 127 128 // check 129 if err != nil { 130 t.Errorf("VerifyRequest failed to authenticate a correctly signed request. It returned this error: %v", err) 131 } 132 133 fmt.Fprintln(w, "Hello, client") 134 })) 135 defer ts.Close() 136 137 // setup request to test with 138 body := strings.NewReader(`{"hello": "world"}`) 139 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 140 if err != nil { 141 t.Errorf("Got unexpected error from http.NewRequest: %v", err) 142 } 143 144 // sign request 145 err = s.SignRequest(request) 146 if err != nil { 147 t.Errorf("Got unexpected error from SignRequest: %v", err) 148 } 149 150 // submit request 151 client := &http.Client{} 152 res, err := client.Do(request) 153 if err != nil { 154 t.Errorf("Got unexpected error from client.Do: %v", err) 155 } 156 err = res.Body.Close() 157 require.NoError(t, err) 158 } 159 160 func TestAuthenticateRequestWithHeaders(t *testing.T) { 161 ctx := context.Background() 162 clock.Freeze(clock.Unix(1330837567, 0)) 163 defer clock.Unfreeze() 164 randomPrv = &fakeRandom{} 165 166 s, err := New( 167 &Config{ 168 KeyBytes: testKey, 169 HeadersToSign: []string{"X-Mailgun-Custom-Header"}, 170 SignVerbAndURI: false, 171 NonceCacheCapacity: defaultCacheCapacity, 172 NonceCacheTimeout: defaultCacheTimeout, 173 }, 174 ) 175 if err != nil { 176 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 177 } 178 179 // http server 180 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 // test 182 err := s.VerifyRequest(r) 183 184 // check 185 if err != nil { 186 t.Errorf("VerifyRequest failed to authenticate a correctly signed request. It returned this error: %v", err) 187 } 188 189 fmt.Fprintln(w, "Hello, client") 190 })) 191 defer ts.Close() 192 193 // setup request to test with 194 body := strings.NewReader(`{"hello": "world"}`) 195 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 196 if err != nil { 197 t.Errorf("Got unexpected error from http.NewRequest: %v", err) 198 } 199 request.Header.Set("X-Mailgun-Custom-Header", "bar") 200 201 // sign request 202 err = s.SignRequest(request) 203 if err != nil { 204 t.Errorf("Got unexpected error from SignRequest: %v", err) 205 } 206 207 // submit request 208 client := &http.Client{} 209 res, err := client.Do(request) 210 if err != nil { 211 t.Errorf("Got unexpected error from client.Do: %v", err) 212 } 213 err = res.Body.Close() 214 require.NoError(t, err) 215 } 216 217 func TestAuthenticateRequestWithKey(t *testing.T) { 218 ctx := context.Background() 219 clock.Freeze(clock.Unix(1330837567, 0)) 220 defer clock.Unfreeze() 221 randomPrv = &fakeRandom{} 222 223 s, err := New( 224 &Config{ 225 KeyBytes: testKey, 226 HeadersToSign: []string{}, 227 SignVerbAndURI: false, 228 NonceCacheCapacity: defaultCacheCapacity, 229 NonceCacheTimeout: defaultCacheTimeout, 230 }, 231 ) 232 if err != nil { 233 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 234 } 235 236 // http server 237 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 238 // test 239 err := s.VerifyRequestWithKey(r, []byte("abc")) 240 241 // check 242 if err != nil { 243 t.Errorf("VerifyRequest failed to authenticate a correctly signed request. It returned this error: %v", err) 244 } 245 246 fmt.Fprintln(w, "Hello, client") 247 })) 248 defer ts.Close() 249 250 // setup request to test with 251 body := strings.NewReader(`{"hello": "world"}`) 252 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 253 if err != nil { 254 t.Errorf("Got unexpected error from http.NewRequest: %v", err) 255 } 256 257 // sign request 258 err = s.SignRequestWithKey(request, []byte("abc")) 259 if err != nil { 260 t.Errorf("Got unexpected error from SignRequest: %v", err) 261 } 262 263 // submit request 264 client := &http.Client{} 265 res, err := client.Do(request) 266 if err != nil { 267 t.Errorf("Got unexpected error from client.Do: %v", err) 268 } 269 err = res.Body.Close() 270 require.NoError(t, err) 271 } 272 273 func TestAuthenticateRequestWithVerbAndUri(t *testing.T) { 274 ctx := context.Background() 275 clock.Freeze(clock.Unix(1330837567, 0)) 276 defer clock.Unfreeze() 277 randomPrv = &fakeRandom{} 278 279 s, err := New( 280 &Config{ 281 KeyBytes: testKey, 282 HeadersToSign: []string{}, 283 SignVerbAndURI: true, 284 NonceCacheCapacity: defaultCacheCapacity, 285 NonceCacheTimeout: defaultCacheTimeout, 286 }, 287 ) 288 if err != nil { 289 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 290 } 291 292 // http server 293 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 294 // test 295 err := s.VerifyRequestWithKey(r, []byte("abc")) 296 297 // check 298 if err != nil { 299 t.Errorf("VerifyRequest failed to authenticate a correctly signed request. It returned this error: %v", err) 300 } 301 302 fmt.Fprintln(w, "Hello, client") 303 })) 304 defer ts.Close() 305 306 // setup request to test with 307 body := strings.NewReader(`{"hello": "world"}`) 308 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 309 if err != nil { 310 t.Errorf("Got unexpected error from http.NewRequest: %v", err) 311 } 312 313 // sign request 314 err = s.SignRequestWithKey(request, []byte("abc")) 315 if err != nil { 316 t.Errorf("Got unexpected error from SignRequest: %v", err) 317 } 318 319 // submit request 320 client := &http.Client{} 321 res, err := client.Do(request) 322 if err != nil { 323 t.Errorf("Got unexpected error from client.Do: %v", err) 324 } 325 err = res.Body.Close() 326 require.NoError(t, err) 327 } 328 329 func TestAuthenticateRequestForged(t *testing.T) { 330 ctx := context.Background() 331 clock.Freeze(clock.Unix(1330837567, 0)) 332 defer clock.Unfreeze() 333 randomPrv = &fakeRandom{} 334 335 s, err := New( 336 &Config{ 337 KeyBytes: testKey, 338 HeadersToSign: []string{}, 339 SignVerbAndURI: false, 340 NonceCacheCapacity: defaultCacheCapacity, 341 NonceCacheTimeout: defaultCacheTimeout, 342 }, 343 ) 344 if err != nil { 345 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 346 } 347 348 // http server 349 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 350 // test 351 err := s.VerifyRequest(r) 352 353 // check 354 if err == nil { 355 t.Error("VerifyRequest failed to authenticate a correctly signed request. It returned this error:", err) 356 } 357 358 fmt.Fprintln(w, "Hello, client") 359 })) 360 defer ts.Close() 361 362 // setup request to test with 363 body := strings.NewReader(`{"hello": "world"}`) 364 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 365 if err != nil { 366 t.Errorf("Error: %v", err) 367 } 368 369 // try and forget signing the request 370 request.Header.Set(XMailgunNonce, "000102030405060708090a0b0c0d0e0f") 371 request.Header.Set(XMailgunTimestamp, "1330837567") 372 request.Header.Set(XMailgunSignature, "0000000000000000000000000000000000000000000000000000000000000000") 373 374 // submit request 375 client := &http.Client{} 376 res, err := client.Do(request) 377 if err != nil { 378 t.Errorf("Got unexpected error from client.Do: %v", err) 379 } 380 err = res.Body.Close() 381 require.NoError(t, err) 382 } 383 384 func TestAuthenticateRequestMissingHeaders(t *testing.T) { 385 ctx := context.Background() 386 clock.Freeze(clock.Unix(1330837567, 0)) 387 defer clock.Unfreeze() 388 randomPrv = &fakeRandom{} 389 390 s, err := New( 391 &Config{ 392 KeyBytes: testKey, 393 HeadersToSign: []string{}, 394 SignVerbAndURI: false, 395 NonceCacheCapacity: defaultCacheCapacity, 396 NonceCacheTimeout: defaultCacheTimeout, 397 }, 398 ) 399 if err != nil { 400 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 401 } 402 403 // http server 404 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 405 // test 406 err := s.VerifyRequest(r) 407 408 // check 409 if err == nil { 410 t.Error("VerifyRequest failed to authenticate a correctly signed request. It returned this error:", err) 411 } 412 413 fmt.Fprintln(w, "Hello, client") 414 })) 415 defer ts.Close() 416 417 // setup request to test with 418 body := strings.NewReader(`{"hello": "world"}`) 419 request, err := http.NewRequestWithContext(ctx, "POST", ts.URL, body) 420 if err != nil { 421 t.Errorf("Got unexpected error from http.NewRequest: %v", err) 422 } 423 424 // try and forget signing the request 425 request.Header.Set(XMailgunNonce, "000102030405060708090a0b0c0d0e0f") 426 request.Header.Set(XMailgunTimestamp, "1330837567") 427 428 // submit request 429 client := &http.Client{} 430 res, err := client.Do(request) 431 if err != nil { 432 t.Errorf("Got unexpected error from client.Do: %v", err) 433 } 434 err = res.Body.Close() 435 require.NoError(t, err) 436 } 437 438 func TestCheckTimestamp(t *testing.T) { 439 clock.Freeze(clock.Unix(1330837567, 0)) 440 defer clock.Unfreeze() 441 randomPrv = &fakeRandom{} 442 443 s, err := New( 444 &Config{ 445 KeyBytes: testKey, 446 HeadersToSign: []string{}, 447 SignVerbAndURI: false, 448 NonceCacheCapacity: 100, 449 NonceCacheTimeout: 30, 450 }, 451 ) 452 if err != nil { 453 t.Errorf("Got unexpected error from NewWithHeadersAndProviders: %v", err) 454 } 455 456 // test goldilocks (perfect) timestamp 457 time0 := time.Unix(1330837567, 0) 458 timestamp0 := strconv.FormatInt(time0.Unix(), 10) 459 isValid0, err := s.checkTimestamp(timestamp0) 460 if !isValid0 { 461 t.Errorf("Got unexpected error from checkTimestamp: %v", err) 462 } 463 464 // test old timestamp 465 time1 := time.Unix(1330837517, 0) 466 timestamp1 := strconv.FormatInt(time1.Unix(), 10) 467 isValid1, err := s.checkTimestamp(timestamp1) 468 if isValid1 { 469 t.Errorf("Got unexpected error from checkTimestamp: %v", err) 470 } 471 472 // test timestamp from the future 473 time2 := time.Unix(1330837587, 0) 474 timestamp2 := strconv.FormatInt(time2.Unix(), 10) 475 isValid2, err := s.checkTimestamp(timestamp2) 476 if isValid2 { 477 t.Errorf("Got unexpected error from checkTimestamp: %v", err) 478 } 479 }