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