yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/util.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Copyright 2019 Huawei Technologies Co.,Ltd. 16 // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 17 // this file except in compliance with the License. You may obtain a copy of the 18 // License at 19 // 20 // http://www.apache.org/licenses/LICENSE-2.0 21 // 22 // Unless required by applicable law or agreed to in writing, software distributed 23 // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 24 // CONDITIONS OF ANY KIND, either express or implied. See the License for the 25 // specific language governing permissions and limitations under the License. 26 27 package obs 28 29 import ( 30 "crypto/hmac" 31 "crypto/md5" 32 "crypto/sha1" 33 "crypto/sha256" 34 "encoding/base64" 35 "encoding/hex" 36 "encoding/xml" 37 "fmt" 38 "net/url" 39 "regexp" 40 "strconv" 41 "strings" 42 "time" 43 ) 44 45 var regex = regexp.MustCompile("^[\u4e00-\u9fa5]$") 46 var ipRegex = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$") 47 var v4AuthRegex = regexp.MustCompile("Credential=(.+?),SignedHeaders=(.+?),Signature=.+") 48 var regionRegex = regexp.MustCompile(".+/\\d+/(.+?)/.+") 49 50 func StringContains(src string, subStr string, subTranscoding string) string { 51 return strings.Replace(src, subStr, subTranscoding, -1) 52 } 53 func XmlTranscoding(src string) string { 54 srcTmp := StringContains(src, "&", "&") 55 srcTmp = StringContains(srcTmp, "<", "<") 56 srcTmp = StringContains(srcTmp, ">", ">") 57 srcTmp = StringContains(srcTmp, "'", "'") 58 srcTmp = StringContains(srcTmp, "\"", """) 59 return srcTmp 60 } 61 func StringToInt(value string, def int) int { 62 ret, err := strconv.Atoi(value) 63 if err != nil { 64 ret = def 65 } 66 return ret 67 } 68 69 func StringToInt64(value string, def int64) int64 { 70 ret, err := strconv.ParseInt(value, 10, 64) 71 if err != nil { 72 ret = def 73 } 74 return ret 75 } 76 77 func IntToString(value int) string { 78 return strconv.Itoa(value) 79 } 80 81 func Int64ToString(value int64) string { 82 return strconv.FormatInt(value, 10) 83 } 84 85 func GetCurrentTimestamp() int64 { 86 return time.Now().UnixNano() / 1000000 87 } 88 89 func FormatUtcNow(format string) string { 90 return time.Now().UTC().Format(format) 91 } 92 93 func FormatUtcToRfc1123(t time.Time) string { 94 ret := t.UTC().Format(time.RFC1123) 95 return ret[:strings.LastIndex(ret, "UTC")] + "GMT" 96 } 97 98 func Md5(value []byte) []byte { 99 m := md5.New() 100 _, err := m.Write(value) 101 if err != nil { 102 doLog(LEVEL_WARN, "MD5 failed to write with reason: %v", err) 103 } 104 return m.Sum(nil) 105 } 106 107 func HmacSha1(key, value []byte) []byte { 108 mac := hmac.New(sha1.New, key) 109 _, err := mac.Write(value) 110 if err != nil { 111 doLog(LEVEL_WARN, "HmacSha1 failed to write with reason: %v", err) 112 } 113 return mac.Sum(nil) 114 } 115 116 func HmacSha256(key, value []byte) []byte { 117 mac := hmac.New(sha256.New, key) 118 _, err := mac.Write(value) 119 if err != nil { 120 doLog(LEVEL_WARN, "HmacSha256 failed to write with reason: %v", err) 121 } 122 return mac.Sum(nil) 123 } 124 125 func Base64Encode(value []byte) string { 126 return base64.StdEncoding.EncodeToString(value) 127 } 128 129 func Base64Decode(value string) ([]byte, error) { 130 return base64.StdEncoding.DecodeString(value) 131 } 132 133 func HexMd5(value []byte) string { 134 return Hex(Md5(value)) 135 } 136 137 func Base64Md5(value []byte) string { 138 return Base64Encode(Md5(value)) 139 } 140 141 func Sha256Hash(value []byte) []byte { 142 hash := sha256.New() 143 _, err := hash.Write(value) 144 if err != nil { 145 doLog(LEVEL_WARN, "Sha256Hash failed to write with reason: %v", err) 146 } 147 return hash.Sum(nil) 148 } 149 150 func ParseXml(value []byte, result interface{}) error { 151 if len(value) == 0 { 152 return nil 153 } 154 return xml.Unmarshal(value, result) 155 } 156 157 func TransToXml(value interface{}) ([]byte, error) { 158 if value == nil { 159 return []byte{}, nil 160 } 161 return xml.Marshal(value) 162 } 163 164 func Hex(value []byte) string { 165 return hex.EncodeToString(value) 166 } 167 168 func HexSha256(value []byte) string { 169 return Hex(Sha256Hash(value)) 170 } 171 172 func UrlDecode(value string) (string, error) { 173 ret, err := url.QueryUnescape(value) 174 if err == nil { 175 return ret, nil 176 } 177 return "", err 178 } 179 180 func UrlDecodeWithoutError(value string) string { 181 ret, err := UrlDecode(value) 182 if err == nil { 183 return ret 184 } 185 if isErrorLogEnabled() { 186 doLog(LEVEL_ERROR, "Url decode error: %v", err) 187 } 188 return "" 189 } 190 191 func IsIP(value string) bool { 192 return ipRegex.MatchString(value) 193 } 194 195 func UrlEncode(value string, chineseOnly bool) string { 196 if chineseOnly { 197 values := make([]string, 0, len(value)) 198 for _, val := range value { 199 _value := string(val) 200 if regex.MatchString(_value) { 201 _value = url.QueryEscape(_value) 202 } 203 values = append(values, _value) 204 } 205 return strings.Join(values, "") 206 } 207 return url.QueryEscape(value) 208 } 209 210 func copyHeaders(m map[string][]string) (ret map[string][]string) { 211 if m != nil { 212 ret = make(map[string][]string, len(m)) 213 for key, values := range m { 214 _values := make([]string, 0, len(values)) 215 for _, value := range values { 216 _values = append(_values, value) 217 } 218 ret[strings.ToLower(key)] = _values 219 } 220 } else { 221 ret = make(map[string][]string) 222 } 223 224 return 225 } 226 227 func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) { 228 signature = "v2" 229 if receviedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receviedAuthorization) > 0 { 230 if strings.HasPrefix(receviedAuthorization[0], V4_HASH_PREFIX) { 231 signature = "v4" 232 matches := v4AuthRegex.FindStringSubmatch(receviedAuthorization[0]) 233 if len(matches) >= 3 { 234 region = matches[1] 235 regions := regionRegex.FindStringSubmatch(region) 236 if len(regions) >= 2 { 237 region = regions[1] 238 } 239 signedHeaders = matches[2] 240 } 241 242 } else if strings.HasPrefix(receviedAuthorization[0], V2_HASH_PREFIX) { 243 signature = "v2" 244 } 245 } 246 return 247 } 248 249 func getTemporaryKeys() []string { 250 return []string{ 251 "Signature", 252 "signature", 253 "X-Amz-Signature", 254 "x-amz-signature", 255 } 256 } 257 258 func getIsObs(isTemporary bool, querys []string, headers map[string][]string) bool { 259 isObs := true 260 if isTemporary { 261 for _, value := range querys { 262 keyPrefix := strings.ToLower(value) 263 if strings.HasPrefix(keyPrefix, HEADER_PREFIX) { 264 isObs = false 265 } else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) { 266 isObs = false 267 } 268 } 269 } else { 270 for key := range headers { 271 keyPrefix := strings.ToLower(key) 272 if strings.HasPrefix(keyPrefix, HEADER_PREFIX) { 273 isObs = false 274 break 275 } 276 } 277 } 278 return isObs 279 } 280 281 func isPathStyle(headers map[string][]string, bucketName string) bool { 282 if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") { 283 return true 284 } 285 return false 286 } 287 288 func GetV2Authorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) { 289 290 if strings.HasPrefix(queryUrl, "?") { 291 queryUrl = queryUrl[1:] 292 } 293 294 method = strings.ToUpper(method) 295 296 querys := strings.Split(queryUrl, "&") 297 querysResult := make([]string, 0) 298 for _, value := range querys { 299 if value != "=" && len(value) != 0 { 300 querysResult = append(querysResult, value) 301 } 302 } 303 params := make(map[string]string) 304 305 for _, value := range querysResult { 306 kv := strings.Split(value, "=") 307 length := len(kv) 308 if length == 1 { 309 key := UrlDecodeWithoutError(kv[0]) 310 params[key] = "" 311 } else if length >= 2 { 312 key := UrlDecodeWithoutError(kv[0]) 313 vals := make([]string, 0, length-1) 314 for i := 1; i < length; i++ { 315 val := UrlDecodeWithoutError(kv[i]) 316 vals = append(vals, val) 317 } 318 params[key] = strings.Join(vals, "=") 319 } 320 } 321 headers = copyHeaders(headers) 322 pathStyle := isPathStyle(headers, bucketName) 323 conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk}, 324 urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443}, 325 pathStyle: pathStyle} 326 conf.signature = SignatureObs 327 _, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false) 328 ret = v2Auth(ak, sk, method, canonicalizedURL, headers, true) 329 v2HashPrefix := OBS_HASH_PREFIX 330 ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"]) 331 return 332 } 333 334 func GetAuthorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) { 335 336 if strings.HasPrefix(queryUrl, "?") { 337 queryUrl = queryUrl[1:] 338 } 339 340 method = strings.ToUpper(method) 341 342 querys := strings.Split(queryUrl, "&") 343 querysResult := make([]string, 0) 344 for _, value := range querys { 345 if value != "=" && len(value) != 0 { 346 querysResult = append(querysResult, value) 347 } 348 } 349 params := make(map[string]string) 350 351 for _, value := range querysResult { 352 kv := strings.Split(value, "=") 353 length := len(kv) 354 if length == 1 { 355 key := UrlDecodeWithoutError(kv[0]) 356 params[key] = "" 357 } else if length >= 2 { 358 key := UrlDecodeWithoutError(kv[0]) 359 vals := make([]string, 0, length-1) 360 for i := 1; i < length; i++ { 361 val := UrlDecodeWithoutError(kv[i]) 362 vals = append(vals, val) 363 } 364 params[key] = strings.Join(vals, "=") 365 } 366 } 367 isTemporary := false 368 signature := "v2" 369 temporaryKeys := getTemporaryKeys() 370 for _, key := range temporaryKeys { 371 if _, ok := params[key]; ok { 372 isTemporary = true 373 if strings.ToLower(key) == "signature" { 374 signature = "v2" 375 } else if strings.ToLower(key) == "x-amz-signature" { 376 signature = "v4" 377 } 378 break 379 } 380 } 381 isObs := getIsObs(isTemporary, querysResult, headers) 382 headers = copyHeaders(headers) 383 pathStyle := false 384 if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") { 385 pathStyle = true 386 } 387 conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk}, 388 urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443}, 389 pathStyle: pathStyle} 390 391 if isTemporary { 392 return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs) 393 } 394 signature, region, signedHeaders := parseHeaders(headers) 395 if signature == "v4" { 396 conf.signature = SignatureV4 397 requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false) 398 parsedRequestUrl, _err := url.Parse(requestUrl) 399 if _err != nil { 400 doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err) 401 return nil 402 } 403 headerKeys := strings.Split(signedHeaders, ";") 404 _headers := make(map[string][]string, len(headerKeys)) 405 for _, headerKey := range headerKeys { 406 _headers[headerKey] = headers[headerKey] 407 } 408 ret = v4Auth(ak, sk, region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, _headers) 409 ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"]) 410 } else if signature == "v2" { 411 if isObs { 412 conf.signature = SignatureObs 413 } else { 414 conf.signature = SignatureV2 415 } 416 _, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false) 417 ret = v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs) 418 v2HashPrefix := V2_HASH_PREFIX 419 if isObs { 420 v2HashPrefix = OBS_HASH_PREFIX 421 } 422 ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"]) 423 } 424 return 425 426 } 427 428 func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string, 429 headers map[string][]string, isObs bool) (ret map[string]string) { 430 431 if signature == "v4" { 432 conf.signature = SignatureV4 433 434 longDate, ok := params[PARAM_DATE_AMZ_CAMEL] 435 if !ok { 436 longDate = params[HEADER_DATE_AMZ] 437 } 438 shortDate := longDate[:8] 439 440 credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL] 441 if !ok { 442 credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)] 443 } 444 445 _credential := UrlDecodeWithoutError(credential) 446 447 regions := regionRegex.FindStringSubmatch(_credential) 448 var region string 449 if len(regions) >= 2 { 450 region = regions[1] 451 } 452 453 _, scope := getCredential(ak, region, shortDate) 454 455 expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL] 456 if !ok { 457 expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)] 458 } 459 460 signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] 461 if !ok { 462 signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)] 463 } 464 465 algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL] 466 if !ok { 467 algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)] 468 } 469 470 if _, ok := params[PARAM_SIGNATURE_AMZ_CAMEL]; ok { 471 delete(params, PARAM_SIGNATURE_AMZ_CAMEL) 472 } else if _, ok := params[strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)]; ok { 473 delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)) 474 } 475 476 ret = make(map[string]string, 6) 477 ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm 478 ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential 479 ret[PARAM_DATE_AMZ_CAMEL] = longDate 480 ret[PARAM_EXPIRES_AMZ_CAMEL] = expires 481 ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders 482 483 requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false) 484 parsedRequestUrl, _err := url.Parse(requestUrl) 485 if _err != nil { 486 doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err) 487 return nil 488 } 489 stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers) 490 ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false) 491 } else if signature == "v2" { 492 if isObs { 493 conf.signature = SignatureObs 494 } else { 495 conf.signature = SignatureV2 496 } 497 _, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false) 498 expires, ok := params["Expires"] 499 if !ok { 500 expires = params["expires"] 501 } 502 headers[HEADER_DATE_CAMEL] = []string{expires} 503 stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, isObs) 504 ret = make(map[string]string, 3) 505 ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false) 506 ret["AWSAccessKeyId"] = UrlEncode(ak, false) 507 ret["Expires"] = UrlEncode(expires, false) 508 } 509 510 return 511 }