github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/openstack/obs/auth.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  	"fmt"
    17  	"net/url"
    18  	"sort"
    19  	"strings"
    20  	"time"
    21  )
    22  
    23  func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, params map[string]string,
    24  	headers map[string][]string, expires int64) (requestURL string, err error) {
    25  	sh := obsClient.getSecurity()
    26  	isAkSkEmpty := sh.ak == "" || sh.sk == ""
    27  	if isAkSkEmpty == false && sh.securityToken != "" {
    28  		if obsClient.conf.signature == SignatureObs {
    29  			params[HEADER_STS_TOKEN_OBS] = sh.securityToken
    30  		} else {
    31  			params[HEADER_STS_TOKEN_AMZ] = sh.securityToken
    32  		}
    33  	}
    34  	requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    35  	parsedRequestURL, err := url.Parse(requestURL)
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  	encodeHeaders(headers)
    40  	hostName := parsedRequestURL.Host
    41  
    42  	isV4 := obsClient.conf.signature == SignatureV4
    43  	prepareHostAndDate(headers, hostName, isV4)
    44  
    45  	if isAkSkEmpty {
    46  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
    47  	} else {
    48  		if isV4 {
    49  			date, parseDateErr := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0])
    50  			if parseDateErr != nil {
    51  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
    52  				return "", parseDateErr
    53  			}
    54  			delete(headers, HEADER_DATE_CAMEL)
    55  			shortDate := date.Format(SHORT_DATE_FORMAT)
    56  			longDate := date.Format(LONG_DATE_FORMAT)
    57  			if len(headers[HEADER_HOST_CAMEL]) != 0 {
    58  				index := strings.LastIndex(headers[HEADER_HOST_CAMEL][0], ":")
    59  				if index != -1 {
    60  					port := headers[HEADER_HOST_CAMEL][0][index+1:]
    61  					if port == "80" || port == "443" {
    62  						headers[HEADER_HOST_CAMEL] = []string{headers[HEADER_HOST_CAMEL][0][:index]}
    63  					}
    64  				}
    65  
    66  			}
    67  
    68  			signedHeaders, _headers := getSignedHeaders(headers)
    69  
    70  			credential, scope := getCredential(sh.ak, obsClient.conf.region, shortDate)
    71  			params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
    72  			params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
    73  			params[PARAM_DATE_AMZ_CAMEL] = longDate
    74  			params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires)
    75  			params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";")
    76  
    77  			requestURL, canonicalizedURL = obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    78  			parsedRequestURL, _err := url.Parse(requestURL)
    79  			if _err != nil {
    80  				return "", _err
    81  			}
    82  
    83  			stringToSign := getV4StringToSign(method, canonicalizedURL, parsedRequestURL.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers)
    84  			signature := getSignature(stringToSign, sh.sk, obsClient.conf.region, shortDate)
    85  
    86  			requestURL += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false))
    87  
    88  		} else {
    89  			originDate := headers[HEADER_DATE_CAMEL][0]
    90  			date, parseDateErr := time.Parse(RFC1123_FORMAT, originDate)
    91  			if parseDateErr != nil {
    92  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
    93  				return "", parseDateErr
    94  			}
    95  			expires += date.Unix()
    96  			headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)}
    97  
    98  			stringToSign := getV2StringToSign(method, canonicalizedURL, headers, obsClient.conf.signature == SignatureObs)
    99  			signature := UrlEncode(Base64Encode(HmacSha1([]byte(sh.sk), []byte(stringToSign))), false)
   100  			if strings.Index(requestURL, "?") < 0 {
   101  				requestURL += "?"
   102  			} else {
   103  				requestURL += "&"
   104  			}
   105  			delete(headers, HEADER_DATE_CAMEL)
   106  
   107  			if obsClient.conf.signature != SignatureObs {
   108  				requestURL += "AWS"
   109  			}
   110  			requestURL += fmt.Sprintf("AccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(sh.ak, false), expires, signature)
   111  		}
   112  	}
   113  
   114  	return
   115  }
   116  
   117  func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string,
   118  	headers map[string][]string, hostName string) (requestURL string, err error) {
   119  	sh := obsClient.getSecurity()
   120  	isAkSkEmpty := sh.ak == "" || sh.sk == ""
   121  	if isAkSkEmpty == false && sh.securityToken != "" {
   122  		if obsClient.conf.signature == SignatureObs {
   123  			headers[HEADER_STS_TOKEN_OBS] = []string{sh.securityToken}
   124  		} else {
   125  			headers[HEADER_STS_TOKEN_AMZ] = []string{sh.securityToken}
   126  		}
   127  	}
   128  	isObs := obsClient.conf.signature == SignatureObs
   129  	requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
   130  	parsedRequestURL, err := url.Parse(requestURL)
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  	encodeHeaders(headers)
   135  
   136  	if hostName == "" {
   137  		hostName = parsedRequestURL.Host
   138  	}
   139  
   140  	isV4 := obsClient.conf.signature == SignatureV4
   141  	prepareHostAndDate(headers, hostName, isV4)
   142  
   143  	if isAkSkEmpty {
   144  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
   145  	} else {
   146  		ak := sh.ak
   147  		sk := sh.sk
   148  		var authorization string
   149  		if isV4 {
   150  			headers[HEADER_CONTENT_SHA256_AMZ] = []string{UNSIGNED_PAYLOAD}
   151  			ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedURL, parsedRequestURL.RawQuery, headers)
   152  			authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   153  		} else {
   154  			ret := v2Auth(ak, sk, method, canonicalizedURL, headers, isObs)
   155  			hashPrefix := V2_HASH_PREFIX
   156  			if isObs {
   157  				hashPrefix = OBS_HASH_PREFIX
   158  			}
   159  			authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"])
   160  		}
   161  		headers[HEADER_AUTH_CAMEL] = []string{authorization}
   162  	}
   163  	return
   164  }
   165  
   166  func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) {
   167  	headers[HEADER_HOST_CAMEL] = []string{hostName}
   168  	if date, ok := headers[HEADER_DATE_AMZ]; ok {
   169  		flag := false
   170  		if len(date) == 1 {
   171  			if isV4 {
   172  				if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil {
   173  					headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)}
   174  					flag = true
   175  				}
   176  			} else {
   177  				if strings.HasSuffix(date[0], "GMT") {
   178  					headers[HEADER_DATE_CAMEL] = []string{date[0]}
   179  					flag = true
   180  				}
   181  			}
   182  		}
   183  		if !flag {
   184  			delete(headers, HEADER_DATE_AMZ)
   185  		}
   186  	}
   187  	if _, ok := headers[HEADER_DATE_CAMEL]; !ok {
   188  		headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())}
   189  	}
   190  
   191  }
   192  
   193  func encodeHeaders(headers map[string][]string) {
   194  	for key, values := range headers {
   195  		for index, value := range values {
   196  			values[index] = UrlEncode(value, true)
   197  		}
   198  		headers[key] = values
   199  	}
   200  }
   201  
   202  func prepareDateHeader(dataHeader, dateCamelHeader string, headers, _headers map[string][]string) {
   203  	if _, ok := _headers[HEADER_DATE_CAMEL]; ok {
   204  		if _, ok := _headers[dataHeader]; ok {
   205  			_headers[HEADER_DATE_CAMEL] = []string{""}
   206  		} else if _, ok := headers[dateCamelHeader]; ok {
   207  			_headers[HEADER_DATE_CAMEL] = []string{""}
   208  		}
   209  	} else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
   210  		if _, ok := _headers[dataHeader]; ok {
   211  			_headers[HEADER_DATE_CAMEL] = []string{""}
   212  		} else if _, ok := headers[dateCamelHeader]; ok {
   213  			_headers[HEADER_DATE_CAMEL] = []string{""}
   214  		}
   215  	}
   216  }
   217  
   218  func getStringToSign(keys []string, isObs bool, _headers map[string][]string) []string {
   219  	stringToSign := make([]string, 0, len(keys))
   220  	for _, key := range keys {
   221  		var value string
   222  		prefixHeader := HEADER_PREFIX
   223  		prefixMetaHeader := HEADER_PREFIX_META
   224  		if isObs {
   225  			prefixHeader = HEADER_PREFIX_OBS
   226  			prefixMetaHeader = HEADER_PREFIX_META_OBS
   227  		}
   228  		if strings.HasPrefix(key, prefixHeader) {
   229  			if strings.HasPrefix(key, prefixMetaHeader) {
   230  				for index, v := range _headers[key] {
   231  					value += strings.TrimSpace(v)
   232  					if index != len(_headers[key])-1 {
   233  						value += ","
   234  					}
   235  				}
   236  			} else {
   237  				value = strings.Join(_headers[key], ",")
   238  			}
   239  			value = fmt.Sprintf("%s:%s", key, value)
   240  		} else {
   241  			value = strings.Join(_headers[key], ",")
   242  		}
   243  		stringToSign = append(stringToSign, value)
   244  	}
   245  	return stringToSign
   246  }
   247  
   248  func attachHeaders(headers map[string][]string, isObs bool) string {
   249  	length := len(headers)
   250  	_headers := make(map[string][]string, length)
   251  	keys := make([]string, 0, length)
   252  
   253  	for key, value := range headers {
   254  		_key := strings.ToLower(strings.TrimSpace(key))
   255  		if _key != "" {
   256  			prefixheader := HEADER_PREFIX
   257  			if isObs {
   258  				prefixheader = HEADER_PREFIX_OBS
   259  			}
   260  			if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) {
   261  				keys = append(keys, _key)
   262  				_headers[_key] = value
   263  			}
   264  		} else {
   265  			delete(headers, key)
   266  		}
   267  	}
   268  
   269  	for _, interestedHeader := range interestedHeaders {
   270  		if _, ok := _headers[interestedHeader]; !ok {
   271  			_headers[interestedHeader] = []string{""}
   272  			keys = append(keys, interestedHeader)
   273  		}
   274  	}
   275  	dateCamelHeader := PARAM_DATE_AMZ_CAMEL
   276  	dataHeader := HEADER_DATE_AMZ
   277  	if isObs {
   278  		dateCamelHeader = PARAM_DATE_OBS_CAMEL
   279  		dataHeader = HEADER_DATE_OBS
   280  	}
   281  	prepareDateHeader(dataHeader, dateCamelHeader, headers, _headers)
   282  
   283  	sort.Strings(keys)
   284  	stringToSign := getStringToSign(keys, isObs, _headers)
   285  	return strings.Join(stringToSign, "\n")
   286  }
   287  
   288  func getV2StringToSign(method, canonicalizedURL string, headers map[string][]string, isObs bool) string {
   289  	stringToSign := strings.Join([]string{method, "\n", attachHeaders(headers, isObs), "\n", canonicalizedURL}, "")
   290  
   291  	var isSecurityToken bool
   292  	var securityToken []string
   293  	if isObs {
   294  		securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_OBS]
   295  	} else {
   296  		securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_AMZ]
   297  	}
   298  	var query []string
   299  	if !isSecurityToken {
   300  		parmas := strings.Split(canonicalizedURL, "?")
   301  		if len(parmas) > 1 {
   302  			query = strings.Split(parmas[1], "&")
   303  			for _, value := range query {
   304  				if strings.HasPrefix(value, HEADER_STS_TOKEN_AMZ+"=") || strings.HasPrefix(value, HEADER_STS_TOKEN_OBS+"=") {
   305  					if value[len(HEADER_STS_TOKEN_AMZ)+1:] != "" {
   306  						securityToken = []string{value[len(HEADER_STS_TOKEN_AMZ)+1:]}
   307  						isSecurityToken = true
   308  					}
   309  				}
   310  			}
   311  		}
   312  	}
   313  	logStringToSign := stringToSign
   314  	if isSecurityToken && len(securityToken) > 0 {
   315  		logStringToSign = strings.Replace(logStringToSign, securityToken[0], "******", -1)
   316  	}
   317  	doLog(LEVEL_DEBUG, "The v2 auth stringToSign:\n%s", logStringToSign)
   318  	return stringToSign
   319  }
   320  
   321  func v2Auth(ak, sk, method, canonicalizedURL string, headers map[string][]string, isObs bool) map[string]string {
   322  	stringToSign := getV2StringToSign(method, canonicalizedURL, headers, isObs)
   323  	return map[string]string{"Signature": Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign)))}
   324  }
   325  
   326  func getScope(region, shortDate string) string {
   327  	return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX)
   328  }
   329  
   330  func getCredential(ak, region, shortDate string) (string, string) {
   331  	scope := getScope(region, shortDate)
   332  	return fmt.Sprintf("%s/%s", ak, scope), scope
   333  }
   334  
   335  func getV4StringToSign(method, canonicalizedURL, queryURL, scope, longDate, payload string, signedHeaders []string, headers map[string][]string) string {
   336  	canonicalRequest := make([]string, 0, 10+len(signedHeaders)*4)
   337  	canonicalRequest = append(canonicalRequest, method)
   338  	canonicalRequest = append(canonicalRequest, "\n")
   339  	canonicalRequest = append(canonicalRequest, canonicalizedURL)
   340  	canonicalRequest = append(canonicalRequest, "\n")
   341  	canonicalRequest = append(canonicalRequest, queryURL)
   342  	canonicalRequest = append(canonicalRequest, "\n")
   343  
   344  	for _, signedHeader := range signedHeaders {
   345  		values, _ := headers[signedHeader]
   346  		for _, value := range values {
   347  			canonicalRequest = append(canonicalRequest, signedHeader)
   348  			canonicalRequest = append(canonicalRequest, ":")
   349  			canonicalRequest = append(canonicalRequest, value)
   350  			canonicalRequest = append(canonicalRequest, "\n")
   351  		}
   352  	}
   353  	canonicalRequest = append(canonicalRequest, "\n")
   354  	canonicalRequest = append(canonicalRequest, strings.Join(signedHeaders, ";"))
   355  	canonicalRequest = append(canonicalRequest, "\n")
   356  	canonicalRequest = append(canonicalRequest, payload)
   357  
   358  	_canonicalRequest := strings.Join(canonicalRequest, "")
   359  
   360  	var isSecurityToken bool
   361  	var securityToken []string
   362  	if securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_OBS]; !isSecurityToken {
   363  		securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_AMZ]
   364  	}
   365  	var query []string
   366  	if !isSecurityToken {
   367  		query = strings.Split(queryURL, "&")
   368  		for _, value := range query {
   369  			if strings.HasPrefix(value, HEADER_STS_TOKEN_AMZ+"=") || strings.HasPrefix(value, HEADER_STS_TOKEN_OBS+"=") {
   370  				if value[len(HEADER_STS_TOKEN_AMZ)+1:] != "" {
   371  					securityToken = []string{value[len(HEADER_STS_TOKEN_AMZ)+1:]}
   372  					isSecurityToken = true
   373  				}
   374  			}
   375  		}
   376  	}
   377  	logCanonicalRequest := _canonicalRequest
   378  	if isSecurityToken && len(securityToken) > 0 {
   379  		logCanonicalRequest = strings.Replace(logCanonicalRequest, securityToken[0], "******", -1)
   380  	}
   381  	doLog(LEVEL_DEBUG, "The v4 auth canonicalRequest:\n%s", logCanonicalRequest)
   382  
   383  	stringToSign := make([]string, 0, 7)
   384  	stringToSign = append(stringToSign, V4_HASH_PREFIX)
   385  	stringToSign = append(stringToSign, "\n")
   386  	stringToSign = append(stringToSign, longDate)
   387  	stringToSign = append(stringToSign, "\n")
   388  	stringToSign = append(stringToSign, scope)
   389  	stringToSign = append(stringToSign, "\n")
   390  	stringToSign = append(stringToSign, HexSha256([]byte(_canonicalRequest)))
   391  
   392  	_stringToSign := strings.Join(stringToSign, "")
   393  
   394  	doLog(LEVEL_DEBUG, "The v4 auth stringToSign:\n%s", _stringToSign)
   395  	return _stringToSign
   396  }
   397  
   398  func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) {
   399  	length := len(headers)
   400  	_headers := make(map[string][]string, length)
   401  	signedHeaders := make([]string, 0, length)
   402  	for key, value := range headers {
   403  		_key := strings.ToLower(strings.TrimSpace(key))
   404  		if _key != "" {
   405  			signedHeaders = append(signedHeaders, _key)
   406  			_headers[_key] = value
   407  		} else {
   408  			delete(headers, key)
   409  		}
   410  	}
   411  	sort.Strings(signedHeaders)
   412  	return signedHeaders, _headers
   413  }
   414  
   415  func getSignature(stringToSign, sk, region, shortDate string) string {
   416  	key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate))
   417  	key = HmacSha256(key, []byte(region))
   418  	key = HmacSha256(key, []byte(V4_SERVICE_NAME))
   419  	key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX))
   420  	return Hex(HmacSha256(key, []byte(stringToSign)))
   421  }
   422  
   423  // V4Auth is a wrapper for v4Auth
   424  func V4Auth(ak, sk, region, method, canonicalizedURL, queryURL string, headers map[string][]string) map[string]string {
   425  	return v4Auth(ak, sk, region, method, canonicalizedURL, queryURL, headers)
   426  }
   427  
   428  func v4Auth(ak, sk, region, method, canonicalizedURL, queryURL string, headers map[string][]string) map[string]string {
   429  	var t time.Time
   430  	if val, ok := headers[HEADER_DATE_AMZ]; ok {
   431  		var err error
   432  		t, err = time.Parse(LONG_DATE_FORMAT, val[0])
   433  		if err != nil {
   434  			t = time.Now().UTC()
   435  		}
   436  	} else if val, ok := headers[PARAM_DATE_AMZ_CAMEL]; ok {
   437  		var err error
   438  		t, err = time.Parse(LONG_DATE_FORMAT, val[0])
   439  		if err != nil {
   440  			t = time.Now().UTC()
   441  		}
   442  	} else if val, ok := headers[HEADER_DATE_CAMEL]; ok {
   443  		var err error
   444  		t, err = time.Parse(RFC1123_FORMAT, val[0])
   445  		if err != nil {
   446  			t = time.Now().UTC()
   447  		}
   448  	} else if val, ok := headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
   449  		var err error
   450  		t, err = time.Parse(RFC1123_FORMAT, val[0])
   451  		if err != nil {
   452  			t = time.Now().UTC()
   453  		}
   454  	} else {
   455  		t = time.Now().UTC()
   456  	}
   457  	shortDate := t.Format(SHORT_DATE_FORMAT)
   458  	longDate := t.Format(LONG_DATE_FORMAT)
   459  
   460  	signedHeaders, _headers := getSignedHeaders(headers)
   461  
   462  	credential, scope := getCredential(ak, region, shortDate)
   463  
   464  	payload := UNSIGNED_PAYLOAD
   465  	if val, ok := headers[HEADER_CONTENT_SHA256_AMZ]; ok {
   466  		payload = val[0]
   467  	}
   468  	stringToSign := getV4StringToSign(method, canonicalizedURL, queryURL, scope, longDate, payload, signedHeaders, _headers)
   469  
   470  	signature := getSignature(stringToSign, sk, region, shortDate)
   471  
   472  	ret := make(map[string]string, 3)
   473  	ret["Credential"] = credential
   474  	ret["SignedHeaders"] = strings.Join(signedHeaders, ";")
   475  	ret["Signature"] = signature
   476  	return ret
   477  }