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  }