Skip to content

Commit

Permalink
fix signature error encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisSchinnerl committed Sep 12, 2023
1 parent 968673d commit 01b911a
Show file tree
Hide file tree
Showing 9 changed files with 790 additions and 10 deletions.
15 changes: 6 additions & 9 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
//
// If you add a code to this list, please also add it to ErrorCode.Status().
//
const (
ErrNone ErrorCode = ""

Expand Down Expand Up @@ -150,13 +149,12 @@ type Error interface {
// Code and Message:
//
// func NotQuiteRight(at time.Time, max time.Duration) error {
// code := ErrNotQuiteRight
// return &notQuiteRightResponse{
// ErrorResponse{Code: code, Message: code.Message()},
// 123456789,
// }
// }
//
// code := ErrNotQuiteRight
// return &notQuiteRightResponse{
// ErrorResponse{Code: code, Message: code.Message()},
// 123456789,
// }
// }
type ErrorResponse struct {
XMLName xml.Name `xml:"Error"`

Expand Down Expand Up @@ -291,7 +289,6 @@ func (e ErrorCode) Status() int {
// }
//
// If err is nil and code is ErrNone, HasErrorCode returns true.
//
func HasErrorCode(err error, code ErrorCode) bool {
if err == nil && code == "" {
return true
Expand Down
2 changes: 1 addition & 1 deletion gofakes3.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"sync/atomic"
"time"

"github.com/Mikubill/gofakes3/signature"
"github.com/SiaFoundation/gofakes3/signature"
)

// GoFakeS3 implements HTTP handlers for processing S3 requests and returning
Expand Down
53 changes: 53 additions & 0 deletions signature/credentials.go
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)
}
147 changes: 147 additions & 0 deletions signature/signature-errors.go
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,
})
}
127 changes: 127 additions & 0 deletions signature/signature-v4-parser.go
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
}
Loading

0 comments on commit 01b911a

Please sign in to comment.