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.

841 lines
26 KiB

5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
2 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
2 years ago
4 years ago
5 years ago
2 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
2 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
2 years ago
5 years ago
5 years ago
5 years ago
  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. "bytes"
  19. "crypto/hmac"
  20. "crypto/sha256"
  21. "crypto/subtle"
  22. "encoding/hex"
  23. "hash"
  24. "io"
  25. "net/http"
  26. "net/url"
  27. "regexp"
  28. "sort"
  29. "strconv"
  30. "strings"
  31. "sync"
  32. "sync/atomic"
  33. "time"
  34. "unicode/utf8"
  35. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  36. )
  37. func (iam *IdentityAccessManagement) reqSignatureV4Verify(r *http.Request) (*Identity, s3err.ErrorCode) {
  38. sha256sum := getContentSha256Cksum(r)
  39. switch {
  40. case isRequestSignatureV4(r):
  41. return iam.doesSignatureMatch(sha256sum, r)
  42. case isRequestPresignedSignatureV4(r):
  43. return iam.doesPresignedSignatureMatch(sha256sum, r)
  44. }
  45. return nil, s3err.ErrAccessDenied
  46. }
  47. // Streaming AWS Signature Version '4' constants.
  48. const (
  49. emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  50. streamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
  51. signV4ChunkedAlgorithm = "AWS4-HMAC-SHA256-PAYLOAD"
  52. // http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
  53. // client did not calculate sha256 of the payload.
  54. unsignedPayload = "UNSIGNED-PAYLOAD"
  55. expect100Continue = "100-contine"
  56. )
  57. // Returns SHA256 for calculating canonical-request.
  58. func getContentSha256Cksum(r *http.Request) string {
  59. var (
  60. defaultSha256Cksum string
  61. v []string
  62. ok bool
  63. )
  64. // For a presigned request we look at the query param for sha256.
  65. if isRequestPresignedSignatureV4(r) {
  66. // X-Amz-Content-Sha256, if not set in presigned requests, checksum
  67. // will default to 'UNSIGNED-PAYLOAD'.
  68. defaultSha256Cksum = unsignedPayload
  69. v, ok = r.URL.Query()["X-Amz-Content-Sha256"]
  70. if !ok {
  71. v, ok = r.Header["X-Amz-Content-Sha256"]
  72. }
  73. } else {
  74. // X-Amz-Content-Sha256, if not set in signed requests, checksum
  75. // will default to sha256([]byte("")).
  76. defaultSha256Cksum = emptySHA256
  77. v, ok = r.Header["X-Amz-Content-Sha256"]
  78. }
  79. // We found 'X-Amz-Content-Sha256' return the captured value.
  80. if ok {
  81. return v[0]
  82. }
  83. // We couldn't find 'X-Amz-Content-Sha256'.
  84. return defaultSha256Cksum
  85. }
  86. // Verify authorization header - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
  87. func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r *http.Request) (*Identity, s3err.ErrorCode) {
  88. // Copy request.
  89. req := *r
  90. // Save authorization header.
  91. v4Auth := req.Header.Get("Authorization")
  92. // Parse signature version '4' header.
  93. signV4Values, err := parseSignV4(v4Auth)
  94. if err != s3err.ErrNone {
  95. return nil, err
  96. }
  97. // Extract all the signed headers along with its values.
  98. extractedSignedHeaders, errCode := extractSignedHeaders(signV4Values.SignedHeaders, r)
  99. if errCode != s3err.ErrNone {
  100. return nil, errCode
  101. }
  102. // Verify if the access key id matches.
  103. identity, cred, found := iam.lookupByAccessKey(signV4Values.Credential.accessKey)
  104. if !found {
  105. return nil, s3err.ErrInvalidAccessKeyID
  106. }
  107. // Extract date, if not present throw error.
  108. var date string
  109. if date = req.Header.Get(http.CanonicalHeaderKey("X-Amz-Date")); date == "" {
  110. if date = r.Header.Get("Date"); date == "" {
  111. return nil, s3err.ErrMissingDateHeader
  112. }
  113. }
  114. // Parse date header.
  115. t, e := time.Parse(iso8601Format, date)
  116. if e != nil {
  117. return nil, s3err.ErrMalformedDate
  118. }
  119. // Query string.
  120. queryStr := req.URL.Query().Encode()
  121. // Get hashed Payload
  122. if signV4Values.Credential.scope.service != "s3" && hashedPayload == emptySHA256 && r.Body != nil {
  123. buf, _ := io.ReadAll(r.Body)
  124. r.Body = io.NopCloser(bytes.NewBuffer(buf))
  125. b, _ := io.ReadAll(bytes.NewBuffer(buf))
  126. if len(b) != 0 {
  127. bodyHash := sha256.Sum256(b)
  128. hashedPayload = hex.EncodeToString(bodyHash[:])
  129. }
  130. }
  131. // Get canonical request.
  132. canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method)
  133. // Get string to sign from canonical request.
  134. stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
  135. // Calculate signature.
  136. newSignature := iam.getSignature(
  137. cred.SecretKey,
  138. signV4Values.Credential.scope.date,
  139. signV4Values.Credential.scope.region,
  140. signV4Values.Credential.scope.service,
  141. stringToSign,
  142. )
  143. // Verify if signature match.
  144. if !compareSignatureV4(newSignature, signV4Values.Signature) {
  145. return nil, s3err.ErrSignatureDoesNotMatch
  146. }
  147. // Return error none.
  148. return identity, s3err.ErrNone
  149. }
  150. // credentialHeader data type represents structured form of Credential
  151. // string from authorization header.
  152. type credentialHeader struct {
  153. accessKey string
  154. scope struct {
  155. date time.Time
  156. region string
  157. service string
  158. request string
  159. }
  160. }
  161. // signValues data type represents structured form of AWS Signature V4 header.
  162. type signValues struct {
  163. Credential credentialHeader
  164. SignedHeaders []string
  165. Signature string
  166. }
  167. // Return scope string.
  168. func (c credentialHeader) getScope() string {
  169. return strings.Join([]string{
  170. c.scope.date.Format(yyyymmdd),
  171. c.scope.region,
  172. c.scope.service,
  173. c.scope.request,
  174. }, "/")
  175. }
  176. // Authorization: algorithm Credential=accessKeyID/credScope, \
  177. // SignedHeaders=signedHeaders, Signature=signature
  178. func parseSignV4(v4Auth string) (sv signValues, aec s3err.ErrorCode) {
  179. // Replace all spaced strings, some clients can send spaced
  180. // parameters and some won't. So we pro-actively remove any spaces
  181. // to make parsing easier.
  182. v4Auth = strings.Replace(v4Auth, " ", "", -1)
  183. if v4Auth == "" {
  184. return sv, s3err.ErrAuthHeaderEmpty
  185. }
  186. // Verify if the header algorithm is supported or not.
  187. if !strings.HasPrefix(v4Auth, signV4Algorithm) {
  188. return sv, s3err.ErrSignatureVersionNotSupported
  189. }
  190. // Strip off the Algorithm prefix.
  191. v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
  192. authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
  193. if len(authFields) != 3 {
  194. return sv, s3err.ErrMissingFields
  195. }
  196. // Initialize signature version '4' structured header.
  197. signV4Values := signValues{}
  198. var err s3err.ErrorCode
  199. // Save credential values.
  200. signV4Values.Credential, err = parseCredentialHeader(authFields[0])
  201. if err != s3err.ErrNone {
  202. return sv, err
  203. }
  204. // Save signed headers.
  205. signV4Values.SignedHeaders, err = parseSignedHeader(authFields[1])
  206. if err != s3err.ErrNone {
  207. return sv, err
  208. }
  209. // Save signature.
  210. signV4Values.Signature, err = parseSignature(authFields[2])
  211. if err != s3err.ErrNone {
  212. return sv, err
  213. }
  214. // Return the structure here.
  215. return signV4Values, s3err.ErrNone
  216. }
  217. // parse credentialHeader string into its structured form.
  218. func parseCredentialHeader(credElement string) (ch credentialHeader, aec s3err.ErrorCode) {
  219. creds := strings.Split(strings.TrimSpace(credElement), "=")
  220. if len(creds) != 2 {
  221. return ch, s3err.ErrMissingFields
  222. }
  223. if creds[0] != "Credential" {
  224. return ch, s3err.ErrMissingCredTag
  225. }
  226. credElements := strings.Split(strings.TrimSpace(creds[1]), "/")
  227. if len(credElements) != 5 {
  228. return ch, s3err.ErrCredMalformed
  229. }
  230. // Save access key id.
  231. cred := credentialHeader{
  232. accessKey: credElements[0],
  233. }
  234. var e error
  235. cred.scope.date, e = time.Parse(yyyymmdd, credElements[1])
  236. if e != nil {
  237. return ch, s3err.ErrMalformedCredentialDate
  238. }
  239. cred.scope.region = credElements[2]
  240. cred.scope.service = credElements[3] // "s3"
  241. cred.scope.request = credElements[4] // "aws4_request"
  242. return cred, s3err.ErrNone
  243. }
  244. // Parse slice of signed headers from signed headers tag.
  245. func parseSignedHeader(signedHdrElement string) ([]string, s3err.ErrorCode) {
  246. signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=")
  247. if len(signedHdrFields) != 2 {
  248. return nil, s3err.ErrMissingFields
  249. }
  250. if signedHdrFields[0] != "SignedHeaders" {
  251. return nil, s3err.ErrMissingSignHeadersTag
  252. }
  253. if signedHdrFields[1] == "" {
  254. return nil, s3err.ErrMissingFields
  255. }
  256. signedHeaders := strings.Split(signedHdrFields[1], ";")
  257. return signedHeaders, s3err.ErrNone
  258. }
  259. // Parse signature from signature tag.
  260. func parseSignature(signElement string) (string, s3err.ErrorCode) {
  261. signFields := strings.Split(strings.TrimSpace(signElement), "=")
  262. if len(signFields) != 2 {
  263. return "", s3err.ErrMissingFields
  264. }
  265. if signFields[0] != "Signature" {
  266. return "", s3err.ErrMissingSignTag
  267. }
  268. if signFields[1] == "" {
  269. return "", s3err.ErrMissingFields
  270. }
  271. signature := signFields[1]
  272. return signature, s3err.ErrNone
  273. }
  274. // doesPolicySignatureV4Match - Verify query headers with post policy
  275. // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  276. //
  277. // returns ErrNone if the signature matches.
  278. func (iam *IdentityAccessManagement) doesPolicySignatureV4Match(formValues http.Header) s3err.ErrorCode {
  279. // Parse credential tag.
  280. credHeader, err := parseCredentialHeader("Credential=" + formValues.Get("X-Amz-Credential"))
  281. if err != s3err.ErrNone {
  282. return s3err.ErrMissingFields
  283. }
  284. _, cred, found := iam.lookupByAccessKey(credHeader.accessKey)
  285. if !found {
  286. return s3err.ErrInvalidAccessKeyID
  287. }
  288. // Get signature.
  289. newSignature := iam.getSignature(
  290. cred.SecretKey,
  291. credHeader.scope.date,
  292. credHeader.scope.region,
  293. credHeader.scope.service,
  294. formValues.Get("Policy"),
  295. )
  296. // Verify signature.
  297. if !compareSignatureV4(newSignature, formValues.Get("X-Amz-Signature")) {
  298. return s3err.ErrSignatureDoesNotMatch
  299. }
  300. // Success.
  301. return s3err.ErrNone
  302. }
  303. // check query headers with presigned signature
  304. // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
  305. func (iam *IdentityAccessManagement) doesPresignedSignatureMatch(hashedPayload string, r *http.Request) (*Identity, s3err.ErrorCode) {
  306. // Copy request
  307. req := *r
  308. // Parse request query string.
  309. pSignValues, err := parsePreSignV4(req.URL.Query())
  310. if err != s3err.ErrNone {
  311. return nil, err
  312. }
  313. // Verify if the access key id matches.
  314. identity, cred, found := iam.lookupByAccessKey(pSignValues.Credential.accessKey)
  315. if !found {
  316. return nil, s3err.ErrInvalidAccessKeyID
  317. }
  318. // Extract all the signed headers along with its values.
  319. extractedSignedHeaders, errCode := extractSignedHeaders(pSignValues.SignedHeaders, r)
  320. if errCode != s3err.ErrNone {
  321. return nil, errCode
  322. }
  323. // Construct new query.
  324. query := make(url.Values)
  325. if req.URL.Query().Get("X-Amz-Content-Sha256") != "" {
  326. query.Set("X-Amz-Content-Sha256", hashedPayload)
  327. }
  328. query.Set("X-Amz-Algorithm", signV4Algorithm)
  329. now := time.Now().UTC()
  330. // If the host which signed the request is slightly ahead in time (by less than globalMaxSkewTime) the
  331. // request should still be allowed.
  332. if pSignValues.Date.After(now.Add(15 * time.Minute)) {
  333. return nil, s3err.ErrRequestNotReadyYet
  334. }
  335. if now.Sub(pSignValues.Date) > pSignValues.Expires {
  336. return nil, s3err.ErrExpiredPresignRequest
  337. }
  338. // Save the date and expires.
  339. t := pSignValues.Date
  340. expireSeconds := int(pSignValues.Expires / time.Second)
  341. // Construct the query.
  342. query.Set("X-Amz-Date", t.Format(iso8601Format))
  343. query.Set("X-Amz-Expires", strconv.Itoa(expireSeconds))
  344. query.Set("X-Amz-SignedHeaders", getSignedHeaders(extractedSignedHeaders))
  345. query.Set("X-Amz-Credential", cred.AccessKey+"/"+getScope(t, pSignValues.Credential.scope.region))
  346. // Save other headers available in the request parameters.
  347. for k, v := range req.URL.Query() {
  348. // Handle the metadata in presigned put query string
  349. if strings.Contains(strings.ToLower(k), "x-amz-meta-") {
  350. query.Set(k, v[0])
  351. }
  352. if strings.HasPrefix(strings.ToLower(k), "x-amz") {
  353. continue
  354. }
  355. query[k] = v
  356. }
  357. // Get the encoded query.
  358. encodedQuery := query.Encode()
  359. // Verify if date query is same.
  360. if req.URL.Query().Get("X-Amz-Date") != query.Get("X-Amz-Date") {
  361. return nil, s3err.ErrSignatureDoesNotMatch
  362. }
  363. // Verify if expires query is same.
  364. if req.URL.Query().Get("X-Amz-Expires") != query.Get("X-Amz-Expires") {
  365. return nil, s3err.ErrSignatureDoesNotMatch
  366. }
  367. // Verify if signed headers query is same.
  368. if req.URL.Query().Get("X-Amz-SignedHeaders") != query.Get("X-Amz-SignedHeaders") {
  369. return nil, s3err.ErrSignatureDoesNotMatch
  370. }
  371. // Verify if credential query is same.
  372. if req.URL.Query().Get("X-Amz-Credential") != query.Get("X-Amz-Credential") {
  373. return nil, s3err.ErrSignatureDoesNotMatch
  374. }
  375. // Verify if sha256 payload query is same.
  376. if req.URL.Query().Get("X-Amz-Content-Sha256") != "" {
  377. if req.URL.Query().Get("X-Amz-Content-Sha256") != query.Get("X-Amz-Content-Sha256") {
  378. return nil, s3err.ErrContentSHA256Mismatch
  379. }
  380. }
  381. // / Verify finally if signature is same.
  382. // Get canonical request.
  383. presignedCanonicalReq := getCanonicalRequest(extractedSignedHeaders, hashedPayload, encodedQuery, req.URL.Path, req.Method)
  384. // Get string to sign from canonical request.
  385. presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
  386. // Get new signature.
  387. newSignature := iam.getSignature(
  388. cred.SecretKey,
  389. pSignValues.Credential.scope.date,
  390. pSignValues.Credential.scope.region,
  391. pSignValues.Credential.scope.service,
  392. presignedStringToSign,
  393. )
  394. // Verify signature.
  395. if !compareSignatureV4(req.URL.Query().Get("X-Amz-Signature"), newSignature) {
  396. return nil, s3err.ErrSignatureDoesNotMatch
  397. }
  398. return identity, s3err.ErrNone
  399. }
  400. func (iam *IdentityAccessManagement) getSignature(secretKey string, t time.Time, region string, service string, stringToSign string) string {
  401. pool := iam.getSignatureHashPool(secretKey, t, region, service)
  402. h := pool.Get().(hash.Hash)
  403. defer pool.Put(h)
  404. h.Reset()
  405. h.Write([]byte(stringToSign))
  406. sig := hex.EncodeToString(h.Sum(nil))
  407. return sig
  408. }
  409. func (iam *IdentityAccessManagement) getSignatureHashPool(secretKey string, t time.Time, region string, service string) *sync.Pool {
  410. // Build a caching key for the pool.
  411. date := t.Format(yyyymmdd)
  412. hashID := "AWS4" + secretKey + "/" + date + "/" + region + "/" + service + "/" + "aws4_request"
  413. // Try to find an existing pool and return it.
  414. iam.hashMu.RLock()
  415. pool, ok := iam.hashes[hashID]
  416. iam.hashMu.RUnlock()
  417. if !ok {
  418. iam.hashMu.Lock()
  419. defer iam.hashMu.Unlock()
  420. pool, ok = iam.hashes[hashID]
  421. }
  422. if ok {
  423. atomic.StoreInt32(iam.hashCounters[hashID], 1)
  424. return pool
  425. }
  426. // Create a pool that returns HMAC hashers for the requested parameters to avoid expensive re-initializing
  427. // of new instances on every request.
  428. iam.hashes[hashID] = &sync.Pool{
  429. New: func() any {
  430. signingKey := getSigningKey(secretKey, date, region, service)
  431. return hmac.New(sha256.New, signingKey)
  432. },
  433. }
  434. iam.hashCounters[hashID] = new(int32)
  435. // Clean up unused pools automatically after one hour of inactivity
  436. ticker := time.NewTicker(time.Hour)
  437. go func() {
  438. for range ticker.C {
  439. old := atomic.SwapInt32(iam.hashCounters[hashID], 0)
  440. if old == 0 {
  441. break
  442. }
  443. }
  444. ticker.Stop()
  445. iam.hashMu.Lock()
  446. delete(iam.hashes, hashID)
  447. delete(iam.hashCounters, hashID)
  448. iam.hashMu.Unlock()
  449. }()
  450. return iam.hashes[hashID]
  451. }
  452. func contains(list []string, elem string) bool {
  453. for _, t := range list {
  454. if t == elem {
  455. return true
  456. }
  457. }
  458. return false
  459. }
  460. // preSignValues data type represents structured form of AWS Signature V4 query string.
  461. type preSignValues struct {
  462. signValues
  463. Date time.Time
  464. Expires time.Duration
  465. }
  466. // Parses signature version '4' query string of the following form.
  467. //
  468. // querystring = X-Amz-Algorithm=algorithm
  469. // querystring += &X-Amz-Credential= urlencode(accessKey + '/' + credential_scope)
  470. // querystring += &X-Amz-Date=date
  471. // querystring += &X-Amz-Expires=timeout interval
  472. // querystring += &X-Amz-SignedHeaders=signed_headers
  473. // querystring += &X-Amz-Signature=signature
  474. //
  475. // verifies if any of the necessary query params are missing in the presigned request.
  476. func doesV4PresignParamsExist(query url.Values) s3err.ErrorCode {
  477. v4PresignQueryParams := []string{"X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Signature", "X-Amz-Date", "X-Amz-SignedHeaders", "X-Amz-Expires"}
  478. for _, v4PresignQueryParam := range v4PresignQueryParams {
  479. if _, ok := query[v4PresignQueryParam]; !ok {
  480. return s3err.ErrInvalidQueryParams
  481. }
  482. }
  483. return s3err.ErrNone
  484. }
  485. // Parses all the presigned signature values into separate elements.
  486. func parsePreSignV4(query url.Values) (psv preSignValues, aec s3err.ErrorCode) {
  487. var err s3err.ErrorCode
  488. // verify whether the required query params exist.
  489. err = doesV4PresignParamsExist(query)
  490. if err != s3err.ErrNone {
  491. return psv, err
  492. }
  493. // Verify if the query algorithm is supported or not.
  494. if query.Get("X-Amz-Algorithm") != signV4Algorithm {
  495. return psv, s3err.ErrInvalidQuerySignatureAlgo
  496. }
  497. // Initialize signature version '4' structured header.
  498. preSignV4Values := preSignValues{}
  499. // Save credential.
  500. preSignV4Values.Credential, err = parseCredentialHeader("Credential=" + query.Get("X-Amz-Credential"))
  501. if err != s3err.ErrNone {
  502. return psv, err
  503. }
  504. var e error
  505. // Save date in native time.Time.
  506. preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get("X-Amz-Date"))
  507. if e != nil {
  508. return psv, s3err.ErrMalformedPresignedDate
  509. }
  510. // Save expires in native time.Duration.
  511. preSignV4Values.Expires, e = time.ParseDuration(query.Get("X-Amz-Expires") + "s")
  512. if e != nil {
  513. return psv, s3err.ErrMalformedExpires
  514. }
  515. if preSignV4Values.Expires < 0 {
  516. return psv, s3err.ErrNegativeExpires
  517. }
  518. // Check if Expiry time is less than 7 days (value in seconds).
  519. if preSignV4Values.Expires.Seconds() > 604800 {
  520. return psv, s3err.ErrMaximumExpires
  521. }
  522. // Save signed headers.
  523. preSignV4Values.SignedHeaders, err = parseSignedHeader("SignedHeaders=" + query.Get("X-Amz-SignedHeaders"))
  524. if err != s3err.ErrNone {
  525. return psv, err
  526. }
  527. // Save signature.
  528. preSignV4Values.Signature, err = parseSignature("Signature=" + query.Get("X-Amz-Signature"))
  529. if err != s3err.ErrNone {
  530. return psv, err
  531. }
  532. // Return structured form of signature query string.
  533. return preSignV4Values, s3err.ErrNone
  534. }
  535. // extractSignedHeaders extract signed headers from Authorization header
  536. func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, s3err.ErrorCode) {
  537. reqHeaders := r.Header
  538. // find whether "host" is part of list of signed headers.
  539. // if not return ErrUnsignedHeaders. "host" is mandatory.
  540. if !contains(signedHeaders, "host") {
  541. return nil, s3err.ErrUnsignedHeaders
  542. }
  543. extractedSignedHeaders := make(http.Header)
  544. for _, header := range signedHeaders {
  545. // `host` will not be found in the headers, can be found in r.Host.
  546. // but its alway necessary that the list of signed headers containing host in it.
  547. val, ok := reqHeaders[http.CanonicalHeaderKey(header)]
  548. if ok {
  549. for _, enc := range val {
  550. extractedSignedHeaders.Add(header, enc)
  551. }
  552. continue
  553. }
  554. switch header {
  555. case "expect":
  556. // Golang http server strips off 'Expect' header, if the
  557. // client sent this as part of signed headers we need to
  558. // handle otherwise we would see a signature mismatch.
  559. // `aws-cli` sets this as part of signed headers.
  560. //
  561. // According to
  562. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20
  563. // Expect header is always of form:
  564. //
  565. // Expect = "Expect" ":" 1#expectation
  566. // expectation = "100-continue" | expectation-extension
  567. //
  568. // So it safe to assume that '100-continue' is what would
  569. // be sent, for the time being keep this work around.
  570. // Adding a *TODO* to remove this later when Golang server
  571. // doesn't filter out the 'Expect' header.
  572. expectHeaderValue := extractedSignedHeaders.Get(header)
  573. // here in order to be compatible with the aws go sdk v1 version, it sets the expect header to '100-Continue'
  574. if !strings.EqualFold(expectHeaderValue, expect100Continue) {
  575. extractedSignedHeaders.Set(header, expect100Continue)
  576. }
  577. case "host":
  578. // Go http server removes "host" from Request.Header
  579. extractedSignedHeaders.Set(header, r.Host)
  580. case "transfer-encoding":
  581. for _, enc := range r.TransferEncoding {
  582. extractedSignedHeaders.Add(header, enc)
  583. }
  584. case "content-length":
  585. // Signature-V4 spec excludes Content-Length from signed headers list for signature calculation.
  586. // But some clients deviate from this rule. Hence we consider Content-Length for signature
  587. // calculation to be compatible with such clients.
  588. extractedSignedHeaders.Set(header, strconv.FormatInt(r.ContentLength, 10))
  589. default:
  590. return nil, s3err.ErrUnsignedHeaders
  591. }
  592. }
  593. return extractedSignedHeaders, s3err.ErrNone
  594. }
  595. // getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
  596. func getSignedHeaders(signedHeaders http.Header) string {
  597. var headers []string
  598. for k := range signedHeaders {
  599. headers = append(headers, strings.ToLower(k))
  600. }
  601. sort.Strings(headers)
  602. return strings.Join(headers, ";")
  603. }
  604. // getScope generate a string of a specific date, an AWS region, and a service.
  605. func getScope(t time.Time, region string) string {
  606. scope := strings.Join([]string{
  607. t.Format(yyyymmdd),
  608. region,
  609. "s3",
  610. "aws4_request",
  611. }, "/")
  612. return scope
  613. }
  614. // getCanonicalRequest generate a canonical request of style
  615. //
  616. // canonicalRequest =
  617. //
  618. // <HTTPMethod>\n
  619. // <CanonicalURI>\n
  620. // <CanonicalQueryString>\n
  621. // <CanonicalHeaders>\n
  622. // <SignedHeaders>\n
  623. // <HashedPayload>
  624. func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr, urlPath, method string) string {
  625. rawQuery := strings.Replace(queryStr, "+", "%20", -1)
  626. encodedPath := encodePath(urlPath)
  627. canonicalRequest := strings.Join([]string{
  628. method,
  629. encodedPath,
  630. rawQuery,
  631. getCanonicalHeaders(extractedSignedHeaders),
  632. getSignedHeaders(extractedSignedHeaders),
  633. payload,
  634. }, "\n")
  635. return canonicalRequest
  636. }
  637. // getStringToSign a string based on selected query values.
  638. func getStringToSign(canonicalRequest string, t time.Time, scope string) string {
  639. stringToSign := signV4Algorithm + "\n" + t.Format(iso8601Format) + "\n"
  640. stringToSign = stringToSign + scope + "\n"
  641. canonicalRequestBytes := sha256.Sum256([]byte(canonicalRequest))
  642. stringToSign = stringToSign + hex.EncodeToString(canonicalRequestBytes[:])
  643. return stringToSign
  644. }
  645. // sumHMAC calculate hmac between two input byte array.
  646. func sumHMAC(key []byte, data []byte) []byte {
  647. hash := hmac.New(sha256.New, key)
  648. hash.Write(data)
  649. return hash.Sum(nil)
  650. }
  651. // getSigningKey hmac seed to calculate final signature.
  652. func getSigningKey(secretKey string, time string, region string, service string) []byte {
  653. date := sumHMAC([]byte("AWS4"+secretKey), []byte(time))
  654. regionBytes := sumHMAC(date, []byte(region))
  655. serviceBytes := sumHMAC(regionBytes, []byte(service))
  656. signingKey := sumHMAC(serviceBytes, []byte("aws4_request"))
  657. return signingKey
  658. }
  659. // getCanonicalHeaders generate a list of request headers with their values
  660. func getCanonicalHeaders(signedHeaders http.Header) string {
  661. var headers []string
  662. vals := make(http.Header)
  663. for k, vv := range signedHeaders {
  664. headers = append(headers, strings.ToLower(k))
  665. vals[strings.ToLower(k)] = vv
  666. }
  667. sort.Strings(headers)
  668. var buf bytes.Buffer
  669. for _, k := range headers {
  670. buf.WriteString(k)
  671. buf.WriteByte(':')
  672. for idx, v := range vals[k] {
  673. if idx > 0 {
  674. buf.WriteByte(',')
  675. }
  676. buf.WriteString(signV4TrimAll(v))
  677. }
  678. buf.WriteByte('\n')
  679. }
  680. return buf.String()
  681. }
  682. // Trim leading and trailing spaces and replace sequential spaces with one space, following Trimall()
  683. // in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
  684. func signV4TrimAll(input string) string {
  685. // Compress adjacent spaces (a space is determined by
  686. // unicode.IsSpace() internally here) to one space and return
  687. return strings.Join(strings.Fields(input), " ")
  688. }
  689. // if object matches reserved string, no need to encode them
  690. var reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
  691. // EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
  692. //
  693. // This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
  694. // non english characters cannot be parsed due to the nature in which url.Encode() is written
  695. //
  696. // This function on the other hand is a direct replacement for url.Encode() technique to support
  697. // pretty much every UTF-8 character.
  698. func encodePath(pathName string) string {
  699. if reservedObjectNames.MatchString(pathName) {
  700. return pathName
  701. }
  702. var encodedPathname string
  703. for _, s := range pathName {
  704. if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
  705. encodedPathname = encodedPathname + string(s)
  706. continue
  707. }
  708. switch s {
  709. case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
  710. encodedPathname = encodedPathname + string(s)
  711. continue
  712. default:
  713. len := utf8.RuneLen(s)
  714. if len < 0 {
  715. // if utf8 cannot convert return the same string as is
  716. return pathName
  717. }
  718. u := make([]byte, len)
  719. utf8.EncodeRune(u, s)
  720. for _, r := range u {
  721. hex := hex.EncodeToString([]byte{r})
  722. encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
  723. }
  724. }
  725. }
  726. return encodedPathname
  727. }
  728. // compareSignatureV4 returns true if and only if both signatures
  729. // are equal. The signatures are expected to be HEX encoded strings
  730. // according to the AWS S3 signature V4 spec.
  731. func compareSignatureV4(sig1, sig2 string) bool {
  732. // The CTC using []byte(str) works because the hex encoding
  733. // is unique for a sequence of bytes. See also compareSignatureV2.
  734. return subtle.ConstantTimeCompare([]byte(sig1), []byte(sig2)) == 1
  735. }