v2.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. package v2
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha1"
  5. "encoding/base64"
  6. "github.com/ks3sdklib/aws-sdk-go/aws/awsutil"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/ks3sdklib/aws-sdk-go/aws/credentials"
  15. "github.com/ks3sdklib/aws-sdk-go/internal/protocol/rest"
  16. "github.com/ks3sdklib/aws-sdk-go/aws"
  17. )
  18. const (
  19. authHeaderPrefix = "AWS"
  20. timeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
  21. )
  22. var signQuerys = map[string]bool{
  23. "acl": true,
  24. "lifecycle": true,
  25. "location": true,
  26. "logging": true,
  27. "notification": true,
  28. "policy": true,
  29. "requestPayment": true,
  30. "torrent": true,
  31. "uploadId": true,
  32. "uploads": true,
  33. "versionId": true,
  34. "versioning": true,
  35. "versions": true,
  36. "website": true,
  37. "delete": true,
  38. "thumbnail": true,
  39. "cors": true,
  40. "pfop": true,
  41. "querypfop": true,
  42. "partNumber": true,
  43. "response-content-type": true,
  44. "response-content-language": true,
  45. "response-expires": true,
  46. "response-cache-control": true,
  47. "response-content-disposition": true,
  48. "response-content-encoding": true,
  49. "tagging": true,
  50. "fetch": true,
  51. "copy": true,
  52. "mirror": true,
  53. "restore": true,
  54. "append": true,
  55. "position": true,
  56. "decompresspolicy": true,
  57. "retention": true,
  58. "crr": true,
  59. "inventory": true,
  60. "recycle": true,
  61. "recover": true,
  62. "clear": true,
  63. "id": true,
  64. "continuation-token": true,
  65. "dataRedundancySwitch": true,
  66. "bucketqos": true,
  67. "requesterqos": true,
  68. "encryption": true,
  69. "accessmonitor": true,
  70. "transferAcceleration": true,
  71. "VpcAccessBlock": true,
  72. }
  73. type signer struct {
  74. Service *aws.Service
  75. Request *http.Request
  76. Time time.Time
  77. ExpireTime int64
  78. ServiceName string
  79. Region string
  80. CredValues credentials.Value
  81. Credentials *credentials.Credentials
  82. Query url.Values
  83. Body io.ReadSeeker
  84. Debug uint
  85. Logger io.Writer
  86. isPresign bool
  87. formattedTime string
  88. canonicalHeaders string
  89. canonicalResource string
  90. stringToSign string
  91. signature string
  92. authorization string
  93. awsRequest *aws.Request
  94. }
  95. func Sign(req *aws.Request) {
  96. if req.Service.Config.Credentials == credentials.AnonymousCredentials {
  97. return
  98. }
  99. region := req.Service.SigningRegion
  100. if region == "" {
  101. region = req.Service.Config.Region
  102. }
  103. name := req.Service.SigningName
  104. if name == "" {
  105. name = req.Service.ServiceName
  106. }
  107. s := signer{
  108. Service: req.Service,
  109. Request: req.HTTPRequest,
  110. Time: req.Time,
  111. ExpireTime: req.ExpireTime,
  112. Query: req.HTTPRequest.URL.Query(),
  113. Body: req.Body,
  114. ServiceName: name,
  115. Region: region,
  116. Credentials: req.Service.Config.Credentials,
  117. Debug: req.Service.Config.LogLevel,
  118. Logger: req.Service.Config.Logger,
  119. awsRequest: req,
  120. }
  121. req.Error = s.sign()
  122. }
  123. func (v2 *signer) sign() error {
  124. if v2.ExpireTime != 0 {
  125. v2.isPresign = true
  126. }
  127. if v2.isRequestSigned() {
  128. if !v2.Credentials.IsExpired() {
  129. // If the request is already signed, and the credentials have not
  130. // expired yet ignore the signing request.
  131. return nil
  132. }
  133. // The credentials have expired for this request. The current signing
  134. // is invalid, and needs to be request because the request will fail.
  135. if v2.isPresign {
  136. v2.removePresign()
  137. // Update the request's query string to ensure the values stays in
  138. // sync in the case retrieving the new credentials fails.
  139. v2.Request.URL.RawQuery = v2.Query.Encode()
  140. }
  141. }
  142. var err error
  143. v2.CredValues, err = v2.Credentials.Get()
  144. if err != nil {
  145. return err
  146. }
  147. v2.build()
  148. v2.logSigningInfo()
  149. return nil
  150. }
  151. func (v2 *signer) logSigningInfo() {
  152. cfg := v2.Service.Config
  153. cfg.LogDebug("%s", "---[ STRING TO SIGN ]--------------------------------")
  154. cfg.LogDebug("%s", v2.stringToSign)
  155. if v2.isPresign {
  156. cfg.LogDebug("---[ SIGNED URL ]--------------------------------")
  157. cfg.LogDebug("%s", v2.Request.URL)
  158. }
  159. cfg.LogDebug("-----------------------------------------------------")
  160. }
  161. func (v2 *signer) build() {
  162. v2.buildTime() // no depends
  163. v2.buildCanonicalHeaders() // depends on cred string
  164. v2.buildCanonicalResource() // depends on canon headers / signed headers
  165. v2.buildStringToSign() // depends on canon string
  166. v2.buildSignature() // depends on string to sign
  167. if v2.isPresign {
  168. var querys url.Values
  169. querys = make(map[string][]string)
  170. querys.Add("Signature", v2.signature)
  171. querys.Add("AWSAccessKeyId", v2.CredValues.AccessKeyID)
  172. v2.Request.URL.RawQuery += "&" + querys.Encode()
  173. } else {
  174. v2.Request.Header.Set("Authorization", "AWS "+v2.CredValues.AccessKeyID+":"+v2.signature)
  175. }
  176. }
  177. func (v2 *signer) buildTime() {
  178. v2.formattedTime = v2.Time.UTC().Format(timeFormat)
  179. if v2.isPresign {
  180. duration := int64(v2.ExpireTime)
  181. v2.Query.Set("Expires", strconv.FormatInt(duration, 10))
  182. } else {
  183. v2.Request.Header.Set("Date", v2.formattedTime)
  184. }
  185. }
  186. func (v2 *signer) buildCanonicalHeaders() {
  187. var headers []string
  188. for k, v := range v2.Request.Header {
  189. if strings.HasPrefix(k, "X-Amz-") {
  190. key := strings.ToLower(http.CanonicalHeaderKey(k))
  191. v2.Request.Header[key] = v
  192. v2.Request.Header.Del(http.CanonicalHeaderKey(k))
  193. headers = append(headers, key)
  194. }
  195. }
  196. sort.Strings(headers)
  197. headerValues := make([]string, len(headers))
  198. for i, k := range headers {
  199. key := strings.ToLower(http.CanonicalHeaderKey(k))
  200. headerValues[i] = key + ":" + strings.Join(v2.Request.Header[key], ",")
  201. }
  202. v2.canonicalHeaders = strings.Join(headerValues, "\n")
  203. }
  204. func (v2 *signer) buildCanonicalResource() {
  205. endpoint := v2.Service.Endpoint
  206. v2.Request.URL.RawQuery = strings.Replace(v2.Query.Encode(), "+", "%20", -1)
  207. url := v2.Request.URL.String()
  208. //在aws.service.go,buildEndpoint会把sheme也加上
  209. pathStyle := strings.HasPrefix(url, endpoint)
  210. uri := v2.Request.URL.Opaque
  211. bucketInHost := ""
  212. if !pathStyle {
  213. if strings.HasPrefix(url, "http://") {
  214. url = url[7:]
  215. endpoint = endpoint[7:]
  216. } else if strings.HasPrefix(url, "https://") {
  217. url = url[8:]
  218. endpoint = endpoint[8:]
  219. }
  220. bucketInHost = url[0 : strings.Index(url, endpoint)-1]
  221. }
  222. if uri != "" {
  223. uris := strings.Split(uri, "/")[3:]
  224. append := false
  225. if len(uris) == 1 && uris[0] != "" && bucketInHost == "" {
  226. //只有bucket
  227. append = true
  228. } else if len(uris) == 0 && bucketInHost != "" {
  229. append = true
  230. }
  231. uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
  232. if bucketInHost != "" {
  233. uri = "/" + bucketInHost + uri
  234. }
  235. if v2.awsRequest.Config.DomainMode {
  236. b := awsutil.ValuesAtPath(v2.awsRequest.Params, "Bucket")
  237. bucket := b[0].(string)
  238. uri = "/" + bucket + uri
  239. append = false
  240. }
  241. if append {
  242. uri += "/"
  243. }
  244. } else {
  245. uri = v2.Request.URL.Path
  246. }
  247. if uri == "" {
  248. uri = "/"
  249. }
  250. if v2.ServiceName != "s3" {
  251. uri = rest.EscapePath(uri, false)
  252. }
  253. var querys []string
  254. for k := range v2.Query {
  255. if _, ok := signQuerys[k]; ok {
  256. querys = append(querys, k)
  257. }
  258. }
  259. sort.Strings(querys)
  260. queryValues := make([]string, len(querys))
  261. for i, k := range querys {
  262. v := v2.Query[k]
  263. vString := strings.Join(v, ",")
  264. if vString != "" {
  265. queryValues[i] = k + "=" + vString
  266. } else {
  267. queryValues[i] = k
  268. }
  269. }
  270. queryString := strings.Join(queryValues, "&")
  271. if queryString == "" {
  272. v2.canonicalResource = uri
  273. } else {
  274. v2.canonicalResource = uri + "?" + queryString
  275. }
  276. }
  277. func (v2 *signer) buildStringToSign() {
  278. md5list := v2.Request.Header["Content-Md5"]
  279. md5 := ""
  280. if len(md5list) > 0 {
  281. md5 = v2.Request.Header["Content-Md5"][0]
  282. }
  283. typelist := v2.Request.Header["Content-Type"]
  284. contenttype := ""
  285. if len(typelist) > 0 {
  286. contenttype = v2.Request.Header["Content-Type"][0]
  287. }
  288. signItems := []string{v2.Request.Method, md5, contenttype}
  289. if v2.isPresign {
  290. signItems = append(signItems, v2.Query["Expires"][0])
  291. } else {
  292. signItems = append(signItems, v2.formattedTime)
  293. }
  294. if v2.canonicalHeaders != "" {
  295. signItems = append(signItems, v2.canonicalHeaders)
  296. }
  297. signItems = append(signItems, v2.canonicalResource)
  298. v2.stringToSign = strings.Join(signItems, "\n")
  299. }
  300. func (v2 *signer) buildSignature() {
  301. secret := v2.CredValues.SecretAccessKey
  302. signature := string(base64Encode(makeHmac([]byte(secret), []byte(v2.stringToSign))))
  303. v2.signature = signature
  304. }
  305. // isRequestSigned returns if the request is currently signed or presigned
  306. func (v2 *signer) isRequestSigned() bool {
  307. if v2.isPresign && v2.Query.Get("Signature") != "" {
  308. return true
  309. }
  310. if v2.Request.Header.Get("Authorization") != "" {
  311. return true
  312. }
  313. return false
  314. }
  315. // unsign removes signing flags for both signed and presigned requests.
  316. func (v2 *signer) removePresign() {
  317. v2.Query.Del("AWSAccessKeyId")
  318. v2.Query.Del("Signature")
  319. v2.Query.Del("Expires")
  320. }
  321. func makeHmac(key []byte, data []byte) []byte {
  322. hash := hmac.New(sha1.New, key)
  323. hash.Write(data)
  324. return hash.Sum(nil)
  325. }
  326. func base64Encode(src []byte) []byte {
  327. return []byte(base64.StdEncoding.EncodeToString(src))
  328. }