s3gof3r/sign.go

196 строки
4.3 KiB
Go

package s3gof3r
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
)
const (
prefix = "AWS4-HMAC-SHA256"
isoFormat = "20060102T150405Z"
shortDate = "20060102"
)
var ignoredHeaders = map[string]bool{
"Authorization": true,
"Content-Type": true,
"Content-Length": true,
"User-Agent": true,
}
type signer struct {
Time time.Time
Request *http.Request
Region string
Keys Keys
credentialString string
signedHeaders string
signature string
canonicalHeaders string
canonicalString string
stringToSign string
}
// Sign signs the http.Request
func (b *Bucket) Sign(req *http.Request) {
if req.Header == nil {
req.Header = http.Header{}
}
if b.S3.Keys.SecurityToken != "" {
req.Header.Set("X-Amz-Security-Token", b.S3.Keys.SecurityToken)
}
req.Header.Set("User-Agent", "S3Gof3r")
s := &signer{
Time: time.Now(),
Request: req,
Region: b.S3.Region(),
Keys: b.S3.Keys,
}
s.sign()
}
func (s *signer) sign() {
s.buildTime()
s.buildCredentialString()
s.buildCanonicalHeaders()
s.buildCanonicalString()
s.buildStringToSign()
s.buildSignature()
parts := []string{
prefix + " Credential=" + s.Keys.AccessKey + "/" + s.credentialString,
"SignedHeaders=" + s.signedHeaders,
"Signature=" + s.signature,
}
s.Request.Header.Set("Authorization", strings.Join(parts, ","))
}
func (s *signer) buildTime() {
s.Request.Header.Set("X-Amz-Date", s.Time.UTC().Format(isoFormat))
}
func (s *signer) buildCredentialString() {
s.credentialString = strings.Join([]string{
s.Time.UTC().Format(shortDate),
s.Region,
"s3",
"aws4_request",
}, "/")
}
func (s *signer) buildCanonicalHeaders() {
var headers []string
headers = append(headers, "host")
for k := range s.Request.Header {
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
continue
}
headers = append(headers, strings.ToLower(k))
}
sort.Strings(headers)
s.signedHeaders = strings.Join(headers, ";")
headerValues := make([]string, len(headers))
for i, k := range headers {
if k == "host" {
headerValues[i] = "host:" + s.Request.URL.Host
} else {
headerValues[i] = k + ":" +
strings.Join(s.Request.Header[http.CanonicalHeaderKey(k)], ",")
}
}
s.canonicalHeaders = strings.Join(headerValues, "\n")
}
func (s *signer) buildCanonicalString() {
s.Request.URL.RawQuery = strings.Replace(s.Request.URL.Query().Encode(), "+", "%20", -1)
uri := s.Request.URL.Opaque
if uri != "" {
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
} else {
uri = s.Request.URL.EscapedPath()
}
if uri == "" {
uri = "/"
}
uri = strings.Replace(uri, "@", "%40", -1)
uri = strings.Replace(uri, ":", "%3A", -1)
s.canonicalString = strings.Join([]string{
s.Request.Method,
uri,
s.Request.URL.RawQuery,
s.canonicalHeaders + "\n",
s.signedHeaders,
s.bodyDigest(),
}, "\n")
}
func (s *signer) buildStringToSign() {
s.stringToSign = strings.Join([]string{
prefix,
s.Time.UTC().Format(isoFormat),
s.credentialString,
hex.EncodeToString(sha([]byte(s.canonicalString))),
}, "\n")
}
func (s *signer) buildSignature() {
secret := s.Keys.SecretKey
date := hmacSign([]byte("AWS4"+secret), []byte(s.Time.UTC().Format(shortDate)))
region := hmacSign(date, []byte(s.Region))
service := hmacSign(region, []byte("s3"))
credentials := hmacSign(service, []byte("aws4_request"))
signature := hmacSign(credentials, []byte(s.stringToSign))
s.signature = hex.EncodeToString(signature)
}
func (s *signer) bodyDigest() string {
hash := s.Request.Header.Get("X-Amz-Content-Sha256")
if hash == "" {
if s.Request.Body == nil {
hash = hex.EncodeToString(sha([]byte{}))
} else {
body, _ := ioutil.ReadAll(s.Request.Body)
s.Request.Body = ioutil.NopCloser(bytes.NewReader(body))
hash = hex.EncodeToString(sha(body))
}
s.Request.Header.Add("X-Amz-Content-Sha256", hash)
}
return hash
}
func hmacSign(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
func sha(data []byte) []byte {
hash := sha256.New()
hash.Write(data)
return hash.Sum(nil)
}
func shaReader(r io.ReadSeeker) string {
hash := sha256.New()
start, _ := r.Seek(0, 1)
defer r.Seek(start, 0)
io.Copy(hash, r)
sum := hash.Sum(nil)
return hex.EncodeToString(sum)
}