From b182e94acc5eb4d9e310ba9f8781ddf131862d97 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 7 Apr 2016 03:04:18 -0700 Subject: [PATCH] signature: Handle presigned payload if set. Validate payload with incoming content. Fixes #1288 --- auth-handler.go | 6 ++--- bucket-handlers.go | 4 ++-- object-handlers.go | 55 ++++++++++++++++++++----------------------- signature-v4-utils.go | 4 ++-- signature-v4.go | 41 ++++++++++---------------------- 5 files changed, 46 insertions(+), 64 deletions(-) diff --git a/auth-handler.go b/auth-handler.go index 61bec64e7..3133615bc 100644 --- a/auth-handler.go +++ b/auth-handler.go @@ -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 } diff --git a/bucket-handlers.go b/bucket-handlers.go index 56954e958..36b192f8e 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -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) diff --git a/object-handlers.go b/object-handlers.go index 852df66b9..7409dffc5 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -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)) diff --git a/signature-v4-utils.go b/signature-v4-utils.go index b01011b8c..33b695213 100644 --- a/signature-v4-utils.go +++ b/signature-v4-utils.go @@ -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) } diff --git a/signature-v4.go b/signature-v4.go index 93d5060e6..2959b7002 100644 --- a/signature-v4.go +++ b/signature-v4.go @@ -113,32 +113,6 @@ func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr, return canonicalRequest } -// getCanonicalRequest generate a canonical request of style -// -// canonicalRequest = -// \n -// \n -// \n -// \n -// \n -// -// -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)