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 }