Merge pull request #1275 from harshavardhana/signature

error: Signature errors should be returned with APIErrorCode.
This commit is contained in:
Anand Babu (AB) Periasamy 2016-03-31 23:37:38 -07:00
commit ae5c65d3c6
10 changed files with 230 additions and 255 deletions

View File

@ -87,6 +87,23 @@ const (
ErrObjectExistsAsPrefix ErrObjectExistsAsPrefix
ErrAllAccessDisabled ErrAllAccessDisabled
ErrMalformedPolicy ErrMalformedPolicy
ErrMissingFields
ErrMissingCredTag
ErrCredMalformed
ErrInvalidRegion
ErrInvalidService
ErrInvalidRequestVersion
ErrMissingSignTag
ErrMissingSignHeadersTag
ErrPolicyAlreadyExpired
ErrMalformedDate
ErrMalformedExpires
ErrAuthHeaderEmpty
ErrDateHeaderMissing
ErrExpiredPresignRequest
ErrMissingDateHeader
ErrInvalidQuerySignatureAlgo
ErrInvalidQueryParams
// Add new error codes here. // Add new error codes here.
) )
@ -295,7 +312,87 @@ var errorCodeResponse = map[APIErrorCode]APIError{
}, },
ErrMalformedPolicy: { ErrMalformedPolicy: {
Code: "MalformedPolicy", 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, HTTPStatusCode: http.StatusBadRequest,
}, },
// Add your error structure here. // Add your error structure here.

View File

@ -130,25 +130,9 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
// Populate back the payload. // Populate back the payload.
r.Body = ioutil.NopCloser(bytes.NewReader(payload)) r.Body = ioutil.NopCloser(bytes.NewReader(payload))
if isRequestSignatureV4(r) { if isRequestSignatureV4(r) {
ok, err := doesSignatureMatch(hex.EncodeToString(sum256(payload)), r) return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r)
if err != nil {
errorIf(err.Trace(), "Signature verification failed.", nil)
return ErrInternalError
}
if !ok {
return ErrSignatureDoesNotMatch
}
return ErrNone
} else if isRequestPresignedSignatureV4(r) { } else if isRequestPresignedSignatureV4(r) {
ok, err := doesPresignedSignatureMatch(r) return doesPresignedSignatureMatch(r)
if err != nil {
errorIf(err.Trace(), "Presigned signature verification failed.", nil)
return ErrInternalError
}
if !ok {
return ErrSignatureDoesNotMatch
}
return ErrNone
} }
return ErrAccessDenied return ErrAccessDenied
} }

View File

@ -474,22 +474,15 @@ func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Req
bucket := mux.Vars(r)["bucket"] bucket := mux.Vars(r)["bucket"]
formValues["Bucket"] = bucket formValues["Bucket"] = bucket
object := formValues["Key"] object := formValues["Key"]
var ok bool
// Verify policy signature. // Verify policy signature.
ok, err = doesPolicySignatureMatch(formValues) apiErr := doesPolicySignatureMatch(formValues)
if err != nil { if apiErr != ErrNone {
errorIf(err.Trace(), "Unable to verify signature.", nil) writeErrorResponse(w, r, apiErr, r.URL.Path)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return return
} }
if !ok { if apiErr = checkPostPolicy(formValues); apiErr != ErrNone {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path) writeErrorResponse(w, r, apiErr, 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)
return return
} }
objectInfo, err := api.Filesystem.CreateObject(bucket, object, -1, fileBody, nil) objectInfo, err := api.Filesystem.CreateObject(bucket, object, -1, fileBody, nil)

View File

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

View File

@ -17,7 +17,6 @@
package main package main
import ( import (
"errors"
"net/http" "net/http"
"path" "path"
"regexp" "regexp"
@ -120,51 +119,43 @@ func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
h.handler.ServeHTTP(w, r) h.handler.ServeHTTP(w, r)
} }
// Supported incoming date formats. // Supported Amz date formats.
var timeFormats = []string{ var amzDateFormats = []string{
time.RFC1123, time.RFC1123,
time.RFC1123Z, time.RFC1123Z,
iso8601Format, iso8601Format,
// Add new AMZ date formats here.
} }
// Attempts to parse date string into known date layouts. Date layouts // parseAmzDate - parses date string into supported amz date formats.
// currently supported are func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
// - ``time.RFC1123`` for _, dateFormat := range amzDateFormats {
// - ``time.RFC1123Z`` amzDate, e := time.Parse(dateFormat, amzDateStr)
// - ``iso8601Format``
func parseDate(date string) (parsedTime time.Time, e error) {
for _, layout := range timeFormats {
parsedTime, e = time.Parse(layout, date)
if e == nil { 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 // Supported Amz date headers.
// follow HTTP headers. var amzDateHeaders = []string{
//
// - X-Amz-Date
// - X-Minio-Date
// - Date
//
// In following time layouts ``time.RFC1123``, ``time.RFC1123Z`` and
// ``iso8601Format``.
var dateHeaders = []string{
"x-amz-date", "x-amz-date",
"x-minio-date", "x-minio-date",
"date", "date",
} }
func parseDateHeader(req *http.Request) (time.Time, error) { // parseAmzDateHeader - parses supported amz date headers, in
for _, dateHeader := range dateHeaders { // supported amz date formats.
date := req.Header.Get(http.CanonicalHeaderKey(dateHeader)) func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
if date != "" { for _, amzDateHeader := range amzDateHeaders {
return parseDate(date) 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 { type timeHandler struct {
@ -179,17 +170,17 @@ func setTimeValidityHandler(h http.Handler) http.Handler {
func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Verify if date headers are set, if not reject the request // Verify if date headers are set, if not reject the request
if _, ok := r.Header["Authorization"]; ok { if _, ok := r.Header["Authorization"]; ok {
date, e := parseDateHeader(r) amzDate, apiErr := parseAmzDateHeader(r)
if e != nil { if apiErr != ErrNone {
// All our internal APIs are sensitive towards Date // All our internal APIs are sensitive towards Date
// header, for all requests where Date header is not // header, for all requests where Date header is not
// present we will reject such clients. // present we will reject such clients.
writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path) writeErrorResponse(w, r, apiErr, r.URL.Path)
return return
} }
// Verify if the request date header is more than 5minutes // Verify if the request date header is more than 5minutes
// late, reject such clients. // 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) writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
return return
} }

View File

@ -19,6 +19,7 @@ package main
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "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) objectInfo, err = api.Filesystem.CreateObject(bucket, object, size, r.Body, nil)
case authTypePresigned: case authTypePresigned:
// For presigned requests verify them right here. // For presigned requests verify them right here.
var ok bool if apiErr := doesPresignedSignatureMatch(r); apiErr != ErrNone {
ok, err = doesPresignedSignatureMatch(r) writeErrorResponse(w, r, apiErr, r.URL.Path)
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)
return return
} }
// Create presigned object. // Create presigned object.
@ -600,14 +594,12 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
shaPayload := shaWriter.Sum(nil) shaPayload := shaWriter.Sum(nil)
ok, serr := doesSignatureMatch(hex.EncodeToString(shaPayload), r) if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r); apiErr != ErrNone {
if serr != nil { if apiErr == ErrSignatureDoesNotMatch {
errorIf(serr.Trace(), "Signature verification failed.", nil) writer.CloseWithError(errSignatureMismatch)
writer.CloseWithError(probe.WrapError(serr)) return
return }
} writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
if !ok {
writer.CloseWithError(errSignatureMismatch)
return return
} }
writer.Close() 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) partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, partID, size, r.Body, nil)
case authTypePresigned: case authTypePresigned:
// For presigned requests verify right here. // For presigned requests verify right here.
var ok bool apiErr := doesPresignedSignatureMatch(r)
ok, err = doesPresignedSignatureMatch(r) if apiErr != ErrNone {
if err != nil { writeErrorResponse(w, r, apiErr, r.URL.Path)
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)
return return
} }
partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, partID, size, r.Body, nil) 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 return
} }
shaPayload := shaWriter.Sum(nil) shaPayload := shaWriter.Sum(nil)
ok, serr := doesSignatureMatch(hex.EncodeToString(shaPayload), r) if apiErr := doesSignatureMatch(hex.EncodeToString(shaPayload), r); apiErr != ErrNone {
if serr != nil { if apiErr == ErrSignatureDoesNotMatch {
errorIf(serr.Trace(), "Signature verification failed.", nil) writer.CloseWithError(errSignatureMismatch)
writer.CloseWithError(probe.WrapError(serr)) return
return }
} writer.CloseWithError(fmt.Errorf("%v", getAPIError(apiErr)))
if !ok {
writer.CloseWithError(errSignatureMismatch)
return return
} }
writer.Close() writer.Close()

View File

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

View File

@ -20,8 +20,6 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/minio/minio/pkg/probe"
) )
// credentialHeader data type represents structured form of Credential // credentialHeader data type represents structured form of Credential
@ -37,20 +35,20 @@ type credentialHeader struct {
} }
// parse credentialHeader string into its structured form. // 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), "=") creds := strings.Split(strings.TrimSpace(credElement), "=")
if len(creds) != 2 { if len(creds) != 2 {
return credentialHeader{}, ErrMissingFields("Credential tag has missing fields.", credElement).Trace(credElement) return credentialHeader{}, ErrMissingFields
} }
if creds[0] != "Credential" { if creds[0] != "Credential" {
return credentialHeader{}, ErrMissingCredTag("Missing credentials tag.", credElement).Trace(credElement) return credentialHeader{}, ErrMissingCredTag
} }
credElements := strings.Split(strings.TrimSpace(creds[1]), "/") credElements := strings.Split(strings.TrimSpace(creds[1]), "/")
if len(credElements) != 5 { if len(credElements) != 5 {
return credentialHeader{}, ErrCredMalformed("Credential values malformed.", credElement).Trace(credElement) return credentialHeader{}, ErrCredMalformed
} }
if !isValidAccessKey.MatchString(credElements[0]) { if !isValidAccessKey.MatchString(credElements[0]) {
return credentialHeader{}, ErrInvalidAccessKey("Invalid access key id.", credElement).Trace(credElement) return credentialHeader{}, ErrInvalidAccessKeyID
} }
// Save access key id. // Save access key id.
cred := credentialHeader{ cred := credentialHeader{
@ -59,47 +57,47 @@ func parseCredentialHeader(credElement string) (credentialHeader, *probe.Error)
var e error var e error
cred.scope.date, e = time.Parse(yyyymmdd, credElements[1]) cred.scope.date, e = time.Parse(yyyymmdd, credElements[1])
if e != nil { if e != nil {
return credentialHeader{}, ErrInvalidDateFormat("Invalid date format.", credElement).Trace(credElement) return credentialHeader{}, ErrMalformedDate
} }
if credElements[2] == "" { if credElements[2] == "" {
return credentialHeader{}, ErrRegionISEmpty("Region is empty.", credElement).Trace(credElement) return credentialHeader{}, ErrInvalidRegion
} }
cred.scope.region = credElements[2] cred.scope.region = credElements[2]
if credElements[3] != "s3" { if credElements[3] != "s3" {
return credentialHeader{}, ErrInvalidService("Invalid service detected.", credElement).Trace(credElement) return credentialHeader{}, ErrInvalidService
} }
cred.scope.service = credElements[3] cred.scope.service = credElements[3]
if credElements[4] != "aws4_request" { if credElements[4] != "aws4_request" {
return credentialHeader{}, ErrInvalidRequestVersion("Invalid request version detected.", credElement).Trace(credElement) return credentialHeader{}, ErrInvalidRequestVersion
} }
cred.scope.request = credElements[4] cred.scope.request = credElements[4]
return cred, nil return cred, ErrNone
} }
// Parse signature string. // Parse signature string.
func parseSignature(signElement string) (string, *probe.Error) { func parseSignature(signElement string) (string, APIErrorCode) {
signFields := strings.Split(strings.TrimSpace(signElement), "=") signFields := strings.Split(strings.TrimSpace(signElement), "=")
if len(signFields) != 2 { if len(signFields) != 2 {
return "", ErrMissingFields("Signature tag has missing fields.", signElement).Trace(signElement) return "", ErrMissingFields
} }
if signFields[0] != "Signature" { if signFields[0] != "Signature" {
return "", ErrMissingSignTag("Signature tag is missing", signElement).Trace(signElement) return "", ErrMissingSignTag
} }
signature := signFields[1] signature := signFields[1]
return signature, nil return signature, ErrNone
} }
// Parse signed headers string. // Parse signed headers string.
func parseSignedHeaders(signedHdrElement string) ([]string, *probe.Error) { func parseSignedHeaders(signedHdrElement string) ([]string, APIErrorCode) {
signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=") signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=")
if len(signedHdrFields) != 2 { if len(signedHdrFields) != 2 {
return nil, ErrMissingFields("Signed headers tag has missing fields.", signedHdrElement).Trace(signedHdrElement) return nil, ErrMissingFields
} }
if signedHdrFields[0] != "SignedHeaders" { if signedHdrFields[0] != "SignedHeaders" {
return nil, ErrMissingSignHeadersTag("Signed headers tag is missing.", signedHdrElement).Trace(signedHdrElement) return nil, ErrMissingSignHeadersTag
} }
signedHeaders := strings.Split(signedHdrFields[1], ";") signedHeaders := strings.Split(signedHdrFields[1], ";")
return signedHeaders, nil return signedHeaders, ErrNone
} }
// signValues data type represents structured form of AWS Signature V4 header. // 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-SignedHeaders=signed_headers
// querystring += &X-Amz-Signature=signature // 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. // Verify if the query algorithm is supported or not.
if query.Get("X-Amz-Algorithm") != signV4Algorithm { 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. // Initialize signature version '4' structured header.
preSignV4Values := preSignValues{} preSignV4Values := preSignValues{}
var err *probe.Error var err APIErrorCode
// Save credential. // Save credential.
preSignV4Values.Credential, err = parseCredentialHeader("Credential=" + query.Get("X-Amz-Credential")) preSignV4Values.Credential, err = parseCredentialHeader("Credential=" + query.Get("X-Amz-Credential"))
if err != nil { if err != ErrNone {
return preSignValues{}, err.Trace(query.Get("X-Amz-Credential")) return preSignValues{}, err
} }
var e error var e error
// Save date in native time.Time. // Save date in native time.Time.
preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get("X-Amz-Date")) preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get("X-Amz-Date"))
if e != nil { 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. // Save expires in native time.Duration.
preSignV4Values.Expires, e = time.ParseDuration(query.Get("X-Amz-Expires") + "s") preSignV4Values.Expires, e = time.ParseDuration(query.Get("X-Amz-Expires") + "s")
if e != nil { 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. // Save signed headers.
preSignV4Values.SignedHeaders, err = parseSignedHeaders("SignedHeaders=" + query.Get("X-Amz-SignedHeaders")) preSignV4Values.SignedHeaders, err = parseSignedHeaders("SignedHeaders=" + query.Get("X-Amz-SignedHeaders"))
if err != nil { if err != ErrNone {
return preSignValues{}, err.Trace(query.Get("X-Amz-SignedHeaders")) return preSignValues{}, err
} }
// Save signature. // Save signature.
preSignV4Values.Signature, err = parseSignature("Signature=" + query.Get("X-Amz-Signature")) preSignV4Values.Signature, err = parseSignature("Signature=" + query.Get("X-Amz-Signature"))
if err != nil { if err != ErrNone {
return preSignValues{}, err.Trace(query.Get("X-Amz-Signature")) return preSignValues{}, err
} }
// Return structed form of signature query string. // Return structed form of signature query string.
return preSignV4Values, nil return preSignV4Values, ErrNone
} }
// Parses signature version '4' header of the following form. // 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, \ // Authorization: algorithm Credential=accessKeyID/credScope, \
// SignedHeaders=signedHeaders, Signature=signature // 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 // Replace all spaced strings, some clients can send spaced
// parameters and some won't. So we pro-actively remove any spaces // parameters and some won't. So we pro-actively remove any spaces
// to make parsing easier. // to make parsing easier.
v4Auth = strings.Replace(v4Auth, " ", "", -1) v4Auth = strings.Replace(v4Auth, " ", "", -1)
if v4Auth == "" { if v4Auth == "" {
return signValues{}, ErrAuthHeaderEmpty("Auth header empty.").Trace(v4Auth) return signValues{}, ErrAuthHeaderEmpty
} }
// Verify if the header algorithm is supported or not. // Verify if the header algorithm is supported or not.
if !strings.HasPrefix(v4Auth, signV4Algorithm) { if !strings.HasPrefix(v4Auth, signV4Algorithm) {
return signValues{}, ErrUnsuppSignAlgo("Unsupported algorithm in authorization header.", v4Auth).Trace(v4Auth) return signValues{}, ErrSignatureVersionNotSupported
} }
// Strip off the Algorithm prefix. // Strip off the Algorithm prefix.
v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm) v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
authFields := strings.Split(strings.TrimSpace(v4Auth), ",") authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
if len(authFields) != 3 { if len(authFields) != 3 {
return signValues{}, ErrMissingFields("Missing fields in authorization header.", v4Auth).Trace(v4Auth) return signValues{}, ErrMissingFields
} }
// Initialize signature version '4' structured header. // Initialize signature version '4' structured header.
signV4Values := signValues{} signV4Values := signValues{}
var err *probe.Error var err APIErrorCode
// Save credentail values. // Save credentail values.
signV4Values.Credential, err = parseCredentialHeader(authFields[0]) signV4Values.Credential, err = parseCredentialHeader(authFields[0])
if err != nil { if err != ErrNone {
return signValues{}, err.Trace(v4Auth) return signValues{}, err
} }
// Save signed headers. // Save signed headers.
signV4Values.SignedHeaders, err = parseSignedHeaders(authFields[1]) signV4Values.SignedHeaders, err = parseSignedHeaders(authFields[1])
if err != nil { if err != ErrNone {
return signValues{}, err.Trace(v4Auth) return signValues{}, err
} }
// Save signature. // Save signature.
signV4Values.Signature, err = parseSignature(authFields[2]) signV4Values.Signature, err = parseSignature(authFields[2])
if err != nil { if err != ErrNone {
return signValues{}, err.Trace(v4Auth) return signValues{}, err
} }
// Return the structure here. // Return the structure here.
return signV4Values, nil return signV4Values, ErrNone
} }

View File

@ -159,51 +159,51 @@ func parsePostPolicyFormV4(policy string) (PostPolicyForm, *probe.Error) {
} }
// checkPostPolicy - apply policy conditions and validate input values. // 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 { 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 /// Decoding policy
policyBytes, e := base64.StdEncoding.DecodeString(formValues["Policy"]) policyBytes, e := base64.StdEncoding.DecodeString(formValues["Policy"])
if e != nil { if e != nil {
return probe.NewError(e) return ErrMalformedPOSTRequest
} }
postPolicyForm, err := parsePostPolicyFormV4(string(policyBytes)) postPolicyForm, err := parsePostPolicyFormV4(string(policyBytes))
if err != nil { if err != nil {
return err.Trace() return ErrMalformedPOSTRequest
} }
if !postPolicyForm.Expiration.After(time.Now().UTC()) { 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 postPolicyForm.Conditions.Policies["$bucket"].Operator == "eq" {
if formValues["Bucket"] != postPolicyForm.Conditions.Policies["$bucket"].Value { 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 postPolicyForm.Conditions.Policies["$x-amz-date"].Operator == "eq" {
if formValues["X-Amz-Date"] != postPolicyForm.Conditions.Policies["$x-amz-date"].Value { 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 postPolicyForm.Conditions.Policies["$Content-Type"].Operator == "starts-with" {
if !strings.HasPrefix(formValues["Content-Type"], postPolicyForm.Conditions.Policies["$Content-Type"].Value) { 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 postPolicyForm.Conditions.Policies["$Content-Type"].Operator == "eq" {
if formValues["Content-Type"] != postPolicyForm.Conditions.Policies["$Content-Type"].Value { 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 postPolicyForm.Conditions.Policies["$key"].Operator == "starts-with" {
if !strings.HasPrefix(formValues["Key"], postPolicyForm.Conditions.Policies["$key"].Value) { 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 postPolicyForm.Conditions.Policies["$key"].Operator == "eq" {
if formValues["Key"] != postPolicyForm.Conditions.Policies["$key"].Value { if formValues["Key"] != postPolicyForm.Conditions.Policies["$key"].Value {
return ErrMissingFields("Policy key is missing.", formValues["Key"]) return ErrMissingFields
} }
} }
return nil return ErrNone
} }

View File

@ -35,7 +35,6 @@ import (
"time" "time"
"github.com/minio/minio/pkg/crypto/sha256" "github.com/minio/minio/pkg/crypto/sha256"
"github.com/minio/minio/pkg/probe"
) )
// AWS Signature Version '4' constants. // AWS Signature Version '4' constants.
@ -177,7 +176,7 @@ func getSignature(signingKey []byte, stringToSign string) string {
// doesPolicySignatureMatch - Verify query headers with post policy // doesPolicySignatureMatch - Verify query headers with post policy
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // - 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 // 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. // Access credentials.
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
@ -186,24 +185,24 @@ func doesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error)
// Parse credential tag. // Parse credential tag.
credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"]) credHeader, err := parseCredentialHeader("Credential=" + formValues["X-Amz-Credential"])
if err != nil { if err != ErrNone {
return false, err.Trace(formValues["X-Amz-Credential"]) return ErrMissingFields
} }
// Verify if the access key id matches. // Verify if the access key id matches.
if credHeader.accessKey != cred.AccessKeyID { 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. // Verify if the region is valid.
if !isValidRegion(credHeader.scope.region, region) { 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. // Parse date string.
t, e := time.Parse(iso8601Format, formValues["X-Amz-Date"]) t, e := time.Parse(iso8601Format, formValues["X-Amz-Date"])
if e != nil { if e != nil {
return false, probe.NewError(e) return ErrMalformedDate
} }
// Get signing key. // Get signing key.
@ -214,15 +213,15 @@ func doesPolicySignatureMatch(formValues map[string]string) (bool, *probe.Error)
// Verify signature. // Verify signature.
if newSignature != formValues["X-Amz-Signature"] { if newSignature != formValues["X-Amz-Signature"] {
return false, nil return ErrSignatureDoesNotMatch
} }
return true, nil return ErrNone
} }
// 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) (bool, *probe.Error) { func doesPresignedSignatureMatch(r *http.Request) APIErrorCode {
// Access credentials. // Access credentials.
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
@ -234,19 +233,19 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
// Parse request query string. // Parse request query string.
preSignValues, err := parsePreSignV4(req.URL.Query()) preSignValues, err := parsePreSignV4(req.URL.Query())
if err != nil { if err != ErrNone {
return false, err.Trace(req.URL.String()) return err
} }
// Verify if the access key id matches. // Verify if the access key id matches.
if preSignValues.Credential.accessKey != cred.AccessKeyID { 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. // Verify if region is valid.
sRegion := preSignValues.Credential.scope.region sRegion := preSignValues.Credential.scope.region
if !isValidRegion(sRegion, 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. // 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) 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) {
return false, ErrExpiredPresignRequest("Presigned request already expired, please initiate a new request.") return ErrExpiredPresignRequest
} }
// Save the date and expires. // Save the date and expires.
@ -283,19 +282,19 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
// Verify if date query is same. // Verify if date query is same.
if req.URL.Query().Get("X-Amz-Date") != query.Get("X-Amz-Date") { if req.URL.Query().Get("X-Amz-Date") != query.Get("X-Amz-Date") {
return false, nil return ErrSignatureDoesNotMatch
} }
// Verify if expires query is same. // Verify if expires query is same.
if req.URL.Query().Get("X-Amz-Expires") != query.Get("X-Amz-Expires") { 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. // Verify if signed headers query is same.
if req.URL.Query().Get("X-Amz-SignedHeaders") != query.Get("X-Amz-SignedHeaders") { if req.URL.Query().Get("X-Amz-SignedHeaders") != query.Get("X-Amz-SignedHeaders") {
return false, nil return ErrSignatureDoesNotMatch
} }
// Verify if credential query is same. // Verify if credential query is same.
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 false, nil return ErrSignatureDoesNotMatch
} }
/// Verify finally if signature is same. /// Verify finally if signature is same.
@ -314,15 +313,15 @@ func doesPresignedSignatureMatch(r *http.Request) (bool, *probe.Error) {
// Verify signature. // Verify signature.
if req.URL.Query().Get("X-Amz-Signature") != newSignature { 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 // doesSignatureMatch - Verify authorization header with calculated header in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html // - 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 // 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. // Access credentials.
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
@ -337,8 +336,8 @@ func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Err
// Parse signature version '4' header. // Parse signature version '4' header.
signV4Values, err := parseSignV4(v4Auth) signV4Values, err := parseSignV4(v4Auth)
if err != nil { if err != ErrNone {
return false, err.Trace(v4Auth) return err
} }
// Extract all the signed headers along with its values. // 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. // Verify if the access key id matches.
if signV4Values.Credential.accessKey != cred.AccessKeyID { 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. // Verify if region is valid.
sRegion := signV4Values.Credential.scope.region sRegion := signV4Values.Credential.scope.region
if !isValidRegion(sRegion, 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. // Extract date, if not present throw error.
var date string var date string
if date = req.Header.Get(http.CanonicalHeaderKey("x-amz-date")); date == "" { if date = req.Header.Get(http.CanonicalHeaderKey("x-amz-date")); date == "" {
if date = r.Header.Get("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. // Parse date header.
t, e := time.Parse(iso8601Format, date) t, e := time.Parse(iso8601Format, date)
if e != nil { if e != nil {
return false, probe.NewError(e) return ErrMalformedDate
} }
// Query string. // Query string.
@ -385,7 +384,7 @@ func doesSignatureMatch(hashedPayload string, r *http.Request) (bool, *probe.Err
// Verify if signature match. // Verify if signature match.
if newSignature != signV4Values.Signature { if newSignature != signV4Values.Signature {
return false, nil return ErrSignatureDoesNotMatch
} }
return true, nil return ErrNone
} }