1package awsauth
2
3import (
4	"encoding/hex"
5	"net/http"
6	"sort"
7	"strings"
8)
9
10func hashedCanonicalRequestV4(request *http.Request, meta *metadata) string {
11	// TASK 1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
12
13	payload := readAndReplaceBody(request)
14	payloadHash := hashSHA256(payload)
15	request.Header.Set("X-Amz-Content-Sha256", payloadHash)
16
17	// Set this in header values to make it appear in the range of headers to sign
18	request.Header.Set("Host", request.Host)
19
20	var sortedHeaderKeys []string
21	for key, _ := range request.Header {
22		switch key {
23		case "Content-Type", "Content-Md5", "Host":
24		default:
25			if !strings.HasPrefix(key, "X-Amz-") {
26				continue
27			}
28		}
29		sortedHeaderKeys = append(sortedHeaderKeys, strings.ToLower(key))
30	}
31	sort.Strings(sortedHeaderKeys)
32
33	var headersToSign string
34	for _, key := range sortedHeaderKeys {
35		value := strings.TrimSpace(request.Header.Get(key))
36		if key == "host" {
37			//AWS does not include port in signing request.
38			if strings.Contains(value, ":") {
39				split := strings.Split(value, ":")
40				port := split[1]
41				if port == "80" || port == "443" {
42					value = split[0]
43				}
44			}
45		}
46		headersToSign += key + ":" + value + "\n"
47	}
48	meta.signedHeaders = concat(";", sortedHeaderKeys...)
49	canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(request.URL.Query()), headersToSign, meta.signedHeaders, payloadHash)
50
51	return hashSHA256([]byte(canonicalRequest))
52}
53
54func stringToSignV4(request *http.Request, hashedCanonReq string, meta *metadata) string {
55	// TASK 2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
56
57	requestTs := request.Header.Get("X-Amz-Date")
58
59	meta.algorithm = "AWS4-HMAC-SHA256"
60	meta.service, meta.region = serviceAndRegion(request.Host)
61	meta.date = tsDateV4(requestTs)
62	meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "aws4_request")
63
64	return concat("\n", meta.algorithm, requestTs, meta.credentialScope, hashedCanonReq)
65}
66
67func signatureV4(signingKey []byte, stringToSign string) string {
68	// TASK 3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
69
70	return hex.EncodeToString(hmacSHA256(signingKey, stringToSign))
71}
72
73func prepareRequestV4(request *http.Request) *http.Request {
74	necessaryDefaults := map[string]string{
75		"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
76		"X-Amz-Date":   timestampV4(),
77	}
78
79	for header, value := range necessaryDefaults {
80		if request.Header.Get(header) == "" {
81			request.Header.Set(header, value)
82		}
83	}
84
85	if request.URL.Path == "" {
86		request.URL.Path += "/"
87	}
88
89	return request
90}
91
92func signingKeyV4(secretKey, date, region, service string) []byte {
93	kDate := hmacSHA256([]byte("AWS4"+secretKey), date)
94	kRegion := hmacSHA256(kDate, region)
95	kService := hmacSHA256(kRegion, service)
96	kSigning := hmacSHA256(kService, "aws4_request")
97	return kSigning
98}
99
100func buildAuthHeaderV4(signature string, meta *metadata, keys Credentials) string {
101	credential := keys.AccessKeyID + "/" + meta.credentialScope
102
103	return meta.algorithm +
104		" Credential=" + credential +
105		", SignedHeaders=" + meta.signedHeaders +
106		", Signature=" + signature
107}
108
109func timestampV4() string {
110	return now().Format(timeFormatV4)
111}
112
113func tsDateV4(timestamp string) string {
114	return timestamp[:8]
115}
116
117const timeFormatV4 = "20060102T150405Z"
118