v2.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package s3auth
  15. import (
  16. "bytes"
  17. "crypto/hmac"
  18. "crypto/sha1"
  19. "encoding/base64"
  20. "net/http"
  21. "net/url"
  22. "sort"
  23. "strings"
  24. "yunion.io/x/jsonutils"
  25. "yunion.io/x/pkg/errors"
  26. )
  27. // Signature and API related constants.
  28. const (
  29. signV2Algorithm = "AWS"
  30. )
  31. // From the Amazon docs:
  32. //
  33. // StringToSign = HTTP-Verb + "\n" +
  34. //
  35. // Content-Md5 + "\n" +
  36. // Content-Type + "\n" +
  37. // Date + "\n" +
  38. // CanonicalizedProtocolHeaders +
  39. // CanonicalizedResource;
  40. func stringToSignV2(req http.Request, virtualHost bool) string {
  41. buf := new(bytes.Buffer)
  42. // Write standard headers.
  43. writeSignV2Headers(buf, req)
  44. // Write canonicalized protocol headers if any.
  45. writeCanonicalizedHeaders(buf, req)
  46. // Write canonicalized Query resources if any.
  47. writeCanonicalizedResource(buf, req, virtualHost)
  48. return buf.String()
  49. }
  50. // writeSignV2Headers - write signV2 required headers.
  51. func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
  52. buf.WriteString(req.Method + "\n")
  53. buf.WriteString(req.Header.Get("Content-Md5") + "\n")
  54. buf.WriteString(req.Header.Get("Content-Type") + "\n")
  55. buf.WriteString(req.Header.Get("Date") + "\n")
  56. }
  57. // writeCanonicalizedHeaders - write canonicalized headers.
  58. func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
  59. var protoHeaders []string
  60. vals := make(map[string][]string)
  61. for k, vv := range req.Header {
  62. // All the AMZ headers should be lowercase
  63. lk := strings.ToLower(k)
  64. if strings.HasPrefix(lk, "x-amz") {
  65. protoHeaders = append(protoHeaders, lk)
  66. vals[lk] = vv
  67. }
  68. }
  69. sort.Strings(protoHeaders)
  70. for _, k := range protoHeaders {
  71. buf.WriteString(k)
  72. buf.WriteByte(':')
  73. for idx, v := range vals[k] {
  74. if idx > 0 {
  75. buf.WriteByte(',')
  76. }
  77. if strings.Contains(v, "\n") {
  78. // TODO: "Unfold" long headers that
  79. // span multiple lines (as allowed by
  80. // RFC 2616, section 4.2) by replacing
  81. // the folding white-space (including
  82. // new-line) by a single space.
  83. buf.WriteString(v)
  84. } else {
  85. buf.WriteString(v)
  86. }
  87. }
  88. buf.WriteByte('\n')
  89. }
  90. }
  91. // AWS S3 Signature V2 calculation rule is give here:
  92. // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign
  93. // Whitelist resource list that will be used in query string for signature-V2 calculation.
  94. // The list should be alphabetically sorted
  95. var resourceList = []string{
  96. "acl",
  97. "delete",
  98. "lifecycle",
  99. "location",
  100. "logging",
  101. "notification",
  102. "partNumber",
  103. "policy",
  104. "requestPayment",
  105. "response-cache-control",
  106. "response-content-disposition",
  107. "response-content-encoding",
  108. "response-content-language",
  109. "response-content-type",
  110. "response-expires",
  111. "torrent",
  112. "uploadId",
  113. "uploads",
  114. "versionId",
  115. "versioning",
  116. "versions",
  117. "website",
  118. }
  119. // From the Amazon docs:
  120. //
  121. // CanonicalizedResource = [ "/" + Bucket ] +
  122. //
  123. // <HTTP-Request-URI, from the protocol name up to the query string> +
  124. // [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
  125. func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, virtualHost bool) {
  126. // Save request URL.
  127. requestURL := req.URL
  128. // Get encoded URL path.
  129. buf.WriteString(encodeURL2Path(req, virtualHost))
  130. if requestURL.RawQuery != "" {
  131. var n int
  132. vals, _ := url.ParseQuery(requestURL.RawQuery)
  133. // Verify if any sub resource queries are present, if yes
  134. // canonicallize them.
  135. for _, resource := range resourceList {
  136. if vv, ok := vals[resource]; ok && len(vv) > 0 {
  137. n++
  138. // First element
  139. switch n {
  140. case 1:
  141. buf.WriteByte('?')
  142. // The rest
  143. default:
  144. buf.WriteByte('&')
  145. }
  146. buf.WriteString(resource)
  147. // Request parameters
  148. if len(vv[0]) > 0 {
  149. buf.WriteByte('=')
  150. buf.WriteString(vv[0])
  151. }
  152. }
  153. }
  154. }
  155. }
  156. // Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
  157. // Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
  158. //
  159. // StringToSign = HTTP-Verb + "\n" +
  160. // Content-Md5 + "\n" +
  161. // Content-Type + "\n" +
  162. // Date + "\n" +
  163. // CanonicalizedProtocolHeaders +
  164. // CanonicalizedResource;
  165. //
  166. // CanonicalizedResource = [ "/" + Bucket ] +
  167. // <HTTP-Request-URI, from the protocol name up to the query string> +
  168. // [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
  169. //
  170. // CanonicalizedProtocolHeaders = <described below>
  171. // https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
  172. /*func verifyV2(ctx context.Context, req http.Request, virtualHost bool) error {
  173. aksk, err := DecodeAccessKeyRequestV2(req, virtualHost)
  174. if err != nil {
  175. return errors.Wrap(err, "DecodeAccessKeyRequestV2")
  176. }
  177. authSession := session.GetAdminSession(ctx)
  178. result, err := modules.Credentials.Get(authSession, aksk.AccessKey, nil)
  179. if err != nil {
  180. return errors.Wrap(err, "modules.Credentials.Get")
  181. }
  182. secret, err := modules.DecodeAccessKeySecret(result)
  183. if err != nil {
  184. return errors.Wrap(err, "modules.DecodeAccessKeySecret")
  185. }
  186. hm := hmac.New(sha1.New, []byte(secret.Secret))
  187. hm.Write([]byte(aksk.RequestString))
  188. signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
  189. if aksk.Signature != signature {
  190. return errors.Error("signature mismatch")
  191. }
  192. return nil
  193. }*/
  194. type SAccessKeyRequestV2 struct {
  195. SAccessKeyRequest
  196. }
  197. func (aksk *SAccessKeyRequestV2) ParseRequest(req http.Request, virtualHost bool) error {
  198. aksk.Request = stringToSignV2(req, virtualHost)
  199. return nil
  200. }
  201. func (aksk SAccessKeyRequestV2) Verify(secret string) error {
  202. hm := hmac.New(sha1.New, []byte(secret))
  203. hm.Write([]byte(aksk.Request))
  204. signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
  205. if signature != aksk.Signature {
  206. return errors.Error("signature mismatch")
  207. }
  208. return nil
  209. }
  210. func (aksk SAccessKeyRequestV2) Encode() string {
  211. return jsonutils.Marshal(aksk).String()
  212. }
  213. func NewV2Request() SAccessKeyRequestV2 {
  214. req := SAccessKeyRequestV2{}
  215. req.Algorithm = signV2Algorithm
  216. return req
  217. }
  218. func decodeAuthHeaderV2(authStr string) (*SAccessKeyRequestV2, error) {
  219. akskReq := NewV2Request()
  220. pos := strings.IndexByte(authStr, ':')
  221. if pos <= 0 {
  222. return nil, errors.Error("illegal authorization header")
  223. }
  224. akskReq.AccessKey = authStr[:pos]
  225. akskReq.Signature = authStr[pos+1:]
  226. return &akskReq, nil
  227. }