mirror of
https://github.com/rclone/rclone
synced 2024-11-11 09:30:44 +01:00
116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
|
// v2 signing
|
||
|
|
||
|
package s3
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha1"
|
||
|
"encoding/base64"
|
||
|
"net/http"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// URL parameters that need to be added to the signature
|
||
|
var s3ParamsToSign = map[string]struct{}{
|
||
|
"acl": struct{}{},
|
||
|
"location": struct{}{},
|
||
|
"logging": struct{}{},
|
||
|
"notification": struct{}{},
|
||
|
"partNumber": struct{}{},
|
||
|
"policy": struct{}{},
|
||
|
"requestPayment": struct{}{},
|
||
|
"torrent": struct{}{},
|
||
|
"uploadId": struct{}{},
|
||
|
"uploads": struct{}{},
|
||
|
"versionId": struct{}{},
|
||
|
"versioning": struct{}{},
|
||
|
"versions": struct{}{},
|
||
|
"response-content-type": struct{}{},
|
||
|
"response-content-language": struct{}{},
|
||
|
"response-expires": struct{}{},
|
||
|
"response-cache-control": struct{}{},
|
||
|
"response-content-disposition": struct{}{},
|
||
|
"response-content-encoding": struct{}{},
|
||
|
}
|
||
|
|
||
|
// sign signs requests using v2 auth
|
||
|
//
|
||
|
// Cobbled together from goamz and aws-sdk-go
|
||
|
func sign(AccessKey, SecretKey string, req *http.Request) {
|
||
|
// Set date
|
||
|
date := time.Now().UTC().Format(time.RFC1123)
|
||
|
req.Header.Set("Date", date)
|
||
|
|
||
|
// Sort out URI
|
||
|
uri := req.URL.Opaque
|
||
|
if uri != "" {
|
||
|
if strings.HasPrefix(uri, "//") {
|
||
|
// Strip off //host/uri
|
||
|
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
|
||
|
req.URL.Opaque = uri // reset to plain URI otherwise Ceph gets confused
|
||
|
}
|
||
|
} else {
|
||
|
uri = req.URL.Path
|
||
|
}
|
||
|
if uri == "" {
|
||
|
uri = "/"
|
||
|
}
|
||
|
|
||
|
// Look through headers of interest
|
||
|
var md5 string
|
||
|
var contentType string
|
||
|
var headersToSign []string
|
||
|
for k, v := range req.Header {
|
||
|
k = strings.ToLower(k)
|
||
|
switch k {
|
||
|
case "content-md5":
|
||
|
md5 = v[0]
|
||
|
case "content-type":
|
||
|
contentType = v[0]
|
||
|
default:
|
||
|
if strings.HasPrefix(k, "x-amz-") {
|
||
|
vall := strings.Join(v, ",")
|
||
|
headersToSign = append(headersToSign, k+":"+vall)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Make headers of interest into canonical string
|
||
|
var joinedHeadersToSign string
|
||
|
if len(headersToSign) > 0 {
|
||
|
sort.StringSlice(headersToSign).Sort()
|
||
|
joinedHeadersToSign = strings.Join(headersToSign, "\n") + "\n"
|
||
|
}
|
||
|
|
||
|
// Look for query parameters which need to be added to the signature
|
||
|
params := req.URL.Query()
|
||
|
var queriesToSign []string
|
||
|
for k, vs := range params {
|
||
|
if _, ok := s3ParamsToSign[k]; ok {
|
||
|
for _, v := range vs {
|
||
|
if v == "" {
|
||
|
queriesToSign = append(queriesToSign, k)
|
||
|
} else {
|
||
|
queriesToSign = append(queriesToSign, k+"="+v)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Add query parameters to URI
|
||
|
if len(queriesToSign) > 0 {
|
||
|
sort.StringSlice(queriesToSign).Sort()
|
||
|
uri += "?" + strings.Join(queriesToSign, "&")
|
||
|
}
|
||
|
|
||
|
// Make signature
|
||
|
payload := req.Method + "\n" + md5 + "\n" + contentType + "\n" + date + "\n" + joinedHeadersToSign + uri
|
||
|
hash := hmac.New(sha1.New, []byte(SecretKey))
|
||
|
hash.Write([]byte(payload))
|
||
|
signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size()))
|
||
|
base64.StdEncoding.Encode(signature, hash.Sum(nil))
|
||
|
|
||
|
// Set signature in request
|
||
|
req.Header.Set("Authorization", "AWS "+AccessKey+":"+string(signature))
|
||
|
}
|