github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.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  	r.URL.RawQuery = c.addAccountSwitchKey(r)
    43  	r.Header.Set("Authorization", c.createAuthHeader(r).String())
    44  }
    45  
    46  func (c Config) createAuthHeader(r *http.Request) authHeader {
    47  	timestamp := Timestamp(time.Now())
    48  
    49  	auth := authHeader{
    50  		authType:    authType,
    51  		clientToken: c.ClientToken,
    52  		accessToken: c.AccessToken,
    53  		timestamp:   timestamp,
    54  		nonce:       uuid.New().String(),
    55  	}
    56  
    57  	msgPath := r.URL.EscapedPath()
    58  	if r.URL.RawQuery != "" {
    59  		msgPath = fmt.Sprintf("%s?%s", msgPath, r.URL.RawQuery)
    60  	}
    61  
    62  	// create the message to be signed
    63  	msgData := []string{
    64  		r.Method,
    65  		r.URL.Scheme,
    66  		r.URL.Host,
    67  		msgPath,
    68  		canonicalizeHeaders(r.Header, c.HeaderToSign),
    69  		createContentHash(r, c.MaxBody),
    70  		auth.String(),
    71  	}
    72  	msg := strings.Join(msgData, "\t")
    73  
    74  	key := createSignature(timestamp, c.ClientSecret)
    75  	auth.signature = createSignature(msg, key)
    76  	return auth
    77  }
    78  
    79  func canonicalizeHeaders(requestHeaders http.Header, headersToSign []string) string {
    80  	var unsortedHeader []string
    81  	var sortedHeader []string
    82  	for k := range requestHeaders {
    83  		unsortedHeader = append(unsortedHeader, k)
    84  	}
    85  	sort.Strings(unsortedHeader)
    86  	for _, k := range unsortedHeader {
    87  		for _, sign := range headersToSign {
    88  			if sign == k {
    89  				v := strings.TrimSpace(requestHeaders.Get(k))
    90  				sortedHeader = append(sortedHeader, fmt.Sprintf("%s:%s", strings.ToLower(k), strings.ToLower(stringMinifier(v))))
    91  			}
    92  		}
    93  	}
    94  	return strings.Join(sortedHeader, "\t")
    95  }
    96  
    97  // The content hash is the base64-encoded SHA–256 hash of the POST body.
    98  // For any other request methods, this field is empty. But the tab separator (\t) must be included.
    99  // The size of the POST body must be less than or equal to the value specified by the service.
   100  // Any request that does not meet this criteria SHOULD be rejected during the signing process,
   101  // as the request will be rejected by EdgeGrid.
   102  func createContentHash(r *http.Request, maxBody int) string {
   103  	var (
   104  		contentHash  string
   105  		preparedBody string
   106  		bodyBytes    []byte
   107  	)
   108  
   109  	if r.Body != nil {
   110  		bodyBytes, _ = ioutil.ReadAll(r.Body)
   111  		r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
   112  		preparedBody = string(bodyBytes)
   113  	}
   114  
   115  	if r.Method == http.MethodPost && len(preparedBody) > 0 {
   116  		if len(preparedBody) > maxBody {
   117  			preparedBody = preparedBody[0:maxBody]
   118  		}
   119  
   120  		sum := sha256.Sum256([]byte(preparedBody))
   121  
   122  		contentHash = base64.StdEncoding.EncodeToString(sum[:])
   123  	}
   124  
   125  	return contentHash
   126  }
   127  
   128  func (a authHeader) String() string {
   129  	auth := fmt.Sprintf("%s client_token=%s;access_token=%s;timestamp=%s;nonce=%s;",
   130  		a.authType,
   131  		a.clientToken,
   132  		a.accessToken,
   133  		a.timestamp,
   134  		a.nonce)
   135  	if a.signature != "" {
   136  		auth += fmt.Sprintf("signature=%s", a.signature)
   137  	}
   138  	return auth
   139  }
   140  
   141  func (c Config) addAccountSwitchKey(r *http.Request) string {
   142  	if c.AccountKey != "" {
   143  		values := r.URL.Query()
   144  		values.Add("accountSwitchKey", c.AccountKey)
   145  		r.URL.RawQuery = values.Encode()
   146  	}
   147  	return r.URL.RawQuery
   148  }