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
This commit is contained in:
Harshavardhana 2016-03-30 20:04:51 -07:00
parent a84c466a40
commit 02ad48466d
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
} }