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 }