v2.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package v2
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha256"
  5. "encoding/base64"
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "sort"
  11. "strings"
  12. "time"
  13. "github.com/aws/aws-sdk-go/aws"
  14. "github.com/aws/aws-sdk-go/aws/credentials"
  15. "github.com/aws/aws-sdk-go/aws/request"
  16. )
  17. var (
  18. errInvalidMethod = errors.New("v2 signer only handles HTTP POST")
  19. )
  20. const (
  21. signatureVersion = "2"
  22. signatureMethod = "HmacSHA256"
  23. timeFormat = "2006-01-02T15:04:05Z"
  24. )
  25. type signer struct {
  26. // Values that must be populated from the request
  27. Request *http.Request
  28. Time time.Time
  29. Credentials *credentials.Credentials
  30. Debug aws.LogLevelType
  31. Logger aws.Logger
  32. Query url.Values
  33. stringToSign string
  34. signature string
  35. }
  36. // SignRequestHandler is a named request handler the SDK will use to sign
  37. // service client request with using the V4 signature.
  38. var SignRequestHandler = request.NamedHandler{
  39. Name: "v2.SignRequestHandler", Fn: SignSDKRequest,
  40. }
  41. // SignSDKRequest requests with signature version 2.
  42. //
  43. // Will sign the requests with the service config's Credentials object
  44. // Signing is skipped if the credentials is the credentials.AnonymousCredentials
  45. // object.
  46. func SignSDKRequest(req *request.Request) {
  47. // If the request does not need to be signed ignore the signing of the
  48. // request if the AnonymousCredentials object is used.
  49. if req.Config.Credentials == credentials.AnonymousCredentials {
  50. return
  51. }
  52. if req.HTTPRequest.Method != "POST" && req.HTTPRequest.Method != "GET" {
  53. // The V2 signer only supports GET and POST
  54. req.Error = errInvalidMethod
  55. return
  56. }
  57. v2 := signer{
  58. Request: req.HTTPRequest,
  59. Time: req.Time,
  60. Credentials: req.Config.Credentials,
  61. Debug: req.Config.LogLevel.Value(),
  62. Logger: req.Config.Logger,
  63. }
  64. req.Error = v2.Sign()
  65. if req.Error != nil {
  66. return
  67. }
  68. if req.HTTPRequest.Method == "POST" {
  69. // Set the body of the request based on the modified query parameters
  70. req.SetStringBody(v2.Query.Encode())
  71. // Now that the body has changed, remove any Content-Length header,
  72. // because it will be incorrect
  73. req.HTTPRequest.ContentLength = 0
  74. req.HTTPRequest.Header.Del("Content-Length")
  75. } else {
  76. req.HTTPRequest.URL.RawQuery = v2.Query.Encode()
  77. }
  78. }
  79. func (v2 *signer) Sign() error {
  80. credValue, err := v2.Credentials.Get()
  81. if err != nil {
  82. return err
  83. }
  84. if v2.Request.Method == "POST" {
  85. // Parse the HTTP request to obtain the query parameters that will
  86. // be used to build the string to sign. Note that because the HTTP
  87. // request will need to be modified, the PostForm and Form properties
  88. // are reset to nil after parsing.
  89. v2.Request.ParseForm()
  90. v2.Query = v2.Request.PostForm
  91. v2.Request.PostForm = nil
  92. v2.Request.Form = nil
  93. } else {
  94. v2.Query = v2.Request.URL.Query()
  95. }
  96. // Set new query parameters
  97. v2.Query.Set("AWSAccessKeyId", credValue.AccessKeyID)
  98. v2.Query.Set("SignatureVersion", signatureVersion)
  99. v2.Query.Set("SignatureMethod", signatureMethod)
  100. v2.Query.Set("Timestamp", v2.Time.UTC().Format(timeFormat))
  101. if credValue.SessionToken != "" {
  102. v2.Query.Set("SecurityToken", credValue.SessionToken)
  103. }
  104. // in case this is a retry, ensure no signature present
  105. v2.Query.Del("Signature")
  106. method := v2.Request.Method
  107. host := v2.Request.URL.Host
  108. path := v2.Request.URL.Path
  109. if path == "" {
  110. path = "/"
  111. }
  112. // obtain all of the query keys and sort them
  113. queryKeys := make([]string, 0, len(v2.Query))
  114. for key := range v2.Query {
  115. queryKeys = append(queryKeys, key)
  116. }
  117. sort.Strings(queryKeys)
  118. // build URL-encoded query keys and values
  119. queryKeysAndValues := make([]string, len(queryKeys))
  120. for i, key := range queryKeys {
  121. k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
  122. v := strings.Replace(url.QueryEscape(v2.Query.Get(key)), "+", "%20", -1)
  123. queryKeysAndValues[i] = k + "=" + v
  124. }
  125. // join into one query string
  126. query := strings.Join(queryKeysAndValues, "&")
  127. // build the canonical string for the V2 signature
  128. v2.stringToSign = strings.Join([]string{
  129. method,
  130. host,
  131. path,
  132. query,
  133. }, "\n")
  134. hash := hmac.New(sha256.New, []byte(credValue.SecretAccessKey))
  135. hash.Write([]byte(v2.stringToSign))
  136. v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
  137. v2.Query.Set("Signature", v2.signature)
  138. if v2.Debug.Matches(aws.LogDebugWithSigning) {
  139. v2.logSigningInfo()
  140. }
  141. return nil
  142. }
  143. const logSignInfoMsg = `DEBUG: Request Signature:
  144. ---[ STRING TO SIGN ]--------------------------------
  145. %s
  146. ---[ SIGNATURE ]-------------------------------------
  147. %s
  148. -----------------------------------------------------`
  149. func (v2 *signer) logSigningInfo() {
  150. msg := fmt.Sprintf(logSignInfoMsg, v2.stringToSign, v2.Query.Get("Signature"))
  151. v2.Logger.Log(msg)
  152. }