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