forked from aws/aws-sdk-go
-
Notifications
You must be signed in to change notification settings - Fork 2
/
v2.go
174 lines (147 loc) · 4.37 KB
/
v2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
}
// Sign 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 Sign(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)
}