1package s3 2 3// Source: https://github.com/pivotal-golang/s3cli 4 5// Copyright (c) 2013 Damien Le Berrigaud and Nick Wade 6 7// Permission is hereby granted, free of charge, to any person obtaining a copy 8// of this software and associated documentation files (the "Software"), to deal 9// in the Software without restriction, including without limitation the rights 10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11// copies of the Software, and to permit persons to whom the Software is 12// furnished to do so, subject to the following conditions: 13 14// The above copyright notice and this permission notice shall be included in 15// all copies or substantial portions of the Software. 16 17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23// THE SOFTWARE. 24 25import ( 26 "crypto/hmac" 27 "crypto/sha1" 28 "encoding/base64" 29 "net/http" 30 "net/url" 31 "sort" 32 "strings" 33 "time" 34 35 "github.com/aws/aws-sdk-go/aws/corehandlers" 36 "github.com/aws/aws-sdk-go/aws/credentials" 37 "github.com/aws/aws-sdk-go/aws/request" 38 "github.com/aws/aws-sdk-go/service/s3" 39 log "github.com/sirupsen/logrus" 40) 41 42const ( 43 signatureVersion = "2" 44 signatureMethod = "HmacSHA1" 45 timeFormat = "2006-01-02T15:04:05Z" 46) 47 48type signer struct { 49 // Values that must be populated from the request 50 Request *http.Request 51 Time time.Time 52 Credentials *credentials.Credentials 53 Query url.Values 54 stringToSign string 55 signature string 56} 57 58var s3ParamsToSign = map[string]bool{ 59 "acl": true, 60 "location": true, 61 "logging": true, 62 "notification": true, 63 "partNumber": true, 64 "policy": true, 65 "requestPayment": true, 66 "torrent": true, 67 "uploadId": true, 68 "uploads": true, 69 "versionId": true, 70 "versioning": true, 71 "versions": true, 72 "response-content-type": true, 73 "response-content-language": true, 74 "response-expires": true, 75 "response-cache-control": true, 76 "response-content-disposition": true, 77 "response-content-encoding": true, 78 "website": true, 79 "delete": true, 80} 81 82// setv2Handlers will setup v2 signature signing on the S3 driver 83func setv2Handlers(svc *s3.S3) { 84 svc.Handlers.Build.PushBack(func(r *request.Request) { 85 parsedURL, err := url.Parse(r.HTTPRequest.URL.String()) 86 if err != nil { 87 log.Fatalf("Failed to parse URL: %v", err) 88 } 89 r.HTTPRequest.URL.Opaque = parsedURL.Path 90 }) 91 92 svc.Handlers.Sign.Clear() 93 svc.Handlers.Sign.PushBack(Sign) 94 svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) 95} 96 97// Sign requests with signature version 2. 98// 99// Will sign the requests with the service config's Credentials object 100// Signing is skipped if the credentials is the credentials.AnonymousCredentials 101// object. 102func Sign(req *request.Request) { 103 // If the request does not need to be signed ignore the signing of the 104 // request if the AnonymousCredentials object is used. 105 if req.Config.Credentials == credentials.AnonymousCredentials { 106 return 107 } 108 109 v2 := signer{ 110 Request: req.HTTPRequest, 111 Time: req.Time, 112 Credentials: req.Config.Credentials, 113 } 114 v2.Sign() 115} 116 117func (v2 *signer) Sign() error { 118 credValue, err := v2.Credentials.Get() 119 if err != nil { 120 return err 121 } 122 accessKey := credValue.AccessKeyID 123 var ( 124 md5, ctype, date, xamz string 125 xamzDate bool 126 sarray []string 127 smap map[string]string 128 sharray []string 129 ) 130 131 headers := v2.Request.Header 132 params := v2.Request.URL.Query() 133 parsedURL, err := url.Parse(v2.Request.URL.String()) 134 if err != nil { 135 return err 136 } 137 host, canonicalPath := parsedURL.Host, parsedURL.Path 138 v2.Request.Header["Host"] = []string{host} 139 v2.Request.Header["date"] = []string{v2.Time.In(time.UTC).Format(time.RFC1123)} 140 if credValue.SessionToken != "" { 141 v2.Request.Header["x-amz-security-token"] = []string{credValue.SessionToken} 142 } 143 144 smap = make(map[string]string) 145 for k, v := range headers { 146 k = strings.ToLower(k) 147 switch k { 148 case "content-md5": 149 md5 = v[0] 150 case "content-type": 151 ctype = v[0] 152 case "date": 153 if !xamzDate { 154 date = v[0] 155 } 156 default: 157 if strings.HasPrefix(k, "x-amz-") { 158 vall := strings.Join(v, ",") 159 smap[k] = k + ":" + vall 160 if k == "x-amz-date" { 161 xamzDate = true 162 date = "" 163 } 164 sharray = append(sharray, k) 165 } 166 } 167 } 168 if len(sharray) > 0 { 169 sort.StringSlice(sharray).Sort() 170 for _, h := range sharray { 171 sarray = append(sarray, smap[h]) 172 } 173 xamz = strings.Join(sarray, "\n") + "\n" 174 } 175 176 expires := false 177 if v, ok := params["Expires"]; ok { 178 expires = true 179 date = v[0] 180 params["AWSAccessKeyId"] = []string{accessKey} 181 } 182 183 sarray = sarray[0:0] 184 for k, v := range params { 185 if s3ParamsToSign[k] { 186 for _, vi := range v { 187 if vi == "" { 188 sarray = append(sarray, k) 189 } else { 190 sarray = append(sarray, k+"="+vi) 191 } 192 } 193 } 194 } 195 if len(sarray) > 0 { 196 sort.StringSlice(sarray).Sort() 197 canonicalPath = canonicalPath + "?" + strings.Join(sarray, "&") 198 } 199 200 v2.stringToSign = strings.Join([]string{ 201 v2.Request.Method, 202 md5, 203 ctype, 204 date, 205 xamz + canonicalPath, 206 }, "\n") 207 hash := hmac.New(sha1.New, []byte(credValue.SecretAccessKey)) 208 hash.Write([]byte(v2.stringToSign)) 209 v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil)) 210 211 if expires { 212 params["Signature"] = []string{v2.signature} 213 } else { 214 headers["Authorization"] = []string{"AWS " + accessKey + ":" + v2.signature} 215 } 216 217 log.WithFields(log.Fields{ 218 "string-to-sign": v2.stringToSign, 219 "signature": v2.signature, 220 }).Debugln("request signature") 221 return nil 222} 223