oss/signer/v1.go (204 lines of code) (raw):
package signer
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"hash"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
var requiredSignedParametersMap = map[string]struct{}{
"acl": {},
"bucketInfo": {},
"location": {},
"stat": {},
"delete": {},
"append": {},
"tagging": {},
"objectMeta": {},
"uploads": {},
"uploadId": {},
"partNumber": {},
"security-token": {},
"position": {},
"response-content-type": {},
"response-content-language": {},
"response-expires": {},
"response-cache-control": {},
"response-content-disposition": {},
"response-content-encoding": {},
"restore": {},
"callback": {},
"callback-var": {},
"versions": {},
"versioning": {},
"versionId": {},
"sequential": {},
"continuation-token": {},
"regionList": {},
"cloudboxes": {},
"symlink": {},
}
const (
// headers
authorizationHeader = "Authorization"
securityTokenHeader = "x-oss-security-token"
dateHeader = "Date"
contentTypeHeader = "Content-Type"
contentMd5Header = "Content-MD5"
ossHeaderPreifx = "x-oss-"
ossDateHeader = "x-oss-date"
//Query
securityTokenQuery = "security-token"
expiresQuery = "Expires"
accessKeyIdQuery = "OSSAccessKeyId"
signatureQuery = "Signature"
defaultExpiresDuration = 15 * time.Minute
)
type SignerV1 struct {
}
func isSubResource(list []string, key string) bool {
for _, k := range list {
if key == k {
return true
}
}
return false
}
func (*SignerV1) calcStringToSign(date string, signingCtx *SigningContext) string {
/*
SignToString =
VERB + "\n"
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Date + "\n"
+ CanonicalizedOSSHeaders
+ CanonicalizedResource
Signature = base64(hmac-sha1(AccessKeySecret, SignToString))
*/
request := signingCtx.Request
contentMd5 := request.Header.Get(contentMd5Header)
contentType := request.Header.Get(contentTypeHeader)
//CanonicalizedOSSHeaders
var headers []string
for k := range request.Header {
lowerCaseKey := strings.ToLower(k)
if strings.HasPrefix(lowerCaseKey, ossHeaderPreifx) {
headers = append(headers, lowerCaseKey)
}
}
sort.Strings(headers)
headerItems := make([]string, len(headers))
for i, k := range headers {
headerValues := make([]string, len(request.Header.Values(k)))
for i, v := range request.Header.Values(k) {
headerValues[i] = strings.TrimSpace(v)
}
headerItems[i] = k + ":" + strings.Join(headerValues, ",") + "\n"
}
canonicalizedOSSHeaders := strings.Join(headerItems, "")
//CanonicalizedResource
query := request.URL.Query()
var params []string
for k := range query {
if _, ok := requiredSignedParametersMap[k]; ok {
params = append(params, k)
} else if strings.HasPrefix(k, ossHeaderPreifx) {
params = append(params, k)
} else if isSubResource(signingCtx.SubResource, k) {
params = append(params, k)
}
}
sort.Strings(params)
paramItems := make([]string, len(params))
for i, k := range params {
v := query.Get(k)
if len(v) > 0 {
paramItems[i] = k + "=" + v
} else {
paramItems[i] = k
}
}
subResource := strings.Join(paramItems, "&")
canonicalizedResource := "/"
if signingCtx.Bucket != nil {
canonicalizedResource += *signingCtx.Bucket + "/"
}
if signingCtx.Key != nil {
canonicalizedResource += *signingCtx.Key
}
if subResource != "" {
canonicalizedResource += "?" + subResource
}
// string to Sign
stringToSign :=
request.Method + "\n" +
contentMd5 + "\n" +
contentType + "\n" +
date + "\n" +
canonicalizedOSSHeaders +
canonicalizedResource
//fmt.Printf("stringToSign:%s\n", stringToSign)
return stringToSign
}
func (s *SignerV1) authHeader(ctx context.Context, signingCtx *SigningContext) error {
request := signingCtx.Request
cred := signingCtx.Credentials
// Date
if signingCtx.Time.IsZero() {
signingCtx.Time = time.Now().Add(signingCtx.ClockOffset)
}
datetime := signingCtx.Time.UTC().Format(http.TimeFormat)
request.Header.Set(dateHeader, datetime)
// Credentials information
if cred.SecurityToken != "" {
request.Header.Set(securityTokenHeader, cred.SecurityToken)
}
// StringToSign
stringToSign := s.calcStringToSign(datetime, signingCtx)
signingCtx.StringToSign = stringToSign
// Signature
h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(cred.AccessKeySecret))
if _, err := io.WriteString(h, stringToSign); err != nil {
return err
}
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// Authorization header
request.Header.Set(authorizationHeader, fmt.Sprintf("OSS %s:%s", cred.AccessKeyID, signature))
return nil
}
func (s *SignerV1) authQuery(ctx context.Context, signingCtx *SigningContext) error {
request := signingCtx.Request
cred := signingCtx.Credentials
// Date
if signingCtx.Time.IsZero() {
signingCtx.Time = time.Now().UTC().Add(defaultExpiresDuration)
}
datetime := fmt.Sprintf("%v", signingCtx.Time.UTC().Unix())
// Credentials information
query, _ := url.ParseQuery(request.URL.RawQuery)
if cred.SecurityToken != "" {
query.Add(securityTokenQuery, cred.SecurityToken)
request.URL.RawQuery = query.Encode()
}
// StringToSign
stringToSign := s.calcStringToSign(datetime, signingCtx)
signingCtx.StringToSign = stringToSign
// Signature
h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(cred.AccessKeySecret))
if _, err := io.WriteString(h, stringToSign); err != nil {
return err
}
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// Authorization query
query.Add(expiresQuery, datetime)
query.Add(accessKeyIdQuery, cred.AccessKeyID)
query.Add(signatureQuery, signature)
request.URL.RawQuery = strings.Replace(query.Encode(), "+", "%20", -1)
return nil
}
func (s *SignerV1) Sign(ctx context.Context, signingCtx *SigningContext) error {
if signingCtx == nil {
return fmt.Errorf("SigningContext is null.")
}
if signingCtx.Credentials == nil || !signingCtx.Credentials.HasKeys() {
return fmt.Errorf("SigningContext.Credentials is null or empty.")
}
if signingCtx.Request == nil {
return fmt.Errorf("SigningContext.Request is null.")
}
if signingCtx.AuthMethodQuery {
return s.authQuery(ctx, signingCtx)
}
return s.authHeader(ctx, signingCtx)
}
func (*SignerV1) IsSignedHeader(additionalHeaders []string, h string) bool {
lowerCaseKey := strings.ToLower(h)
if strings.HasPrefix(lowerCaseKey, ossHeaderPreifx) ||
lowerCaseKey == "date" ||
lowerCaseKey == "content-type" ||
lowerCaseKey == "content-md5" {
return true
}
return false
}