github.com/aavshr/aws-sdk-go@v1.41.3/private/signer/v2/v2.go (about)

     1  package v2
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"encoding/base64"
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/aavshr/aws-sdk-go/aws"
    16  	"github.com/aavshr/aws-sdk-go/aws/credentials"
    17  	"github.com/aavshr/aws-sdk-go/aws/request"
    18  )
    19  
    20  var (
    21  	errInvalidMethod = errors.New("v2 signer only handles HTTP POST")
    22  )
    23  
    24  const (
    25  	signatureVersion = "2"
    26  	signatureMethod  = "HmacSHA256"
    27  	timeFormat       = "2006-01-02T15:04:05Z"
    28  )
    29  
    30  type signer struct {
    31  	// Values that must be populated from the request
    32  	Request     *http.Request
    33  	Time        time.Time
    34  	Credentials *credentials.Credentials
    35  	Debug       aws.LogLevelType
    36  	Logger      aws.Logger
    37  
    38  	Query        url.Values
    39  	stringToSign string
    40  	signature    string
    41  }
    42  
    43  // SignRequestHandler is a named request handler the SDK will use to sign
    44  // service client request with using the V4 signature.
    45  var SignRequestHandler = request.NamedHandler{
    46  	Name: "v2.SignRequestHandler", Fn: SignSDKRequest,
    47  }
    48  
    49  // SignSDKRequest requests with signature version 2.
    50  //
    51  // Will sign the requests with the service config's Credentials object
    52  // Signing is skipped if the credentials is the credentials.AnonymousCredentials
    53  // object.
    54  func SignSDKRequest(req *request.Request) {
    55  	// If the request does not need to be signed ignore the signing of the
    56  	// request if the AnonymousCredentials object is used.
    57  	if req.Config.Credentials == credentials.AnonymousCredentials {
    58  		return
    59  	}
    60  
    61  	if req.HTTPRequest.Method != "POST" && req.HTTPRequest.Method != "GET" {
    62  		// The V2 signer only supports GET and POST
    63  		req.Error = errInvalidMethod
    64  		return
    65  	}
    66  
    67  	v2 := signer{
    68  		Request:     req.HTTPRequest,
    69  		Time:        req.Time,
    70  		Credentials: req.Config.Credentials,
    71  		Debug:       req.Config.LogLevel.Value(),
    72  		Logger:      req.Config.Logger,
    73  	}
    74  
    75  	req.Error = v2.Sign()
    76  
    77  	if req.Error != nil {
    78  		return
    79  	}
    80  
    81  	if req.HTTPRequest.Method == "POST" {
    82  		// Set the body of the request based on the modified query parameters
    83  		req.SetStringBody(v2.Query.Encode())
    84  
    85  		// Now that the body has changed, remove any Content-Length header,
    86  		// because it will be incorrect
    87  		req.HTTPRequest.ContentLength = 0
    88  		req.HTTPRequest.Header.Del("Content-Length")
    89  	} else {
    90  		req.HTTPRequest.URL.RawQuery = v2.Query.Encode()
    91  	}
    92  }
    93  
    94  func (v2 *signer) Sign() error {
    95  	credValue, err := v2.Credentials.Get()
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	if v2.Request.Method == "POST" {
   101  		// Parse the HTTP request to obtain the query parameters that will
   102  		// be used to build the string to sign. Note that because the HTTP
   103  		// request will need to be modified, the PostForm and Form properties
   104  		// are reset to nil after parsing.
   105  		v2.Request.ParseForm()
   106  		v2.Query = v2.Request.PostForm
   107  		v2.Request.PostForm = nil
   108  		v2.Request.Form = nil
   109  	} else {
   110  		v2.Query = v2.Request.URL.Query()
   111  	}
   112  
   113  	// Set new query parameters
   114  	v2.Query.Set("AWSAccessKeyId", credValue.AccessKeyID)
   115  	v2.Query.Set("SignatureVersion", signatureVersion)
   116  	v2.Query.Set("SignatureMethod", signatureMethod)
   117  	v2.Query.Set("Timestamp", v2.Time.UTC().Format(timeFormat))
   118  	if credValue.SessionToken != "" {
   119  		v2.Query.Set("SecurityToken", credValue.SessionToken)
   120  	}
   121  
   122  	// in case this is a retry, ensure no signature present
   123  	v2.Query.Del("Signature")
   124  
   125  	method := v2.Request.Method
   126  	host := v2.Request.URL.Host
   127  	path := v2.Request.URL.Path
   128  	if path == "" {
   129  		path = "/"
   130  	}
   131  
   132  	// obtain all of the query keys and sort them
   133  	queryKeys := make([]string, 0, len(v2.Query))
   134  	for key := range v2.Query {
   135  		queryKeys = append(queryKeys, key)
   136  	}
   137  	sort.Strings(queryKeys)
   138  
   139  	// build URL-encoded query keys and values
   140  	queryKeysAndValues := make([]string, len(queryKeys))
   141  	for i, key := range queryKeys {
   142  		k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
   143  		v := strings.Replace(url.QueryEscape(v2.Query.Get(key)), "+", "%20", -1)
   144  		queryKeysAndValues[i] = k + "=" + v
   145  	}
   146  
   147  	// join into one query string
   148  	query := strings.Join(queryKeysAndValues, "&")
   149  
   150  	// build the canonical string for the V2 signature
   151  	v2.stringToSign = strings.Join([]string{
   152  		method,
   153  		host,
   154  		path,
   155  		query,
   156  	}, "\n")
   157  
   158  	hash := hmac.New(sha256.New, []byte(credValue.SecretAccessKey))
   159  	hash.Write([]byte(v2.stringToSign))
   160  	v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
   161  	v2.Query.Set("Signature", v2.signature)
   162  
   163  	if v2.Debug.Matches(aws.LogDebugWithSigning) {
   164  		v2.logSigningInfo()
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  const logSignInfoMsg = `DEBUG: Request Signature:
   171  ---[ STRING TO SIGN ]--------------------------------
   172  %s
   173  ---[ SIGNATURE ]-------------------------------------
   174  %s
   175  -----------------------------------------------------`
   176  
   177  func (v2 *signer) logSigningInfo() {
   178  	msg := fmt.Sprintf(logSignInfoMsg, v2.stringToSign, v2.Query.Get("Signature"))
   179  	v2.Logger.Log(msg)
   180  }