github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/s3/v2sign.go (about)

     1  // v2 signing
     2  
     3  package s3
     4  
     5  import (
     6  	"crypto/hmac"
     7  	"crypto/sha1"
     8  	"encoding/base64"
     9  	"net/http"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  // URL parameters that need to be added to the signature
    16  var s3ParamsToSign = map[string]struct{}{
    17  	"delete":                       {},
    18  	"acl":                          {},
    19  	"location":                     {},
    20  	"logging":                      {},
    21  	"notification":                 {},
    22  	"partNumber":                   {},
    23  	"policy":                       {},
    24  	"requestPayment":               {},
    25  	"torrent":                      {},
    26  	"uploadId":                     {},
    27  	"uploads":                      {},
    28  	"versionId":                    {},
    29  	"versioning":                   {},
    30  	"versions":                     {},
    31  	"response-content-type":        {},
    32  	"response-content-language":    {},
    33  	"response-expires":             {},
    34  	"response-cache-control":       {},
    35  	"response-content-disposition": {},
    36  	"response-content-encoding":    {},
    37  }
    38  
    39  // sign signs requests using v2 auth
    40  //
    41  // Cobbled together from goamz and aws-sdk-go
    42  func sign(AccessKey, SecretKey string, req *http.Request) {
    43  	// Set date
    44  	date := time.Now().UTC().Format(time.RFC1123)
    45  	req.Header.Set("Date", date)
    46  
    47  	// Sort out URI
    48  	uri := req.URL.EscapedPath()
    49  	if uri == "" {
    50  		uri = "/"
    51  	}
    52  
    53  	// Look through headers of interest
    54  	var md5 string
    55  	var contentType string
    56  	var headersToSign []string
    57  	tmpHeadersToSign := make(map[string][]string)
    58  	for k, v := range req.Header {
    59  		k = strings.ToLower(k)
    60  		switch k {
    61  		case "content-md5":
    62  			md5 = v[0]
    63  		case "content-type":
    64  			contentType = v[0]
    65  		default:
    66  			if strings.HasPrefix(k, "x-amz-") {
    67  				tmpHeadersToSign[k] = v
    68  			}
    69  		}
    70  	}
    71  	var keys []string
    72  	for k := range tmpHeadersToSign {
    73  		keys = append(keys, k)
    74  	}
    75  	// https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
    76  	sort.Strings(keys)
    77  
    78  	for _, key := range keys {
    79  		vall := strings.Join(tmpHeadersToSign[key], ",")
    80  		headersToSign = append(headersToSign, key+":"+vall)
    81  	}
    82  	// Make headers of interest into canonical string
    83  	var joinedHeadersToSign string
    84  	if len(headersToSign) > 0 {
    85  		joinedHeadersToSign = strings.Join(headersToSign, "\n") + "\n"
    86  	}
    87  
    88  	// Look for query parameters which need to be added to the signature
    89  	params := req.URL.Query()
    90  	var queriesToSign []string
    91  	for k, vs := range params {
    92  		if _, ok := s3ParamsToSign[k]; ok {
    93  			for _, v := range vs {
    94  				if v == "" {
    95  					queriesToSign = append(queriesToSign, k)
    96  				} else {
    97  					queriesToSign = append(queriesToSign, k+"="+v)
    98  				}
    99  			}
   100  		}
   101  	}
   102  	// Add query parameters to URI
   103  	if len(queriesToSign) > 0 {
   104  		sort.StringSlice(queriesToSign).Sort()
   105  		uri += "?" + strings.Join(queriesToSign, "&")
   106  	}
   107  
   108  	// Make signature
   109  	payload := req.Method + "\n" + md5 + "\n" + contentType + "\n" + date + "\n" + joinedHeadersToSign + uri
   110  	hash := hmac.New(sha1.New, []byte(SecretKey))
   111  	_, _ = hash.Write([]byte(payload))
   112  	signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size()))
   113  	base64.StdEncoding.Encode(signature, hash.Sum(nil))
   114  
   115  	// Set signature in request
   116  	req.Header.Set("Authorization", "AWS "+AccessKey+":"+string(signature))
   117  }