forked from johannesboyne/gofakes3
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
968673d
commit 01b911a
Showing
9 changed files
with
790 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Package signature verify request for any AWS service. | ||
package signature | ||
|
||
import ( | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
var credStore sync.Map | ||
|
||
// Credentials holds access and secret keys. | ||
type Credentials struct { | ||
AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"` | ||
SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"` | ||
Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"` | ||
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"` | ||
Status string `xml:"-" json:"status,omitempty"` | ||
ParentUser string `xml:"-" json:"parentUser,omitempty"` | ||
Groups []string `xml:"-" json:"groups,omitempty"` | ||
Claims map[string]interface{} `xml:"-" json:"claims,omitempty"` | ||
} | ||
|
||
// signValues data type represents structured form of AWS Signature V4 header. | ||
type signValues struct { | ||
Credential credentialHeader | ||
SignedHeaders []string | ||
Signature string | ||
} | ||
|
||
// credentialHeader data type represents structured form of Credential | ||
// string from authorization header. | ||
type credentialHeader struct { | ||
accessKey string | ||
scope signScope | ||
} | ||
|
||
type signScope struct { | ||
date time.Time | ||
region string | ||
service string | ||
request string | ||
} | ||
|
||
// Return scope string. | ||
func (c credentialHeader) getScope() string { | ||
return strings.Join([]string{ | ||
c.scope.date.Format(yyyymmdd), | ||
c.scope.region, | ||
c.scope.service, | ||
c.scope.request, | ||
}, slashSeparator) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package signature | ||
|
||
import ( | ||
"bytes" | ||
"encoding/xml" | ||
"net/http" | ||
) | ||
|
||
// ErrorCode is code[int] of APIError | ||
type ErrorCode int | ||
|
||
// APIError is API Error structure | ||
type APIError struct { | ||
Code string | ||
Description string | ||
HTTPStatusCode int | ||
} | ||
|
||
// the format of error response | ||
type errorResponse struct { | ||
XMLName xml.Name `xml:"Error"` | ||
|
||
Code string | ||
Message string | ||
} | ||
|
||
type errorCodeMap map[ErrorCode]APIError | ||
|
||
const ( | ||
errMissingFields ErrorCode = iota | ||
errMissingCredTag | ||
errCredMalformed | ||
errInvalidAccessKeyID | ||
errMalformedCredentialDate | ||
errInvalidRequestVersion | ||
errInvalidServiceS3 | ||
errMissingSignHeadersTag | ||
errMissingSignTag | ||
errUnsignedHeaders | ||
errMissingDateHeader | ||
errMalformedDate | ||
errUnsupportAlgorithm | ||
errSignatureDoesNotMatch | ||
|
||
// ErrNone is None(err=nil) | ||
ErrNone | ||
) | ||
|
||
// error code to APIerror structure, these fields carry respective | ||
// descriptions for all the error responses. | ||
var errorCodes = errorCodeMap{ | ||
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: "AuthorizationQueryParameterserror", | ||
Description: "error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errInvalidAccessKeyID: { | ||
Code: "InvalidAccessKeyId", | ||
Description: "The Access Key Id you provided does not exist in our records.", | ||
HTTPStatusCode: http.StatusForbidden, | ||
}, | ||
errMalformedCredentialDate: { | ||
Code: "AuthorizationQueryParameterserror", | ||
Description: "error parsing the X-Amz-Credential parameter; incorrect date format. This date in the credential must be in the format \"yyyyMMdd\".", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errInvalidRequestVersion: { | ||
Code: "AuthorizationQueryParameterserror", | ||
Description: "error parsing the X-Amz-Credential parameter; incorrect terminal. This endpoint uses \"aws4_request\".", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errInvalidServiceS3: { | ||
Code: "AuthorizationParameterserror", | ||
Description: "error parsing the Credential/X-Amz-Credential parameter; incorrect service. This endpoint belongs to \"s3\".", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errMissingSignHeadersTag: { | ||
Code: "InvalidArgument", | ||
Description: "Signature header missing SignedHeaders field.", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errMissingSignTag: { | ||
Code: "AccessDenied", | ||
Description: "Signature header missing Signature field.", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errUnsignedHeaders: { | ||
Code: "AccessDenied", | ||
Description: "There were headers present in the request which were not signed", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errMissingDateHeader: { | ||
Code: "AccessDenied", | ||
Description: "AWS authentication requires a valid Date or x-amz-date header", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errMalformedDate: { | ||
Code: "MalformedDate", | ||
Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errUnsupportAlgorithm: { | ||
Code: "UnsupportedAlgorithm", | ||
Description: "Encountered an unsupported algorithm.", | ||
HTTPStatusCode: http.StatusBadRequest, | ||
}, | ||
errSignatureDoesNotMatch: { | ||
Code: "SignatureDoesNotMatch", | ||
Description: "The request signature we calculated does not match the signature you provided. Check your key and signing method.", | ||
HTTPStatusCode: http.StatusForbidden, | ||
}, | ||
} | ||
|
||
// EncodeResponse encodes the response headers into XML format. | ||
func EncodeResponse(response interface{}) []byte { | ||
var bytesBuffer bytes.Buffer | ||
bytesBuffer.WriteString(xml.Header) | ||
buf, err := xml.Marshal(response) | ||
if err != nil { | ||
return nil | ||
} | ||
bytesBuffer.Write(buf) | ||
return bytesBuffer.Bytes() | ||
} | ||
|
||
// GetAPIError decodes Signerror[int] to APIerror[encodable] | ||
func GetAPIError(errCode ErrorCode) APIError { | ||
return errorCodes[errCode] | ||
} | ||
|
||
// EncodeAPIErrorToResponse encodes APIerror to http response | ||
func EncodeAPIErrorToResponse(err APIError) []byte { | ||
return EncodeResponse(errorResponse{ | ||
Code: err.Code, | ||
Message: err.Description, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package signature | ||
|
||
import ( | ||
"strings" | ||
"time" | ||
) | ||
|
||
// parse credentialHeader string into its structured form. | ||
// | ||
// example: Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request | ||
func parseCredentialHeader(credElement string) (ch credentialHeader, err ErrorCode) { | ||
|
||
creds, err := extractFields(credElement, "Credential") | ||
if err != ErrNone { | ||
return ch, err | ||
} | ||
|
||
credElements := strings.Split(strings.TrimSpace(creds), slashSeparator) | ||
if len(credElements) < 5 { | ||
return ch, errCredMalformed | ||
} | ||
|
||
accessKey := strings.Join(credElements[:len(credElements)-4], slashSeparator) | ||
credElements = credElements[len(credElements)-4:] | ||
signDate, e := time.Parse(yyyymmdd, credElements[0]) | ||
if e != nil { | ||
return ch, errMalformedCredentialDate | ||
} | ||
|
||
// Save access key id. | ||
cred := credentialHeader{ | ||
accessKey: accessKey, | ||
scope: signScope{ | ||
date: signDate, | ||
region: credElements[1], | ||
service: credElements[2], | ||
request: credElements[3], | ||
}, | ||
} | ||
|
||
if len(accessKey) < accessKeyMinLen { | ||
return ch, errInvalidAccessKeyID | ||
} | ||
|
||
if cred.scope.service != serviceS3 { | ||
return ch, errInvalidServiceS3 | ||
} | ||
|
||
if cred.scope.request != "aws4_request" { | ||
return ch, errInvalidRequestVersion | ||
} | ||
|
||
return cred, ErrNone | ||
} | ||
|
||
// Parse slice of signed headers from signed headers tag. | ||
// | ||
// example: SignedHeaders=host;range;x-amz-date | ||
func parseSignedHeader(hdrElement string) ([]string, ErrorCode) { | ||
signedHdrFields, err := extractFields(hdrElement, "SignedHeaders") | ||
if err != ErrNone { | ||
return nil, err | ||
} | ||
signedHeaders := strings.Split(signedHdrFields, ";") | ||
return signedHeaders, ErrNone | ||
} | ||
|
||
// Parse signature from signature tag. | ||
// | ||
// exmaple: Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024 | ||
func parseSignature(signElement string) (string, ErrorCode) { | ||
return extractFields(signElement, "Signature") | ||
} | ||
|
||
func extractFields(signElement, fieldName string) (string, ErrorCode) { | ||
signFields := strings.Split(strings.TrimSpace(signElement), "=") | ||
if len(signFields) != 2 { | ||
return "", errMissingFields | ||
} | ||
if signFields[0] != fieldName { | ||
return "", errMissingSignTag | ||
} | ||
if signFields[1] == "" { | ||
return "", errMissingFields | ||
} | ||
return signFields[1], ErrNone | ||
} | ||
|
||
// Parses signature version '4' header of the following form. | ||
// | ||
// Authorization: algorithm Credential=accessKeyID/credScope, SignedHeaders=signedHeaders, Signature=signature | ||
func parseSignV4(v4Auth string) (sv signValues, err ErrorCode) { | ||
|
||
if !strings.HasPrefix(v4Auth, signV4Algorithm) { | ||
return sv, errUnsupportAlgorithm | ||
} | ||
|
||
rawCred := strings.ReplaceAll(strings.TrimPrefix(v4Auth, signV4Algorithm), " ", "") | ||
authFields := strings.Split(strings.TrimSpace(rawCred), ",") | ||
if len(authFields) != 3 { | ||
return sv, errMissingFields | ||
} | ||
|
||
// Initialize signature version '4' structured header. | ||
signV4Values := signValues{} | ||
|
||
// Save credentail values. | ||
signV4Values.Credential, err = parseCredentialHeader(authFields[0]) | ||
if err != ErrNone { | ||
return sv, err | ||
} | ||
|
||
// Save signed headers. | ||
signV4Values.SignedHeaders, err = parseSignedHeader(authFields[1]) | ||
if err != ErrNone { | ||
return sv, err | ||
} | ||
|
||
// Save signature. | ||
signV4Values.Signature, err = parseSignature(authFields[2]) | ||
if err != ErrNone { | ||
return sv, err | ||
} | ||
|
||
// Return the structure here. | ||
return signV4Values, ErrNone | ||
} |
Oops, something went wrong.