Skip to content

Commit

Permalink
add support for stream signing
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisSchinnerl committed Sep 13, 2023
1 parent ae483b0 commit f354909
Show file tree
Hide file tree
Showing 8 changed files with 712 additions and 30 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/SiaFoundation/gofakes3
go 1.16

require (
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700
github.com/aws/aws-sdk-go v1.44.256
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500
Expand Down
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700 h1:r3fp2/Ro+0RtpjNY0/wsbN7vRmCW//dXTOZDQTct25Q=
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700/go.mod h1:OSXqXEGUe9CmPiwLMMnVrbXonMf4BeLBkBdLufxxiyY=
github.com/aws/aws-sdk-go v1.44.124/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.256 h1:O8VH+bJqgLDguqkH/xQBFz5o/YheeZqgcOYIgsTVWY4=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -29,14 +26,11 @@ go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
Expand All @@ -49,7 +43,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -72,7 +65,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
Expand Down
68 changes: 68 additions & 0 deletions signature/auth-types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package signature

import (
"net/http"
"net/url"
"strings"
)

// ref: https://github.com/minio/minio/cmd/auth-handler.go

type authType int

// List of all supported auth types.
const (
authTypeUnknown authType = iota
authTypeStreamingSigned
authTypeSigned
authTypeStreamingSignedTrailer
authTypeStreamingUnsignedTrailer
)

// Verify if request has AWS Signature Version '4'.
func isRequestSignatureV4(r *http.Request) bool {
return strings.HasPrefix(r.Header.Get(headerAuth), "AWS4-HMAC-SHA256")
}

// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
func isRequestSignStreamingV4(r *http.Request) bool {
return r.Header.Get("X-Amz-Content-Sha256") == streamingContentSHA256 &&
r.Method == http.MethodPut
}

// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
func isRequestSignStreamingTrailerV4(r *http.Request) bool {
return r.Header.Get("X-Amz-Content-Sha256") == streamingContentSHA256Trailer &&
r.Method == http.MethodPut
}

// Verify if the request has AWS Streaming Signature Version '4', with unsigned content and trailer.
func isRequestUnsignedTrailerV4(r *http.Request) bool {
return r.Header.Get("X-Amz-Content-Sha256") == "STREAMING-UNSIGNED-PAYLOAD-TRAILER" &&
r.Method == http.MethodPut && strings.Contains(r.Header.Get("Content-Encoding"), "aws-chunked")
}

// Get request authentication type.
func getRequestAuthType(r *http.Request) (at authType) {
if r.URL != nil {
var err error
r.Form, err = url.ParseQuery(r.URL.RawQuery)
if err != nil {
return authTypeUnknown
}
}
if isRequestSignStreamingV4(r) {
return authTypeStreamingSigned
} else if isRequestSignStreamingTrailerV4(r) {
return authTypeStreamingSignedTrailer
} else if isRequestUnsignedTrailerV4(r) {
return authTypeStreamingUnsignedTrailer
} else if isRequestSignatureV4(r) {
return authTypeSigned
}
return authTypeUnknown
}

func IsSupportedAuthentication(req *http.Request) bool {
return getRequestAuthType(req) != authTypeUnknown
}
12 changes: 12 additions & 0 deletions signature/signature-errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ type errorCodeMap map[ErrorCode]APIError
const (
errMissingFields ErrorCode = iota
errMissingCredTag
errContentSHA256Mismatch
errCredMalformed
errInvalidAccessKeyID
errMalformedCredentialDate
errInvalidRequestVersion
errInvalidServiceS3
errMissingContentLength
errMissingSignHeadersTag
errMissingSignTag
errUnsignedHeaders
Expand All @@ -59,6 +61,11 @@ var errorCodes = errorCodeMap{
Description: "Missing Credential field for this request.",
HTTPStatusCode: http.StatusBadRequest,
},
errContentSHA256Mismatch: {
Code: "XAmzContentSHA256Mismatch",
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
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\".",
Expand All @@ -84,6 +91,11 @@ var errorCodes = errorCodeMap{
Description: "error parsing the Credential/X-Amz-Credential parameter; incorrect service. This endpoint belongs to \"s3\".",
HTTPStatusCode: http.StatusBadRequest,
},
errMissingContentLength: {
Code: "MissingContentLength",
Description: "You must provide the Content-Length HTTP header.",
HTTPStatusCode: http.StatusLengthRequired,
},
errMissingSignHeadersTag: {
Code: "InvalidArgument",
Description: "Signature header missing SignedHeaders field.",
Expand Down
7 changes: 1 addition & 6 deletions signature/signature-v4-parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,7 @@ func extractFields(signElement, fieldName string) (string, ErrorCode) {
//
// 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), " ", "")
rawCred := strings.ReplaceAll(strings.TrimPrefix(v4Auth, "AWS4-HMAC-SHA256"), " ", "")
authFields := strings.Split(strings.TrimSpace(rawCred), ",")
if len(authFields) != 3 {
return sv, errMissingFields
Expand Down
82 changes: 70 additions & 12 deletions signature/signature-v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"errors"
"io"
"net/http"
"sort"
"strconv"
"strings"
"time"
)

// ref: https://github.com/minio/minio/cmd/auth-handler.go

const (
signV4Algorithm = "AWS4-HMAC-SHA256"
iso8601Format = "20060102T150405Z"
yyyymmdd = "20060102"
serviceS3 = "s3"
slashSeparator = "/"
stype = serviceS3
iso8601Format = "20060102T150405Z"
yyyymmdd = "20060102"
serviceS3 = "s3"
slashSeparator = "/"
stype = serviceS3

headerAuth = "Authorization"
headerDate = "Date"
amzContentSha256 = "X-Amz-Content-Sha256"
amzDate = "X-Amz-Date"
)

var errSignatureMismatch = errors.New("Signature does not match")

// getCanonicalHeaders generate a list of request headers with their values
func getCanonicalHeaders(signedHeaders http.Header) string {
var headers []string
Expand All @@ -52,6 +56,17 @@ func getCanonicalHeaders(signedHeaders http.Header) string {
return buf.String()
}

// getScope generate a string of a specific date, an AWS region, and a service.
func getScope(t time.Time, region string) string {
scope := strings.Join([]string{
t.Format(yyyymmdd),
region,
string(serviceS3),
"aws4_request",
}, slashSeparator)
return scope
}

// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
func getSignedHeaders(signedHeaders http.Header) string {
var headers []string
Expand Down Expand Up @@ -110,7 +125,7 @@ func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr,

// getStringToSign a string based on selected query values.
func getStringToSign(canonicalRequest string, t time.Time, scope string) string {
stringToSign := signV4Algorithm + "\n" + t.Format(iso8601Format) + "\n"
stringToSign := "AWS4-HMAC-SHA256\n" + t.Format(iso8601Format) + "\n"
stringToSign += scope + "\n"
canonicalRequestBytes := sha256.Sum256([]byte(canonicalRequest))
stringToSign += hex.EncodeToString(canonicalRequestBytes[:])
Expand All @@ -126,11 +141,7 @@ func getSigningKey(secretKey string, t time.Time, region string) []byte {
return signingKey
}

// V4SignVerify - Verify authorization header with calculated header in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
//
// returns nil if signature matches.
func V4SignVerify(r *http.Request) ErrorCode {
func authTypeSignedVerify(r *http.Request) ErrorCode {
// Copy request.
req := *r
hashedPayload := getContentSha256Cksum(r)
Expand Down Expand Up @@ -192,3 +203,50 @@ func V4SignVerify(r *http.Request) ErrorCode {
// Return Error none.
return ErrNone
}

func authTypeStreamingVerify(r *http.Request, authType authType) ErrorCode {
var size int64
if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok {
if sizeStr[0] == "" {
return errMissingContentLength
}
var err error
size, err = strconv.ParseInt(sizeStr[0], 10, 64)
if err != nil {
return errMissingContentLength
}
}
var rc io.ReadCloser
var ec ErrorCode
switch authType {
case authTypeStreamingSigned, authTypeStreamingSignedTrailer:
rc, ec = newSignV4ChunkedReader(r, authType == authTypeStreamingSignedTrailer)
case authTypeStreamingUnsignedTrailer:
return errUnsupportAlgorithm // not supported
default:
panic("can't call authTypeStreamingVerify with a non streaming auth type")
}
if ec != ErrNone {
return ec
}
r.Body = rc
r.ContentLength = size
return ErrNone
}

// V4SignVerify - Verify authorization header with calculated header in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
//
// returns nil if signature matches.
func V4SignVerify(r *http.Request) ErrorCode {
// Make sure the authentication type is supported.
authType := getRequestAuthType(r)
switch authType {
case authTypeStreamingSigned, authTypeStreamingSignedTrailer, authTypeStreamingUnsignedTrailer:
return authTypeStreamingVerify(r, authType)
case authTypeSigned:
return authTypeSignedVerify(r)
default:
return errUnsupportAlgorithm
}
}
Loading

0 comments on commit f354909

Please sign in to comment.