You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

112 lines
3.4 KiB

package baidu
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
// Auth is auth module of baidu api authenticator
type auth struct {
AccessKey string
SecretKey string
}
// getCanonicalTime returns RFC3339 style time string
// current param provides convenience for testing
func getCanonicalTime(current time.Time) (isoStr string) {
utc, _ := time.LoadLocation("UTC")
isoStr = current.In(utc).Format(time.RFC3339)
return
}
func hash(data string, key string) string {
h := hmac.New(sha256.New, []byte(key))
io.WriteString(h, data)
return fmt.Sprintf("%x", h.Sum(nil))
}
func stringNormalize(str string, encodingSlash bool) string {
var b strings.Builder
for _, ch := range str {
if (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.' {
b.WriteRune(rune(ch))
} else if ch == '/' {
if encodingSlash {
b.WriteString("%2F")
} else {
b.WriteRune(rune(ch))
}
} else {
// query escape turn space to +, but baidu api turn space to %20
// issue: https://github.com/golang/go/issues/4013
b.WriteString(strings.ReplaceAll(url.QueryEscape(string(ch)), "+", "%20"))
}
}
return b.String()
}
func uriCanonicalization(path string) string {
return path
}
func queryStringCanonicalization(query url.Values) string {
var queryPatternArray []string
for k, values := range query {
if strings.ToLower(k) == "authorization" {
continue
}
if len(values) > 0 {
for _, v := range values {
queryPatternArray = append(queryPatternArray, fmt.Sprintf("%s=%s", k, stringNormalize(v, true)))
}
} else {
queryPatternArray = append(queryPatternArray, fmt.Sprintf("%s=", k))
}
}
sort.Strings(queryPatternArray)
return strings.Join(queryPatternArray, "&")
}
func headersCanonicalization(headers http.Header) (string, string) {
headersToSign := map[string]bool{"host": true, "content-md5": true, "content-length": true, "content-type": true}
var canonicalHeaders []string
var signedHeaders []string
for k, values := range headers {
k = strings.ToLower(k)
if headersToSign[k] || strings.HasPrefix(k, "x-bce-") {
signedHeaders = append(signedHeaders, k)
for _, v := range values {
if v != "" {
canonicalHeaders = append(canonicalHeaders, fmt.Sprintf("%s:%s", stringNormalize(k, true), stringNormalize(v, true)))
}
}
}
}
sort.Strings(canonicalHeaders)
sort.Strings(signedHeaders)
return strings.Join(canonicalHeaders, "\n"), strings.Join(signedHeaders, ";")
}
var defaultExpirationInSeconds = 1800
// timestamp int64, expirationInSeconds int, headersToSign []string 这个三个不支持了
func (auth auth) generateAuthorization(httpMethod string, path string, headers http.Header, query url.Values, currentTime time.Time) string {
rawSessionKey := fmt.Sprintf("bce-auth-v1/%s/%s/%d", auth.AccessKey, getCanonicalTime(currentTime), defaultExpirationInSeconds)
sessionKey := hash(rawSessionKey, auth.SecretKey)
canonicalURI := uriCanonicalization(path)
canonicalQueryString := queryStringCanonicalization(query)
canonicalHeaders, signedHeaders := headersCanonicalization(headers)
rawSignature := fmt.Sprintf("%s\n%s\n%s\n%s", httpMethod, canonicalURI, canonicalQueryString, canonicalHeaders)
signature := hash(rawSignature, sessionKey)
if len(signedHeaders) > 0 {
return fmt.Sprintf("%s/%s/%s", rawSessionKey, signedHeaders, signature)
}
return fmt.Sprintf("%s//%s", rawSessionKey, signature)
}