123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- // HWS API Gateway Signature
- // based on https://github.com/datastream/aws/blob/master/signv4.go
- // Copyright (c) 2014, Xianjie
- package core
- import (
- "bytes"
- "crypto/hmac"
- "crypto/sha256"
- "fmt"
- "io/ioutil"
- "net/http"
- "sort"
- "strings"
- "time"
- )
- const (
- BasicDateFormat = "20060102T150405Z"
- Algorithm = "SDK-HMAC-SHA256"
- HeaderXDate = "X-Sdk-Date"
- HeaderHost = "host"
- HeaderAuthorization = "Authorization"
- HeaderContentSha256 = "X-Sdk-Content-Sha256"
- )
- func hmacsha256(key []byte, data string) ([]byte, error) {
- h := hmac.New(sha256.New, []byte(key))
- if _, err := h.Write([]byte(data)); err != nil {
- return nil, err
- }
- return h.Sum(nil), nil
- }
- // Build a CanonicalRequest from a regular request string
- //
- // CanonicalRequest =
- // HTTPRequestMethod + '\n' +
- // CanonicalURI + '\n' +
- // CanonicalQueryString + '\n' +
- // CanonicalHeaders + '\n' +
- // SignedHeaders + '\n' +
- // HexEncode(Hash(RequestPayload))
- func CanonicalRequest(r *http.Request, signedHeaders []string) (string, error) {
- var hexencode string
- var err error
- if hex := r.Header.Get(HeaderContentSha256); hex != "" {
- hexencode = hex
- } else {
- data, err := RequestPayload(r)
- if err != nil {
- return "", err
- }
- hexencode, err = HexEncodeSHA256Hash(data)
- if err != nil {
- return "", err
- }
- }
- 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
- }
- // CanonicalURI returns request uri
- func CanonicalURI(r *http.Request) string {
- pattens := strings.Split(r.URL.Path, "/")
- var uri []string
- for _, v := range pattens {
- uri = append(uri, escape(v))
- }
- urlpath := strings.Join(uri, "/")
- if len(urlpath) == 0 || urlpath[len(urlpath)-1] != '/' {
- urlpath = urlpath + "/"
- }
- return urlpath
- }
- // CanonicalQueryString
- func CanonicalQueryString(r *http.Request) string {
- var keys []string
- query := r.URL.Query()
- for key := range query {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- var a []string
- for _, key := range keys {
- k := escape(key)
- sort.Strings(query[key])
- for _, v := range query[key] {
- kv := fmt.Sprintf("%s=%s", k, escape(v))
- a = append(a, kv)
- }
- }
- queryStr := strings.Join(a, "&")
- r.URL.RawQuery = queryStr
- return queryStr
- }
- // CanonicalHeaders
- func CanonicalHeaders(r *http.Request, signerHeaders []string) string {
- var a []string
- header := make(map[string][]string)
- for k, v := range r.Header {
- header[strings.ToLower(k)] = v
- }
- for _, key := range signerHeaders {
- value := header[key]
- if strings.EqualFold(key, HeaderHost) {
- value = []string{r.Host}
- }
- sort.Strings(value)
- for _, v := range value {
- a = append(a, key+":"+strings.TrimSpace(v))
- }
- }
- return fmt.Sprintf("%s\n", strings.Join(a, "\n"))
- }
- // SignedHeaders
- func SignedHeaders(r *http.Request) []string {
- var a []string
- for key := range r.Header {
- a = append(a, strings.ToLower(key))
- }
- sort.Strings(a)
- return a
- }
- // RequestPayload
- func RequestPayload(r *http.Request) ([]byte, error) {
- if r.Body == nil {
- return []byte(""), nil
- }
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return []byte(""), err
- }
- r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
- return b, err
- }
- // Create a "String to Sign".
- func StringToSign(canonicalRequest string, t time.Time) (string, error) {
- hash := sha256.New()
- _, err := hash.Write([]byte(canonicalRequest))
- if err != nil {
- return "", err
- }
- return fmt.Sprintf("%s\n%s\n%x",
- Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil
- }
- // Create the HWS Signature.
- func SignStringToSign(stringToSign string, signingKey []byte) (string, error) {
- hm, err := hmacsha256(signingKey, stringToSign)
- return fmt.Sprintf("%x", hm), err
- }
- // HexEncodeSHA256Hash returns hexcode of sha256
- func HexEncodeSHA256Hash(body []byte) (string, error) {
- hash := sha256.New()
- if body == nil {
- body = []byte("")
- }
- _, err := hash.Write(body)
- return fmt.Sprintf("%x", hash.Sum(nil)), err
- }
- // Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign
- func AuthHeaderValue(signature, accessKey string, signedHeaders []string) string {
- return fmt.Sprintf("%s Access=%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, strings.Join(signedHeaders, ";"), signature)
- }
- // Signature HWS meta
- type Signer struct {
- Key string
- Secret string
- }
- // SignRequest set Authorization header
- func (s *Signer) Sign(r *http.Request) error {
- var t time.Time
- var err error
- var dt string
- if dt = r.Header.Get(HeaderXDate); dt != "" {
- t, err = time.Parse(BasicDateFormat, dt)
- }
- if err != nil || dt == "" {
- t = time.Now()
- r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat))
- }
- signedHeaders := SignedHeaders(r)
- canonicalRequest, err := CanonicalRequest(r, signedHeaders)
- if err != nil {
- return err
- }
- stringToSign, err := StringToSign(canonicalRequest, t)
- if err != nil {
- return err
- }
- signature, err := SignStringToSign(stringToSign, []byte(s.Secret))
- if err != nil {
- return err
- }
- authValue := AuthHeaderValue(signature, s.Key, signedHeaders)
- r.Header.Set(HeaderAuthorization, authValue)
- return nil
- }
|