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