From 02ad48466dcbe0760450b402c63c80588526bcfd Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 30 Mar 2016 20:04:51 -0700 Subject: [PATCH] error: Signature errors should be returned with APIErrorCode. The reasoning is that we can reply back with wide range of S3 error responses, which would provide more richer context to S3 client. Fixes #1267 --- api-errors.go | 99 +++++++++++++++++++++++++++++++++- auth-handler.go | 20 +------ bucket-handlers.go | 17 ++---- flags.go | 17 ------ generic-handlers.go | 57 +++++++++----------- object-handlers.go | 52 +++++++----------- signature-v4-errors.go | 54 ------------------- signature-v4-parser.go | 86 +++++++++++++++-------------- signature-v4-postpolicyform.go | 24 ++++----- signature-v4.go | 59 ++++++++++---------- 10 files changed, 230 insertions(+), 255 deletions(-) delete mode 100644 flags.go delete mode 100644 signature-v4-errors.go diff --git a/api-errors.go b/api-errors.go index 4b28ac8bb..0ef40712d 100644 --- a/api-errors.go +++ b/api-errors.go @@ -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. diff --git a/auth-handler.go b/auth-handler.go index 5d6eb5974..286f67ec1 100644 --- a/auth-handler.go +++ b/auth-handler.go @@ -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 } diff --git a/bucket-handlers.go b/bucket-handlers.go index 16494417f..61117e7a7 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -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) - return - } - if !ok { - writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path) + apiErr := doesPolicySignatureMatch(formValues) + if apiErr != ErrNone { + 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) + if apiErr = checkPostPolicy(formValues); apiErr != ErrNone { + writeErrorResponse(w, r, apiErr, r.URL.Path) return } objectInfo, err := api.Filesystem.CreateObject(bucket, object, -1, fileBody, nil) diff --git a/flags.go b/flags.go deleted file mode 100644 index d2b3439d7..000000000 --- a/flags.go +++ /dev/null @@ -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 diff --git a/generic-handlers.go b/generic-handlers.go index e25d78c18..cc2a03bd3 100644 --- a/generic-handlers.go +++ b/generic-handlers.go @@ -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 } diff --git a/object-handlers.go b/object-handlers.go index ec5542e4f..cc2199c86 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -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() diff --git a/signature-v4-errors.go b/signature-v4-errors.go deleted file mode 100644 index dce0bd391..000000000 --- a/signature-v4-errors.go +++ /dev/null @@ -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() -) diff --git a/signature-v4-parser.go b/signature-v4-parser.go index e4c7eab64..afcb4ce0d 100644 --- a/signature-v4-parser.go +++ b/signature-v4-parser.go @@ -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 } diff --git a/signature-v4-postpolicyform.go b/signature-v4-postpolicyform.go index 9cd5c4dad..20ea45dc7 100644 --- a/signature-v4-postpolicyform.go +++ b/signature-v4-postpolicyform.go @@ -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 } diff --git a/signature-v4.go b/signature-v4.go index 7ca6544f4..9bdd65d19 100644 --- a/signature-v4.go +++ b/signature-v4.go @@ -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 }