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 }