mirror of
https://github.com/minio/minio.git
synced 2025-04-13 07:50:15 -04: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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
fastSha256 "github.com/minio/minio/pkg/crypto/sha256"
|
||||||
"github.com/minio/minio/pkg/probe"
|
"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
|
// sum256 calculate sha256 sum for an input byte array
|
||||||
func sum256(data []byte) []byte {
|
func sum256(data []byte) []byte {
|
||||||
hash := sha256.New()
|
hash := fastSha256.New()
|
||||||
hash.Write(data)
|
hash.Write(data)
|
||||||
return hash.Sum(nil)
|
return hash.Sum(nil)
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
|
|||||||
if isRequestSignatureV4(r) {
|
if isRequestSignatureV4(r) {
|
||||||
return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
} else if isRequestPresignedSignatureV4(r) {
|
} else if isRequestPresignedSignatureV4(r) {
|
||||||
return doesPresignedSignatureMatch(r, validateRegion)
|
return doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
}
|
}
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ func (api objectStorageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *h
|
|||||||
if isRequestSignatureV4(r) {
|
if isRequestSignatureV4(r) {
|
||||||
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
} else if isRequestPresignedSignatureV4(r) {
|
} else if isRequestPresignedSignatureV4(r) {
|
||||||
s3Error = doesPresignedSignatureMatch(r, validateRegion)
|
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
}
|
}
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
@ -330,7 +330,7 @@ func (api objectStorageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Re
|
|||||||
if isRequestSignatureV4(r) {
|
if isRequestSignatureV4(r) {
|
||||||
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
} else if isRequestPresignedSignatureV4(r) {
|
} else if isRequestPresignedSignatureV4(r) {
|
||||||
s3Error = doesPresignedSignatureMatch(r, validateRegion)
|
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
|
||||||
}
|
}
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -30,6 +29,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
fastSha256 "github.com/minio/minio/pkg/crypto/sha256"
|
||||||
|
|
||||||
mux "github.com/gorilla/mux"
|
mux "github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/pkg/probe"
|
"github.com/minio/minio/pkg/probe"
|
||||||
)
|
)
|
||||||
@ -619,22 +620,13 @@ func (api objectStorageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
// Create anonymous object.
|
// Create anonymous object.
|
||||||
objInfo, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, nil)
|
objInfo, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, nil)
|
||||||
case authTypePresigned:
|
case authTypePresigned, authTypeSigned:
|
||||||
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:
|
|
||||||
// Initialize a pipe for data pipe line.
|
// Initialize a pipe for data pipe line.
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
// Start writing in a routine.
|
// Start writing in a routine.
|
||||||
go func() {
|
go func() {
|
||||||
shaWriter := sha256.New()
|
shaWriter := fastSha256.New()
|
||||||
multiWriter := io.MultiWriter(shaWriter, writer)
|
multiWriter := io.MultiWriter(shaWriter, writer)
|
||||||
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
||||||
errorIf(probe.NewError(e), "Unable to read HTTP body.", 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)
|
shaPayload := shaWriter.Sum(nil)
|
||||||
validateRegion := true // Validate region.
|
validateRegion := true // Validate region.
|
||||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion); apiErr != ErrNone {
|
var s3Error APIErrorCode
|
||||||
if apiErr == ErrSignatureDoesNotMatch {
|
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)
|
writer.CloseWithError(errSignatureMismatch)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
writer.CloseWithError(fmt.Errorf("%v", getAPIError(s3Error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Close the writer.
|
||||||
writer.Close()
|
writer.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -799,22 +798,14 @@ func (api objectStorageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.
|
|||||||
// No need to verify signature, anonymous request access is
|
// No need to verify signature, anonymous request access is
|
||||||
// already allowed.
|
// already allowed.
|
||||||
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, hex.EncodeToString(md5Bytes))
|
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, hex.EncodeToString(md5Bytes))
|
||||||
case authTypePresigned:
|
case authTypePresigned, authTypeSigned:
|
||||||
validateRegion := true // Validate region.
|
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.
|
// Initialize a pipe for data pipe line.
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
// Start writing in a routine.
|
// Start writing in a routine.
|
||||||
go func() {
|
go func() {
|
||||||
shaWriter := sha256.New()
|
shaWriter := fastSha256.New()
|
||||||
multiWriter := io.MultiWriter(shaWriter, writer)
|
multiWriter := io.MultiWriter(shaWriter, writer)
|
||||||
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
if _, e := io.CopyN(multiWriter, r.Body, size); e != nil {
|
||||||
errorIf(probe.NewError(e), "Unable to read HTTP body.", 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
|
return
|
||||||
}
|
}
|
||||||
shaPayload := shaWriter.Sum(nil)
|
shaPayload := shaWriter.Sum(nil)
|
||||||
validateRegion := true // Validate region.
|
var s3Error APIErrorCode
|
||||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r, validateRegion); apiErr != ErrNone {
|
if isRequestSignatureV4(r) {
|
||||||
if apiErr == ErrSignatureDoesNotMatch {
|
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)
|
writer.CloseWithError(errSignatureMismatch)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
writer.CloseWithError(fmt.Errorf("%v", getAPIError(s3Error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Close the writer.
|
||||||
writer.Close()
|
writer.Close()
|
||||||
}()
|
}()
|
||||||
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, hex.EncodeToString(md5Bytes))
|
partMD5, err = api.ObjectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, hex.EncodeToString(md5Bytes))
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"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.
|
// 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.
|
// sumHMAC calculate hmac between two input byte array.
|
||||||
func sumHMAC(key []byte, data []byte) []byte {
|
func sumHMAC(key []byte, data []byte) []byte {
|
||||||
hash := hmac.New(sha256.New, key)
|
hash := hmac.New(fastSha256.New, key)
|
||||||
hash.Write(data)
|
hash.Write(data)
|
||||||
return hash.Sum(nil)
|
return hash.Sum(nil)
|
||||||
}
|
}
|
||||||
|
@ -113,32 +113,6 @@ func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr,
|
|||||||
return canonicalRequest
|
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.
|
// getScope generate a string of a specific date, an AWS region, and a service.
|
||||||
func getScope(t time.Time, region string) string {
|
func getScope(t time.Time, region string) string {
|
||||||
scope := strings.Join([]string{
|
scope := strings.Join([]string{
|
||||||
@ -222,7 +196,7 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode {
|
|||||||
// doesPresignedSignatureMatch - Verify query headers with presigned signature
|
// doesPresignedSignatureMatch - Verify query headers with presigned signature
|
||||||
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
|
// - 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
|
// 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.
|
// Access credentials.
|
||||||
cred := serverConfig.GetCredential()
|
cred := serverConfig.GetCredential()
|
||||||
|
|
||||||
@ -260,6 +234,11 @@ func doesPresignedSignatureMatch(r *http.Request, validateRegion bool) APIErrorC
|
|||||||
|
|
||||||
// Construct new query.
|
// Construct new query.
|
||||||
query := make(url.Values)
|
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)
|
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||||
|
|
||||||
if time.Now().UTC().Sub(preSignValues.Date) > time.Duration(preSignValues.Expires) {
|
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") {
|
if req.URL.Query().Get("X-Amz-Credential") != query.Get("X-Amz-Credential") {
|
||||||
return ErrSignatureDoesNotMatch
|
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.
|
/// Verify finally if signature is same.
|
||||||
|
|
||||||
// Get canonical request.
|
// 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.
|
// Get string to sign from canonical request.
|
||||||
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, region)
|
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, region)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user