github.com/shindo/goofys@v0.24.1-0.20210326210429-9e930f0b2d5c/internal/v2signer.go (about)

     1  // Copyright 2015 - 2017 Ka-Hing Cheung
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package internal
    16  
    17  import (
    18  	"crypto/hmac"
    19  	"crypto/sha1"
    20  	"encoding/base64"
    21  	"errors"
    22  	"fmt"
    23  	"net/http"
    24  	"net/url"
    25  	"sort"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/aws/aws-sdk-go/aws"
    30  	"github.com/aws/aws-sdk-go/aws/credentials"
    31  	"github.com/aws/aws-sdk-go/aws/request"
    32  	"github.com/aws/aws-sdk-go/private/protocol/rest"
    33  )
    34  
    35  var (
    36  	errInvalidMethod = errors.New("v2 signer does not handle HTTP POST")
    37  )
    38  
    39  const (
    40  	signatureVersion = "2"
    41  	signatureMethod  = "HmacSHA1"
    42  	timeFormat       = "Mon, 2 Jan 2006 15:04:05 +0000"
    43  )
    44  
    45  var subresources = []string{
    46  	"acl",
    47  	"delete",
    48  	"lifecycle",
    49  	"location",
    50  	"logging",
    51  	"notification",
    52  	"partNumber",
    53  	"policy",
    54  	"requestPayment",
    55  	"torrent",
    56  	"uploadId",
    57  	"uploads",
    58  	"versionId",
    59  	"versioning",
    60  	"versions",
    61  	"website",
    62  }
    63  
    64  type signer struct {
    65  	// Values that must be populated from the request
    66  	Request     *http.Request
    67  	Time        time.Time
    68  	Credentials *credentials.Credentials
    69  	Debug       aws.LogLevelType
    70  	Logger      aws.Logger
    71  	pathStyle   bool
    72  	bucket      string
    73  
    74  	Query        url.Values
    75  	stringToSign string
    76  	signature    string
    77  }
    78  
    79  // Sign requests with signature version 2.
    80  //
    81  // Will sign the requests with the service config's Credentials object
    82  // Signing is skipped if the credentials is the credentials.AnonymousCredentials
    83  // object.
    84  func SignV2(req *request.Request) {
    85  	// If the request does not need to be signed ignore the signing of the
    86  	// request if the AnonymousCredentials object is used.
    87  	if req.Config.Credentials == credentials.AnonymousCredentials {
    88  		return
    89  	}
    90  
    91  	v2 := signer{
    92  		Request:     req.HTTPRequest,
    93  		Time:        req.Time,
    94  		Credentials: req.Config.Credentials,
    95  		Debug:       req.Config.LogLevel.Value(),
    96  		Logger:      req.Config.Logger,
    97  		pathStyle:   aws.BoolValue(req.Config.S3ForcePathStyle),
    98  	}
    99  
   100  	req.Error = v2.Sign()
   101  }
   102  
   103  func (v2 *signer) Sign() error {
   104  	credValue, err := v2.Credentials.Get()
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	v2.Query = v2.Request.URL.Query()
   110  
   111  	contentMD5 := v2.Request.Header.Get("Content-MD5")
   112  	contentType := v2.Request.Header.Get("Content-Type")
   113  	date := v2.Time.UTC().Format(timeFormat)
   114  	v2.Request.Header.Set("x-amz-date", date)
   115  
   116  	if credValue.SessionToken != "" {
   117  		v2.Request.Header.Set("x-amz-security-token", credValue.SessionToken)
   118  	}
   119  
   120  	// in case this is a retry, ensure no signature present
   121  	v2.Request.Header.Del("Authorization")
   122  
   123  	method := v2.Request.Method
   124  
   125  	uri := v2.Request.URL.Opaque
   126  	if uri != "" {
   127  		if questionMark := strings.Index(uri, "?"); questionMark != -1 {
   128  			uri = uri[0:questionMark]
   129  		}
   130  		uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
   131  	} else {
   132  		uri = v2.Request.URL.Path
   133  	}
   134  	path := rest.EscapePath(uri, false)
   135  	if !v2.pathStyle {
   136  		host := strings.SplitN(v2.Request.URL.Host, ".", 2)[0]
   137  		path = "/" + host + uri
   138  	}
   139  	if path == "" {
   140  		path = "/"
   141  	}
   142  
   143  	// build URL-encoded query keys and values
   144  	queryKeysAndValues := []string{}
   145  	for _, key := range subresources {
   146  		if _, ok := v2.Query[key]; ok {
   147  			k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
   148  			v := strings.Replace(url.QueryEscape(v2.Query.Get(key)), "+", "%20", -1)
   149  			if v != "" {
   150  				v = "=" + v
   151  			}
   152  			queryKeysAndValues = append(queryKeysAndValues, k+v)
   153  		}
   154  	}
   155  
   156  	// join into one query string
   157  	query := strings.Join(queryKeysAndValues, "&")
   158  
   159  	if query != "" {
   160  		path += "?" + query
   161  	}
   162  
   163  	tmp := []string{
   164  		method,
   165  		contentMD5,
   166  		contentType,
   167  		"",
   168  	}
   169  
   170  	var headers []string
   171  	for k := range v2.Request.Header {
   172  		k = strings.ToLower(k)
   173  		if strings.HasPrefix(k, "x-amz-") {
   174  			headers = append(headers, k)
   175  		}
   176  	}
   177  	sort.Strings(headers)
   178  
   179  	for _, k := range headers {
   180  		v := strings.Join(v2.Request.Header[http.CanonicalHeaderKey(k)], ",")
   181  		tmp = append(tmp, k+":"+v)
   182  	}
   183  
   184  	tmp = append(tmp, path)
   185  
   186  	// build the canonical string for the V2 signature
   187  	v2.stringToSign = strings.Join(tmp, "\n")
   188  
   189  	hash := hmac.New(sha1.New, []byte(credValue.SecretAccessKey))
   190  	hash.Write([]byte(v2.stringToSign))
   191  	v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
   192  	v2.Request.Header.Set("Authorization",
   193  		"AWS "+credValue.AccessKeyID+":"+v2.signature)
   194  
   195  	if v2.Debug.Matches(aws.LogDebugWithSigning) {
   196  		v2.logSigningInfo()
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  const logSignInfoMsg = `DEBUG: Request Signature:
   203  ---[ STRING TO SIGN ]--------------------------------
   204  %s
   205  ---[ SIGNATURE ]-------------------------------------
   206  %s
   207  -----------------------------------------------------`
   208  
   209  func (v2 *signer) logSigningInfo() {
   210  	msg := fmt.Sprintf(logSignInfoMsg, v2.stringToSign, v2.Request.Header.Get("Authorization"))
   211  	v2.Logger.Log(msg)
   212  }