| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- package tos
- import (
- "bytes"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "fmt"
- "net/http"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "time"
- )
- const (
- emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
- unsignedPayload = "UNSIGNED-PAYLOAD"
- signPrefix = "TOS4-HMAC-SHA256"
- iso8601Layout = "20060102T150405Z"
- yyMMdd = "20060102"
- serverTimeFormat = "2006-01-02T15:04:05Z"
- authorization = "Authorization"
- v4Algorithm = "X-Tos-Algorithm"
- v4Credential = "X-Tos-Credential"
- v4Date = "X-Tos-Date"
- v4Expires = "X-Tos-Expires"
- v4SignedHeaders = "X-Tos-SignedHeaders"
- v4Signature = "X-Tos-Signature"
- v4SignatureLower = "x-tos-signature"
- v4ContentSHA256 = "X-Tos-Content-Sha256"
- v4SecurityToken = "X-Tos-Security-Token"
- v4Prefix = "x-tos"
- )
- func defaultSigningQueryV4(key string) bool {
- return key != v4SignatureLower
- }
- func defaultSigningHeaderV4(key string, isSigningQuery bool) bool {
- return (key == "content-type" && !isSigningQuery) || strings.HasPrefix(key, v4Prefix)
- }
- func UTCNow() time.Time {
- return time.Now().UTC()
- }
- type Signer interface {
- SignHeader(req *Request) http.Header
- SignQuery(req *Request, ttl time.Duration) url.Values
- }
- type SigningKeyInfo struct {
- Date string
- Region string
- Credential *Credential
- }
- type SignV4 struct {
- credentials Credentials
- region string
- signingHeader func(key string, isSigningQuery bool) bool
- signingQuery func(key string) bool
- now func() time.Time
- signingKey func(*SigningKeyInfo) []byte
- logger Logger
- }
- type signedRes struct {
- CanonicalString string
- StringToSign string
- Sign string
- }
- type signedHeader struct {
- CanonicalString string
- StringToSign string
- Header http.Header
- }
- type signedQuery struct {
- CanonicalString string
- StringToSign string
- Query url.Values
- }
- type SignV4Option func(*SignV4)
- func (sv *SignV4) WithSignLogger(logger Logger) {
- sv.logger = logger
- }
- // NewSignV4 create SignV4
- // use WithSignKey to set self-defined sign-key generator
- // use WithSignLogger to set logger
- func NewSignV4(credentials Credentials, region string) *SignV4 {
- signV4 := &SignV4{
- credentials: credentials,
- region: region,
- signingHeader: defaultSigningHeaderV4,
- signingQuery: defaultSigningQueryV4,
- now: UTCNow,
- signingKey: SigningKey,
- }
- return signV4
- }
- // WithSigningKey for self-defined sign-key generator
- func (sv *SignV4) WithSigningKey(signingKey func(*SigningKeyInfo) []byte) {
- sv.signingKey = signingKey
- }
- func (sv *SignV4) signedHeader(header http.Header, isSignedQuery bool) KVs {
- var signed = make(KVs, 0, 10)
- for key, values := range header {
- kk := strings.ToLower(key)
- if sv.signingHeader(kk, isSignedQuery) {
- vv := make([]string, 0, len(values))
- for _, value := range values {
- vv = append(vv, strings.Join(strings.Fields(value), " "))
- }
- signed = append(signed, KV{Key: kk, Values: vv})
- }
- }
- return signed
- }
- func (sv *SignV4) signedQuery(query url.Values, extra url.Values) KVs {
- var signed = make(KVs, 0, len(query)+len(extra))
- for key, values := range query {
- if sv.signingQuery(strings.ToLower(key)) {
- signed = append(signed, KV{Key: key, Values: values})
- }
- }
- for key, values := range extra {
- if sv.signingQuery(strings.ToLower(key)) {
- signed = append(signed, KV{Key: key, Values: values})
- }
- }
- return signed
- }
- func (sv *SignV4) canonicalRequest(method, path, contentSha256 string, header, query KVs) string {
- const split = byte('\n')
- var buf bytes.Buffer
- buf.Grow(512)
- // Method
- buf.WriteString(method)
- buf.WriteByte(split)
- // URI
- buf.Write(encodePath(path))
- buf.WriteByte(split)
- // query
- buf.Write(encodeQuery(query))
- buf.WriteByte(split)
- // canonical headers
- keys := make([]string, 0, len(header))
- for _, kv := range header {
- keys = append(keys, kv.Key)
- buf.WriteString(kv.Key)
- buf.WriteByte(':')
- buf.WriteString(strings.Join(kv.Values, ","))
- buf.WriteByte('\n')
- }
- buf.WriteByte(split)
- // signed headers
- buf.WriteString(strings.Join(keys, ";"))
- buf.WriteByte(split)
- if len(contentSha256) > 0 {
- buf.WriteString(contentSha256)
- } else {
- buf.WriteString(emptySHA256)
- }
- return buf.String()
- }
- func SigningKey(info *SigningKeyInfo) []byte {
- date := hmacSHA256([]byte(info.Credential.AccessKeySecret), []byte(info.Date))
- region := hmacSHA256(date, []byte(info.Region))
- service := hmacSHA256(region, []byte("tos"))
- return hmacSHA256(service, []byte("request"))
- }
- func (sv *SignV4) doSign(method, path, contentSha256 string, header, query KVs, now time.Time, cred *Credential) signedRes {
- const split = byte('\n')
- canonicalStr := sv.canonicalRequest(method, path, contentSha256, header, query)
- var buf bytes.Buffer
- buf.Grow(len(signPrefix) + 128)
- buf.WriteString(signPrefix)
- buf.WriteByte(split)
- buf.WriteString(now.Format(iso8601Layout))
- buf.WriteByte(split)
- date := now.Format(yyMMdd)
- buf.WriteString(date) // yyMMdd + '/' + region + '/' + service + '/' + request
- buf.WriteByte('/')
- buf.WriteString(sv.region)
- buf.WriteString("/tos/request")
- buf.WriteByte(split)
- sum := sha256.Sum256([]byte(canonicalStr))
- buf.WriteString(hex.EncodeToString(sum[:]))
- signK := sv.signingKey(&SigningKeyInfo{Date: date, Region: sv.region, Credential: cred})
- sign := hmacSHA256(signK, buf.Bytes())
- return signedRes{
- CanonicalString: canonicalStr,
- StringToSign: buf.String(),
- Sign: hex.EncodeToString(sign),
- }
- }
- func (sv *SignV4) SignHeader(req *Request) http.Header {
- signed := make(http.Header, 4)
- now := sv.now()
- date := now.Format(iso8601Layout)
- contentSha256 := req.Header.Get(v4ContentSHA256)
- signedHeader := sv.signedHeader(req.Header, false)
- signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4Date), Values: []string{date}})
- signedHeader = append(signedHeader, KV{Key: "date", Values: []string{date}})
- signedHeader = append(signedHeader, KV{Key: "host", Values: []string{req.Host}})
- // if len(contentSha256) == 0 {
- // signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4ContentSHA256), Values: []string{unsignedPayload}})
- // signed.Set(v4ContentSHA256, unsignedPayload)
- // }
- cred := sv.credentials.Credential()
- if sts := cred.SecurityToken; len(sts) > 0 {
- signedHeader = append(signedHeader, KV{Key: strings.ToLower(v4SecurityToken), Values: []string{sts}})
- signed.Set(v4SecurityToken, sts)
- }
- sort.Sort(signedHeader)
- signedQuery := sv.signedQuery(req.Query, nil)
- signRes := sv.doSign(req.Method, req.Path, contentSha256, signedHeader, signedQuery, now, &cred)
- credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, now.Format(yyMMdd), sv.region)
- auth := fmt.Sprintf("TOS4-HMAC-SHA256 Credential=%s,SignedHeaders=%s,Signature=%s", credential, joinKeys(signedHeader), signRes.Sign)
- signed.Set(authorization, auth)
- signed.Set(v4Date, date)
- signed.Set("Date", date)
- if sv.logger != nil {
- sv.logger.Debug("[tos] CanonicalString:" + "\n" + signRes.CanonicalString + "\n")
- sv.logger.Debug("[tos] StringToSign:" + "\n" + signRes.StringToSign + "\n")
- }
- return signed
- }
- func (sv *SignV4) SignQuery(req *Request, ttl time.Duration) url.Values {
- now := sv.now()
- date := now.Format(iso8601Layout)
- query := req.Query
- extra := make(url.Values)
- cred := sv.credentials.Credential()
- credential := fmt.Sprintf("%s/%s/%s/tos/request", cred.AccessKeyID, now.Format(yyMMdd), sv.region)
- extra.Add(v4Algorithm, signPrefix)
- extra.Add(v4Credential, credential)
- extra.Add(v4Date, date)
- extra.Add(v4Expires, strconv.FormatInt(ttl.Milliseconds()/1000, 10))
- if sts := cred.SecurityToken; len(sts) > 0 {
- extra.Add(v4SecurityToken, sts)
- }
- signedHeader := sv.signedHeader(req.Header, true)
- signedHeader = append(signedHeader, KV{Key: "host", Values: []string{req.Host}})
- sort.Sort(signedHeader)
- extra.Add(v4SignedHeaders, joinKeys(signedHeader))
- signedQuery := sv.signedQuery(query, extra)
- signRes := sv.doSign(req.Method, req.Path, unsignedPayload, signedHeader, signedQuery, now, &cred)
- extra.Add(v4Signature, signRes.Sign)
- if sv.logger != nil {
- sv.logger.Debug("[tos] CanonicalString:" + "\n" + signRes.CanonicalString + "\n")
- sv.logger.Debug("[tos] StringToSign:" + "\n" + signRes.StringToSign + "\n")
- }
- return extra
- }
- type KV struct {
- Key string
- Values []string
- }
- type KVs []KV
- func (kvs KVs) Len() int { return len(kvs) }
- func (kvs KVs) Swap(i, j int) { kvs[i], kvs[j] = kvs[j], kvs[i] }
- func (kvs KVs) Less(i, j int) bool { return kvs[i].Key < kvs[j].Key }
- func joinKeys(kvs KVs) string {
- keys := make([]string, 0, len(kvs))
- for i := range kvs {
- keys = append(keys, kvs[i].Key)
- }
- sort.Strings(keys)
- return strings.Join(keys, ";")
- }
- func hmacSHA256(key []byte, value []byte) []byte {
- h := hmac.New(sha256.New, key)
- h.Write(value)
- return h.Sum(nil)
- }
- var (
- nonEscape [256]bool
- )
- // ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.')
- func init() {
- for i := 'a'; i <= 'z'; i++ {
- nonEscape[i] = true
- }
- for i := 'A'; i <= 'Z'; i++ {
- nonEscape[i] = true
- }
- for i := '0'; i <= '9'; i++ {
- nonEscape[i] = true
- }
- nonEscape['-'] = true
- nonEscape['_'] = true
- nonEscape['.'] = true
- nonEscape['~'] = true
- }
- func encodePath(path string) []byte {
- if len(path) == 0 {
- return []byte{'/'}
- }
- return URIEncode(path, false)
- }
- func encodeQuery(query KVs) []byte {
- if len(query) == 0 {
- return make([]byte, 0)
- }
- var buf bytes.Buffer
- buf.Grow(512)
- sort.Sort(query)
- for _, kv := range query {
- keyEscaped := URIEncode(kv.Key, true)
- for _, v := range kv.Values {
- if buf.Len() > 0 {
- buf.WriteByte('&')
- }
- buf.Write(keyEscaped)
- buf.WriteByte('=')
- buf.Write(URIEncode(v, true))
- }
- }
- return buf.Bytes()
- }
- func URIEncode(in string, encodeSlash bool) []byte {
- hexCount := 0
- for i := 0; i < len(in); i++ {
- c := uint8(in[i])
- if c == '/' {
- if encodeSlash {
- hexCount++
- }
- } else if !nonEscape[c] {
- hexCount++
- }
- }
- encoded := make([]byte, len(in)+2*hexCount)
- for i, j := 0, 0; i < len(in); i++ {
- c := uint8(in[i])
- if c == '/' {
- if encodeSlash {
- encoded[j] = '%'
- encoded[j+1] = '2'
- encoded[j+2] = 'F'
- j += 3
- } else {
- encoded[j] = c
- j++
- }
- } else if !nonEscape[c] {
- encoded[j] = '%'
- encoded[j+1] = "0123456789ABCDEF"[c>>4]
- encoded[j+2] = "0123456789ABCDEF"[c&15]
- j += 3
- } else {
- encoded[j] = c
- j++
- }
- }
- return encoded
- }
|