sign.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package base
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/md5"
  6. "crypto/sha256"
  7. "encoding/base64"
  8. "encoding/hex"
  9. "fmt"
  10. "io/ioutil"
  11. "net/http"
  12. "net/url"
  13. "sort"
  14. "strings"
  15. "time"
  16. )
  17. func (c Credentials) Sign(request *http.Request) *http.Request {
  18. query := request.URL.Query()
  19. request.URL.RawQuery = query.Encode()
  20. return Sign4(request, c)
  21. }
  22. func (c Credentials) SignUrl(request *http.Request) string {
  23. query := request.URL.Query()
  24. ldt := timestampV4()
  25. sdt := ldt[:8]
  26. meta := new(metadata)
  27. meta.date, meta.service, meta.region, meta.signedHeaders, meta.algorithm = sdt, c.Service, c.Region, "", "HMAC-SHA256"
  28. meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "request")
  29. query.Set("X-Date", ldt)
  30. query.Set("X-NotSignBody", "")
  31. query.Set("X-Credential", c.AccessKeyID+"/"+meta.credentialScope)
  32. query.Set("X-Algorithm", meta.algorithm)
  33. query.Set("X-SignedHeaders", meta.signedHeaders)
  34. query.Set("X-SignedQueries", "")
  35. keys := make([]string, 0, len(query))
  36. for k := range query {
  37. keys = append(keys, k)
  38. }
  39. sort.Strings(keys)
  40. query.Set("X-SignedQueries", strings.Join(keys, ";"))
  41. if c.SessionToken != "" {
  42. query.Set("X-Security-Token", c.SessionToken)
  43. }
  44. // Task 1
  45. hashedCanonReq := hashedSimpleCanonicalRequestV4(request, query, meta)
  46. // Task 2
  47. stringToSign := concat("\n", meta.algorithm, ldt, meta.credentialScope, hashedCanonReq)
  48. // Task 3
  49. signingKey := signingKeyV4(c.SecretAccessKey, meta.date, meta.region, meta.service)
  50. signature := signatureV4(signingKey, stringToSign)
  51. query.Set("X-Signature", signature)
  52. return query.Encode()
  53. }
  54. // Sign4 signs a request with Signed Signature Version 4.
  55. func Sign4(request *http.Request, credential Credentials) *http.Request {
  56. keys := credential
  57. prepareRequestV4(request)
  58. meta := new(metadata)
  59. meta.service, meta.region = keys.Service, keys.Region
  60. // Task 0 设置SessionToken的header
  61. if credential.SessionToken != "" {
  62. request.Header.Set("X-Security-Token", credential.SessionToken)
  63. }
  64. // Task 1
  65. hashedCanonReq := hashedCanonicalRequestV4(request, meta)
  66. // Task 2
  67. stringToSign := stringToSignV4(request, hashedCanonReq, meta)
  68. // Task 3
  69. signingKey := signingKeyV4(keys.SecretAccessKey, meta.date, meta.region, meta.service)
  70. signature := signatureV4(signingKey, stringToSign)
  71. request.Header.Set("Authorization", buildAuthHeaderV4(signature, meta, keys))
  72. return request
  73. }
  74. func hashedSimpleCanonicalRequestV4(request *http.Request, query url.Values, meta *metadata) string {
  75. payloadHash := hashSHA256([]byte(""))
  76. if request.URL.Path == "" {
  77. request.URL.Path = "/"
  78. }
  79. canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(query), "\n", meta.signedHeaders, payloadHash)
  80. return hashSHA256([]byte(canonicalRequest))
  81. }
  82. func hashedCanonicalRequestV4(request *http.Request, meta *metadata) string {
  83. payload := readAndReplaceBody(request)
  84. payloadHash := hashSHA256(payload)
  85. request.Header.Set("X-Content-Sha256", payloadHash)
  86. request.Header.Set("Host", request.Host)
  87. var sortedHeaderKeys []string
  88. for key := range request.Header {
  89. switch key {
  90. case "Content-Type", "Content-Md5", "Host", "X-Security-Token":
  91. default:
  92. if !strings.HasPrefix(key, "X-") {
  93. continue
  94. }
  95. }
  96. sortedHeaderKeys = append(sortedHeaderKeys, strings.ToLower(key))
  97. }
  98. sort.Strings(sortedHeaderKeys)
  99. var headersToSign string
  100. for _, key := range sortedHeaderKeys {
  101. value := strings.TrimSpace(request.Header.Get(key))
  102. if key == "host" {
  103. if strings.Contains(value, ":") {
  104. split := strings.Split(value, ":")
  105. port := split[1]
  106. if port == "80" || port == "443" {
  107. value = split[0]
  108. }
  109. }
  110. }
  111. headersToSign += key + ":" + value + "\n"
  112. }
  113. meta.signedHeaders = concat(";", sortedHeaderKeys...)
  114. canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(request.URL.Query()), headersToSign, meta.signedHeaders, payloadHash)
  115. return hashSHA256([]byte(canonicalRequest))
  116. }
  117. func stringToSignV4(request *http.Request, hashedCanonReq string, meta *metadata) string {
  118. requestTs := request.Header.Get("X-Date")
  119. meta.algorithm = "HMAC-SHA256"
  120. meta.date = tsDateV4(requestTs)
  121. meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "request")
  122. return concat("\n", meta.algorithm, requestTs, meta.credentialScope, hashedCanonReq)
  123. }
  124. func signatureV4(signingKey []byte, stringToSign string) string {
  125. return hex.EncodeToString(hmacSHA256(signingKey, stringToSign))
  126. }
  127. func prepareRequestV4(request *http.Request) *http.Request {
  128. necessaryDefaults := map[string]string{
  129. "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
  130. "X-Date": timestampV4(),
  131. }
  132. for header, value := range necessaryDefaults {
  133. if request.Header.Get(header) == "" {
  134. request.Header.Set(header, value)
  135. }
  136. }
  137. if request.URL.Path == "" {
  138. request.URL.Path += "/"
  139. }
  140. return request
  141. }
  142. func signingKeyV4(secretKey, date, region, service string) []byte {
  143. kDate := hmacSHA256([]byte(secretKey), date)
  144. kRegion := hmacSHA256(kDate, region)
  145. kService := hmacSHA256(kRegion, service)
  146. kSigning := hmacSHA256(kService, "request")
  147. return kSigning
  148. }
  149. func buildAuthHeaderV4(signature string, meta *metadata, keys Credentials) string {
  150. credential := keys.AccessKeyID + "/" + meta.credentialScope
  151. return meta.algorithm +
  152. " Credential=" + credential +
  153. ", SignedHeaders=" + meta.signedHeaders +
  154. ", Signature=" + signature
  155. }
  156. func timestampV4() string {
  157. return now().Format(timeFormatV4)
  158. }
  159. func tsDateV4(timestamp string) string {
  160. return timestamp[:8]
  161. }
  162. func hmacSHA256(key []byte, content string) []byte {
  163. mac := hmac.New(sha256.New, key)
  164. mac.Write([]byte(content))
  165. return mac.Sum(nil)
  166. }
  167. func hashSHA256(content []byte) string {
  168. h := sha256.New()
  169. h.Write(content)
  170. return fmt.Sprintf("%x", h.Sum(nil))
  171. }
  172. func hashMD5(content []byte) string {
  173. h := md5.New()
  174. h.Write(content)
  175. return base64.StdEncoding.EncodeToString(h.Sum(nil))
  176. }
  177. func readAndReplaceBody(request *http.Request) []byte {
  178. if request.Body == nil {
  179. return []byte{}
  180. }
  181. payload, _ := ioutil.ReadAll(request.Body)
  182. request.Body = ioutil.NopCloser(bytes.NewReader(payload))
  183. return payload
  184. }
  185. func concat(delim string, str ...string) string {
  186. return strings.Join(str, delim)
  187. }
  188. var now = func() time.Time {
  189. return time.Now().UTC()
  190. }
  191. func normuri(uri string) string {
  192. parts := strings.Split(uri, "/")
  193. for i := range parts {
  194. parts[i] = encodePathFrag(parts[i])
  195. }
  196. return strings.Join(parts, "/")
  197. }
  198. func encodePathFrag(s string) string {
  199. hexCount := 0
  200. for i := 0; i < len(s); i++ {
  201. c := s[i]
  202. if shouldEscape(c) {
  203. hexCount++
  204. }
  205. }
  206. t := make([]byte, len(s)+2*hexCount)
  207. j := 0
  208. for i := 0; i < len(s); i++ {
  209. c := s[i]
  210. if shouldEscape(c) {
  211. t[j] = '%'
  212. t[j+1] = "0123456789ABCDEF"[c>>4]
  213. t[j+2] = "0123456789ABCDEF"[c&15]
  214. j += 3
  215. } else {
  216. t[j] = c
  217. j++
  218. }
  219. }
  220. return string(t)
  221. }
  222. func shouldEscape(c byte) bool {
  223. if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
  224. return false
  225. }
  226. if '0' <= c && c <= '9' {
  227. return false
  228. }
  229. if c == '-' || c == '_' || c == '.' || c == '~' {
  230. return false
  231. }
  232. return true
  233. }
  234. func normquery(v url.Values) string {
  235. queryString := v.Encode()
  236. return strings.Replace(queryString, "+", "%20", -1)
  237. }