|
@@ -0,0 +1,208 @@
|
|
|
+// 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
|
|
|
+}
|