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