mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
signature: Handle presigned payload if set.
Validate payload with incoming content. Fixes #1288
This commit is contained in:
parent
4e6c4da518
commit
b182e94acc
@ -19,13 +19,13 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
fastSha256 "github.com/minio/minio/pkg/crypto/sha256"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
@ -98,7 +98,7 @@ func getRequestAuthType(r *http.Request) authType {
|
||||
|
||||
// sum256 calculate sha256 sum for an input byte array
|
||||
func sum256(data []byte) []byte {
|
||||
hash := sha256.New()
|
||||
hash := fastSha256.New()
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
@ -133,7 +133,7 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
|
||||
if isRequestSignatureV4(r) {
|
||||
return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
return doesPresignedSignatureMatch(r, validateRegion)
|
||||
return doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
}
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func (api objectStorageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *h
|
||||
if isRequestSignatureV4(r) {
|
||||
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
s3Error = doesPresignedSignatureMatch(r, validateRegion)
|
||||
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
@ -330,7 +330,7 @@ func (api objectStorageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Re
|
||||
if isRequestSignatureV4(r) {
|
||||
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
s3Error = doesPresignedSignatureMatch(r, validateRegion)
|
||||
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
|
@ -17,7 +17,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
@ -30,6 +29,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
fastSha256 "github.com/minio/minio/pkg/crypto/sha256"
|
||||
|
||||
mux "github.com/gorilla/mux"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
@ -619,22 +620,13 @@ func (api objectStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
// Create anonymous object.
|
||||
objInfo, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, nil)
|
||||
case authTypePresigned:
|
||||
validateRegion := true // Validate region.
|
||||
// For presigned requests verify them right here.
|
||||
if apiErr := doesPresignedSignatureMatch(r, validateRegion); apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Create presigned object.
|
||||
objInfo, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, nil)
|
||||
case authTypeSigned:
|
||||
case authTypePresigned, authTypeSigned:
|
||||
// Initialize a pipe for data pipe line.
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
// Start writing in a routine.
|
||||
go func() {
|
||||
shaWriter := sha256.New()
|
||||
shaWriter := fastSha256.New()
|
||||
multiWriter := io.MultiWriter(shaWriter, writer)
|
||||
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
||||
errorIf(probe.NewError(e), "Unable to read HTTP body.", nil)
|
||||
@ -643,14 +635,21 @@ func (api objectStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
shaPayload := shaWriter.Sum(nil)
|
||||
validateRegion := true // Validate region.
|
||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion); apiErr != ErrNone {
|
||||
if apiErr == ErrSignatureDoesNotMatch {
|
||||
var s3Error APIErrorCode
|
||||
if isRequestSignatureV4(r) {
|
||||
s3Error = doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion)
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
if s3Error == ErrSignatureDoesNotMatch {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
return
|
||||
}
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(s3Error)))
|
||||
return
|
||||
}
|
||||
// Close the writer.
|
||||
writer.Close()
|
||||
}()
|
||||
|
||||
@ -799,22 +798,14 @@ func (api objectStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.
|
||||
// No need to verify signature, anonymous request access is
|
||||
// already allowed.
|
||||
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, hex.EncodeToString(md5Bytes))
|
||||
case authTypePresigned:
|
||||
case authTypePresigned, authTypeSigned:
|
||||
validateRegion := true // Validate region.
|
||||
// For presigned requests verify right here.
|
||||
apiErr := doesPresignedSignatureMatch(r, validateRegion)
|
||||
if apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, hex.EncodeToString(md5Bytes))
|
||||
case authTypeSigned:
|
||||
// Initialize a pipe for data pipe line.
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
// Start writing in a routine.
|
||||
go func() {
|
||||
shaWriter := sha256.New()
|
||||
shaWriter := fastSha256.New()
|
||||
multiWriter := io.MultiWriter(shaWriter, writer)
|
||||
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
||||
errorIf(probe.NewError(e), "Unable to read HTTP body.", nil)
|
||||
@ -822,15 +813,21 @@ func (api objectStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
shaPayload := shaWriter.Sum(nil)
|
||||
validateRegion := true // Validate region.
|
||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion); apiErr != ErrNone {
|
||||
if apiErr == ErrSignatureDoesNotMatch {
|
||||
var s3Error APIErrorCode
|
||||
if isRequestSignatureV4(r) {
|
||||
s3Error = doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion)
|
||||
}
|
||||
if s3Error != ErrNone {
|
||||
if s3Error == ErrSignatureDoesNotMatch {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
return
|
||||
}
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(s3Error)))
|
||||
return
|
||||
}
|
||||
// Close the writer.
|
||||
writer.Close()
|
||||
}()
|
||||
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, hex.EncodeToString(md5Bytes))
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/minio/minio/pkg/crypto/sha256"
|
||||
fastSha256 "github.com/minio/minio/pkg/crypto/sha256"
|
||||
)
|
||||
|
||||
// isValidRegion - verify if incoming region value is valid with configured Region.
|
||||
@ -42,7 +42,7 @@ func isValidRegion(reqRegion string, confRegion string) bool {
|
||||
|
||||
// sumHMAC calculate hmac between two input byte array.
|
||||
func sumHMAC(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash := hmac.New(fastSha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
@ -113,32 +113,6 @@ func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr,
|
||||
return canonicalRequest
|
||||
}
|
||||
|
||||
// getCanonicalRequest generate a canonical request of style
|
||||
//
|
||||
// canonicalRequest =
|
||||
// <HTTPMethod>\n
|
||||
// <CanonicalURI>\n
|
||||
// <CanonicalQueryString>\n
|
||||
// <CanonicalHeaders>\n
|
||||
// <SignedHeaders>\n
|
||||
// <HashedPayload>
|
||||
//
|
||||
func getPresignCanonicalRequest(extractedSignedHeaders http.Header, presignedQuery, urlPath, method, host string) string {
|
||||
rawQuery := strings.Replace(presignedQuery, "+", "%20", -1)
|
||||
encodedPath := getURLEncodedName(urlPath)
|
||||
// Convert any space strings back to "+".
|
||||
encodedPath = strings.Replace(encodedPath, "+", "%20", -1)
|
||||
canonicalRequest := strings.Join([]string{
|
||||
method,
|
||||
encodedPath,
|
||||
rawQuery,
|
||||
getCanonicalHeaders(extractedSignedHeaders, host),
|
||||
getSignedHeaders(extractedSignedHeaders),
|
||||
"UNSIGNED-PAYLOAD",
|
||||
}, "\n")
|
||||
return canonicalRequest
|
||||
}
|
||||
|
||||
// getScope generate a string of a specific date, an AWS region, and a service.
|
||||
func getScope(t time.Time, region string) string {
|
||||
scope := strings.Join([]string{
|
||||
@ -222,7 +196,7 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode {
|
||||
// doesPresignedSignatureMatch - Verify query headers with presigned signature
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
|
||||
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||
func doesPresignedSignatureMatch(r *http.Request, validateRegion bool) APIErrorCode {
|
||||
func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, validateRegion bool) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
@ -260,6 +234,11 @@ func doesPresignedSignatureMatch(r *http.Request, validateRegion bool) APIErrorC
|
||||
|
||||
// Construct new query.
|
||||
query := make(url.Values)
|
||||
if req.URL.Query().Get("X-Amz-Content-Sha256") != "" {
|
||||
query.Set("X-Amz-Content-Sha256", hashedPayload)
|
||||
} else {
|
||||
hashedPayload = "UNSIGNED-PAYLOAD"
|
||||
}
|
||||
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||
|
||||
if time.Now().UTC().Sub(preSignValues.Date) > time.Duration(preSignValues.Expires) {
|
||||
@ -303,11 +282,17 @@ func doesPresignedSignatureMatch(r *http.Request, validateRegion bool) APIErrorC
|
||||
if req.URL.Query().Get("X-Amz-Credential") != query.Get("X-Amz-Credential") {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
// Verify if sha256 payload query is same.
|
||||
if req.URL.Query().Get("X-Amz-Content-Sha256") != "" {
|
||||
if req.URL.Query().Get("X-Amz-Content-Sha256") != query.Get("X-Amz-Content-Sha256") {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify finally if signature is same.
|
||||
|
||||
// Get canonical request.
|
||||
presignedCanonicalReq := getPresignCanonicalRequest(extractedSignedHeaders, encodedQuery, req.URL.Path, req.Method, req.Host)
|
||||
presignedCanonicalReq := getCanonicalRequest(extractedSignedHeaders, hashedPayload, encodedQuery, req.URL.Path, req.Method, req.Host)
|
||||
|
||||
// Get string to sign from canonical request.
|
||||
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, region)
|
||||
|
Loading…
Reference in New Issue
Block a user