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

     1  // Package v4 implements signing for AWS V4 signer
     2  //
     3  // Provides request signing for request that need to be signed with
     4  // AWS V4 Signatures.
     5  //
     6  // Standalone Signer
     7  //
     8  // Generally using the signer outside of the SDK should not require any additional
     9  // logic when using Go v1.5 or higher. The signer does this by taking advantage
    10  // of the URL.EscapedPath method. If your request URI requires additional escaping
    11  // you many need to use the URL.Opaque to define what the raw URI should be sent
    12  // to the service as.
    13  //
    14  // The signer will first check the URL.Opaque field, and use its value if set.
    15  // The signer does require the URL.Opaque field to be set in the form of:
    16  //
    17  //     "//<hostname>/<path>"
    18  //
    19  //     // e.g.
    20  //     "//example.com/some/path"
    21  //
    22  // The leading "//" and hostname are required or the URL.Opaque escaping will
    23  // not work correctly.
    24  //
    25  // If URL.Opaque is not set the signer will fallback to the URL.EscapedPath()
    26  // method and using the returned value. If you're using Go v1.4 you must set
    27  // URL.Opaque if the URI path needs escaping. If URL.Opaque is not set with
    28  // Go v1.5 the signer will fallback to URL.Path.
    29  //
    30  // AWS v4 signature validation requires that the canonical string's URI path
    31  // element must be the URI escaped form of the HTTP request's path.
    32  // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
    33  //
    34  // The Go HTTP client will perform escaping automatically on the request. Some
    35  // of these escaping may cause signature validation errors because the HTTP
    36  // request differs from the URI path or query that the signature was generated.
    37  // https://golang.org/pkg/net/url/#URL.EscapedPath
    38  //
    39  // Because of this, it is recommended that when using the signer outside of the
    40  // SDK that explicitly escaping the request prior to being signed is preferable,
    41  // and will help prevent signature validation errors. This can be done by setting
    42  // the URL.Opaque or URL.RawPath. The SDK will use URL.Opaque first and then
    43  // call URL.EscapedPath() if Opaque is not set.
    44  //
    45  // If signing a request intended for HTTP2 server, and you're using Go 1.6.2
    46  // through 1.7.4 you should use the URL.RawPath as the pre-escaped form of the
    47  // request URL. https://github.com/golang/go/issues/16847 points to a bug in
    48  // Go pre 1.8 that fails to make HTTP2 requests using absolute URL in the HTTP
    49  // message. URL.Opaque generally will force Go to make requests with absolute URL.
    50  // URL.RawPath does not do this, but RawPath must be a valid escaping of Path
    51  // or url.EscapedPath will ignore the RawPath escaping.
    52  //
    53  // Test `TestStandaloneSign` provides a complete example of using the signer
    54  // outside of the SDK and pre-escaping the URI path.
    55  package v4
    56  
    57  import (
    58  	"crypto/hmac"
    59  	"crypto/sha256"
    60  	"encoding/hex"
    61  	"fmt"
    62  	"io"
    63  	"io/ioutil"
    64  	"net/http"
    65  	"net/url"
    66  	"sort"
    67  	"strconv"
    68  	"strings"
    69  	"time"
    70  
    71  	"github.com/aavshr/aws-sdk-go/aws"
    72  	"github.com/aavshr/aws-sdk-go/aws/credentials"
    73  	"github.com/aavshr/aws-sdk-go/aws/request"
    74  	"github.com/aavshr/aws-sdk-go/internal/sdkio"
    75  	"github.com/aavshr/aws-sdk-go/private/protocol/rest"
    76  )
    77  
    78  const (
    79  	authorizationHeader     = "Authorization"
    80  	authHeaderSignatureElem = "Signature="
    81  	signatureQueryKey       = "X-Amz-Signature"
    82  
    83  	authHeaderPrefix = "AWS4-HMAC-SHA256"
    84  	timeFormat       = "20060102T150405Z"
    85  	shortTimeFormat  = "20060102"
    86  	awsV4Request     = "aws4_request"
    87  
    88  	// emptyStringSHA256 is a SHA256 of an empty string
    89  	emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
    90  )
    91  
    92  var ignoredHeaders = rules{
    93  	excludeList{
    94  		mapRule{
    95  			authorizationHeader: struct{}{},
    96  			"User-Agent":        struct{}{},
    97  			"X-Amzn-Trace-Id":   struct{}{},
    98  		},
    99  	},
   100  }
   101  
   102  // requiredSignedHeaders is a allow list for build canonical headers.
   103  var requiredSignedHeaders = rules{
   104  	allowList{
   105  		mapRule{
   106  			"Cache-Control":                         struct{}{},
   107  			"Content-Disposition":                   struct{}{},
   108  			"Content-Encoding":                      struct{}{},
   109  			"Content-Language":                      struct{}{},
   110  			"Content-Md5":                           struct{}{},
   111  			"Content-Type":                          struct{}{},
   112  			"Expires":                               struct{}{},
   113  			"If-Match":                              struct{}{},
   114  			"If-Modified-Since":                     struct{}{},
   115  			"If-None-Match":                         struct{}{},
   116  			"If-Unmodified-Since":                   struct{}{},
   117  			"Range":                                 struct{}{},
   118  			"X-Amz-Acl":                             struct{}{},
   119  			"X-Amz-Copy-Source":                     struct{}{},
   120  			"X-Amz-Copy-Source-If-Match":            struct{}{},
   121  			"X-Amz-Copy-Source-If-Modified-Since":   struct{}{},
   122  			"X-Amz-Copy-Source-If-None-Match":       struct{}{},
   123  			"X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
   124  			"X-Amz-Copy-Source-Range":               struct{}{},
   125  			"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
   126  			"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key":       struct{}{},
   127  			"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5":   struct{}{},
   128  			"X-Amz-Grant-Full-control":                                    struct{}{},
   129  			"X-Amz-Grant-Read":                                            struct{}{},
   130  			"X-Amz-Grant-Read-Acp":                                        struct{}{},
   131  			"X-Amz-Grant-Write":                                           struct{}{},
   132  			"X-Amz-Grant-Write-Acp":                                       struct{}{},
   133  			"X-Amz-Metadata-Directive":                                    struct{}{},
   134  			"X-Amz-Mfa":                                                   struct{}{},
   135  			"X-Amz-Request-Payer":                                         struct{}{},
   136  			"X-Amz-Server-Side-Encryption":                                struct{}{},
   137  			"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id":                 struct{}{},
   138  			"X-Amz-Server-Side-Encryption-Customer-Algorithm":             struct{}{},
   139  			"X-Amz-Server-Side-Encryption-Customer-Key":                   struct{}{},
   140  			"X-Amz-Server-Side-Encryption-Customer-Key-Md5":               struct{}{},
   141  			"X-Amz-Storage-Class":                                         struct{}{},
   142  			"X-Amz-Tagging":                                               struct{}{},
   143  			"X-Amz-Website-Redirect-Location":                             struct{}{},
   144  			"X-Amz-Content-Sha256":                                        struct{}{},
   145  		},
   146  	},
   147  	patterns{"X-Amz-Meta-"},
   148  	patterns{"X-Amz-Object-Lock-"},
   149  }
   150  
   151  // allowedHoisting is a allow list for build query headers. The boolean value
   152  // represents whether or not it is a pattern.
   153  var allowedQueryHoisting = inclusiveRules{
   154  	excludeList{requiredSignedHeaders},
   155  	patterns{"X-Amz-"},
   156  }
   157  
   158  // Signer applies AWS v4 signing to given request. Use this to sign requests
   159  // that need to be signed with AWS V4 Signatures.
   160  type Signer struct {
   161  	// The authentication credentials the request will be signed against.
   162  	// This value must be set to sign requests.
   163  	Credentials *credentials.Credentials
   164  
   165  	// Sets the log level the signer should use when reporting information to
   166  	// the logger. If the logger is nil nothing will be logged. See
   167  	// aws.LogLevelType for more information on available logging levels
   168  	//
   169  	// By default nothing will be logged.
   170  	Debug aws.LogLevelType
   171  
   172  	// The logger loging information will be written to. If there the logger
   173  	// is nil, nothing will be logged.
   174  	Logger aws.Logger
   175  
   176  	// Disables the Signer's moving HTTP header key/value pairs from the HTTP
   177  	// request header to the request's query string. This is most commonly used
   178  	// with pre-signed requests preventing headers from being added to the
   179  	// request's query string.
   180  	DisableHeaderHoisting bool
   181  
   182  	// Disables the automatic escaping of the URI path of the request for the
   183  	// siganture's canonical string's path. For services that do not need additional
   184  	// escaping then use this to disable the signer escaping the path.
   185  	//
   186  	// S3 is an example of a service that does not need additional escaping.
   187  	//
   188  	// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
   189  	DisableURIPathEscaping bool
   190  
   191  	// Disables the automatical setting of the HTTP request's Body field with the
   192  	// io.ReadSeeker passed in to the signer. This is useful if you're using a
   193  	// custom wrapper around the body for the io.ReadSeeker and want to preserve
   194  	// the Body value on the Request.Body.
   195  	//
   196  	// This does run the risk of signing a request with a body that will not be
   197  	// sent in the request. Need to ensure that the underlying data of the Body
   198  	// values are the same.
   199  	DisableRequestBodyOverwrite bool
   200  
   201  	// currentTimeFn returns the time value which represents the current time.
   202  	// This value should only be used for testing. If it is nil the default
   203  	// time.Now will be used.
   204  	currentTimeFn func() time.Time
   205  
   206  	// UnsignedPayload will prevent signing of the payload. This will only
   207  	// work for services that have support for this.
   208  	UnsignedPayload bool
   209  }
   210  
   211  // NewSigner returns a Signer pointer configured with the credentials and optional
   212  // option values provided. If not options are provided the Signer will use its
   213  // default configuration.
   214  func NewSigner(credentials *credentials.Credentials, options ...func(*Signer)) *Signer {
   215  	v4 := &Signer{
   216  		Credentials: credentials,
   217  	}
   218  
   219  	for _, option := range options {
   220  		option(v4)
   221  	}
   222  
   223  	return v4
   224  }
   225  
   226  type signingCtx struct {
   227  	ServiceName      string
   228  	Region           string
   229  	Request          *http.Request
   230  	Body             io.ReadSeeker
   231  	Query            url.Values
   232  	Time             time.Time
   233  	ExpireTime       time.Duration
   234  	SignedHeaderVals http.Header
   235  
   236  	DisableURIPathEscaping bool
   237  
   238  	credValues      credentials.Value
   239  	isPresign       bool
   240  	unsignedPayload bool
   241  
   242  	bodyDigest       string
   243  	signedHeaders    string
   244  	canonicalHeaders string
   245  	canonicalString  string
   246  	credentialString string
   247  	stringToSign     string
   248  	signature        string
   249  	authorization    string
   250  }
   251  
   252  // Sign signs AWS v4 requests with the provided body, service name, region the
   253  // request is made to, and time the request is signed at. The signTime allows
   254  // you to specify that a request is signed for the future, and cannot be
   255  // used until then.
   256  //
   257  // Returns a list of HTTP headers that were included in the signature or an
   258  // error if signing the request failed. Generally for signed requests this value
   259  // is not needed as the full request context will be captured by the http.Request
   260  // value. It is included for reference though.
   261  //
   262  // Sign will set the request's Body to be the `body` parameter passed in. If
   263  // the body is not already an io.ReadCloser, it will be wrapped within one. If
   264  // a `nil` body parameter passed to Sign, the request's Body field will be
   265  // also set to nil. Its important to note that this functionality will not
   266  // change the request's ContentLength of the request.
   267  //
   268  // Sign differs from Presign in that it will sign the request using HTTP
   269  // header values. This type of signing is intended for http.Request values that
   270  // will not be shared, or are shared in a way the header values on the request
   271  // will not be lost.
   272  //
   273  // The requests body is an io.ReadSeeker so the SHA256 of the body can be
   274  // generated. To bypass the signer computing the hash you can set the
   275  // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
   276  // only compute the hash if the request header value is empty.
   277  func (v4 Signer) Sign(r *http.Request, body io.ReadSeeker, service, region string, signTime time.Time) (http.Header, error) {
   278  	return v4.signWithBody(r, body, service, region, 0, false, signTime)
   279  }
   280  
   281  // Presign signs AWS v4 requests with the provided body, service name, region
   282  // the request is made to, and time the request is signed at. The signTime
   283  // allows you to specify that a request is signed for the future, and cannot
   284  // be used until then.
   285  //
   286  // Returns a list of HTTP headers that were included in the signature or an
   287  // error if signing the request failed. For presigned requests these headers
   288  // and their values must be included on the HTTP request when it is made. This
   289  // is helpful to know what header values need to be shared with the party the
   290  // presigned request will be distributed to.
   291  //
   292  // Presign differs from Sign in that it will sign the request using query string
   293  // instead of header values. This allows you to share the Presigned Request's
   294  // URL with third parties, or distribute it throughout your system with minimal
   295  // dependencies.
   296  //
   297  // Presign also takes an exp value which is the duration the
   298  // signed request will be valid after the signing time. This is allows you to
   299  // set when the request will expire.
   300  //
   301  // The requests body is an io.ReadSeeker so the SHA256 of the body can be
   302  // generated. To bypass the signer computing the hash you can set the
   303  // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
   304  // only compute the hash if the request header value is empty.
   305  //
   306  // Presigning a S3 request will not compute the body's SHA256 hash by default.
   307  // This is done due to the general use case for S3 presigned URLs is to share
   308  // PUT/GET capabilities. If you would like to include the body's SHA256 in the
   309  // presigned request's signature you can set the "X-Amz-Content-Sha256"
   310  // HTTP header and that will be included in the request's signature.
   311  func (v4 Signer) Presign(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, signTime time.Time) (http.Header, error) {
   312  	return v4.signWithBody(r, body, service, region, exp, true, signTime)
   313  }
   314  
   315  func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, isPresign bool, signTime time.Time) (http.Header, error) {
   316  	currentTimeFn := v4.currentTimeFn
   317  	if currentTimeFn == nil {
   318  		currentTimeFn = time.Now
   319  	}
   320  
   321  	ctx := &signingCtx{
   322  		Request:                r,
   323  		Body:                   body,
   324  		Query:                  r.URL.Query(),
   325  		Time:                   signTime,
   326  		ExpireTime:             exp,
   327  		isPresign:              isPresign,
   328  		ServiceName:            service,
   329  		Region:                 region,
   330  		DisableURIPathEscaping: v4.DisableURIPathEscaping,
   331  		unsignedPayload:        v4.UnsignedPayload,
   332  	}
   333  
   334  	for key := range ctx.Query {
   335  		sort.Strings(ctx.Query[key])
   336  	}
   337  
   338  	if ctx.isRequestSigned() {
   339  		ctx.Time = currentTimeFn()
   340  		ctx.handlePresignRemoval()
   341  	}
   342  
   343  	var err error
   344  	ctx.credValues, err = v4.Credentials.GetWithContext(requestContext(r))
   345  	if err != nil {
   346  		return http.Header{}, err
   347  	}
   348  
   349  	ctx.sanitizeHostForHeader()
   350  	ctx.assignAmzQueryValues()
   351  	if err := ctx.build(v4.DisableHeaderHoisting); err != nil {
   352  		return nil, err
   353  	}
   354  
   355  	// If the request is not presigned the body should be attached to it. This
   356  	// prevents the confusion of wanting to send a signed request without
   357  	// the body the request was signed for attached.
   358  	if !(v4.DisableRequestBodyOverwrite || ctx.isPresign) {
   359  		var reader io.ReadCloser
   360  		if body != nil {
   361  			var ok bool
   362  			if reader, ok = body.(io.ReadCloser); !ok {
   363  				reader = ioutil.NopCloser(body)
   364  			}
   365  		}
   366  		r.Body = reader
   367  	}
   368  
   369  	if v4.Debug.Matches(aws.LogDebugWithSigning) {
   370  		v4.logSigningInfo(ctx)
   371  	}
   372  
   373  	return ctx.SignedHeaderVals, nil
   374  }
   375  
   376  func (ctx *signingCtx) sanitizeHostForHeader() {
   377  	request.SanitizeHostForHeader(ctx.Request)
   378  }
   379  
   380  func (ctx *signingCtx) handlePresignRemoval() {
   381  	if !ctx.isPresign {
   382  		return
   383  	}
   384  
   385  	// The credentials have expired for this request. The current signing
   386  	// is invalid, and needs to be request because the request will fail.
   387  	ctx.removePresign()
   388  
   389  	// Update the request's query string to ensure the values stays in
   390  	// sync in the case retrieving the new credentials fails.
   391  	ctx.Request.URL.RawQuery = ctx.Query.Encode()
   392  }
   393  
   394  func (ctx *signingCtx) assignAmzQueryValues() {
   395  	if ctx.isPresign {
   396  		ctx.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
   397  		if ctx.credValues.SessionToken != "" {
   398  			ctx.Query.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
   399  		} else {
   400  			ctx.Query.Del("X-Amz-Security-Token")
   401  		}
   402  
   403  		return
   404  	}
   405  
   406  	if ctx.credValues.SessionToken != "" {
   407  		ctx.Request.Header.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
   408  	}
   409  }
   410  
   411  // SignRequestHandler is a named request handler the SDK will use to sign
   412  // service client request with using the V4 signature.
   413  var SignRequestHandler = request.NamedHandler{
   414  	Name: "v4.SignRequestHandler", Fn: SignSDKRequest,
   415  }
   416  
   417  // SignSDKRequest signs an AWS request with the V4 signature. This
   418  // request handler should only be used with the SDK's built in service client's
   419  // API operation requests.
   420  //
   421  // This function should not be used on its own, but in conjunction with
   422  // an AWS service client's API operation call. To sign a standalone request
   423  // not created by a service client's API operation method use the "Sign" or
   424  // "Presign" functions of the "Signer" type.
   425  //
   426  // If the credentials of the request's config are set to
   427  // credentials.AnonymousCredentials the request will not be signed.
   428  func SignSDKRequest(req *request.Request) {
   429  	SignSDKRequestWithCurrentTime(req, time.Now)
   430  }
   431  
   432  // BuildNamedHandler will build a generic handler for signing.
   433  func BuildNamedHandler(name string, opts ...func(*Signer)) request.NamedHandler {
   434  	return request.NamedHandler{
   435  		Name: name,
   436  		Fn: func(req *request.Request) {
   437  			SignSDKRequestWithCurrentTime(req, time.Now, opts...)
   438  		},
   439  	}
   440  }
   441  
   442  // SignSDKRequestWithCurrentTime will sign the SDK's request using the time
   443  // function passed in. Behaves the same as SignSDKRequest with the exception
   444  // the request is signed with the value returned by the current time function.
   445  func SignSDKRequestWithCurrentTime(req *request.Request, curTimeFn func() time.Time, opts ...func(*Signer)) {
   446  	// If the request does not need to be signed ignore the signing of the
   447  	// request if the AnonymousCredentials object is used.
   448  	if req.Config.Credentials == credentials.AnonymousCredentials {
   449  		return
   450  	}
   451  
   452  	region := req.ClientInfo.SigningRegion
   453  	if region == "" {
   454  		region = aws.StringValue(req.Config.Region)
   455  	}
   456  
   457  	name := req.ClientInfo.SigningName
   458  	if name == "" {
   459  		name = req.ClientInfo.ServiceName
   460  	}
   461  
   462  	v4 := NewSigner(req.Config.Credentials, func(v4 *Signer) {
   463  		v4.Debug = req.Config.LogLevel.Value()
   464  		v4.Logger = req.Config.Logger
   465  		v4.DisableHeaderHoisting = req.NotHoist
   466  		v4.currentTimeFn = curTimeFn
   467  		if name == "s3" {
   468  			// S3 service should not have any escaping applied
   469  			v4.DisableURIPathEscaping = true
   470  		}
   471  		// Prevents setting the HTTPRequest's Body. Since the Body could be
   472  		// wrapped in a custom io.Closer that we do not want to be stompped
   473  		// on top of by the signer.
   474  		v4.DisableRequestBodyOverwrite = true
   475  	})
   476  
   477  	for _, opt := range opts {
   478  		opt(v4)
   479  	}
   480  
   481  	curTime := curTimeFn()
   482  	signedHeaders, err := v4.signWithBody(req.HTTPRequest, req.GetBody(),
   483  		name, region, req.ExpireTime, req.ExpireTime > 0, curTime,
   484  	)
   485  	if err != nil {
   486  		req.Error = err
   487  		req.SignedHeaderVals = nil
   488  		return
   489  	}
   490  
   491  	req.SignedHeaderVals = signedHeaders
   492  	req.LastSignedAt = curTime
   493  }
   494  
   495  const logSignInfoMsg = `DEBUG: Request Signature:
   496  ---[ CANONICAL STRING  ]-----------------------------
   497  %s
   498  ---[ STRING TO SIGN ]--------------------------------
   499  %s%s
   500  -----------------------------------------------------`
   501  const logSignedURLMsg = `
   502  ---[ SIGNED URL ]------------------------------------
   503  %s`
   504  
   505  func (v4 *Signer) logSigningInfo(ctx *signingCtx) {
   506  	signedURLMsg := ""
   507  	if ctx.isPresign {
   508  		signedURLMsg = fmt.Sprintf(logSignedURLMsg, ctx.Request.URL.String())
   509  	}
   510  	msg := fmt.Sprintf(logSignInfoMsg, ctx.canonicalString, ctx.stringToSign, signedURLMsg)
   511  	v4.Logger.Log(msg)
   512  }
   513  
   514  func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
   515  	ctx.buildTime()             // no depends
   516  	ctx.buildCredentialString() // no depends
   517  
   518  	if err := ctx.buildBodyDigest(); err != nil {
   519  		return err
   520  	}
   521  
   522  	unsignedHeaders := ctx.Request.Header
   523  	if ctx.isPresign {
   524  		if !disableHeaderHoisting {
   525  			urlValues := url.Values{}
   526  			urlValues, unsignedHeaders = buildQuery(allowedQueryHoisting, unsignedHeaders) // no depends
   527  			for k := range urlValues {
   528  				ctx.Query[k] = urlValues[k]
   529  			}
   530  		}
   531  	}
   532  
   533  	ctx.buildCanonicalHeaders(ignoredHeaders, unsignedHeaders)
   534  	ctx.buildCanonicalString() // depends on canon headers / signed headers
   535  	ctx.buildStringToSign()    // depends on canon string
   536  	ctx.buildSignature()       // depends on string to sign
   537  
   538  	if ctx.isPresign {
   539  		ctx.Request.URL.RawQuery += "&" + signatureQueryKey + "=" + ctx.signature
   540  	} else {
   541  		parts := []string{
   542  			authHeaderPrefix + " Credential=" + ctx.credValues.AccessKeyID + "/" + ctx.credentialString,
   543  			"SignedHeaders=" + ctx.signedHeaders,
   544  			authHeaderSignatureElem + ctx.signature,
   545  		}
   546  		ctx.Request.Header.Set(authorizationHeader, strings.Join(parts, ", "))
   547  	}
   548  
   549  	return nil
   550  }
   551  
   552  // GetSignedRequestSignature attempts to extract the signature of the request.
   553  // Returning an error if the request is unsigned, or unable to extract the
   554  // signature.
   555  func GetSignedRequestSignature(r *http.Request) ([]byte, error) {
   556  
   557  	if auth := r.Header.Get(authorizationHeader); len(auth) != 0 {
   558  		ps := strings.Split(auth, ", ")
   559  		for _, p := range ps {
   560  			if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 {
   561  				sig := p[len(authHeaderSignatureElem):]
   562  				if len(sig) == 0 {
   563  					return nil, fmt.Errorf("invalid request signature authorization header")
   564  				}
   565  				return hex.DecodeString(sig)
   566  			}
   567  		}
   568  	}
   569  
   570  	if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 {
   571  		return hex.DecodeString(sig)
   572  	}
   573  
   574  	return nil, fmt.Errorf("request not signed")
   575  }
   576  
   577  func (ctx *signingCtx) buildTime() {
   578  	if ctx.isPresign {
   579  		duration := int64(ctx.ExpireTime / time.Second)
   580  		ctx.Query.Set("X-Amz-Date", formatTime(ctx.Time))
   581  		ctx.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
   582  	} else {
   583  		ctx.Request.Header.Set("X-Amz-Date", formatTime(ctx.Time))
   584  	}
   585  }
   586  
   587  func (ctx *signingCtx) buildCredentialString() {
   588  	ctx.credentialString = buildSigningScope(ctx.Region, ctx.ServiceName, ctx.Time)
   589  
   590  	if ctx.isPresign {
   591  		ctx.Query.Set("X-Amz-Credential", ctx.credValues.AccessKeyID+"/"+ctx.credentialString)
   592  	}
   593  }
   594  
   595  func buildQuery(r rule, header http.Header) (url.Values, http.Header) {
   596  	query := url.Values{}
   597  	unsignedHeaders := http.Header{}
   598  	for k, h := range header {
   599  		if r.IsValid(k) {
   600  			query[k] = h
   601  		} else {
   602  			unsignedHeaders[k] = h
   603  		}
   604  	}
   605  
   606  	return query, unsignedHeaders
   607  }
   608  func (ctx *signingCtx) buildCanonicalHeaders(r rule, header http.Header) {
   609  	var headers []string
   610  	headers = append(headers, "host")
   611  	for k, v := range header {
   612  		if !r.IsValid(k) {
   613  			continue // ignored header
   614  		}
   615  		if ctx.SignedHeaderVals == nil {
   616  			ctx.SignedHeaderVals = make(http.Header)
   617  		}
   618  
   619  		lowerCaseKey := strings.ToLower(k)
   620  		if _, ok := ctx.SignedHeaderVals[lowerCaseKey]; ok {
   621  			// include additional values
   622  			ctx.SignedHeaderVals[lowerCaseKey] = append(ctx.SignedHeaderVals[lowerCaseKey], v...)
   623  			continue
   624  		}
   625  
   626  		headers = append(headers, lowerCaseKey)
   627  		ctx.SignedHeaderVals[lowerCaseKey] = v
   628  	}
   629  	sort.Strings(headers)
   630  
   631  	ctx.signedHeaders = strings.Join(headers, ";")
   632  
   633  	if ctx.isPresign {
   634  		ctx.Query.Set("X-Amz-SignedHeaders", ctx.signedHeaders)
   635  	}
   636  
   637  	headerValues := make([]string, len(headers))
   638  	for i, k := range headers {
   639  		if k == "host" {
   640  			if ctx.Request.Host != "" {
   641  				headerValues[i] = "host:" + ctx.Request.Host
   642  			} else {
   643  				headerValues[i] = "host:" + ctx.Request.URL.Host
   644  			}
   645  		} else {
   646  			headerValues[i] = k + ":" +
   647  				strings.Join(ctx.SignedHeaderVals[k], ",")
   648  		}
   649  	}
   650  	stripExcessSpaces(headerValues)
   651  	ctx.canonicalHeaders = strings.Join(headerValues, "\n")
   652  }
   653  
   654  func (ctx *signingCtx) buildCanonicalString() {
   655  	ctx.Request.URL.RawQuery = strings.Replace(ctx.Query.Encode(), "+", "%20", -1)
   656  
   657  	uri := getURIPath(ctx.Request.URL)
   658  
   659  	if !ctx.DisableURIPathEscaping {
   660  		uri = rest.EscapePath(uri, false)
   661  	}
   662  
   663  	ctx.canonicalString = strings.Join([]string{
   664  		ctx.Request.Method,
   665  		uri,
   666  		ctx.Request.URL.RawQuery,
   667  		ctx.canonicalHeaders + "\n",
   668  		ctx.signedHeaders,
   669  		ctx.bodyDigest,
   670  	}, "\n")
   671  }
   672  
   673  func (ctx *signingCtx) buildStringToSign() {
   674  	ctx.stringToSign = strings.Join([]string{
   675  		authHeaderPrefix,
   676  		formatTime(ctx.Time),
   677  		ctx.credentialString,
   678  		hex.EncodeToString(hashSHA256([]byte(ctx.canonicalString))),
   679  	}, "\n")
   680  }
   681  
   682  func (ctx *signingCtx) buildSignature() {
   683  	creds := deriveSigningKey(ctx.Region, ctx.ServiceName, ctx.credValues.SecretAccessKey, ctx.Time)
   684  	signature := hmacSHA256(creds, []byte(ctx.stringToSign))
   685  	ctx.signature = hex.EncodeToString(signature)
   686  }
   687  
   688  func (ctx *signingCtx) buildBodyDigest() error {
   689  	hash := ctx.Request.Header.Get("X-Amz-Content-Sha256")
   690  	if hash == "" {
   691  		includeSHA256Header := ctx.unsignedPayload ||
   692  			ctx.ServiceName == "s3" ||
   693  			ctx.ServiceName == "s3-object-lambda" ||
   694  			ctx.ServiceName == "glacier"
   695  
   696  		s3Presign := ctx.isPresign &&
   697  			(ctx.ServiceName == "s3" ||
   698  				ctx.ServiceName == "s3-object-lambda")
   699  
   700  		if ctx.unsignedPayload || s3Presign {
   701  			hash = "UNSIGNED-PAYLOAD"
   702  			includeSHA256Header = !s3Presign
   703  		} else if ctx.Body == nil {
   704  			hash = emptyStringSHA256
   705  		} else {
   706  			if !aws.IsReaderSeekable(ctx.Body) {
   707  				return fmt.Errorf("cannot use unseekable request body %T, for signed request with body", ctx.Body)
   708  			}
   709  			hashBytes, err := makeSha256Reader(ctx.Body)
   710  			if err != nil {
   711  				return err
   712  			}
   713  			hash = hex.EncodeToString(hashBytes)
   714  		}
   715  
   716  		if includeSHA256Header {
   717  			ctx.Request.Header.Set("X-Amz-Content-Sha256", hash)
   718  		}
   719  	}
   720  	ctx.bodyDigest = hash
   721  
   722  	return nil
   723  }
   724  
   725  // isRequestSigned returns if the request is currently signed or presigned
   726  func (ctx *signingCtx) isRequestSigned() bool {
   727  	if ctx.isPresign && ctx.Query.Get("X-Amz-Signature") != "" {
   728  		return true
   729  	}
   730  	if ctx.Request.Header.Get("Authorization") != "" {
   731  		return true
   732  	}
   733  
   734  	return false
   735  }
   736  
   737  // unsign removes signing flags for both signed and presigned requests.
   738  func (ctx *signingCtx) removePresign() {
   739  	ctx.Query.Del("X-Amz-Algorithm")
   740  	ctx.Query.Del("X-Amz-Signature")
   741  	ctx.Query.Del("X-Amz-Security-Token")
   742  	ctx.Query.Del("X-Amz-Date")
   743  	ctx.Query.Del("X-Amz-Expires")
   744  	ctx.Query.Del("X-Amz-Credential")
   745  	ctx.Query.Del("X-Amz-SignedHeaders")
   746  }
   747  
   748  func hmacSHA256(key []byte, data []byte) []byte {
   749  	hash := hmac.New(sha256.New, key)
   750  	hash.Write(data)
   751  	return hash.Sum(nil)
   752  }
   753  
   754  func hashSHA256(data []byte) []byte {
   755  	hash := sha256.New()
   756  	hash.Write(data)
   757  	return hash.Sum(nil)
   758  }
   759  
   760  func makeSha256Reader(reader io.ReadSeeker) (hashBytes []byte, err error) {
   761  	hash := sha256.New()
   762  	start, err := reader.Seek(0, sdkio.SeekCurrent)
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  	defer func() {
   767  		// ensure error is return if unable to seek back to start of payload.
   768  		_, err = reader.Seek(start, sdkio.SeekStart)
   769  	}()
   770  
   771  	// Use CopyN to avoid allocating the 32KB buffer in io.Copy for bodies
   772  	// smaller than 32KB. Fall back to io.Copy if we fail to determine the size.
   773  	size, err := aws.SeekerLen(reader)
   774  	if err != nil {
   775  		io.Copy(hash, reader)
   776  	} else {
   777  		io.CopyN(hash, reader, size)
   778  	}
   779  
   780  	return hash.Sum(nil), nil
   781  }
   782  
   783  const doubleSpace = "  "
   784  
   785  // stripExcessSpaces will rewrite the passed in slice's string values to not
   786  // contain multiple side-by-side spaces.
   787  func stripExcessSpaces(vals []string) {
   788  	var j, k, l, m, spaces int
   789  	for i, str := range vals {
   790  		// Trim trailing spaces
   791  		for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
   792  		}
   793  
   794  		// Trim leading spaces
   795  		for k = 0; k < j && str[k] == ' '; k++ {
   796  		}
   797  		str = str[k : j+1]
   798  
   799  		// Strip multiple spaces.
   800  		j = strings.Index(str, doubleSpace)
   801  		if j < 0 {
   802  			vals[i] = str
   803  			continue
   804  		}
   805  
   806  		buf := []byte(str)
   807  		for k, m, l = j, j, len(buf); k < l; k++ {
   808  			if buf[k] == ' ' {
   809  				if spaces == 0 {
   810  					// First space.
   811  					buf[m] = buf[k]
   812  					m++
   813  				}
   814  				spaces++
   815  			} else {
   816  				// End of multiple spaces.
   817  				spaces = 0
   818  				buf[m] = buf[k]
   819  				m++
   820  			}
   821  		}
   822  
   823  		vals[i] = string(buf[:m])
   824  	}
   825  }
   826  
   827  func buildSigningScope(region, service string, dt time.Time) string {
   828  	return strings.Join([]string{
   829  		formatShortTime(dt),
   830  		region,
   831  		service,
   832  		awsV4Request,
   833  	}, "/")
   834  }
   835  
   836  func deriveSigningKey(region, service, secretKey string, dt time.Time) []byte {
   837  	kDate := hmacSHA256([]byte("AWS4"+secretKey), []byte(formatShortTime(dt)))
   838  	kRegion := hmacSHA256(kDate, []byte(region))
   839  	kService := hmacSHA256(kRegion, []byte(service))
   840  	signingKey := hmacSHA256(kService, []byte(awsV4Request))
   841  	return signingKey
   842  }
   843  
   844  func formatShortTime(dt time.Time) string {
   845  	return dt.UTC().Format(shortTimeFormat)
   846  }
   847  
   848  func formatTime(dt time.Time) string {
   849  	return dt.UTC().Format(timeFormat)
   850  }