sign.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // Package s3 signs HTTP requests for Amazon S3 and compatible services.
  2. package s3
  3. // See
  4. // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/RESTAuthentication.html.
  5. import (
  6. "crypto/hmac"
  7. "crypto/sha1"
  8. "encoding/base64"
  9. "io"
  10. "net/http"
  11. "sort"
  12. "strings"
  13. )
  14. var signParams = map[string]bool{
  15. "acl": true,
  16. "delete": true,
  17. "lifecycle": true,
  18. "location": true,
  19. "logging": true,
  20. "notification": true,
  21. "partNumber": true,
  22. "policy": true,
  23. "requestPayment": true,
  24. "response-cache-control": true,
  25. "response-content-disposition": true,
  26. "response-content-encoding": true,
  27. "response-content-language": true,
  28. "response-content-type": true,
  29. "response-expires": true,
  30. "restore": true,
  31. "torrent": true,
  32. "uploadId": true,
  33. "uploads": true,
  34. "versionId": true,
  35. "versioning": true,
  36. "versions": true,
  37. "website": true,
  38. }
  39. // Keys holds a set of Amazon Security Credentials.
  40. type Keys struct {
  41. AccessKey string
  42. SecretKey string
  43. // SecurityToken is used for temporary security credentials.
  44. // If set, it will be added to header field X-Amz-Security-Token
  45. // before signing a request.
  46. SecurityToken string
  47. // See http://docs.aws.amazon.com/AmazonS3/latest/dev/MakingRequests.html#TypesofSecurityCredentials
  48. }
  49. // IdentityBucket returns subdomain.
  50. // It is designed to be used with S3-compatible services that
  51. // treat the entire subdomain as the bucket name, for example
  52. // storage.io.
  53. func IdentityBucket(subdomain string) string {
  54. return subdomain
  55. }
  56. // AmazonBucket returns everything up to the last '.' in subdomain.
  57. // It is designed to be used with the Amazon service.
  58. // "johnsmith.s3" becomes "johnsmith"
  59. // "johnsmith.s3-eu-west-1" becomes "johnsmith"
  60. // "www.example.com.s3" becomes "www.example.com"
  61. func AmazonBucket(subdomain string) string {
  62. if i := strings.LastIndex(subdomain, "."); i != -1 {
  63. return subdomain[:i]
  64. }
  65. return ""
  66. }
  67. // DefaultService is the default Service used by Sign.
  68. var DefaultService = &Service{Domain: "amazonaws.com"}
  69. // Sign signs an HTTP request with the given S3 keys.
  70. //
  71. // This function is a wrapper around DefaultService.Sign.
  72. func Sign(r *http.Request, k Keys) {
  73. DefaultService.Sign(r, k)
  74. }
  75. // Service represents an S3-compatible service.
  76. type Service struct {
  77. // Domain is the service's root domain. It is used to extract
  78. // the subdomain from an http.Request before passing the
  79. // subdomain to Bucket.
  80. Domain string
  81. // Bucket derives the bucket name from a subdomain.
  82. // If nil, AmazonBucket is used.
  83. Bucket func(subdomain string) string
  84. }
  85. // Sign signs an HTTP request with the given S3 keys for use on service s.
  86. func (s *Service) Sign(r *http.Request, k Keys) {
  87. if k.SecurityToken != "" {
  88. r.Header.Set("X-Amz-Security-Token", k.SecurityToken)
  89. }
  90. h := hmac.New(sha1.New, []byte(k.SecretKey))
  91. s.writeSigData(h, r)
  92. sig := make([]byte, base64.StdEncoding.EncodedLen(h.Size()))
  93. base64.StdEncoding.Encode(sig, h.Sum(nil))
  94. r.Header.Set("Authorization", "AWS "+k.AccessKey+":"+string(sig))
  95. }
  96. func (s *Service) writeSigData(w io.Writer, r *http.Request) {
  97. w.Write([]byte(r.Method))
  98. w.Write([]byte{'\n'})
  99. w.Write([]byte(r.Header.Get("content-md5")))
  100. w.Write([]byte{'\n'})
  101. w.Write([]byte(r.Header.Get("content-type")))
  102. w.Write([]byte{'\n'})
  103. if _, ok := r.Header["X-Amz-Date"]; !ok {
  104. w.Write([]byte(r.Header.Get("date")))
  105. }
  106. w.Write([]byte{'\n'})
  107. writeAmzHeaders(w, r)
  108. s.writeResource(w, r)
  109. }
  110. func (s *Service) writeResource(w io.Writer, r *http.Request) {
  111. s.writeVhostBucket(w, strings.ToLower(r.Host))
  112. path := r.URL.RequestURI()
  113. if r.URL.RawQuery != "" {
  114. path = path[:len(path)-len(r.URL.RawQuery)-1]
  115. }
  116. w.Write([]byte(path))
  117. s.writeSubResource(w, r)
  118. }
  119. func (s *Service) writeVhostBucket(w io.Writer, host string) {
  120. if i := strings.Index(host, ":"); i != -1 {
  121. host = host[:i]
  122. }
  123. if host == s.Domain {
  124. // no vhost - do nothing
  125. } else if strings.HasSuffix(host, "."+s.Domain) {
  126. // vhost - bucket may be in prefix
  127. b := s.Bucket
  128. if b == nil {
  129. b = AmazonBucket
  130. }
  131. bucket := b(host[:len(host)-len(s.Domain)-1])
  132. if bucket != "" {
  133. w.Write([]byte{'/'})
  134. w.Write([]byte(bucket))
  135. }
  136. } else {
  137. // cname - bucket is host
  138. w.Write([]byte{'/'})
  139. w.Write([]byte(host))
  140. }
  141. }
  142. func (s *Service) writeSubResource(w io.Writer, r *http.Request) {
  143. var a []string
  144. for k, vs := range r.URL.Query() {
  145. if signParams[k] {
  146. for _, v := range vs {
  147. if v == "" {
  148. a = append(a, k)
  149. } else {
  150. a = append(a, k+"="+v)
  151. }
  152. }
  153. }
  154. }
  155. sort.Strings(a)
  156. var p byte = '?'
  157. for _, s := range a {
  158. w.Write([]byte{p})
  159. w.Write([]byte(s))
  160. p = '&'
  161. }
  162. }
  163. func writeAmzHeaders(w io.Writer, r *http.Request) {
  164. var keys []string
  165. for k, _ := range r.Header {
  166. if strings.HasPrefix(strings.ToLower(k), "x-amz-") {
  167. keys = append(keys, k)
  168. }
  169. }
  170. sort.Strings(keys)
  171. var a []string
  172. for _, k := range keys {
  173. v := r.Header[k]
  174. a = append(a, strings.ToLower(k)+":"+strings.Join(v, ","))
  175. }
  176. for _, h := range a {
  177. w.Write([]byte(h))
  178. w.Write([]byte{'\n'})
  179. }
  180. }