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