github.com/akamai/AkamaiOPEN-edgegrid-golang/v4@v4.1.0/pkg/edgegrid/signer.go (about)

     1  package edgegrid
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/google/uuid"
    15  )
    16  
    17  type (
    18  	// Signer is the request signer interface
    19  	Signer interface {
    20  		SignRequest(r *http.Request)
    21  	}
    22  
    23  	authHeader struct {
    24  		authType    string
    25  		clientToken string
    26  		accessToken string
    27  		timestamp   string
    28  		nonce       string
    29  		signature   string
    30  	}
    31  )
    32  
    33  const (
    34  	authType = "EG1-HMAC-SHA256"
    35  )
    36  
    37  // SignRequest adds a signed authorization header to the http request
    38  func (c Config) SignRequest(r *http.Request) {
    39  	if r.URL.Host == "" {
    40  		r.URL.Host = c.Host
    41  	}
    42  	if r.URL.Scheme == "" {
    43  		r.URL.Scheme = "https"
    44  	}
    45  	r.URL.RawQuery = c.addAccountSwitchKey(r)
    46  	r.Header.Set("Authorization", c.createAuthHeader(r).String())
    47  }
    48  
    49  func (c Config) createAuthHeader(r *http.Request) authHeader {
    50  	timestamp := Timestamp(time.Now())
    51  
    52  	auth := authHeader{
    53  		authType:    authType,
    54  		clientToken: c.ClientToken,
    55  		accessToken: c.AccessToken,
    56  		timestamp:   timestamp,
    57  		nonce:       uuid.New().String(),
    58  	}
    59  
    60  	msgPath := r.URL.EscapedPath()
    61  	if r.URL.RawQuery != "" {
    62  		msgPath = fmt.Sprintf("%s?%s", msgPath, r.URL.RawQuery)
    63  	}
    64  
    65  	// create the message to be signed
    66  	msgData := []string{
    67  		r.Method,
    68  		r.URL.Scheme,
    69  		r.URL.Host,
    70  		msgPath,
    71  		canonicalizeHeaders(r.Header, c.HeaderToSign),
    72  		createContentHash(r, c.MaxBody),
    73  		auth.String(),
    74  	}
    75  	msg := strings.Join(msgData, "\t")
    76  
    77  	key := createSignature(timestamp, c.ClientSecret)
    78  	auth.signature = createSignature(msg, key)
    79  	return auth
    80  }
    81  
    82  func canonicalizeHeaders(requestHeaders http.Header, headersToSign []string) string {
    83  	var unsortedHeader []string
    84  	var sortedHeader []string
    85  	for k := range requestHeaders {
    86  		unsortedHeader = append(unsortedHeader, k)
    87  	}
    88  	sort.Strings(unsortedHeader)
    89  	for _, k := range unsortedHeader {
    90  		for _, sign := range headersToSign {
    91  			if sign == k {
    92  				v := strings.TrimSpace(requestHeaders.Get(k))
    93  				sortedHeader = append(sortedHeader, fmt.Sprintf("%s:%s", strings.ToLower(k), strings.ToLower(stringMinifier(v))))
    94  			}
    95  		}
    96  	}
    97  	return strings.Join(sortedHeader, "\t")
    98  }
    99  
   100  // The content hash is the base64-encoded SHA–256 hash of the POST body.
   101  // For any other request methods, this field is empty. But the tab separator (\t) must be included.
   102  // The size of the POST body must be less than or equal to the value specified by the service.
   103  // Any request that does not meet this criteria SHOULD be rejected during the signing process,
   104  // as the request will be rejected by EdgeGrid.
   105  func createContentHash(r *http.Request, maxBody int) string {
   106  	var (
   107  		contentHash  string
   108  		preparedBody string
   109  		bodyBytes    []byte
   110  	)
   111  
   112  	if r.Body != nil {
   113  		bodyBytes, _ = ioutil.ReadAll(r.Body)
   114  		r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
   115  		preparedBody = string(bodyBytes)
   116  	}
   117  
   118  	if r.Method == http.MethodPost && len(preparedBody) > 0 {
   119  		if len(preparedBody) > maxBody {
   120  			preparedBody = preparedBody[0:maxBody]
   121  		}
   122  
   123  		sum := sha256.Sum256([]byte(preparedBody))
   124  
   125  		contentHash = base64.StdEncoding.EncodeToString(sum[:])
   126  	}
   127  
   128  	return contentHash
   129  }
   130  
   131  func (a authHeader) String() string {
   132  	auth := fmt.Sprintf("%s client_token=%s;access_token=%s;timestamp=%s;nonce=%s;",
   133  		a.authType,
   134  		a.clientToken,
   135  		a.accessToken,
   136  		a.timestamp,
   137  		a.nonce)
   138  	if a.signature != "" {
   139  		auth += fmt.Sprintf("signature=%s", a.signature)
   140  	}
   141  	return auth
   142  }
   143  
   144  func (c Config) addAccountSwitchKey(r *http.Request) string {
   145  	if c.AccountKey != "" {
   146  		values := r.URL.Query()
   147  		values.Add("accountSwitchKey", c.AccountKey)
   148  		r.URL.RawQuery = values.Encode()
   149  	}
   150  	return r.URL.RawQuery
   151  }