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