You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
9.4 KiB

  1. // Copyright (c) 2015-2021 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package cmd
  18. import (
  19. "net/http"
  20. "net/url"
  21. "strings"
  22. "time"
  23. "github.com/minio/minio/internal/auth"
  24. xhttp "github.com/minio/minio/internal/http"
  25. )
  26. // credentialHeader data type represents structured form of Credential
  27. // string from authorization header.
  28. type credentialHeader struct {
  29. accessKey string
  30. scope struct {
  31. date time.Time
  32. region string
  33. service string
  34. request string
  35. }
  36. }
  37. // Return scope string.
  38. func (c credentialHeader) getScope() string {
  39. return strings.Join([]string{
  40. c.scope.date.Format(yyyymmdd),
  41. c.scope.region,
  42. c.scope.service,
  43. c.scope.request,
  44. }, SlashSeparator)
  45. }
  46. func getReqAccessKeyV4(r *http.Request, region string, stype serviceType) (auth.Credentials, bool, APIErrorCode) {
  47. ch, s3Err := parseCredentialHeader("Credential="+r.Form.Get(xhttp.AmzCredential), region, stype)
  48. if s3Err != ErrNone {
  49. // Strip off the Algorithm prefix.
  50. v4Auth := strings.TrimPrefix(r.Header.Get("Authorization"), signV4Algorithm)
  51. authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
  52. if len(authFields) != 3 {
  53. return auth.Credentials{}, false, ErrMissingFields
  54. }
  55. ch, s3Err = parseCredentialHeader(authFields[0], region, stype)
  56. if s3Err != ErrNone {
  57. return auth.Credentials{}, false, s3Err
  58. }
  59. }
  60. return checkKeyValid(r, ch.accessKey)
  61. }
  62. // parse credentialHeader string into its structured form.
  63. func parseCredentialHeader(credElement string, region string, stype serviceType) (ch credentialHeader, aec APIErrorCode) {
  64. creds := strings.SplitN(strings.TrimSpace(credElement), "=", 2)
  65. if len(creds) != 2 {
  66. return ch, ErrMissingFields
  67. }
  68. if creds[0] != "Credential" {
  69. return ch, ErrMissingCredTag
  70. }
  71. credElements := strings.Split(strings.TrimSpace(creds[1]), SlashSeparator)
  72. if len(credElements) < 5 {
  73. return ch, ErrCredMalformed
  74. }
  75. accessKey := strings.Join(credElements[:len(credElements)-4], SlashSeparator) // The access key may contain one or more `/`
  76. if !auth.IsAccessKeyValid(accessKey) {
  77. return ch, ErrInvalidAccessKeyID
  78. }
  79. // Save access key id.
  80. cred := credentialHeader{
  81. accessKey: accessKey,
  82. }
  83. credElements = credElements[len(credElements)-4:]
  84. var e error
  85. cred.scope.date, e = time.Parse(yyyymmdd, credElements[0])
  86. if e != nil {
  87. return ch, ErrMalformedCredentialDate
  88. }
  89. cred.scope.region = credElements[1]
  90. // Verify if region is valid.
  91. sRegion := cred.scope.region
  92. // Region is set to be empty, we use whatever was sent by the
  93. // request and proceed further. This is a work-around to address
  94. // an important problem for ListBuckets() getting signed with
  95. // different regions.
  96. if region == "" {
  97. region = sRegion
  98. }
  99. // Should validate region, only if region is set.
  100. if !isValidRegion(sRegion, region) {
  101. return ch, ErrAuthorizationHeaderMalformed
  102. }
  103. if credElements[2] != string(stype) {
  104. if stype == serviceSTS {
  105. return ch, ErrInvalidServiceSTS
  106. }
  107. return ch, ErrInvalidServiceS3
  108. }
  109. cred.scope.service = credElements[2]
  110. if credElements[3] != "aws4_request" {
  111. return ch, ErrInvalidRequestVersion
  112. }
  113. cred.scope.request = credElements[3]
  114. return cred, ErrNone
  115. }
  116. // Parse signature from signature tag.
  117. func parseSignature(signElement string) (string, APIErrorCode) {
  118. signFields := strings.Split(strings.TrimSpace(signElement), "=")
  119. if len(signFields) != 2 {
  120. return "", ErrMissingFields
  121. }
  122. if signFields[0] != "Signature" {
  123. return "", ErrMissingSignTag
  124. }
  125. if signFields[1] == "" {
  126. return "", ErrMissingFields
  127. }
  128. signature := signFields[1]
  129. return signature, ErrNone
  130. }
  131. // Parse slice of signed headers from signed headers tag.
  132. func parseSignedHeader(signedHdrElement string) ([]string, APIErrorCode) {
  133. signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=")
  134. if len(signedHdrFields) != 2 {
  135. return nil, ErrMissingFields
  136. }
  137. if signedHdrFields[0] != "SignedHeaders" {
  138. return nil, ErrMissingSignHeadersTag
  139. }
  140. if signedHdrFields[1] == "" {
  141. return nil, ErrMissingFields
  142. }
  143. signedHeaders := strings.Split(signedHdrFields[1], ";")
  144. return signedHeaders, ErrNone
  145. }
  146. // signValues data type represents structured form of AWS Signature V4 header.
  147. type signValues struct {
  148. Credential credentialHeader
  149. SignedHeaders []string
  150. Signature string
  151. }
  152. // preSignValues data type represents structured form of AWS Signature V4 query string.
  153. type preSignValues struct {
  154. signValues
  155. Date time.Time
  156. Expires time.Duration
  157. }
  158. // Parses signature version '4' query string of the following form.
  159. //
  160. // querystring = X-Amz-Algorithm=algorithm
  161. // querystring += &X-Amz-Credential= urlencode(accessKey + '/' + credential_scope)
  162. // querystring += &X-Amz-Date=date
  163. // querystring += &X-Amz-Expires=timeout interval
  164. // querystring += &X-Amz-SignedHeaders=signed_headers
  165. // querystring += &X-Amz-Signature=signature
  166. //
  167. // verifies if any of the necessary query params are missing in the presigned request.
  168. func doesV4PresignParamsExist(query url.Values) APIErrorCode {
  169. v4PresignQueryParams := []string{xhttp.AmzAlgorithm, xhttp.AmzCredential, xhttp.AmzSignature, xhttp.AmzDate, xhttp.AmzSignedHeaders, xhttp.AmzExpires}
  170. for _, v4PresignQueryParam := range v4PresignQueryParams {
  171. if _, ok := query[v4PresignQueryParam]; !ok {
  172. return ErrInvalidQueryParams
  173. }
  174. }
  175. return ErrNone
  176. }
  177. // Parses all the presigned signature values into separate elements.
  178. func parsePreSignV4(query url.Values, region string, stype serviceType) (psv preSignValues, aec APIErrorCode) {
  179. // verify whether the required query params exist.
  180. aec = doesV4PresignParamsExist(query)
  181. if aec != ErrNone {
  182. return psv, aec
  183. }
  184. // Verify if the query algorithm is supported or not.
  185. if query.Get(xhttp.AmzAlgorithm) != signV4Algorithm {
  186. return psv, ErrInvalidQuerySignatureAlgo
  187. }
  188. // Initialize signature version '4' structured header.
  189. preSignV4Values := preSignValues{}
  190. // Save credential.
  191. preSignV4Values.Credential, aec = parseCredentialHeader("Credential="+query.Get(xhttp.AmzCredential), region, stype)
  192. if aec != ErrNone {
  193. return psv, aec
  194. }
  195. var e error
  196. // Save date in native time.Time.
  197. preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get(xhttp.AmzDate))
  198. if e != nil {
  199. return psv, ErrMalformedPresignedDate
  200. }
  201. // Save expires in native time.Duration.
  202. preSignV4Values.Expires, e = time.ParseDuration(query.Get(xhttp.AmzExpires) + "s")
  203. if e != nil {
  204. return psv, ErrMalformedExpires
  205. }
  206. if preSignV4Values.Expires < 0 {
  207. return psv, ErrNegativeExpires
  208. }
  209. // Check if Expiry time is less than 7 days (value in seconds).
  210. if preSignV4Values.Expires.Seconds() > 604800 {
  211. return psv, ErrMaximumExpires
  212. }
  213. if preSignV4Values.Date.IsZero() || preSignV4Values.Date.Equal(timeSentinel) {
  214. return psv, ErrMalformedPresignedDate
  215. }
  216. // Save signed headers.
  217. preSignV4Values.SignedHeaders, aec = parseSignedHeader("SignedHeaders=" + query.Get(xhttp.AmzSignedHeaders))
  218. if aec != ErrNone {
  219. return psv, aec
  220. }
  221. // Save signature.
  222. preSignV4Values.Signature, aec = parseSignature("Signature=" + query.Get(xhttp.AmzSignature))
  223. if aec != ErrNone {
  224. return psv, aec
  225. }
  226. // Return structured form of signature query string.
  227. return preSignV4Values, ErrNone
  228. }
  229. // Parses signature version '4' header of the following form.
  230. //
  231. // Authorization: algorithm Credential=accessKeyID/credScope, \
  232. // SignedHeaders=signedHeaders, Signature=signature
  233. func parseSignV4(v4Auth string, region string, stype serviceType) (sv signValues, aec APIErrorCode) {
  234. // credElement is fetched first to skip replacing the space in access key.
  235. credElement := strings.TrimPrefix(strings.Split(strings.TrimSpace(v4Auth), ",")[0], signV4Algorithm)
  236. // Replace all spaced strings, some clients can send spaced
  237. // parameters and some won't. So we pro-actively remove any spaces
  238. // to make parsing easier.
  239. v4Auth = strings.ReplaceAll(v4Auth, " ", "")
  240. if v4Auth == "" {
  241. return sv, ErrAuthHeaderEmpty
  242. }
  243. // Verify if the header algorithm is supported or not.
  244. if !strings.HasPrefix(v4Auth, signV4Algorithm) {
  245. return sv, ErrSignatureVersionNotSupported
  246. }
  247. // Strip off the Algorithm prefix.
  248. v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
  249. authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
  250. if len(authFields) != 3 {
  251. return sv, ErrMissingFields
  252. }
  253. // Initialize signature version '4' structured header.
  254. signV4Values := signValues{}
  255. var s3Err APIErrorCode
  256. // Save credential values.
  257. signV4Values.Credential, s3Err = parseCredentialHeader(strings.TrimSpace(credElement), region, stype)
  258. if s3Err != ErrNone {
  259. return sv, s3Err
  260. }
  261. // Save signed headers.
  262. signV4Values.SignedHeaders, s3Err = parseSignedHeader(authFields[1])
  263. if s3Err != ErrNone {
  264. return sv, s3Err
  265. }
  266. // Save signature.
  267. signV4Values.Signature, s3Err = parseSignature(authFields[2])
  268. if s3Err != ErrNone {
  269. return sv, s3Err
  270. }
  271. // Return the structure here.
  272. return signV4Values, ErrNone
  273. }