181 lines
4.6 KiB
Go
181 lines
4.6 KiB
Go
|
package v2
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha256"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/aws/aws-sdk-go/aws"
|
||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||
|
"github.com/aws/aws-sdk-go/aws/request"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
errInvalidMethod = errors.New("v2 signer only handles HTTP POST")
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
signatureVersion = "2"
|
||
|
signatureMethod = "HmacSHA256"
|
||
|
timeFormat = "2006-01-02T15:04:05Z"
|
||
|
)
|
||
|
|
||
|
type signer struct {
|
||
|
// Values that must be populated from the request
|
||
|
Request *http.Request
|
||
|
Time time.Time
|
||
|
Credentials *credentials.Credentials
|
||
|
Debug aws.LogLevelType
|
||
|
Logger aws.Logger
|
||
|
|
||
|
Query url.Values
|
||
|
stringToSign string
|
||
|
signature string
|
||
|
}
|
||
|
|
||
|
// SignRequestHandler is a named request handler the SDK will use to sign
|
||
|
// service client request with using the V4 signature.
|
||
|
var SignRequestHandler = request.NamedHandler{
|
||
|
Name: "v2.SignRequestHandler", Fn: SignSDKRequest,
|
||
|
}
|
||
|
|
||
|
// SignSDKRequest requests with signature version 2.
|
||
|
//
|
||
|
// Will sign the requests with the service config's Credentials object
|
||
|
// Signing is skipped if the credentials is the credentials.AnonymousCredentials
|
||
|
// object.
|
||
|
func SignSDKRequest(req *request.Request) {
|
||
|
// If the request does not need to be signed ignore the signing of the
|
||
|
// request if the AnonymousCredentials object is used.
|
||
|
if req.Config.Credentials == credentials.AnonymousCredentials {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if req.HTTPRequest.Method != "POST" && req.HTTPRequest.Method != "GET" {
|
||
|
// The V2 signer only supports GET and POST
|
||
|
req.Error = errInvalidMethod
|
||
|
return
|
||
|
}
|
||
|
|
||
|
v2 := signer{
|
||
|
Request: req.HTTPRequest,
|
||
|
Time: req.Time,
|
||
|
Credentials: req.Config.Credentials,
|
||
|
Debug: req.Config.LogLevel.Value(),
|
||
|
Logger: req.Config.Logger,
|
||
|
}
|
||
|
|
||
|
req.Error = v2.Sign()
|
||
|
|
||
|
if req.Error != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if req.HTTPRequest.Method == "POST" {
|
||
|
// Set the body of the request based on the modified query parameters
|
||
|
req.SetStringBody(v2.Query.Encode())
|
||
|
|
||
|
// Now that the body has changed, remove any Content-Length header,
|
||
|
// because it will be incorrect
|
||
|
req.HTTPRequest.ContentLength = 0
|
||
|
req.HTTPRequest.Header.Del("Content-Length")
|
||
|
} else {
|
||
|
req.HTTPRequest.URL.RawQuery = v2.Query.Encode()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (v2 *signer) Sign() error {
|
||
|
credValue, err := v2.Credentials.Get()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if v2.Request.Method == "POST" {
|
||
|
// Parse the HTTP request to obtain the query parameters that will
|
||
|
// be used to build the string to sign. Note that because the HTTP
|
||
|
// request will need to be modified, the PostForm and Form properties
|
||
|
// are reset to nil after parsing.
|
||
|
v2.Request.ParseForm()
|
||
|
v2.Query = v2.Request.PostForm
|
||
|
v2.Request.PostForm = nil
|
||
|
v2.Request.Form = nil
|
||
|
} else {
|
||
|
v2.Query = v2.Request.URL.Query()
|
||
|
}
|
||
|
|
||
|
// Set new query parameters
|
||
|
v2.Query.Set("AWSAccessKeyId", credValue.AccessKeyID)
|
||
|
v2.Query.Set("SignatureVersion", signatureVersion)
|
||
|
v2.Query.Set("SignatureMethod", signatureMethod)
|
||
|
v2.Query.Set("Timestamp", v2.Time.UTC().Format(timeFormat))
|
||
|
if credValue.SessionToken != "" {
|
||
|
v2.Query.Set("SecurityToken", credValue.SessionToken)
|
||
|
}
|
||
|
|
||
|
// in case this is a retry, ensure no signature present
|
||
|
v2.Query.Del("Signature")
|
||
|
|
||
|
method := v2.Request.Method
|
||
|
host := v2.Request.URL.Host
|
||
|
path := v2.Request.URL.Path
|
||
|
if path == "" {
|
||
|
path = "/"
|
||
|
}
|
||
|
|
||
|
// obtain all of the query keys and sort them
|
||
|
queryKeys := make([]string, 0, len(v2.Query))
|
||
|
for key := range v2.Query {
|
||
|
queryKeys = append(queryKeys, key)
|
||
|
}
|
||
|
sort.Strings(queryKeys)
|
||
|
|
||
|
// build URL-encoded query keys and values
|
||
|
queryKeysAndValues := make([]string, len(queryKeys))
|
||
|
for i, key := range queryKeys {
|
||
|
k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
|
||
|
v := strings.Replace(url.QueryEscape(v2.Query.Get(key)), "+", "%20", -1)
|
||
|
queryKeysAndValues[i] = k + "=" + v
|
||
|
}
|
||
|
|
||
|
// join into one query string
|
||
|
query := strings.Join(queryKeysAndValues, "&")
|
||
|
|
||
|
// build the canonical string for the V2 signature
|
||
|
v2.stringToSign = strings.Join([]string{
|
||
|
method,
|
||
|
host,
|
||
|
path,
|
||
|
query,
|
||
|
}, "\n")
|
||
|
|
||
|
hash := hmac.New(sha256.New, []byte(credValue.SecretAccessKey))
|
||
|
hash.Write([]byte(v2.stringToSign))
|
||
|
v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||
|
v2.Query.Set("Signature", v2.signature)
|
||
|
|
||
|
if v2.Debug.Matches(aws.LogDebugWithSigning) {
|
||
|
v2.logSigningInfo()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
const logSignInfoMsg = `DEBUG: Request Signature:
|
||
|
---[ STRING TO SIGN ]--------------------------------
|
||
|
%s
|
||
|
---[ SIGNATURE ]-------------------------------------
|
||
|
%s
|
||
|
-----------------------------------------------------`
|
||
|
|
||
|
func (v2 *signer) logSigningInfo() {
|
||
|
msg := fmt.Sprintf(logSignInfoMsg, v2.stringToSign, v2.Query.Get("Signature"))
|
||
|
v2.Logger.Log(msg)
|
||
|
}
|