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, "<", "&lt;")
    46  	srcTmp = StringContains(srcTmp, ">", "&gt;")
    47  	srcTmp = StringContains(srcTmp, "'", "&apos;")
    48  	srcTmp = StringContains(srcTmp, "\"", "&quot;")
    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  }