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