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 }