mirror of https://github.com/minio/minio.git
Merge pull request #1275 from harshavardhana/signature
error: Signature errors should be returned with APIErrorCode.
This commit is contained in:
commit
ae5c65d3c6
|
@ -87,6 +87,23 @@ const (
|
|||
ErrObjectExistsAsPrefix
|
||||
ErrAllAccessDisabled
|
||||
ErrMalformedPolicy
|
||||
ErrMissingFields
|
||||
ErrMissingCredTag
|
||||
ErrCredMalformed
|
||||
ErrInvalidRegion
|
||||
ErrInvalidService
|
||||
ErrInvalidRequestVersion
|
||||
ErrMissingSignTag
|
||||
ErrMissingSignHeadersTag
|
||||
ErrPolicyAlreadyExpired
|
||||
ErrMalformedDate
|
||||
ErrMalformedExpires
|
||||
ErrAuthHeaderEmpty
|
||||
ErrDateHeaderMissing
|
||||
ErrExpiredPresignRequest
|
||||
ErrMissingDateHeader
|
||||
ErrInvalidQuerySignatureAlgo
|
||||
ErrInvalidQueryParams
|
||||
// Add new error codes here.
|
||||
)
|
||||
|
||||
|
@ -295,7 +312,87 @@ var errorCodeResponse = map[APIErrorCode]APIError{
|
|||
},
|
||||
ErrMalformedPolicy: {
|
||||
Code: "MalformedPolicy",
|
||||
Description: "Policy has invalid resource",
|
||||
Description: "Policy has invalid resource.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingFields: {
|
||||
Code: "MissingFields",
|
||||
Description: "Missing fields in request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingCredTag: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Missing Credential field for this request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrCredMalformed: {
|
||||
Code: "CredentialMalformed",
|
||||
Description: "Credential field malformed does not follow accessKeyID/credScope.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedDate: {
|
||||
Code: "MalformedDate",
|
||||
Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRegion: {
|
||||
Code: "InvalidRegion",
|
||||
Description: "Region does not match.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidService: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Service scope should be of value 's3'.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRequestVersion: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Request scope should be of value 'aws4_request'.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignTag: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Signature header missing Signature field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignHeadersTag: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Signature header missing SignedHeaders field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPolicyAlreadyExpired: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Invalid according to Policy: Policy expired.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedExpires: {
|
||||
Code: "MalformedExpires",
|
||||
Description: "Malformed expires header, expected non-zero number.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAuthHeaderEmpty: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingDateHeader: {
|
||||
Code: "AccessDenied",
|
||||
Description: "AWS authentication requires a valid Date or x-amz-date header",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidQuerySignatureAlgo: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrExpiredPresignRequest: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Request has expired.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidQueryParams: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
// Add your error structure here.
|
||||
|
|
|
@ -130,25 +130,9 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
|
|||
// Populate back the payload.
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
if isRequestSignatureV4(r) {
|
||||
ok, err := doesSignatureMatch(hex.EncodeToString(sum256(payload)), r)
|
||||
if err != nil {
|
||||
errorIf(err.Trace(), "Signature verification failed.", nil)
|
||||
return ErrInternalError
|
||||
}
|
||||
if !ok {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
return ErrNone
|
||||
return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r)
|
||||
} else if isRequestPresignedSignatureV4(r) {
|
||||
ok, err := doesPresignedSignatureMatch(r)
|
||||
if err != nil {
|
||||
errorIf(err.Trace(), "Presigned signature verification failed.", nil)
|
||||
return ErrInternalError
|
||||
}
|
||||
if !ok {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
return ErrNone
|
||||
return doesPresignedSignatureMatch(r)
|
||||
}
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
|
|
@ -474,22 +474,15 @@ func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Req
|
|||
bucket := mux.Vars(r)["bucket"]
|
||||
formValues["Bucket"] = bucket
|
||||
object := formValues["Key"]
|
||||
var ok bool
|
||||
|
||||
// Verify policy signature.
|
||||
ok, err = doesPolicySignatureMatch(formValues)
|
||||
if err != nil {
|
||||
errorIf(err.Trace(), "Unable to verify signature.", nil)
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
apiErr := doesPolicySignatureMatch(formValues)
|
||||
if apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if err = checkPostPolicy(formValues); err != nil {
|
||||
errorIf(err.Trace(), "Invalid request, policy doesn't match.", nil)
|
||||
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
|
||||
if apiErr = checkPostPolicy(formValues); apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
objectInfo, err := api.Filesystem.CreateObject(bucket, object, -1, fileBody, nil)
|
||||
|
|
17
flags.go
17
flags.go
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
|
@ -17,7 +17,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
|
@ -120,51 +119,43 @@ func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
|
|||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Supported incoming date formats.
|
||||
var timeFormats = []string{
|
||||
// Supported Amz date formats.
|
||||
var amzDateFormats = []string{
|
||||
time.RFC1123,
|
||||
time.RFC1123Z,
|
||||
iso8601Format,
|
||||
// Add new AMZ date formats here.
|
||||
}
|
||||
|
||||
// Attempts to parse date string into known date layouts. Date layouts
|
||||
// currently supported are
|
||||
// - ``time.RFC1123``
|
||||
// - ``time.RFC1123Z``
|
||||
// - ``iso8601Format``
|
||||
func parseDate(date string) (parsedTime time.Time, e error) {
|
||||
for _, layout := range timeFormats {
|
||||
parsedTime, e = time.Parse(layout, date)
|
||||
// parseAmzDate - parses date string into supported amz date formats.
|
||||
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
|
||||
for _, dateFormat := range amzDateFormats {
|
||||
amzDate, e := time.Parse(dateFormat, amzDateStr)
|
||||
if e == nil {
|
||||
return parsedTime, nil
|
||||
return amzDate, ErrNone
|
||||
}
|
||||
}
|
||||
return time.Time{}, e
|
||||
return time.Time{}, ErrMalformedDate
|
||||
}
|
||||
|
||||
// Parse date string from incoming header, current supports and verifies
|
||||
// follow HTTP headers.
|
||||
//
|
||||
// - X-Amz-Date
|
||||
// - X-Minio-Date
|
||||
// - Date
|
||||
//
|
||||
// In following time layouts ``time.RFC1123``, ``time.RFC1123Z`` and
|
||||
// ``iso8601Format``.
|
||||
var dateHeaders = []string{
|
||||
// Supported Amz date headers.
|
||||
var amzDateHeaders = []string{
|
||||
"x-amz-date",
|
||||
"x-minio-date",
|
||||
"date",
|
||||
}
|
||||
|
||||
func parseDateHeader(req *http.Request) (time.Time, error) {
|
||||
for _, dateHeader := range dateHeaders {
|
||||
date := req.Header.Get(http.CanonicalHeaderKey(dateHeader))
|
||||
if date != "" {
|
||||
return parseDate(date)
|
||||
// parseAmzDateHeader - parses supported amz date headers, in
|
||||
// supported amz date formats.
|
||||
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
|
||||
for _, amzDateHeader := range amzDateHeaders {
|
||||
amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
|
||||
if amzDateStr != "" {
|
||||
return parseAmzDate(amzDateStr)
|
||||
}
|
||||
}
|
||||
return time.Time{}, errors.New("Date header missing, invalid request.")
|
||||
// Date header missing.
|
||||
return time.Time{}, ErrMissingDateHeader
|
||||
}
|
||||
|
||||
type timeHandler struct {
|
||||
|
@ -179,17 +170,17 @@ func setTimeValidityHandler(h http.Handler) http.Handler {
|
|||
func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify if date headers are set, if not reject the request
|
||||
if _, ok := r.Header["Authorization"]; ok {
|
||||
date, e := parseDateHeader(r)
|
||||
if e != nil {
|
||||
amzDate, apiErr := parseAmzDateHeader(r)
|
||||
if apiErr != ErrNone {
|
||||
// All our internal APIs are sensitive towards Date
|
||||
// header, for all requests where Date header is not
|
||||
// present we will reject such clients.
|
||||
writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Verify if the request date header is more than 5minutes
|
||||
// late, reject such clients.
|
||||
if time.Now().UTC().Sub(date)/time.Minute > time.Duration(5)*time.Minute {
|
||||
if time.Now().UTC().Sub(amzDate)/time.Minute > time.Duration(5)*time.Minute {
|
||||
writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package main
|
|||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -573,15 +574,8 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
objectInfo, err = api.Filesystem.CreateObject(bucket, object, size, r.Body, nil)
|
||||
case authTypePresigned:
|
||||
// For presigned requests verify them right here.
|
||||
var ok bool
|
||||
ok, err = doesPresignedSignatureMatch(r)
|
||||
if err != nil {
|
||||
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil)
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
if apiErr := doesPresignedSignatureMatch(r); apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
// Create presigned object.
|
||||
|
@ -600,14 +594,12 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
shaPayload := shaWriter.Sum(nil)
|
||||
ok, serr := doesSignatureMatch(hex.EncodeToString(shaPayload), r)
|
||||
if serr != nil {
|
||||
errorIf(serr.Trace(), "Signature verification failed.", nil)
|
||||
writer.CloseWithError(probe.WrapError(serr))
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r); apiErr != ErrNone {
|
||||
if apiErr == ErrSignatureDoesNotMatch {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
return
|
||||
}
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
||||
return
|
||||
}
|
||||
writer.Close()
|
||||
|
@ -756,15 +748,9 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques
|
|||
partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, partID, size, r.Body, nil)
|
||||
case authTypePresigned:
|
||||
// For presigned requests verify right here.
|
||||
var ok bool
|
||||
ok, err = doesPresignedSignatureMatch(r)
|
||||
if err != nil {
|
||||
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil)
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
|
||||
apiErr := doesPresignedSignatureMatch(r)
|
||||
if apiErr != ErrNone {
|
||||
writeErrorResponse(w, r, apiErr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, partID, size, r.Body, nil)
|
||||
|
@ -782,14 +768,12 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
}
|
||||
shaPayload := shaWriter.Sum(nil)
|
||||
ok, serr := doesSignatureMatch(hex.EncodeToString(shaPayload), r)
|
||||
if serr != nil {
|
||||
errorIf(serr.Trace(), "Signature verification failed.", nil)
|
||||
writer.CloseWithError(probe.WrapError(serr))
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r); apiErr != ErrNone {
|
||||
if apiErr == ErrSignatureDoesNotMatch {
|
||||
writer.CloseWithError(errSignatureMismatch)
|
||||
return
|
||||
}
|
||||
writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
|
||||
return
|
||||
}
|
||||
writer.Close()
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
type errFunc func(msg string, a ...string) *probe.Error
|
||||
|
||||
// generic error factory which wraps around probe.NewError()
|
||||
func errFactory() errFunc {
|
||||
return func(msg string, a ...string) *probe.Error {
|
||||
return probe.NewError(fmt.Errorf("%s, Args: %s", msg, a)).Untrace()
|
||||
}
|
||||
}
|
||||
|
||||
// Various signature v4 errors.
|
||||
var (
|
||||
ErrPolicyAlreadyExpired = errFactory()
|
||||
ErrInvalidRegion = errFactory()
|
||||
ErrInvalidDateFormat = errFactory()
|
||||
ErrInvalidService = errFactory()
|
||||
ErrInvalidRequestVersion = errFactory()
|
||||
ErrMissingFields = errFactory()
|
||||
ErrMissingCredTag = errFactory()
|
||||
ErrCredMalformed = errFactory()
|
||||
ErrMissingSignTag = errFactory()
|
||||
ErrMissingSignHeadersTag = errFactory()
|
||||
ErrMissingDateHeader = errFactory()
|
||||
ErrMalformedDate = errFactory()
|
||||
ErrMalformedExpires = errFactory()
|
||||
ErrAuthHeaderEmpty = errFactory()
|
||||
ErrUnsuppSignAlgo = errFactory()
|
||||
ErrExpiredPresignRequest = errFactory()
|
||||
ErrRegionISEmpty = errFactory()
|
||||
ErrInvalidAccessKey = errFactory()
|
||||
)
|
|
@ -20,8 +20,6 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// credentialHeader data type represents structured form of Credential
|
||||
|
@ -37,20 +35,20 @@ type credentialHeader struct {
|
|||
}
|
||||
|
||||
// parse credentialHeader string into its structured form.
|
||||
func parseCredentialHeader(credElement string) (credentialHeader, *probe.Error) {
|
||||
func parseCredentialHeader(credElement string) (credentialHeader, APIErrorCode) {
|
||||
creds := strings.Split(strings.TrimSpace(credElement), "=")
|
||||
if len(creds) != 2 {
|
||||
return credentialHeader{}, ErrMissingFields("Credential tag has missing fields.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrMissingFields
|
||||
}
|
||||
if creds[0] != "Credential" {
|
||||
return credentialHeader{}, ErrMissingCredTag("Missing credentials tag.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrMissingCredTag
|
||||
}
|
||||
credElements := strings.Split(strings.TrimSpace(creds[1]), "/")
|
||||
if len(credElements) != 5 {
|
||||
return credentialHeader{}, ErrCredMalformed("Credential values malformed.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrCredMalformed
|
||||
}
|
||||
if !isValidAccessKey.MatchString(credElements[0]) {
|
||||
return credentialHeader{}, ErrInvalidAccessKey("Invalid access key id.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrInvalidAccessKeyID
|
||||
}
|
||||
// Save access key id.
|
||||
cred := credentialHeader{
|
||||
|
@ -59,47 +57,47 @@ func parseCredentialHeader(credElement string) (credentialHeader, *probe.Error)
|
|||
var e error
|
||||
cred.scope.date, e = time.Parse(yyyymmdd, credElements[1])
|
||||
if e != nil {
|
||||
return credentialHeader{}, ErrInvalidDateFormat("Invalid date format.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrMalformedDate
|
||||
}
|
||||
if credElements[2] == "" {
|
||||
return credentialHeader{}, ErrRegionISEmpty("Region is empty.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrInvalidRegion
|
||||
}
|
||||
cred.scope.region = credElements[2]
|
||||
if credElements[3] != "s3" {
|
||||
return credentialHeader{}, ErrInvalidService("Invalid service detected.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrInvalidService
|
||||
}
|
||||
cred.scope.service = credElements[3]
|
||||
if credElements[4] != "aws4_request" {
|
||||
return credentialHeader{}, ErrInvalidRequestVersion("Invalid request version detected.", credElement).Trace(credElement)
|
||||
return credentialHeader{}, ErrInvalidRequestVersion
|
||||
}
|
||||
cred.scope.request = credElements[4]
|
||||
return cred, nil
|
||||
return cred, ErrNone
|
||||
}
|
||||
|
||||
// Parse signature string.
|
||||
func parseSignature(signElement string) (string, *probe.Error) {
|
||||
func parseSignature(signElement string) (string, APIErrorCode) {
|
||||
signFields := strings.Split(strings.TrimSpace(signElement), "=")
|
||||
if len(signFields) != 2 {
|
||||
return "", ErrMissingFields("Signature tag has missing fields.", signElement).Trace(signElement)
|
||||
return "", ErrMissingFields
|
||||
}
|
||||
if signFields[0] != "Signature" {
|
||||
return "", ErrMissingSignTag("Signature tag is missing", signElement).Trace(signElement)
|
||||
return "", ErrMissingSignTag
|
||||
}
|
||||
signature := signFields[1]
|
||||
return signature, nil
|
||||
return signature, ErrNone
|
||||
}
|
||||
|
||||
// Parse signed headers string.
|
||||
func parseSignedHeaders(signedHdrElement string) ([]string, *probe.Error) {
|
||||
func parseSignedHeaders(signedHdrElement string) ([]string, APIErrorCode) {
|
||||
signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=")
|
||||
if len(signedHdrFields) != 2 {
|
||||
return nil, ErrMissingFields("Signed headers tag has missing fields.", signedHdrElement).Trace(signedHdrElement)
|
||||
return nil, ErrMissingFields
|
||||
}
|
||||
if signedHdrFields[0] != "SignedHeaders" {
|
||||
return nil, ErrMissingSignHeadersTag("Signed headers tag is missing.", signedHdrElement).Trace(signedHdrElement)
|
||||
return nil, ErrMissingSignHeadersTag
|
||||
}
|
||||
signedHeaders := strings.Split(signedHdrFields[1], ";")
|
||||
return signedHeaders, nil
|
||||
return signedHeaders, ErrNone
|
||||
}
|
||||
|
||||
// signValues data type represents structured form of AWS Signature V4 header.
|
||||
|
@ -125,49 +123,49 @@ type preSignValues struct {
|
|||
// querystring += &X-Amz-SignedHeaders=signed_headers
|
||||
// querystring += &X-Amz-Signature=signature
|
||||
//
|
||||
func parsePreSignV4(query url.Values) (preSignValues, *probe.Error) {
|
||||
func parsePreSignV4(query url.Values) (preSignValues, APIErrorCode) {
|
||||
// Verify if the query algorithm is supported or not.
|
||||
if query.Get("X-Amz-Algorithm") != signV4Algorithm {
|
||||
return preSignValues{}, ErrUnsuppSignAlgo("Unsupported algorithm in query string.", query.Get("X-Amz-Algorithm"))
|
||||
return preSignValues{}, ErrInvalidQuerySignatureAlgo
|
||||
}
|
||||
|
||||
// Initialize signature version '4' structured header.
|
||||
preSignV4Values := preSignValues{}
|
||||
|
||||
var err *probe.Error
|
||||
var err APIErrorCode
|
||||
// Save credential.
|
||||
preSignV4Values.Credential, err = parseCredentialHeader("Credential=" + query.Get("X-Amz-Credential"))
|
||||
if err != nil {
|
||||
return preSignValues{}, err.Trace(query.Get("X-Amz-Credential"))
|
||||
if err != ErrNone {
|
||||
return preSignValues{}, err
|
||||
}
|
||||
|
||||
var e error
|
||||
// Save date in native time.Time.
|
||||
preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get("X-Amz-Date"))
|
||||
if e != nil {
|
||||
return preSignValues{}, ErrMalformedDate("Malformed date string.", query.Get("X-Amz-Date")).Trace(query.Get("X-Amz-Date"))
|
||||
return preSignValues{}, ErrMalformedDate
|
||||
}
|
||||
|
||||
// Save expires in native time.Duration.
|
||||
preSignV4Values.Expires, e = time.ParseDuration(query.Get("X-Amz-Expires") + "s")
|
||||
if e != nil {
|
||||
return preSignValues{}, ErrMalformedExpires("Malformed expires string.", query.Get("X-Amz-Expires")).Trace(query.Get("X-Amz-Expires"))
|
||||
return preSignValues{}, ErrMalformedExpires
|
||||
}
|
||||
|
||||
// Save signed headers.
|
||||
preSignV4Values.SignedHeaders, err = parseSignedHeaders("SignedHeaders=" + query.Get("X-Amz-SignedHeaders"))
|
||||
if err != nil {
|
||||
return preSignValues{}, err.Trace(query.Get("X-Amz-SignedHeaders"))
|
||||
if err != ErrNone {
|
||||
return preSignValues{}, err
|
||||
}
|
||||
|
||||
// Save signature.
|
||||
preSignV4Values.Signature, err = parseSignature("Signature=" + query.Get("X-Amz-Signature"))
|
||||
if err != nil {
|
||||
return preSignValues{}, err.Trace(query.Get("X-Amz-Signature"))
|
||||
if err != ErrNone {
|
||||
return preSignValues{}, err
|
||||
}
|
||||
|
||||
// Return structed form of signature query string.
|
||||
return preSignV4Values, nil
|
||||
return preSignV4Values, ErrNone
|
||||
}
|
||||
|
||||
// Parses signature version '4' header of the following form.
|
||||
|
@ -175,49 +173,49 @@ func parsePreSignV4(query url.Values) (preSignValues, *probe.Error) {
|
|||
// Authorization: algorithm Credential=accessKeyID/credScope, \
|
||||
// SignedHeaders=signedHeaders, Signature=signature
|
||||
//
|
||||
func parseSignV4(v4Auth string) (signValues, *probe.Error) {
|
||||
func parseSignV4(v4Auth string) (signValues, APIErrorCode) {
|
||||
// Replace all spaced strings, some clients can send spaced
|
||||
// parameters and some won't. So we pro-actively remove any spaces
|
||||
// to make parsing easier.
|
||||
v4Auth = strings.Replace(v4Auth, " ", "", -1)
|
||||
if v4Auth == "" {
|
||||
return signValues{}, ErrAuthHeaderEmpty("Auth header empty.").Trace(v4Auth)
|
||||
return signValues{}, ErrAuthHeaderEmpty
|
||||
}
|
||||
|
||||
// Verify if the header algorithm is supported or not.
|
||||
if !strings.HasPrefix(v4Auth, signV4Algorithm) {
|
||||
return signValues{}, ErrUnsuppSignAlgo("Unsupported algorithm in authorization header.", v4Auth).Trace(v4Auth)
|
||||
return signValues{}, ErrSignatureVersionNotSupported
|
||||
}
|
||||
|
||||
// Strip off the Algorithm prefix.
|
||||
v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
|
||||
authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
|
||||
if len(authFields) != 3 {
|
||||
return signValues{}, ErrMissingFields("Missing fields in authorization header.", v4Auth).Trace(v4Auth)
|
||||
return signValues{}, ErrMissingFields
|
||||
}
|
||||
|
||||
// Initialize signature version '4' structured header.
|
||||
signV4Values := signValues{}
|
||||
|
||||
var err *probe.Error
|
||||
var err APIErrorCode
|
||||
// Save credentail values.
|
||||
signV4Values.Credential, err = parseCredentialHeader(authFields[0])
|
||||
if err != nil {
|
||||
return signValues{}, err.Trace(v4Auth)
|
||||
if err != ErrNone {
|
||||
return signValues{}, err
|
||||
}
|
||||
|
||||
// Save signed headers.
|
||||
signV4Values.SignedHeaders, err = parseSignedHeaders(authFields[1])
|
||||
if err != nil {
|
||||
return signValues{}, err.Trace(v4Auth)
|
||||
if err != ErrNone {
|
||||
return signValues{}, err
|
||||
}
|
||||
|
||||
// Save signature.
|
||||
signV4Values.Signature, err = parseSignature(authFields[2])
|
||||
if err != nil {
|
||||
return signValues{}, err.Trace(v4Auth)
|
||||
if err != ErrNone {
|
||||
return signValues{}, err
|
||||
}
|
||||
|
||||
// Return the structure here.
|
||||
return signV4Values, nil
|
||||
return signV4Values, ErrNone
|
||||
}
|
||||
|
|
|
@ -159,51 +159,51 @@ func parsePostPolicyFormV4(policy string) (PostPolicyForm, *probe.Error) {
|
|||
}
|
||||
|
||||
// checkPostPolicy - apply policy conditions and validate input values.
|
||||
func checkPostPolicy(formValues map[string]string) *probe.Error {
|
||||
func checkPostPolicy(formValues map[string]string) APIErrorCode {
|
||||
if formValues["X-Amz-Algorithm"] != signV4Algorithm {
|
||||
return ErrUnsuppSignAlgo("Unsupported signature algorithm in policy form data.", formValues["X-Amz-Algorithm"]).Trace(formValues["X-Amz-Algorithm"])
|
||||
return ErrSignatureVersionNotSupported
|
||||
}
|
||||
/// Decoding policy
|
||||
policyBytes, e := base64.StdEncoding.DecodeString(formValues["Policy"])
|
||||
if e != nil {
|
||||
return probe.NewError(e)
|
||||
return ErrMalformedPOSTRequest
|
||||
}
|
||||
postPolicyForm, err := parsePostPolicyFormV4(string(policyBytes))
|
||||
if err != nil {
|
||||
return err.Trace()
|
||||
return ErrMalformedPOSTRequest
|
||||
}
|
||||
if !postPolicyForm.Expiration.After(time.Now().UTC()) {
|
||||
return ErrPolicyAlreadyExpired("Policy has already expired, please generate a new one.")
|
||||
return ErrPolicyAlreadyExpired
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$bucket"].Operator == "eq" {
|
||||
if formValues["Bucket"] != postPolicyForm.Conditions.Policies["$bucket"].Value {
|
||||
return ErrMissingFields("Policy bucket is missing.", formValues["Bucket"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$x-amz-date"].Operator == "eq" {
|
||||
if formValues["X-Amz-Date"] != postPolicyForm.Conditions.Policies["$x-amz-date"].Value {
|
||||
return ErrMissingFields("Policy date is missing.", formValues["X-Amz-Date"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$Content-Type"].Operator == "starts-with" {
|
||||
if !strings.HasPrefix(formValues["Content-Type"], postPolicyForm.Conditions.Policies["$Content-Type"].Value) {
|
||||
return ErrMissingFields("Policy content-type is missing or invalid.", formValues["Content-Type"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$Content-Type"].Operator == "eq" {
|
||||
if formValues["Content-Type"] != postPolicyForm.Conditions.Policies["$Content-Type"].Value {
|
||||
return ErrMissingFields("Policy content-Type is missing or invalid.", formValues["Content-Type"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$key"].Operator == "starts-with" {
|
||||
if !strings.HasPrefix(formValues["Key"], postPolicyForm.Conditions.Policies["$key"].Value) {
|
||||
return ErrMissingFields("Policy key is missing.", formValues["Key"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
if postPolicyForm.Conditions.Policies["$key"].Operator == "eq" {
|
||||
if formValues["Key"] != postPolicyForm.Conditions.Policies["$key"].Value {
|
||||
return ErrMissingFields("Policy key is missing.", formValues["Key"])
|
||||
return ErrMissingFields
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return ErrNone
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/crypto/sha256"
|
||||
"github.com/minio/minio/pkg/probe"
|
||||
)
|
||||
|
||||
// AWS Signature Version '4' constants.
|
||||
|
@ -177,7 +176,7 @@ func getSignature(signingKey []byte, stringToSign string) string {
|
|||
// doesPolicySignatureMatch - Verify query headers with post policy
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
|
||||
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||
func doesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error) {
|
||||
func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
|
@ -186,24 +185,24 @@ func doesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error)
|
|||
|
||||
// Parse credential tag.
|
||||
credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"])
|
||||
if err != nil {
|
||||
return false, err.Trace(formValues["X-Amz-Credential"])
|
||||
if err != ErrNone {
|
||||
return ErrMissingFields
|
||||
}
|
||||
|
||||
// Verify if the access key id matches.
|
||||
if credHeader.accessKey != cred.AccessKeyID {
|
||||
return false, ErrInvalidAccessKey("Access key id does not match with our records.", credHeader.accessKey).Trace(credHeader.accessKey)
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
|
||||
// Verify if the region is valid.
|
||||
if !isValidRegion(credHeader.scope.region, region) {
|
||||
return false, ErrInvalidRegion("Requested region is not recognized.", credHeader.scope.region).Trace(credHeader.scope.region)
|
||||
return ErrInvalidRegion
|
||||
}
|
||||
|
||||
// Parse date string.
|
||||
t, e := time.Parse(iso8601Format, formValues["X-Amz-Date"])
|
||||
if e != nil {
|
||||
return false, probe.NewError(e)
|
||||
return ErrMalformedDate
|
||||
}
|
||||
|
||||
// Get signing key.
|
||||
|
@ -214,15 +213,15 @@ func doesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error)
|
|||
|
||||
// Verify signature.
|
||||
if newSignature != formValues["X-Amz-Signature"] {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
return true, nil
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
// 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) (bool, *probe.Error) {
|
||||
func doesPresignedSignatureMatch(r *http.Request) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
|
@ -234,19 +233,19 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
|
|||
|
||||
// Parse request query string.
|
||||
preSignValues, err := parsePreSignV4(req.URL.Query())
|
||||
if err != nil {
|
||||
return false, err.Trace(req.URL.String())
|
||||
if err != ErrNone {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify if the access key id matches.
|
||||
if preSignValues.Credential.accessKey != cred.AccessKeyID {
|
||||
return false, ErrInvalidAccessKey("Access key id does not match with our records.", preSignValues.Credential.accessKey).Trace(preSignValues.Credential.accessKey)
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
|
||||
// Verify if region is valid.
|
||||
sRegion := preSignValues.Credential.scope.region
|
||||
if !isValidRegion(sRegion, region) {
|
||||
return false, ErrInvalidRegion("Requested region is not recognized.", sRegion).Trace(sRegion)
|
||||
return ErrInvalidRegion
|
||||
}
|
||||
|
||||
// Extract all the signed headers along with its values.
|
||||
|
@ -257,7 +256,7 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
|
|||
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||
|
||||
if time.Now().UTC().Sub(preSignValues.Date) > time.Duration(preSignValues.Expires) {
|
||||
return false, ErrExpiredPresignRequest("Presigned request already expired, please initiate a new request.")
|
||||
return ErrExpiredPresignRequest
|
||||
}
|
||||
|
||||
// Save the date and expires.
|
||||
|
@ -283,19 +282,19 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
|
|||
|
||||
// Verify if date query is same.
|
||||
if req.URL.Query().Get("X-Amz-Date") != query.Get("X-Amz-Date") {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
// Verify if expires query is same.
|
||||
if req.URL.Query().Get("X-Amz-Expires") != query.Get("X-Amz-Expires") {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
// Verify if signed headers query is same.
|
||||
if req.URL.Query().Get("X-Amz-SignedHeaders") != query.Get("X-Amz-SignedHeaders") {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
// Verify if credential query is same.
|
||||
if req.URL.Query().Get("X-Amz-Credential") != query.Get("X-Amz-Credential") {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
|
||||
/// Verify finally if signature is same.
|
||||
|
@ -314,15 +313,15 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
|
|||
|
||||
// Verify signature.
|
||||
if req.URL.Query().Get("X-Amz-Signature") != newSignature {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
return true, nil
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
// doesSignatureMatch - Verify authorization header with calculated header in accordance with
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
|
||||
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||
func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Error) {
|
||||
func doesSignatureMatch(hashedPayload string, r *http.Request) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
|
@ -337,8 +336,8 @@ func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Err
|
|||
|
||||
// Parse signature version '4' header.
|
||||
signV4Values, err := parseSignV4(v4Auth)
|
||||
if err != nil {
|
||||
return false, err.Trace(v4Auth)
|
||||
if err != ErrNone {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract all the signed headers along with its values.
|
||||
|
@ -346,26 +345,26 @@ func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Err
|
|||
|
||||
// Verify if the access key id matches.
|
||||
if signV4Values.Credential.accessKey != cred.AccessKeyID {
|
||||
return false, ErrInvalidAccessKey("Access key id does not match with our records.", signV4Values.Credential.accessKey).Trace(signV4Values.Credential.accessKey)
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
|
||||
// Verify if region is valid.
|
||||
sRegion := signV4Values.Credential.scope.region
|
||||
if !isValidRegion(sRegion, region) {
|
||||
return false, ErrInvalidRegion("Requested region is not recognized.", sRegion).Trace(sRegion)
|
||||
return ErrInvalidRegion
|
||||
}
|
||||
|
||||
// Extract date, if not present throw error.
|
||||
var date string
|
||||
if date = req.Header.Get(http.CanonicalHeaderKey("x-amz-date")); date == "" {
|
||||
if date = r.Header.Get("Date"); date == "" {
|
||||
return false, ErrMissingDateHeader("Date header is missing from the request.").Trace()
|
||||
return ErrMissingDateHeader
|
||||
}
|
||||
}
|
||||
// Parse date header.
|
||||
t, e := time.Parse(iso8601Format, date)
|
||||
if e != nil {
|
||||
return false, probe.NewError(e)
|
||||
return ErrMalformedDate
|
||||
}
|
||||
|
||||
// Query string.
|
||||
|
@ -385,7 +384,7 @@ func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Err
|
|||
|
||||
// Verify if signature match.
|
||||
if newSignature != signV4Values.Signature {
|
||||
return false, nil
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
return true, nil
|
||||
return ErrNone
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue