signature: Handle presigned payload if set.

Validate payload with incoming content.



Fixes #1288
This commit is contained in:
Harshavardhana 2016-04-07 03:04:18 -07:00 committed by Anand Babu (AB) Periasamy
parent 4e6c4da518
commit b182e94acc
5 changed files with 46 additions and 64 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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))

View file

@ -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)
}

View file

@ -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)