github.com/aavshr/aws-sdk-go@v1.41.3/aws/signer/v4/v4_test.go (about)

     1  package v4
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/aavshr/aws-sdk-go/aws"
    16  	"github.com/aavshr/aws-sdk-go/aws/credentials"
    17  	"github.com/aavshr/aws-sdk-go/aws/request"
    18  	"github.com/aavshr/aws-sdk-go/awstesting"
    19  )
    20  
    21  func epochTime() time.Time { return time.Unix(0, 0) }
    22  
    23  func TestStripExcessHeaders(t *testing.T) {
    24  	vals := []string{
    25  		"",
    26  		"123",
    27  		"1 2 3",
    28  		"1 2 3 ",
    29  		"  1 2 3",
    30  		"1  2 3",
    31  		"1  23",
    32  		"1  2  3",
    33  		"1  2  ",
    34  		" 1  2  ",
    35  		"12   3",
    36  		"12   3   1",
    37  		"12           3     1",
    38  		"12     3       1abc123",
    39  	}
    40  
    41  	expected := []string{
    42  		"",
    43  		"123",
    44  		"1 2 3",
    45  		"1 2 3",
    46  		"1 2 3",
    47  		"1 2 3",
    48  		"1 23",
    49  		"1 2 3",
    50  		"1 2",
    51  		"1 2",
    52  		"12 3",
    53  		"12 3 1",
    54  		"12 3 1",
    55  		"12 3 1abc123",
    56  	}
    57  
    58  	stripExcessSpaces(vals)
    59  	for i := 0; i < len(vals); i++ {
    60  		if e, a := expected[i], vals[i]; e != a {
    61  			t.Errorf("%d, expect %v, got %v", i, e, a)
    62  		}
    63  	}
    64  }
    65  
    66  func buildRequest(serviceName, region, body string) (*http.Request, io.ReadSeeker) {
    67  	reader := strings.NewReader(body)
    68  	return buildRequestWithBodyReader(serviceName, region, reader)
    69  }
    70  
    71  func buildRequestReaderSeeker(serviceName, region, body string) (*http.Request, io.ReadSeeker) {
    72  	reader := &readerSeekerWrapper{strings.NewReader(body)}
    73  	return buildRequestWithBodyReader(serviceName, region, reader)
    74  }
    75  
    76  func buildRequestWithBodyReader(serviceName, region string, body io.Reader) (*http.Request, io.ReadSeeker) {
    77  	var bodyLen int
    78  
    79  	type lenner interface {
    80  		Len() int
    81  	}
    82  	if lr, ok := body.(lenner); ok {
    83  		bodyLen = lr.Len()
    84  	}
    85  
    86  	endpoint := "https://" + serviceName + "." + region + ".amazonaws.com"
    87  	req, _ := http.NewRequest("POST", endpoint, body)
    88  	req.URL.Opaque = "//example.org/bucket/key-._~,!@#$%^&*()"
    89  	req.Header.Set("X-Amz-Target", "prefix.Operation")
    90  	req.Header.Set("Content-Type", "application/x-amz-json-1.0")
    91  
    92  	if bodyLen > 0 {
    93  		req.Header.Set("Content-Length", strconv.Itoa(bodyLen))
    94  	}
    95  
    96  	req.Header.Set("X-Amz-Meta-Other-Header", "some-value=!@#$%^&* (+)")
    97  	req.Header.Add("X-Amz-Meta-Other-Header_With_Underscore", "some-value=!@#$%^&* (+)")
    98  	req.Header.Add("X-amz-Meta-Other-Header_With_Underscore", "some-value=!@#$%^&* (+)")
    99  
   100  	var seeker io.ReadSeeker
   101  	if sr, ok := body.(io.ReadSeeker); ok {
   102  		seeker = sr
   103  	} else {
   104  		seeker = aws.ReadSeekCloser(body)
   105  	}
   106  
   107  	return req, seeker
   108  }
   109  
   110  func buildSigner() Signer {
   111  	return Signer{
   112  		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
   113  	}
   114  }
   115  
   116  func removeWS(text string) string {
   117  	text = strings.Replace(text, " ", "", -1)
   118  	text = strings.Replace(text, "\n", "", -1)
   119  	text = strings.Replace(text, "\t", "", -1)
   120  	return text
   121  }
   122  
   123  func assertEqual(t *testing.T, expected, given string) {
   124  	if removeWS(expected) != removeWS(given) {
   125  		t.Errorf("\nExpected: %s\nGiven:    %s", expected, given)
   126  	}
   127  }
   128  
   129  func TestPresignRequest(t *testing.T) {
   130  	req, body := buildRequest("dynamodb", "us-east-1", "{}")
   131  
   132  	signer := buildSigner()
   133  	signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, epochTime())
   134  
   135  	expectedDate := "19700101T000000Z"
   136  	expectedHeaders := "content-length;content-type;host;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore"
   137  	expectedSig := "122f0b9e091e4ba84286097e2b3404a1f1f4c4aad479adda95b7dff0ccbe5581"
   138  	expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
   139  	expectedTarget := "prefix.Operation"
   140  
   141  	q := req.URL.Query()
   142  	if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
   143  		t.Errorf("expect %v, got %v", e, a)
   144  	}
   145  	if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
   146  		t.Errorf("expect %v, got %v", e, a)
   147  	}
   148  	if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
   149  		t.Errorf("expect %v, got %v", e, a)
   150  	}
   151  	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
   152  		t.Errorf("expect %v, got %v", e, a)
   153  	}
   154  	if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
   155  		t.Errorf("expect %v to be empty", a)
   156  	}
   157  	if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
   158  		t.Errorf("expect %v, got %v", e, a)
   159  	}
   160  }
   161  
   162  func TestPresignBodyWithArrayRequest(t *testing.T) {
   163  	req, body := buildRequest("dynamodb", "us-east-1", "{}")
   164  	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
   165  
   166  	signer := buildSigner()
   167  	signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, epochTime())
   168  
   169  	expectedDate := "19700101T000000Z"
   170  	expectedHeaders := "content-length;content-type;host;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore"
   171  	expectedSig := "e3ac55addee8711b76c6d608d762cff285fe8b627a057f8b5ec9268cf82c08b1"
   172  	expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
   173  	expectedTarget := "prefix.Operation"
   174  
   175  	q := req.URL.Query()
   176  	if e, a := expectedSig, q.Get("X-Amz-Signature"); e != a {
   177  		t.Errorf("expect %v, got %v", e, a)
   178  	}
   179  	if e, a := expectedCred, q.Get("X-Amz-Credential"); e != a {
   180  		t.Errorf("expect %v, got %v", e, a)
   181  	}
   182  	if e, a := expectedHeaders, q.Get("X-Amz-SignedHeaders"); e != a {
   183  		t.Errorf("expect %v, got %v", e, a)
   184  	}
   185  	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
   186  		t.Errorf("expect %v, got %v", e, a)
   187  	}
   188  	if a := q.Get("X-Amz-Meta-Other-Header"); len(a) != 0 {
   189  		t.Errorf("expect %v to be empty, was not", a)
   190  	}
   191  	if e, a := expectedTarget, q.Get("X-Amz-Target"); e != a {
   192  		t.Errorf("expect %v, got %v", e, a)
   193  	}
   194  }
   195  
   196  func TestSignRequest(t *testing.T) {
   197  	req, body := buildRequest("dynamodb", "us-east-1", "{}")
   198  	signer := buildSigner()
   199  	signer.Sign(req, body, "dynamodb", "us-east-1", epochTime())
   200  
   201  	expectedDate := "19700101T000000Z"
   202  	expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-meta-other-header;x-amz-meta-other-header_with_underscore;x-amz-security-token;x-amz-target, Signature=a518299330494908a70222cec6899f6f32f297f8595f6df1776d998936652ad9"
   203  
   204  	q := req.Header
   205  	if e, a := expectedSig, q.Get("Authorization"); e != a {
   206  		t.Errorf("expect\n%v\nactual\n%v\n", e, a)
   207  	}
   208  	if e, a := expectedDate, q.Get("X-Amz-Date"); e != a {
   209  		t.Errorf("expect\n%v\nactual\n%v\n", e, a)
   210  	}
   211  }
   212  
   213  func TestSignBodyS3(t *testing.T) {
   214  	req, body := buildRequest("s3", "us-east-1", "hello")
   215  	signer := buildSigner()
   216  	signer.Sign(req, body, "s3", "us-east-1", time.Now())
   217  	hash := req.Header.Get("X-Amz-Content-Sha256")
   218  	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
   219  		t.Errorf("expect %v, got %v", e, a)
   220  	}
   221  }
   222  
   223  func TestSignBodyGlacier(t *testing.T) {
   224  	req, body := buildRequest("glacier", "us-east-1", "hello")
   225  	signer := buildSigner()
   226  	signer.Sign(req, body, "glacier", "us-east-1", time.Now())
   227  	hash := req.Header.Get("X-Amz-Content-Sha256")
   228  	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
   229  		t.Errorf("expect %v, got %v", e, a)
   230  	}
   231  }
   232  
   233  func TestPresign_SignedPayload(t *testing.T) {
   234  	req, body := buildRequest("glacier", "us-east-1", "hello")
   235  	signer := buildSigner()
   236  	signer.Presign(req, body, "glacier", "us-east-1", 5*time.Minute, time.Now())
   237  	hash := req.Header.Get("X-Amz-Content-Sha256")
   238  	if e, a := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash; e != a {
   239  		t.Errorf("expect %v, got %v", e, a)
   240  	}
   241  }
   242  
   243  func TestPresign_UnsignedPayload(t *testing.T) {
   244  	req, body := buildRequest("service-name", "us-east-1", "hello")
   245  	signer := buildSigner()
   246  	signer.UnsignedPayload = true
   247  	signer.Presign(req, body, "service-name", "us-east-1", 5*time.Minute, time.Now())
   248  	hash := req.Header.Get("X-Amz-Content-Sha256")
   249  	if e, a := "UNSIGNED-PAYLOAD", hash; e != a {
   250  		t.Errorf("expect %v, got %v", e, a)
   251  	}
   252  }
   253  
   254  func TestPresign_UnsignedPayload_S3(t *testing.T) {
   255  	req, body := buildRequest("s3", "us-east-1", "hello")
   256  	signer := buildSigner()
   257  	signer.Presign(req, body, "s3", "us-east-1", 5*time.Minute, time.Now())
   258  	if a := req.Header.Get("X-Amz-Content-Sha256"); len(a) != 0 {
   259  		t.Errorf("expect no content sha256 got %v", a)
   260  	}
   261  }
   262  
   263  func TestSignUnseekableBody(t *testing.T) {
   264  	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
   265  	signer := buildSigner()
   266  	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
   267  	if err == nil {
   268  		t.Fatalf("expect error signing request")
   269  	}
   270  
   271  	if e, a := "unseekable request body", err.Error(); !strings.Contains(a, e) {
   272  		t.Errorf("expect %q to be in %q", e, a)
   273  	}
   274  }
   275  
   276  func TestSignUnsignedPayloadUnseekableBody(t *testing.T) {
   277  	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
   278  
   279  	signer := buildSigner()
   280  	signer.UnsignedPayload = true
   281  
   282  	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
   283  	if err != nil {
   284  		t.Fatalf("expect no error, got %v", err)
   285  	}
   286  
   287  	hash := req.Header.Get("X-Amz-Content-Sha256")
   288  	if e, a := "UNSIGNED-PAYLOAD", hash; e != a {
   289  		t.Errorf("expect %v, got %v", e, a)
   290  	}
   291  }
   292  
   293  func TestSignPreComputedHashUnseekableBody(t *testing.T) {
   294  	req, body := buildRequestWithBodyReader("mock-service", "mock-region", bytes.NewBuffer([]byte("hello")))
   295  
   296  	signer := buildSigner()
   297  
   298  	req.Header.Set("X-Amz-Content-Sha256", "some-content-sha256")
   299  	_, err := signer.Sign(req, body, "mock-service", "mock-region", time.Now())
   300  	if err != nil {
   301  		t.Fatalf("expect no error, got %v", err)
   302  	}
   303  
   304  	hash := req.Header.Get("X-Amz-Content-Sha256")
   305  	if e, a := "some-content-sha256", hash; e != a {
   306  		t.Errorf("expect %v, got %v", e, a)
   307  	}
   308  }
   309  
   310  func TestSignPrecomputedBodyChecksum(t *testing.T) {
   311  	req, body := buildRequest("dynamodb", "us-east-1", "hello")
   312  	req.Header.Set("X-Amz-Content-Sha256", "PRECOMPUTED")
   313  	signer := buildSigner()
   314  	signer.Sign(req, body, "dynamodb", "us-east-1", time.Now())
   315  	hash := req.Header.Get("X-Amz-Content-Sha256")
   316  	if e, a := "PRECOMPUTED", hash; e != a {
   317  		t.Errorf("expect %v, got %v", e, a)
   318  	}
   319  }
   320  
   321  func TestAnonymousCredentials(t *testing.T) {
   322  	svc := awstesting.NewClient(&aws.Config{Credentials: credentials.AnonymousCredentials})
   323  	r := svc.NewRequest(
   324  		&request.Operation{
   325  			Name:       "BatchGetItem",
   326  			HTTPMethod: "POST",
   327  			HTTPPath:   "/",
   328  		},
   329  		nil,
   330  		nil,
   331  	)
   332  	SignSDKRequest(r)
   333  
   334  	urlQ := r.HTTPRequest.URL.Query()
   335  	if a := urlQ.Get("X-Amz-Signature"); len(a) != 0 {
   336  		t.Errorf("expect %v to be empty, was not", a)
   337  	}
   338  	if a := urlQ.Get("X-Amz-Credential"); len(a) != 0 {
   339  		t.Errorf("expect %v to be empty, was not", a)
   340  	}
   341  	if a := urlQ.Get("X-Amz-SignedHeaders"); len(a) != 0 {
   342  		t.Errorf("expect %v to be empty, was not", a)
   343  	}
   344  	if a := urlQ.Get("X-Amz-Date"); len(a) != 0 {
   345  		t.Errorf("expect %v to be empty, was not", a)
   346  	}
   347  
   348  	hQ := r.HTTPRequest.Header
   349  	if a := hQ.Get("Authorization"); len(a) != 0 {
   350  		t.Errorf("expect %v to be empty, was not", a)
   351  	}
   352  	if a := hQ.Get("X-Amz-Date"); len(a) != 0 {
   353  		t.Errorf("expect %v to be empty, was not", a)
   354  	}
   355  }
   356  
   357  func TestIgnoreResignRequestWithValidCreds(t *testing.T) {
   358  	svc := awstesting.NewClient(&aws.Config{
   359  		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
   360  		Region:      aws.String("us-west-2"),
   361  	})
   362  	r := svc.NewRequest(
   363  		&request.Operation{
   364  			Name:       "BatchGetItem",
   365  			HTTPMethod: "POST",
   366  			HTTPPath:   "/",
   367  		},
   368  		nil,
   369  		nil,
   370  	)
   371  
   372  	SignSDKRequest(r)
   373  	sig := r.HTTPRequest.Header.Get("Authorization")
   374  
   375  	SignSDKRequestWithCurrentTime(r, func() time.Time {
   376  		// Simulate one second has passed so that signature's date changes
   377  		// when it is resigned.
   378  		return time.Now().Add(1 * time.Second)
   379  	})
   380  	if e, a := sig, r.HTTPRequest.Header.Get("Authorization"); e == a {
   381  		t.Errorf("expect %v to be %v, but was not", e, a)
   382  	}
   383  }
   384  
   385  func TestIgnorePreResignRequestWithValidCreds(t *testing.T) {
   386  	svc := awstesting.NewClient(&aws.Config{
   387  		Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"),
   388  		Region:      aws.String("us-west-2"),
   389  	})
   390  	r := svc.NewRequest(
   391  		&request.Operation{
   392  			Name:       "BatchGetItem",
   393  			HTTPMethod: "POST",
   394  			HTTPPath:   "/",
   395  		},
   396  		nil,
   397  		nil,
   398  	)
   399  	r.ExpireTime = time.Minute * 10
   400  
   401  	SignSDKRequest(r)
   402  	sig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
   403  
   404  	SignSDKRequestWithCurrentTime(r, func() time.Time {
   405  		// Simulate one second has passed so that signature's date changes
   406  		// when it is resigned.
   407  		return time.Now().Add(1 * time.Second)
   408  	})
   409  	if e, a := sig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
   410  		t.Errorf("expect %v to be %v, but was not", e, a)
   411  	}
   412  }
   413  
   414  func TestResignRequestExpiredCreds(t *testing.T) {
   415  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   416  	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
   417  	r := svc.NewRequest(
   418  		&request.Operation{
   419  			Name:       "BatchGetItem",
   420  			HTTPMethod: "POST",
   421  			HTTPPath:   "/",
   422  		},
   423  		nil,
   424  		nil,
   425  	)
   426  	SignSDKRequest(r)
   427  	querySig := r.HTTPRequest.Header.Get("Authorization")
   428  	var origSignedHeaders string
   429  	for _, p := range strings.Split(querySig, ", ") {
   430  		if strings.HasPrefix(p, "SignedHeaders=") {
   431  			origSignedHeaders = p[len("SignedHeaders="):]
   432  			break
   433  		}
   434  	}
   435  	if a := origSignedHeaders; len(a) == 0 {
   436  		t.Errorf("expect not to be empty, but was")
   437  	}
   438  	if e, a := origSignedHeaders, "authorization"; strings.Contains(a, e) {
   439  		t.Errorf("expect %v to not be in %v, but was", e, a)
   440  	}
   441  	origSignedAt := r.LastSignedAt
   442  
   443  	creds.Expire()
   444  
   445  	SignSDKRequestWithCurrentTime(r, func() time.Time {
   446  		// Simulate one second has passed so that signature's date changes
   447  		// when it is resigned.
   448  		return time.Now().Add(1 * time.Second)
   449  	})
   450  	updatedQuerySig := r.HTTPRequest.Header.Get("Authorization")
   451  	if e, a := querySig, updatedQuerySig; e == a {
   452  		t.Errorf("expect %v to be %v, was not", e, a)
   453  	}
   454  
   455  	var updatedSignedHeaders string
   456  	for _, p := range strings.Split(updatedQuerySig, ", ") {
   457  		if strings.HasPrefix(p, "SignedHeaders=") {
   458  			updatedSignedHeaders = p[len("SignedHeaders="):]
   459  			break
   460  		}
   461  	}
   462  	if a := updatedSignedHeaders; len(a) == 0 {
   463  		t.Errorf("expect not to be empty, but was")
   464  	}
   465  	if e, a := updatedQuerySig, "authorization"; strings.Contains(a, e) {
   466  		t.Errorf("expect %v to not be in %v, but was", e, a)
   467  	}
   468  	if e, a := origSignedAt, r.LastSignedAt; e == a {
   469  		t.Errorf("expect %v to be %v, was not", e, a)
   470  	}
   471  }
   472  
   473  func TestPreResignRequestExpiredCreds(t *testing.T) {
   474  	provider := &credentials.StaticProvider{Value: credentials.Value{
   475  		AccessKeyID:     "AKID",
   476  		SecretAccessKey: "SECRET",
   477  		SessionToken:    "SESSION",
   478  	}}
   479  	creds := credentials.NewCredentials(provider)
   480  	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
   481  	r := svc.NewRequest(
   482  		&request.Operation{
   483  			Name:       "BatchGetItem",
   484  			HTTPMethod: "POST",
   485  			HTTPPath:   "/",
   486  		},
   487  		nil,
   488  		nil,
   489  	)
   490  	r.ExpireTime = time.Minute * 10
   491  
   492  	SignSDKRequest(r)
   493  	querySig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature")
   494  	signedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
   495  	if a := signedHeaders; len(a) == 0 {
   496  		t.Errorf("expect not to be empty, but was")
   497  	}
   498  	origSignedAt := r.LastSignedAt
   499  
   500  	creds.Expire()
   501  
   502  	SignSDKRequestWithCurrentTime(r, func() time.Time {
   503  		// Simulate the request occurred 15 minutes in the past
   504  		return time.Now().Add(-48 * time.Hour)
   505  	})
   506  	if e, a := querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature"); e == a {
   507  		t.Errorf("expect %v to be %v, was not", e, a)
   508  	}
   509  	resignedHeaders := r.HTTPRequest.URL.Query().Get("X-Amz-SignedHeaders")
   510  	if e, a := signedHeaders, resignedHeaders; e != a {
   511  		t.Errorf("expect %v, got %v", e, a)
   512  	}
   513  	if e, a := signedHeaders, "x-amz-signedHeaders"; strings.Contains(a, e) {
   514  		t.Errorf("expect %v to not be in %v, but was", e, a)
   515  	}
   516  	if e, a := origSignedAt, r.LastSignedAt; e == a {
   517  		t.Errorf("expect %v to be %v, was not", e, a)
   518  	}
   519  }
   520  
   521  func TestResignRequestExpiredRequest(t *testing.T) {
   522  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   523  	svc := awstesting.NewClient(&aws.Config{Credentials: creds})
   524  	r := svc.NewRequest(
   525  		&request.Operation{
   526  			Name:       "BatchGetItem",
   527  			HTTPMethod: "POST",
   528  			HTTPPath:   "/",
   529  		},
   530  		nil,
   531  		nil,
   532  	)
   533  
   534  	SignSDKRequest(r)
   535  	querySig := r.HTTPRequest.Header.Get("Authorization")
   536  	origSignedAt := r.LastSignedAt
   537  
   538  	SignSDKRequestWithCurrentTime(r, func() time.Time {
   539  		// Simulate the request occurred 15 minutes in the past
   540  		return time.Now().Add(15 * time.Minute)
   541  	})
   542  	if e, a := querySig, r.HTTPRequest.Header.Get("Authorization"); e == a {
   543  		t.Errorf("expect %v to be %v, was not", e, a)
   544  	}
   545  	if e, a := origSignedAt, r.LastSignedAt; e == a {
   546  		t.Errorf("expect %v to be %v, was not", e, a)
   547  	}
   548  }
   549  
   550  func TestSignWithRequestBody(t *testing.T) {
   551  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   552  	signer := NewSigner(creds)
   553  
   554  	expectBody := []byte("abc123")
   555  
   556  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   557  		b, err := ioutil.ReadAll(r.Body)
   558  		r.Body.Close()
   559  		if err != nil {
   560  			t.Errorf("expect no error, got %v", err)
   561  		}
   562  		if e, a := expectBody, b; !reflect.DeepEqual(e, a) {
   563  			t.Errorf("expect %v, got %v", e, a)
   564  		}
   565  		w.WriteHeader(http.StatusOK)
   566  	}))
   567  	defer server.Close()
   568  
   569  	req, err := http.NewRequest("POST", server.URL, nil)
   570  	if err != nil {
   571  		t.Errorf("expect not no error, got %v", err)
   572  	}
   573  
   574  	_, err = signer.Sign(req, bytes.NewReader(expectBody), "service", "region", time.Now())
   575  	if err != nil {
   576  		t.Errorf("expect not no error, got %v", err)
   577  	}
   578  
   579  	resp, err := http.DefaultClient.Do(req)
   580  	if err != nil {
   581  		t.Errorf("expect not no error, got %v", err)
   582  	}
   583  	if e, a := http.StatusOK, resp.StatusCode; e != a {
   584  		t.Errorf("expect %v, got %v", e, a)
   585  	}
   586  }
   587  
   588  func TestSignWithRequestBody_Overwrite(t *testing.T) {
   589  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   590  	signer := NewSigner(creds)
   591  
   592  	var expectBody []byte
   593  
   594  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   595  		b, err := ioutil.ReadAll(r.Body)
   596  		r.Body.Close()
   597  		if err != nil {
   598  			t.Errorf("expect not no error, got %v", err)
   599  		}
   600  		if e, a := len(expectBody), len(b); e != a {
   601  			t.Errorf("expect %v, got %v", e, a)
   602  		}
   603  		w.WriteHeader(http.StatusOK)
   604  	}))
   605  	defer server.Close()
   606  
   607  	req, err := http.NewRequest("GET", server.URL, strings.NewReader("invalid body"))
   608  	if err != nil {
   609  		t.Errorf("expect not no error, got %v", err)
   610  	}
   611  
   612  	_, err = signer.Sign(req, nil, "service", "region", time.Now())
   613  	req.ContentLength = 0
   614  
   615  	if err != nil {
   616  		t.Errorf("expect not no error, got %v", err)
   617  	}
   618  
   619  	resp, err := http.DefaultClient.Do(req)
   620  	if err != nil {
   621  		t.Errorf("expect not no error, got %v", err)
   622  	}
   623  	if e, a := http.StatusOK, resp.StatusCode; e != a {
   624  		t.Errorf("expect %v, got %v", e, a)
   625  	}
   626  }
   627  
   628  func TestBuildCanonicalRequest(t *testing.T) {
   629  	req, body := buildRequest("dynamodb", "us-east-1", "{}")
   630  	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
   631  	ctx := &signingCtx{
   632  		ServiceName: "dynamodb",
   633  		Region:      "us-east-1",
   634  		Request:     req,
   635  		Body:        body,
   636  		Query:       req.URL.Query(),
   637  		Time:        time.Now(),
   638  		ExpireTime:  5 * time.Second,
   639  	}
   640  
   641  	ctx.buildCanonicalString()
   642  	expected := "https://example.org/bucket/key-._~,!@#$%^&*()?Foo=z&Foo=o&Foo=m&Foo=a"
   643  	if e, a := expected, ctx.Request.URL.String(); e != a {
   644  		t.Errorf("expect %v, got %v", e, a)
   645  	}
   646  }
   647  
   648  func TestSignWithBody_ReplaceRequestBody(t *testing.T) {
   649  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   650  	req, seekerBody := buildRequest("dynamodb", "us-east-1", "{}")
   651  	req.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
   652  
   653  	s := NewSigner(creds)
   654  	origBody := req.Body
   655  
   656  	_, err := s.Sign(req, seekerBody, "dynamodb", "us-east-1", time.Now())
   657  	if err != nil {
   658  		t.Fatalf("expect no error, got %v", err)
   659  	}
   660  
   661  	if req.Body == origBody {
   662  		t.Errorf("expeect request body to not be origBody")
   663  	}
   664  
   665  	if req.Body == nil {
   666  		t.Errorf("expect request body to be changed but was nil")
   667  	}
   668  }
   669  
   670  func TestSignWithBody_NoReplaceRequestBody(t *testing.T) {
   671  	creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
   672  	req, seekerBody := buildRequest("dynamodb", "us-east-1", "{}")
   673  	req.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
   674  
   675  	s := NewSigner(creds, func(signer *Signer) {
   676  		signer.DisableRequestBodyOverwrite = true
   677  	})
   678  
   679  	origBody := req.Body
   680  
   681  	_, err := s.Sign(req, seekerBody, "dynamodb", "us-east-1", time.Now())
   682  	if err != nil {
   683  		t.Fatalf("expect no error, got %v", err)
   684  	}
   685  
   686  	if req.Body != origBody {
   687  		t.Errorf("expect request body to not be chagned")
   688  	}
   689  }
   690  
   691  func TestRequestHost(t *testing.T) {
   692  	req, body := buildRequest("dynamodb", "us-east-1", "{}")
   693  	req.URL.RawQuery = "Foo=z&Foo=o&Foo=m&Foo=a"
   694  	req.Host = "myhost"
   695  	ctx := &signingCtx{
   696  		ServiceName: "dynamodb",
   697  		Region:      "us-east-1",
   698  		Request:     req,
   699  		Body:        body,
   700  		Query:       req.URL.Query(),
   701  		Time:        time.Now(),
   702  		ExpireTime:  5 * time.Second,
   703  	}
   704  
   705  	ctx.buildCanonicalHeaders(ignoredHeaders, ctx.Request.Header)
   706  	if !strings.Contains(ctx.canonicalHeaders, "host:"+req.Host) {
   707  		t.Errorf("canonical host header invalid")
   708  	}
   709  }
   710  
   711  func BenchmarkPresignRequest(b *testing.B) {
   712  	signer := buildSigner()
   713  	req, body := buildRequestReaderSeeker("dynamodb", "us-east-1", "{}")
   714  	for i := 0; i < b.N; i++ {
   715  		signer.Presign(req, body, "dynamodb", "us-east-1", 300*time.Second, time.Now())
   716  	}
   717  }
   718  
   719  func BenchmarkSignRequest(b *testing.B) {
   720  	signer := buildSigner()
   721  	req, body := buildRequestReaderSeeker("dynamodb", "us-east-1", "{}")
   722  	for i := 0; i < b.N; i++ {
   723  		signer.Sign(req, body, "dynamodb", "us-east-1", time.Now())
   724  	}
   725  }
   726  
   727  var stripExcessSpaceCases = []string{
   728  	`AWS4-HMAC-SHA256 Credential=AKIDFAKEIDFAKEID/20160628/us-west-2/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=1234567890abcdef1234567890abcdef1234567890abcdef`,
   729  	`123   321   123   321`,
   730  	`   123   321   123   321   `,
   731  	`   123    321    123          321   `,
   732  	"123",
   733  	"1 2 3",
   734  	"  1 2 3",
   735  	"1  2 3",
   736  	"1  23",
   737  	"1  2  3",
   738  	"1  2  ",
   739  	" 1  2  ",
   740  	"12   3",
   741  	"12   3   1",
   742  	"12           3     1",
   743  	"12     3       1abc123",
   744  }
   745  
   746  func BenchmarkStripExcessSpaces(b *testing.B) {
   747  	for i := 0; i < b.N; i++ {
   748  		// Make sure to start with a copy of the cases
   749  		cases := append([]string{}, stripExcessSpaceCases...)
   750  		stripExcessSpaces(cases)
   751  	}
   752  }
   753  
   754  // readerSeekerWrapper mimics the interface provided by request.offsetReader
   755  type readerSeekerWrapper struct {
   756  	r *strings.Reader
   757  }
   758  
   759  func (r *readerSeekerWrapper) Read(p []byte) (n int, err error) {
   760  	return r.r.Read(p)
   761  }
   762  
   763  func (r *readerSeekerWrapper) Seek(offset int64, whence int) (int64, error) {
   764  	return r.r.Seek(offset, whence)
   765  }
   766  
   767  func (r *readerSeekerWrapper) Len() int {
   768  	return r.r.Len()
   769  }