forked from crunchytom/go-aws-auth
/
sign4.go
145 lines (121 loc) · 3.85 KB
/
sign4.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
package awsauth
import (
"encoding/hex"
"net/http"
"sort"
"strings"
)
func hashedCanonicalRequestV4(request *http.Request, meta *metadata, signedHeaders []string, queryString bool) string {
// TASK 1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
var payloadHash string
if queryString {
payloadHash = "UNSIGNED-PAYLOAD"
} else {
payload := readAndReplaceBody(request)
payloadHash = hashSHA256(payload)
request.Header.Set("X-Amz-Content-Sha256", payloadHash)
}
// Set this in header values to make it appear in the range of headers to sign
request.Header.Set("Host", request.Host)
var sortedHeaderKeys []string
for key, _ := range request.Header {
keyLower := strings.ToLower(key)
switch key {
case "Content-Md5", "Host":
default:
foundInSigned := false
for _, h := range signedHeaders {
if keyLower == h {
arleadyOnList := false
for _, sh := range sortedHeaderKeys {
if sh == h {
arleadyOnList = true
break
}
}
if arleadyOnList == false {
foundInSigned = true
}
break
}
}
if !foundInSigned && !strings.HasPrefix(key, "X-Amz-") {
continue
}
}
sortedHeaderKeys = append(sortedHeaderKeys, keyLower)
}
sort.Strings(sortedHeaderKeys)
var headersToSign string
for _, key := range sortedHeaderKeys {
value := strings.TrimSpace(request.Header.Get(key))
if key == "host" {
//AWS does not include port in signing request.
if strings.Contains(value, ":") {
split := strings.Split(value, ":")
port := split[1]
if port == "80" || port == "443" {
value = split[0]
}
}
}
headersToSign += key + ":" + value + "\n"
}
meta.signedHeaders = concat(";", sortedHeaderKeys...)
canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(request.URL.Query()), headersToSign, meta.signedHeaders, payloadHash)
return hashSHA256([]byte(canonicalRequest))
}
func stringToSignV4(request *http.Request, hashedCanonReq string, meta *metadata, requestTs string) string {
// TASK 2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
meta.algorithm = "AWS4-HMAC-SHA256"
service, region := serviceAndRegion(request.Host)
if meta.service == "" {
meta.service = service
}
if meta.region == "" {
meta.region = region
}
meta.date = tsDateV4(requestTs)
meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "aws4_request")
return concat("\n", meta.algorithm, requestTs, meta.credentialScope, hashedCanonReq)
}
func signatureV4(signingKey []byte, stringToSign string) string {
// TASK 3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
return hex.EncodeToString(hmacSHA256(signingKey, stringToSign))
}
func prepareRequestV4(request *http.Request) *http.Request {
necessaryDefaults := map[string]string{
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
"X-Amz-Date": timestampV4(),
}
for header, value := range necessaryDefaults {
if request.Header.Get(header) == "" {
request.Header.Set(header, value)
}
}
if request.URL.Path == "" {
request.URL.Path += "/"
}
return request
}
func signingKeyV4(secretKey, date, region, service string) []byte {
kDate := hmacSHA256([]byte("AWS4"+secretKey), date)
kRegion := hmacSHA256(kDate, region)
kService := hmacSHA256(kRegion, service)
kSigning := hmacSHA256(kService, "aws4_request")
return kSigning
}
func buildAuthHeaderV4(signature string, meta *metadata, keys Credentials) string {
credential := keys.AccessKeyID + "/" + meta.credentialScope
return meta.algorithm +
" Credential=" + credential +
", SignedHeaders=" + meta.signedHeaders +
", Signature=" + signature
}
func timestampV4() string {
return now().Format(timeFormatV4)
}
func tsDateV4(timestamp string) string {
return timestamp[:8]
}
const timeFormatV4 = "20060102T150405Z"