]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/aws/aws-sdk-go/aws/signer/v4/v4.go
8aa0681d3405376364cc999931981340e453ab93
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / aws / aws-sdk-go / aws / signer / v4 / v4.go
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/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
78 const (
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
87 var 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.
98 var requiredSignedHeaders = rules{
99 whitelist{
100 mapRule{
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{}{},
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{}{},
137 "X-Amz-Website-Redirect-Location": struct{}{},
138 "X-Amz-Content-Sha256": struct{}{},
139 },
140 },
141 patterns{"X-Amz-Meta-"},
142 }
143
144 // allowedHoisting is a whitelist for build query headers. The boolean value
145 // represents whether or not it is a pattern.
146 var allowedQueryHoisting = inclusiveRules{
147 blacklist{requiredSignedHeaders},
148 patterns{"X-Amz-"},
149 }
150
151 // Signer applies AWS v4 signing to given request. Use this to sign requests
152 // that need to be signed with AWS V4 Signatures.
153 type Signer struct {
154 // The authentication credentials the request will be signed against.
155 // This value must be set to sign requests.
156 Credentials *credentials.Credentials
157
158 // Sets the log level the signer should use when reporting information to
159 // the logger. If the logger is nil nothing will be logged. See
160 // aws.LogLevelType for more information on available logging levels
161 //
162 // By default nothing will be logged.
163 Debug aws.LogLevelType
164
165 // The logger loging information will be written to. If there the logger
166 // is nil, nothing will be logged.
167 Logger aws.Logger
168
169 // Disables the Signer's moving HTTP header key/value pairs from the HTTP
170 // request header to the request's query string. This is most commonly used
171 // with pre-signed requests preventing headers from being added to the
172 // request's query string.
173 DisableHeaderHoisting bool
174
175 // Disables the automatic escaping of the URI path of the request for the
176 // siganture's canonical string's path. For services that do not need additional
177 // escaping then use this to disable the signer escaping the path.
178 //
179 // S3 is an example of a service that does not need additional escaping.
180 //
181 // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
182 DisableURIPathEscaping bool
183
184 // Disales the automatical setting of the HTTP request's Body field with the
185 // io.ReadSeeker passed in to the signer. This is useful if you're using a
186 // custom wrapper around the body for the io.ReadSeeker and want to preserve
187 // the Body value on the Request.Body.
188 //
189 // This does run the risk of signing a request with a body that will not be
190 // sent in the request. Need to ensure that the underlying data of the Body
191 // values are the same.
192 DisableRequestBodyOverwrite bool
193
194 // currentTimeFn returns the time value which represents the current time.
195 // This value should only be used for testing. If it is nil the default
196 // time.Now will be used.
197 currentTimeFn func() time.Time
198
199 // UnsignedPayload will prevent signing of the payload. This will only
200 // work for services that have support for this.
201 UnsignedPayload bool
202 }
203
204 // NewSigner returns a Signer pointer configured with the credentials and optional
205 // option values provided. If not options are provided the Signer will use its
206 // default configuration.
207 func NewSigner(credentials *credentials.Credentials, options ...func(*Signer)) *Signer {
208 v4 := &Signer{
209 Credentials: credentials,
210 }
211
212 for _, option := range options {
213 option(v4)
214 }
215
216 return v4
217 }
218
219 type signingCtx struct {
220 ServiceName string
221 Region string
222 Request *http.Request
223 Body io.ReadSeeker
224 Query url.Values
225 Time time.Time
226 ExpireTime time.Duration
227 SignedHeaderVals http.Header
228
229 DisableURIPathEscaping bool
230
231 credValues credentials.Value
232 isPresign bool
233 formattedTime string
234 formattedShortTime string
235 unsignedPayload bool
236
237 bodyDigest string
238 signedHeaders string
239 canonicalHeaders string
240 canonicalString string
241 credentialString string
242 stringToSign string
243 signature string
244 authorization string
245 }
246
247 // Sign signs AWS v4 requests with the provided body, service name, region the
248 // request is made to, and time the request is signed at. The signTime allows
249 // you to specify that a request is signed for the future, and cannot be
250 // used until then.
251 //
252 // Returns a list of HTTP headers that were included in the signature or an
253 // error if signing the request failed. Generally for signed requests this value
254 // is not needed as the full request context will be captured by the http.Request
255 // value. It is included for reference though.
256 //
257 // Sign will set the request's Body to be the `body` parameter passed in. If
258 // the body is not already an io.ReadCloser, it will be wrapped within one. If
259 // a `nil` body parameter passed to Sign, the request's Body field will be
260 // also set to nil. Its important to note that this functionality will not
261 // change the request's ContentLength of the request.
262 //
263 // Sign differs from Presign in that it will sign the request using HTTP
264 // header values. This type of signing is intended for http.Request values that
265 // will not be shared, or are shared in a way the header values on the request
266 // will not be lost.
267 //
268 // The requests body is an io.ReadSeeker so the SHA256 of the body can be
269 // generated. To bypass the signer computing the hash you can set the
270 // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
271 // only compute the hash if the request header value is empty.
272 func (v4 Signer) Sign(r *http.Request, body io.ReadSeeker, service, region string, signTime time.Time) (http.Header, error) {
273 return v4.signWithBody(r, body, service, region, 0, false, signTime)
274 }
275
276 // Presign signs AWS v4 requests with the provided body, service name, region
277 // the request is made to, and time the request is signed at. The signTime
278 // allows you to specify that a request is signed for the future, and cannot
279 // be used until then.
280 //
281 // Returns a list of HTTP headers that were included in the signature or an
282 // error if signing the request failed. For presigned requests these headers
283 // and their values must be included on the HTTP request when it is made. This
284 // is helpful to know what header values need to be shared with the party the
285 // presigned request will be distributed to.
286 //
287 // Presign differs from Sign in that it will sign the request using query string
288 // instead of header values. This allows you to share the Presigned Request's
289 // URL with third parties, or distribute it throughout your system with minimal
290 // dependencies.
291 //
292 // Presign also takes an exp value which is the duration the
293 // signed request will be valid after the signing time. This is allows you to
294 // set when the request will expire.
295 //
296 // The requests body is an io.ReadSeeker so the SHA256 of the body can be
297 // generated. To bypass the signer computing the hash you can set the
298 // "X-Amz-Content-Sha256" header with a precomputed value. The signer will
299 // only compute the hash if the request header value is empty.
300 //
301 // Presigning a S3 request will not compute the body's SHA256 hash by default.
302 // This is done due to the general use case for S3 presigned URLs is to share
303 // PUT/GET capabilities. If you would like to include the body's SHA256 in the
304 // presigned request's signature you can set the "X-Amz-Content-Sha256"
305 // HTTP header and that will be included in the request's signature.
306 func (v4 Signer) Presign(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, signTime time.Time) (http.Header, error) {
307 return v4.signWithBody(r, body, service, region, exp, true, signTime)
308 }
309
310 func (v4 Signer) signWithBody(r *http.Request, body io.ReadSeeker, service, region string, exp time.Duration, isPresign bool, signTime time.Time) (http.Header, error) {
311 currentTimeFn := v4.currentTimeFn
312 if currentTimeFn == nil {
313 currentTimeFn = time.Now
314 }
315
316 ctx := &signingCtx{
317 Request: r,
318 Body: body,
319 Query: r.URL.Query(),
320 Time: signTime,
321 ExpireTime: exp,
322 isPresign: isPresign,
323 ServiceName: service,
324 Region: region,
325 DisableURIPathEscaping: v4.DisableURIPathEscaping,
326 unsignedPayload: v4.UnsignedPayload,
327 }
328
329 for key := range ctx.Query {
330 sort.Strings(ctx.Query[key])
331 }
332
333 if ctx.isRequestSigned() {
334 ctx.Time = currentTimeFn()
335 ctx.handlePresignRemoval()
336 }
337
338 var err error
339 ctx.credValues, err = v4.Credentials.Get()
340 if err != nil {
341 return http.Header{}, err
342 }
343
344 ctx.sanitizeHostForHeader()
345 ctx.assignAmzQueryValues()
346 if err := ctx.build(v4.DisableHeaderHoisting); err != nil {
347 return nil, err
348 }
349
350 // If the request is not presigned the body should be attached to it. This
351 // prevents the confusion of wanting to send a signed request without
352 // the body the request was signed for attached.
353 if !(v4.DisableRequestBodyOverwrite || ctx.isPresign) {
354 var reader io.ReadCloser
355 if body != nil {
356 var ok bool
357 if reader, ok = body.(io.ReadCloser); !ok {
358 reader = ioutil.NopCloser(body)
359 }
360 }
361 r.Body = reader
362 }
363
364 if v4.Debug.Matches(aws.LogDebugWithSigning) {
365 v4.logSigningInfo(ctx)
366 }
367
368 return ctx.SignedHeaderVals, nil
369 }
370
371 func (ctx *signingCtx) sanitizeHostForHeader() {
372 request.SanitizeHostForHeader(ctx.Request)
373 }
374
375 func (ctx *signingCtx) handlePresignRemoval() {
376 if !ctx.isPresign {
377 return
378 }
379
380 // The credentials have expired for this request. The current signing
381 // is invalid, and needs to be request because the request will fail.
382 ctx.removePresign()
383
384 // Update the request's query string to ensure the values stays in
385 // sync in the case retrieving the new credentials fails.
386 ctx.Request.URL.RawQuery = ctx.Query.Encode()
387 }
388
389 func (ctx *signingCtx) assignAmzQueryValues() {
390 if ctx.isPresign {
391 ctx.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
392 if ctx.credValues.SessionToken != "" {
393 ctx.Query.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
394 } else {
395 ctx.Query.Del("X-Amz-Security-Token")
396 }
397
398 return
399 }
400
401 if ctx.credValues.SessionToken != "" {
402 ctx.Request.Header.Set("X-Amz-Security-Token", ctx.credValues.SessionToken)
403 }
404 }
405
406 // SignRequestHandler is a named request handler the SDK will use to sign
407 // service client request with using the V4 signature.
408 var SignRequestHandler = request.NamedHandler{
409 Name: "v4.SignRequestHandler", Fn: SignSDKRequest,
410 }
411
412 // SignSDKRequest signs an AWS request with the V4 signature. This
413 // request handler should only be used with the SDK's built in service client's
414 // API operation requests.
415 //
416 // This function should not be used on its on its own, but in conjunction with
417 // an AWS service client's API operation call. To sign a standalone request
418 // not created by a service client's API operation method use the "Sign" or
419 // "Presign" functions of the "Signer" type.
420 //
421 // If the credentials of the request's config are set to
422 // credentials.AnonymousCredentials the request will not be signed.
423 func SignSDKRequest(req *request.Request) {
424 signSDKRequestWithCurrTime(req, time.Now)
425 }
426
427 // BuildNamedHandler will build a generic handler for signing.
428 func BuildNamedHandler(name string, opts ...func(*Signer)) request.NamedHandler {
429 return request.NamedHandler{
430 Name: name,
431 Fn: func(req *request.Request) {
432 signSDKRequestWithCurrTime(req, time.Now, opts...)
433 },
434 }
435 }
436
437 func signSDKRequestWithCurrTime(req *request.Request, curTimeFn func() time.Time, opts ...func(*Signer)) {
438 // If the request does not need to be signed ignore the signing of the
439 // request if the AnonymousCredentials object is used.
440 if req.Config.Credentials == credentials.AnonymousCredentials {
441 return
442 }
443
444 region := req.ClientInfo.SigningRegion
445 if region == "" {
446 region = aws.StringValue(req.Config.Region)
447 }
448
449 name := req.ClientInfo.SigningName
450 if name == "" {
451 name = req.ClientInfo.ServiceName
452 }
453
454 v4 := NewSigner(req.Config.Credentials, func(v4 *Signer) {
455 v4.Debug = req.Config.LogLevel.Value()
456 v4.Logger = req.Config.Logger
457 v4.DisableHeaderHoisting = req.NotHoist
458 v4.currentTimeFn = curTimeFn
459 if name == "s3" {
460 // S3 service should not have any escaping applied
461 v4.DisableURIPathEscaping = true
462 }
463 // Prevents setting the HTTPRequest's Body. Since the Body could be
464 // wrapped in a custom io.Closer that we do not want to be stompped
465 // on top of by the signer.
466 v4.DisableRequestBodyOverwrite = true
467 })
468
469 for _, opt := range opts {
470 opt(v4)
471 }
472
473 signingTime := req.Time
474 if !req.LastSignedAt.IsZero() {
475 signingTime = req.LastSignedAt
476 }
477
478 signedHeaders, err := v4.signWithBody(req.HTTPRequest, req.GetBody(),
479 name, region, req.ExpireTime, req.ExpireTime > 0, signingTime,
480 )
481 if err != nil {
482 req.Error = err
483 req.SignedHeaderVals = nil
484 return
485 }
486
487 req.SignedHeaderVals = signedHeaders
488 req.LastSignedAt = curTimeFn()
489 }
490
491 const logSignInfoMsg = `DEBUG: Request Signature:
492 ---[ CANONICAL STRING ]-----------------------------
493 %s
494 ---[ STRING TO SIGN ]--------------------------------
495 %s%s
496 -----------------------------------------------------`
497 const logSignedURLMsg = `
498 ---[ SIGNED URL ]------------------------------------
499 %s`
500
501 func (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
510 func (ctx *signingCtx) build(disableHeaderHoisting bool) error {
511 ctx.buildTime() // no depends
512 ctx.buildCredentialString() // no depends
513
514 if err := ctx.buildBodyDigest(); err != nil {
515 return err
516 }
517
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
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 }
544
545 return nil
546 }
547
548 func (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
561 func (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
574 func 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 }
587 func (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" {
620 if ctx.Request.Host != "" {
621 headerValues[i] = "host:" + ctx.Request.Host
622 } else {
623 headerValues[i] = "host:" + ctx.Request.URL.Host
624 }
625 } else {
626 headerValues[i] = k + ":" +
627 strings.Join(ctx.SignedHeaderVals[k], ",")
628 }
629 }
630 stripExcessSpaces(headerValues)
631 ctx.canonicalHeaders = strings.Join(headerValues, "\n")
632 }
633
634 func (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
653 func (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
662 func (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
672 func (ctx *signingCtx) buildBodyDigest() error {
673 hash := ctx.Request.Header.Get("X-Amz-Content-Sha256")
674 if hash == "" {
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 {
682 hash = "UNSIGNED-PAYLOAD"
683 includeSHA256Header = !s3Presign
684 } else if ctx.Body == nil {
685 hash = emptyStringSHA256
686 } else {
687 if !aws.IsReaderSeekable(ctx.Body) {
688 return fmt.Errorf("cannot use unseekable request body %T, for signed request with body", ctx.Body)
689 }
690 hash = hex.EncodeToString(makeSha256Reader(ctx.Body))
691 }
692
693 if includeSHA256Header {
694 ctx.Request.Header.Set("X-Amz-Content-Sha256", hash)
695 }
696 }
697 ctx.bodyDigest = hash
698
699 return nil
700 }
701
702 // isRequestSigned returns if the request is currently signed or presigned
703 func (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.
715 func (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
725 func makeHmac(key []byte, data []byte) []byte {
726 hash := hmac.New(sha256.New, key)
727 hash.Write(data)
728 return hash.Sum(nil)
729 }
730
731 func makeSha256(data []byte) []byte {
732 hash := sha256.New()
733 hash.Write(data)
734 return hash.Sum(nil)
735 }
736
737 func makeSha256Reader(reader io.ReadSeeker) []byte {
738 hash := sha256.New()
739 start, _ := reader.Seek(0, sdkio.SeekCurrent)
740 defer reader.Seek(start, sdkio.SeekStart)
741
742 io.Copy(hash, reader)
743 return hash.Sum(nil)
744 }
745
746 const doubleSpace = " "
747
748 // stripExcessSpaces will rewrite the passed in slice's string values to not
749 // contain muliple side-by-side spaces.
750 func stripExcessSpaces(vals []string) {
751 var j, k, l, m, spaces int
752 for i, str := range vals {
753 // Trim trailing spaces
754 for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
755 }
756
757 // Trim leading spaces
758 for k = 0; k < j && str[k] == ' '; k++ {
759 }
760 str = str[k : j+1]
761
762 // Strip multiple spaces.
763 j = strings.Index(str, doubleSpace)
764 if j < 0 {
765 vals[i] = str
766 continue
767 }
768
769 buf := []byte(str)
770 for k, m, l = j, j, len(buf); k < l; k++ {
771 if buf[k] == ' ' {
772 if spaces == 0 {
773 // First space.
774 buf[m] = buf[k]
775 m++
776 }
777 spaces++
778 } else {
779 // End of multiple spaces.
780 spaces = 0
781 buf[m] = buf[k]
782 m++
783 }
784 }
785
786 vals[i] = string(buf[:m])
787 }
788 }