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  }