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