auth_signature_v2.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. * The following code tries to reverse engineer the Amazon S3 APIs,
  3. * and is mostly copied from minio implementation.
  4. */
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  14. // implied. See the License for the specific language governing
  15. // permissions and limitations under the License.
  16. package s3api
  17. import (
  18. "crypto/hmac"
  19. "crypto/sha1"
  20. "crypto/subtle"
  21. "encoding/base64"
  22. "net/http"
  23. "sort"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  28. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  29. )
  30. // Whitelist resource list that will be used in query string for signature-V2 calculation.
  31. // The list should be alphabetically sorted
  32. var resourceList = []string{
  33. "acl",
  34. "delete",
  35. "lifecycle",
  36. "location",
  37. "logging",
  38. "notification",
  39. "partNumber",
  40. "policy",
  41. "requestPayment",
  42. "response-cache-control",
  43. "response-content-disposition",
  44. "response-content-encoding",
  45. "response-content-language",
  46. "response-content-type",
  47. "response-expires",
  48. "torrent",
  49. "uploadId",
  50. "uploads",
  51. "versionId",
  52. "versioning",
  53. "versions",
  54. "website",
  55. }
  56. // Verify if request has AWS Signature Version '2'.
  57. func (iam *IdentityAccessManagement) isReqAuthenticatedV2(r *http.Request) (*Identity, s3err.ErrorCode) {
  58. if isRequestSignatureV2(r) {
  59. return iam.doesSignV2Match(r)
  60. }
  61. return iam.doesPresignV2SignatureMatch(r)
  62. }
  63. func (iam *IdentityAccessManagement) doesPolicySignatureV2Match(formValues http.Header) s3err.ErrorCode {
  64. accessKey := formValues.Get("AWSAccessKeyId")
  65. if accessKey == "" {
  66. return s3err.ErrMissingFields
  67. }
  68. identity, cred, found := iam.lookupByAccessKey(accessKey)
  69. if !found {
  70. return s3err.ErrInvalidAccessKeyID
  71. }
  72. bucket := formValues.Get("bucket")
  73. if !identity.canDo(s3_constants.ACTION_WRITE, bucket, "") {
  74. return s3err.ErrAccessDenied
  75. }
  76. policy := formValues.Get("Policy")
  77. if policy == "" {
  78. return s3err.ErrMissingFields
  79. }
  80. signature := formValues.Get("Signature")
  81. if signature == "" {
  82. return s3err.ErrMissingFields
  83. }
  84. if !compareSignatureV2(signature, calculateSignatureV2(policy, cred.SecretKey)) {
  85. return s3err.ErrSignatureDoesNotMatch
  86. }
  87. return s3err.ErrNone
  88. }
  89. // doesSignV2Match - Verify authorization header with calculated header in accordance with
  90. // - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
  91. //
  92. // returns ErrNone if the signature matches.
  93. func (iam *IdentityAccessManagement) doesSignV2Match(r *http.Request) (*Identity, s3err.ErrorCode) {
  94. v2Auth := r.Header.Get("Authorization")
  95. accessKey, errCode := validateV2AuthHeader(v2Auth)
  96. if errCode != s3err.ErrNone {
  97. return nil, errCode
  98. }
  99. identity, cred, found := iam.lookupByAccessKey(accessKey)
  100. if !found {
  101. return nil, s3err.ErrInvalidAccessKeyID
  102. }
  103. bucket, object := s3_constants.GetBucketAndObject(r)
  104. if !identity.canDo(s3_constants.ACTION_WRITE, bucket, object) {
  105. return nil, s3err.ErrAccessDenied
  106. }
  107. expectedAuth := signatureV2(cred, r.Method, r.URL.Path, r.URL.Query().Encode(), r.Header)
  108. if !compareSignatureV2(v2Auth, expectedAuth) {
  109. return nil, s3err.ErrSignatureDoesNotMatch
  110. }
  111. return identity, s3err.ErrNone
  112. }
  113. // doesPresignV2SignatureMatch - Verify query headers with calculated header in accordance with
  114. // - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
  115. //
  116. // returns ErrNone if the signature matches.
  117. func (iam *IdentityAccessManagement) doesPresignV2SignatureMatch(r *http.Request) (*Identity, s3err.ErrorCode) {
  118. query := r.URL.Query()
  119. expires := query.Get("Expires")
  120. if expires == "" {
  121. return nil, s3err.ErrMissingFields
  122. }
  123. expireTimestamp, err := strconv.ParseInt(expires, 10, 64)
  124. if err != nil {
  125. return nil, s3err.ErrMalformedExpires
  126. }
  127. if time.Unix(expireTimestamp, 0).Before(time.Now().UTC()) {
  128. return nil, s3err.ErrExpiredPresignRequest
  129. }
  130. accessKey := query.Get("AWSAccessKeyId")
  131. if accessKey == "" {
  132. return nil, s3err.ErrInvalidAccessKeyID
  133. }
  134. signature := query.Get("Signature")
  135. if signature == "" {
  136. return nil, s3err.ErrMissingFields
  137. }
  138. identity, cred, found := iam.lookupByAccessKey(accessKey)
  139. if !found {
  140. return nil, s3err.ErrInvalidAccessKeyID
  141. }
  142. bucket, object := s3_constants.GetBucketAndObject(r)
  143. if !identity.canDo(s3_constants.ACTION_READ, bucket, object) {
  144. return nil, s3err.ErrAccessDenied
  145. }
  146. expectedSignature := preSignatureV2(cred, r.Method, r.URL.Path, r.URL.Query().Encode(), r.Header, expires)
  147. if !compareSignatureV2(signature, expectedSignature) {
  148. return nil, s3err.ErrSignatureDoesNotMatch
  149. }
  150. return identity, s3err.ErrNone
  151. }
  152. // validateV2AuthHeader validates AWS Signature Version '2' authentication header.
  153. func validateV2AuthHeader(v2Auth string) (accessKey string, errCode s3err.ErrorCode) {
  154. if v2Auth == "" {
  155. return "", s3err.ErrAuthHeaderEmpty
  156. }
  157. // Signature V2 authorization header format:
  158. // Authorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUN8DYpKDtOLCwo//yllqDzg=
  159. if !strings.HasPrefix(v2Auth, signV2Algorithm) {
  160. return "", s3err.ErrSignatureVersionNotSupported
  161. }
  162. // Strip off the Algorithm prefix.
  163. v2Auth = v2Auth[len(signV2Algorithm):]
  164. authFields := strings.Split(v2Auth, ":")
  165. if len(authFields) != 2 {
  166. return "", s3err.ErrMissingFields
  167. }
  168. // The first field is Access Key ID.
  169. if authFields[0] == "" {
  170. return "", s3err.ErrInvalidAccessKeyID
  171. }
  172. // The second field is signature.
  173. if authFields[1] == "" {
  174. return "", s3err.ErrMissingFields
  175. }
  176. return authFields[0], s3err.ErrNone
  177. }
  178. // signatureV2 - calculates signature version 2 for request.
  179. func signatureV2(cred *Credential, method string, encodedResource string, encodedQuery string, headers http.Header) string {
  180. stringToSign := getStringToSignV2(method, encodedResource, encodedQuery, headers, "")
  181. signature := calculateSignatureV2(stringToSign, cred.SecretKey)
  182. return signV2Algorithm + cred.AccessKey + ":" + signature
  183. }
  184. // getStringToSignV2 - string to sign in accordance with
  185. // - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
  186. func getStringToSignV2(method string, encodedResource, encodedQuery string, headers http.Header, expires string) string {
  187. canonicalHeaders := canonicalizedAmzHeadersV2(headers)
  188. if len(canonicalHeaders) > 0 {
  189. canonicalHeaders += "\n"
  190. }
  191. // From the Amazon docs:
  192. //
  193. // StringToSign = HTTP-Verb + "\n" +
  194. // Content-MD5 + "\n" +
  195. // Content-Type + "\n" +
  196. // Date + "\n" +
  197. // CanonicalizedAmzHeaders +
  198. // CanonicalizedResource;
  199. stringToSign := method + "\n"
  200. stringToSign += headers.Get("Content-Md5") + "\n"
  201. stringToSign += headers.Get("Content-Type") + "\n"
  202. if expires != "" {
  203. stringToSign += expires + "\n"
  204. } else {
  205. stringToSign += headers.Get("Date") + "\n"
  206. if v := headers.Get("x-amz-date"); v != "" {
  207. stringToSign = strings.Replace(stringToSign, headers.Get("Date")+"\n", "\n", -1)
  208. }
  209. }
  210. stringToSign += canonicalHeaders
  211. stringToSign += canonicalizedResourceV2(encodedResource, encodedQuery)
  212. return stringToSign
  213. }
  214. // canonicalizedResourceV2 - canonicalize the resource string for signature V2.
  215. func canonicalizedResourceV2(encodedResource, encodedQuery string) string {
  216. queries := strings.Split(encodedQuery, "&")
  217. keyval := make(map[string]string)
  218. for _, query := range queries {
  219. key := query
  220. val := ""
  221. index := strings.Index(query, "=")
  222. if index != -1 {
  223. key = query[:index]
  224. val = query[index+1:]
  225. }
  226. keyval[key] = val
  227. }
  228. var canonicalQueries []string
  229. for _, resource := range resourceList {
  230. if val, ok := keyval[resource]; ok {
  231. if val == "" {
  232. canonicalQueries = append(canonicalQueries, resource)
  233. continue
  234. }
  235. canonicalQueries = append(canonicalQueries, resource+"="+val)
  236. }
  237. }
  238. // The queries will be already sorted as resourceList is sorted.
  239. if len(canonicalQueries) == 0 {
  240. return encodedResource
  241. }
  242. // If queries are present then the canonicalized resource is set to encodedResource + "?" + strings.Join(canonicalQueries, "&")
  243. return encodedResource + "?" + strings.Join(canonicalQueries, "&")
  244. }
  245. // canonicalizedAmzHeadersV2 - canonicalize the x-amz-* headers for signature V2.
  246. func canonicalizedAmzHeadersV2(headers http.Header) string {
  247. var keys []string
  248. keyval := make(map[string]string)
  249. for key := range headers {
  250. lkey := strings.ToLower(key)
  251. if !strings.HasPrefix(lkey, "x-amz-") {
  252. continue
  253. }
  254. keys = append(keys, lkey)
  255. keyval[lkey] = strings.Join(headers[key], ",")
  256. }
  257. sort.Strings(keys)
  258. var canonicalHeaders []string
  259. for _, key := range keys {
  260. canonicalHeaders = append(canonicalHeaders, key+":"+keyval[key])
  261. }
  262. return strings.Join(canonicalHeaders, "\n")
  263. }
  264. // calculateSignatureV2 - calculates signature version 2.
  265. func calculateSignatureV2(stringToSign string, secret string) string {
  266. hm := hmac.New(sha1.New, []byte(secret))
  267. hm.Write([]byte(stringToSign))
  268. return base64.StdEncoding.EncodeToString(hm.Sum(nil))
  269. }
  270. // compareSignatureV2 returns true if and only if both signatures
  271. // are equal. The signatures are expected to be base64 encoded strings
  272. // according to the AWS S3 signature V2 spec.
  273. func compareSignatureV2(sig1, sig2 string) bool {
  274. // Decode signature string to binary byte-sequence representation is required
  275. // as Base64 encoding of a value is not unique:
  276. // For example "aGVsbG8=" and "aGVsbG8=\r" will result in the same byte slice.
  277. signature1, err := base64.StdEncoding.DecodeString(sig1)
  278. if err != nil {
  279. return false
  280. }
  281. signature2, err := base64.StdEncoding.DecodeString(sig2)
  282. if err != nil {
  283. return false
  284. }
  285. return subtle.ConstantTimeCompare(signature1, signature2) == 1
  286. }
  287. // Return signature-v2 for the presigned request.
  288. func preSignatureV2(cred *Credential, method string, encodedResource string, encodedQuery string, headers http.Header, expires string) string {
  289. stringToSign := getStringToSignV2(method, encodedResource, encodedQuery, headers, expires)
  290. return calculateSignatureV2(stringToSign, cred.SecretKey)
  291. }