github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_hmac.go (about) 1 package gateway 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha1" 6 "crypto/sha256" 7 "crypto/sha512" 8 "encoding/base64" 9 "errors" 10 "hash" 11 "math" 12 "net/http" 13 "net/url" 14 "strings" 15 "time" 16 17 "github.com/sirupsen/logrus" 18 19 "github.com/TykTechnologies/tyk/apidef" 20 "github.com/TykTechnologies/tyk/headers" 21 "github.com/TykTechnologies/tyk/regexp" 22 "github.com/TykTechnologies/tyk/user" 23 ) 24 25 const dateHeaderSpec = "Date" 26 const altHeaderSpec = "x-aux-date" 27 28 // HMACMiddleware will check if the request has a signature, and if the request is allowed through 29 type HMACMiddleware struct { 30 BaseMiddleware 31 lowercasePattern *regexp.Regexp 32 } 33 34 func (hm *HMACMiddleware) Name() string { 35 return "HMAC" 36 } 37 38 func (k *HMACMiddleware) EnabledForSpec() bool { 39 return k.Spec.EnableSignatureChecking 40 } 41 42 func (hm *HMACMiddleware) Init() { 43 hm.lowercasePattern = regexp.MustCompile(`%[a-f0-9][a-f0-9]`) 44 } 45 46 func (hm *HMACMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) { 47 token := r.Header.Get("Authorization") 48 if token == "" { 49 return hm.authorizationError(r) 50 } 51 logger := hm.Logger().WithField("key", obfuscateKey(token)) 52 53 // Clean it 54 token = stripSignature(token) 55 56 // Separate out the field values 57 fieldValues, err := getFieldValues(token) 58 if err != nil { 59 logger.WithError(err).Error("Field extraction failed") 60 return hm.authorizationError(r) 61 } 62 63 // Generate a signature string 64 signatureString, err := generateHMACSignatureStringFromRequest(r, fieldValues.Headers) 65 if err != nil { 66 logger.WithError(err).WithField("signature_string", signatureString).Error("Signature string generation failed") 67 return hm.authorizationError(r) 68 } 69 70 // Get a session for the Key ID 71 secret, session, err := hm.getSecretAndSessionForKeyID(r, fieldValues.KeyID) 72 if err != nil { 73 logger.WithError(err).WithFields(logrus.Fields{ 74 "keyID": fieldValues.KeyID, 75 }).Error("No HMAC secret for this key") 76 return hm.authorizationError(r) 77 } 78 79 if len(hm.Spec.HmacAllowedAlgorithms) > 0 { 80 algorithmAllowed := false 81 for _, alg := range hm.Spec.HmacAllowedAlgorithms { 82 if alg == fieldValues.Algorthm { 83 algorithmAllowed = true 84 break 85 } 86 } 87 if !algorithmAllowed { 88 logger.WithError(err).WithField("algorithm", fieldValues.Algorthm).Error("Algorithm not supported") 89 return hm.authorizationError(r) 90 } 91 } 92 93 // Create a signed string with the secret 94 encodedSignature := generateEncodedSignature(signatureString, secret, fieldValues.Algorthm) 95 96 // Compare 97 matchPass := encodedSignature == fieldValues.Signature 98 99 // Check for lower case encoding (.Net issues, again) 100 if !matchPass { 101 isLower, lowerList := hm.hasLowerCaseEscaped(fieldValues.Signature) 102 if isLower { 103 logger.Debug("--- Detected lower case encoding! ---") 104 upperedSignature := hm.replaceWithUpperCase(fieldValues.Signature, lowerList) 105 if encodedSignature == upperedSignature { 106 matchPass = true 107 encodedSignature = upperedSignature 108 } 109 } 110 } 111 112 if !matchPass { 113 logger.WithFields(logrus.Fields{ 114 "expected": encodedSignature, 115 "got": fieldValues.Signature, 116 }).Error("Signature string does not match!") 117 return hm.authorizationError(r) 118 } 119 120 // Check clock skew 121 _, dateVal := getDateHeader(r) 122 if !hm.checkClockSkew(dateVal) { 123 logger.Error("Clock skew outside of acceptable bounds") 124 return hm.authorizationError(r) 125 } 126 127 // Set session state on context, we will need it later 128 switch hm.Spec.BaseIdentityProvidedBy { 129 case apidef.HMACKey, apidef.UnsetAuth: 130 ctxSetSession(r, &session, fieldValues.KeyID, false) 131 hm.setContextVars(r, fieldValues.KeyID) 132 } 133 134 // Everything seems in order let the request through 135 return nil, http.StatusOK 136 } 137 138 func stripSignature(token string) string { 139 token = strings.TrimPrefix(token, "Signature") 140 token = strings.TrimPrefix(token, "signature") 141 return strings.TrimSpace(token) 142 } 143 144 func (hm *HMACMiddleware) hasLowerCaseEscaped(signature string) (bool, []string) { 145 foundList := hm.lowercasePattern.FindAllString(signature, -1) 146 return len(foundList) > 0, foundList 147 } 148 149 func (hm *HMACMiddleware) replaceWithUpperCase(originalSignature string, lowercaseList []string) string { 150 newSignature := originalSignature 151 for _, lStr := range lowercaseList { 152 asUpper := strings.ToUpper(lStr) 153 newSignature = strings.Replace(newSignature, lStr, asUpper, -1) 154 } 155 156 return newSignature 157 } 158 159 func (hm *HMACMiddleware) setContextVars(r *http.Request, token string) { 160 if !hm.Spec.EnableContextVars { 161 return 162 } 163 // Flatten claims and add to context 164 if cnt := ctxGetData(r); cnt != nil { 165 // Key data 166 cnt["token"] = token 167 ctxSetData(r, cnt) 168 } 169 } 170 171 func (hm *HMACMiddleware) authorizationError(r *http.Request) (error, int) { 172 hm.Logger().Info("Authorization field missing or malformed") 173 174 AuthFailed(hm, r, r.Header.Get(headers.Authorization)) 175 176 return errors.New("Authorization field missing, malformed or invalid"), http.StatusBadRequest 177 } 178 179 func (hm HMACMiddleware) checkClockSkew(dateHeaderValue string) bool { 180 // Reference layout for parsing time: "Mon Jan 2 15:04:05 MST 2006" 181 refDate := "Mon, 02 Jan 2006 15:04:05 MST" 182 // Fall back to a numeric timezone, since some environments don't provide a timezone name code 183 refDateNumeric := "Mon, 02 Jan 2006 15:04:05 -07" 184 185 tim, err := time.Parse(refDate, dateHeaderValue) 186 if err != nil { 187 tim, err = time.Parse(refDateNumeric, dateHeaderValue) 188 } 189 190 if err != nil { 191 hm.Logger().WithError(err).WithField("date_string", tim).Error("Date parsing failed") 192 return false 193 } 194 195 inSec := tim.UnixNano() 196 now := time.Now().UnixNano() 197 198 diff := now - inSec 199 200 in_ms := diff / 1000000 201 202 if hm.Spec.HmacAllowedClockSkew <= 0 { 203 return true 204 } 205 206 if math.Abs(float64(in_ms)) > hm.Spec.HmacAllowedClockSkew { 207 hm.Logger().Debug("Difference is: ", math.Abs(float64(in_ms))) 208 return false 209 } 210 211 return true 212 } 213 214 type HMACFieldValues struct { 215 KeyID string 216 Algorthm string 217 Headers []string 218 Signature string 219 } 220 221 func (hm *HMACMiddleware) getSecretAndSessionForKeyID(r *http.Request, keyId string) (string, user.SessionState, error) { 222 session, keyExists := hm.CheckSessionAndIdentityForValidKey(keyId, r) 223 if !keyExists { 224 return "", session, errors.New("Key ID does not exist") 225 } 226 227 if session.HmacSecret == "" || !session.HMACEnabled { 228 hm.Logger().Info("API Requires HMAC signature, session missing HMACSecret or HMAC not enabled for key") 229 230 return "", session, errors.New("This key ID is invalid") 231 } 232 233 return session.HmacSecret, session, nil 234 } 235 236 func getDateHeader(r *http.Request) (string, string) { 237 auxHeaderVal := r.Header.Get(altHeaderSpec) 238 // Prefer aux if present 239 if auxHeaderVal != "" { 240 token := r.Header.Get(headers.Authorization) 241 log.WithFields(logrus.Fields{ 242 "prefix": "hmac", 243 "auth_header": token, 244 }).Warning("Using auxiliary header for this request") 245 return strings.ToLower(altHeaderSpec), auxHeaderVal 246 } 247 248 dateHeaderVal := r.Header.Get(dateHeaderSpec) 249 if dateHeaderVal != "" { 250 log.WithFields(logrus.Fields{ 251 "prefix": "hmac", 252 }).Debug("Got date header") 253 return strings.ToLower(dateHeaderSpec), dateHeaderVal 254 } 255 256 return "", "" 257 } 258 259 func getFieldValues(authHeader string) (*HMACFieldValues, error) { 260 set := HMACFieldValues{} 261 262 for _, element := range strings.Split(authHeader, ",") { 263 kv := strings.Split(element, "=") 264 if len(kv) != 2 { 265 return nil, errors.New("Header field value malformed (need two elements in field)") 266 } 267 268 key := strings.ToLower(kv[0]) 269 value := strings.Trim(kv[1], `"`) 270 271 switch key { 272 case "keyid": 273 set.KeyID = value 274 case "algorithm": 275 set.Algorthm = value 276 case "headers": 277 set.Headers = strings.Split(value, " ") 278 case "signature": 279 set.Signature = value 280 default: 281 log.WithFields(logrus.Fields{ 282 "prefix": "hmac", 283 "field": kv[0], 284 }).Warning("Invalid header field found") 285 return nil, errors.New("Header key is not valid, not in allowed parameter list") 286 } 287 } 288 289 // Date is the absolute minimum header set 290 if len(set.Headers) == 0 { 291 set.Headers = append(set.Headers, "date") 292 } 293 294 return &set, nil 295 } 296 297 // "Signature keyId="9876",algorithm="hmac-sha1",headers="x-test x-test-2",signature="queryEscape(base64(sig))"") 298 299 func generateHMACSignatureStringFromRequest(r *http.Request, headers []string) (string, error) { 300 signatureString := "" 301 for i, header := range headers { 302 loweredHeader := strings.TrimSpace(strings.ToLower(header)) 303 if loweredHeader == "(request-target)" { 304 requestHeaderField := "(request-target): " + strings.ToLower(r.Method) + " " + r.URL.Path 305 signatureString += requestHeaderField 306 } else { 307 // exception for dates and .Net oddness 308 headerVal := r.Header.Get(loweredHeader) 309 if loweredHeader == "date" { 310 loweredHeader, headerVal = getDateHeader(r) 311 } 312 headerField := strings.TrimSpace(loweredHeader) + ": " + strings.TrimSpace(headerVal) 313 signatureString += headerField 314 } 315 316 if i != len(headers)-1 { 317 signatureString += "\n" 318 } 319 } 320 log.Debug("Generated sig string: ", signatureString) 321 return signatureString, nil 322 } 323 324 func generateEncodedSignature(signatureString, secret string, algorithm string) string { 325 key := []byte(secret) 326 327 var hashFunction func() hash.Hash 328 329 switch algorithm { 330 case "hmac-sha256": 331 hashFunction = sha256.New 332 case "hmac-sha384": 333 hashFunction = sha512.New384 334 case "hmac-sha512": 335 hashFunction = sha512.New 336 default: 337 hashFunction = sha1.New 338 } 339 340 h := hmac.New(hashFunction, key) 341 h.Write([]byte(signatureString)) 342 encodedString := base64.StdEncoding.EncodeToString(h.Sum(nil)) 343 return url.QueryEscape(encodedString) 344 }