yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/signer.go (about)

     1  // HWS API Gateway Signature
     2  // based on https://github.com/datastream/aws/blob/master/signv4.go
     3  // Copyright (c) 2014, Xianjie
     4  
     5  package hcs
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/hmac"
    10  	"crypto/sha256"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  const (
    20  	BasicDateFormat     = "20060102T150405Z"
    21  	Algorithm           = "SDK-HMAC-SHA256"
    22  	HeaderXDate         = "X-Sdk-Date"
    23  	HeaderHost          = "host"
    24  	HeaderAuthorization = "Authorization"
    25  	HeaderContentSha256 = "X-Sdk-Content-Sha256"
    26  )
    27  
    28  func shouldEscape(c byte) bool {
    29  	if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' {
    30  		return false
    31  	}
    32  	return true
    33  }
    34  func escape(s string) string {
    35  	hexCount := 0
    36  	for i := 0; i < len(s); i++ {
    37  		c := s[i]
    38  		if shouldEscape(c) {
    39  			hexCount++
    40  		}
    41  	}
    42  
    43  	if hexCount == 0 {
    44  		return s
    45  	}
    46  
    47  	t := make([]byte, len(s)+2*hexCount)
    48  	j := 0
    49  	for i := 0; i < len(s); i++ {
    50  		switch c := s[i]; {
    51  		case shouldEscape(c):
    52  			t[j] = '%'
    53  			t[j+1] = "0123456789ABCDEF"[c>>4]
    54  			t[j+2] = "0123456789ABCDEF"[c&15]
    55  			j += 3
    56  		default:
    57  			t[j] = s[i]
    58  			j++
    59  		}
    60  	}
    61  	return string(t)
    62  }
    63  
    64  func hmacsha256(key []byte, data string) ([]byte, error) {
    65  	h := hmac.New(sha256.New, []byte(key))
    66  	if _, err := h.Write([]byte(data)); err != nil {
    67  		return nil, err
    68  	}
    69  	return h.Sum(nil), nil
    70  }
    71  
    72  // Build a CanonicalRequest from a regular request string
    73  //
    74  // CanonicalRequest =
    75  //  HTTPRequestMethod + '\n' +
    76  //  CanonicalURI + '\n' +
    77  //  CanonicalQueryString + '\n' +
    78  //  CanonicalHeaders + '\n' +
    79  //  SignedHeaders + '\n' +
    80  //  HexEncode(Hash(RequestPayload))
    81  func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) {
    82  	var hexencode string
    83  	var err error
    84  	if hex := r.Header.Get(HeaderContentSha256); hex != "" {
    85  		hexencode = hex
    86  	} else {
    87  		data, err := RequestPayload(r)
    88  		if err != nil {
    89  			return "", err
    90  		}
    91  		hexencode, err = HexEncodeSHA256Hash(data)
    92  		if err != nil {
    93  			return "", err
    94  		}
    95  	}
    96  	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r, signedHeaders), strings.Join(signedHeaders, ";"), hexencode), err
    97  }
    98  
    99  // CanonicalURI returns request uri
   100  func CanonicalURI(r *http.Request) string {
   101  	pattens := strings.Split(r.URL.Path, "/")
   102  	var uri []string
   103  	for _, v := range pattens {
   104  		uri = append(uri, escape(v))
   105  	}
   106  	urlpath := strings.Join(uri, "/")
   107  	if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' {
   108  		urlpath = urlpath + "/"
   109  	}
   110  	return urlpath
   111  }
   112  
   113  // CanonicalQueryString
   114  func CanonicalQueryString(r *http.Request) string {
   115  	var keys []string
   116  	query := r.URL.Query()
   117  	for key := range query {
   118  		keys = append(keys, key)
   119  	}
   120  	sort.Strings(keys)
   121  	var a []string
   122  	for _, key := range keys {
   123  		k := escape(key)
   124  		sort.Strings(query[key])
   125  		for _, v := range query[key] {
   126  			kv := fmt.Sprintf("%s=%s", k, escape(v))
   127  			a = append(a, kv)
   128  		}
   129  	}
   130  	queryStr := strings.Join(a, "&")
   131  	r.URL.RawQuery = queryStr
   132  	return queryStr
   133  }
   134  
   135  // CanonicalHeaders
   136  func CanonicalHeaders(r *http.Request, signerHeaders []string) string {
   137  	var a []string
   138  	header := make(map[string][]string)
   139  	for k, v := range r.Header {
   140  		header[strings.ToLower(k)] = v
   141  	}
   142  	for _, key := range signerHeaders {
   143  		value := header[key]
   144  		if strings.EqualFold(key, HeaderHost) {
   145  			value = []string{r.Host}
   146  		}
   147  		sort.Strings(value)
   148  		for _, v := range value {
   149  			a = append(a, key+":"+strings.TrimSpace(v))
   150  		}
   151  	}
   152  	return fmt.Sprintf("%s\n", strings.Join(a, "\n"))
   153  }
   154  
   155  // SignedHeaders
   156  func SignedHeaders(r *http.Request) []string {
   157  	var a []string
   158  	for key := range r.Header {
   159  		a = append(a, strings.ToLower(key))
   160  	}
   161  	sort.Strings(a)
   162  	return a
   163  }
   164  
   165  // RequestPayload
   166  func RequestPayload(r *http.Request) ([]byte, error) {
   167  	if r.Body == nil {
   168  		return []byte(""), nil
   169  	}
   170  	b, err := ioutil.ReadAll(r.Body)
   171  	if err != nil {
   172  		return []byte(""), err
   173  	}
   174  	r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
   175  	return b, err
   176  }
   177  
   178  // Create a "String to Sign".
   179  func StringToSign(canonicalRequest string, t time.Time) (string, error) {
   180  	hash := sha256.New()
   181  	_, err := hash.Write([]byte(canonicalRequest))
   182  	if err != nil {
   183  		return "", err
   184  	}
   185  	return fmt.Sprintf("%s\n%s\n%x",
   186  		Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil
   187  }
   188  
   189  // Create the HWS Signature.
   190  func SignStringToSign(stringToSign string, signingKey []byte) (string, error) {
   191  	hm, err := hmacsha256(signingKey, stringToSign)
   192  	return fmt.Sprintf("%x", hm), err
   193  }
   194  
   195  // HexEncodeSHA256Hash returns hexcode of sha256
   196  func HexEncodeSHA256Hash(body []byte) (string, error) {
   197  	hash := sha256.New()
   198  	if body == nil {
   199  		body = []byte("")
   200  	}
   201  	_, err := hash.Write(body)
   202  	return fmt.Sprintf("%x", hash.Sum(nil)), err
   203  }
   204  
   205  // Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign
   206  func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string {
   207  	return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature)
   208  }
   209  
   210  // Signature HWS meta
   211  type Signer struct {
   212  	Key    string
   213  	Secret string
   214  }
   215  
   216  // SignRequest set Authorization header
   217  func (s *Signer) Sign(r *http.Request) error {
   218  	var t time.Time
   219  	var err error
   220  	var dt string
   221  	if dt = r.Header.Get(HeaderXDate); dt != "" {
   222  		t, err = time.Parse(BasicDateFormat, dt)
   223  	}
   224  	if err != nil || dt == "" {
   225  		t = time.Now()
   226  		r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat))
   227  	}
   228  	signedHeaders := SignedHeaders(r)
   229  	canonicalRequest, err := CanonicalRequest(r, signedHeaders)
   230  	if err != nil {
   231  		return err
   232  	}
   233  	stringToSign, err := StringToSign(canonicalRequest, t)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	signature, err := SignStringToSign(stringToSign, []byte(s.Secret))
   238  	if err != nil {
   239  		return err
   240  	}
   241  	authValue := AuthHeaderValue(signature, s.Key, signedHeaders)
   242  	r.Header.Set(HeaderAuthorization, authValue)
   243  	return nil
   244  }