github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/mw_http_signature_validation.go (about)

     1  package gateway
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/hmac"
     6  	"crypto/rsa"
     7  	"crypto/sha1"
     8  	"crypto/sha256"
     9  	"crypto/sha512"
    10  	"encoding/base64"
    11  	"errors"
    12  	"hash"
    13  	"math"
    14  	"net/http"
    15  	"net/url"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"text/scanner"
    20  	"time"
    21  
    22  	"github.com/sirupsen/logrus"
    23  
    24  	"github.com/TykTechnologies/tyk/apidef"
    25  	"github.com/TykTechnologies/tyk/regexp"
    26  	"github.com/TykTechnologies/tyk/user"
    27  )
    28  
    29  const dateHeaderSpec = "Date"
    30  const altHeaderSpec = "x-aux-date"
    31  
    32  // HTTPSignatureValidationMiddleware will check if the request has a signature, and if the request is allowed through
    33  type HTTPSignatureValidationMiddleware struct {
    34  	BaseMiddleware
    35  	lowercasePattern *regexp.Regexp
    36  }
    37  
    38  func (hm *HTTPSignatureValidationMiddleware) Name() string {
    39  	return "HTTPSignatureValidationMiddleware"
    40  }
    41  
    42  func (k *HTTPSignatureValidationMiddleware) EnabledForSpec() bool {
    43  	return k.Spec.EnableSignatureChecking
    44  }
    45  
    46  func (hm *HTTPSignatureValidationMiddleware) Init() {
    47  	hm.lowercasePattern = regexp.MustCompile(`%[a-f0-9][a-f0-9]`)
    48  }
    49  
    50  // getAuthType overrides BaseMiddleware.getAuthType.
    51  func (hm *HTTPSignatureValidationMiddleware) getAuthType() string {
    52  	return hmacType
    53  }
    54  
    55  func (hm *HTTPSignatureValidationMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
    56  	if ctxGetRequestStatus(r) == StatusOkAndIgnore {
    57  		return nil, http.StatusOK
    58  	}
    59  
    60  	token, _ := hm.getAuthToken(hm.getAuthType(), r)
    61  	if token == "" {
    62  		return hm.authorizationError(r)
    63  	}
    64  	logger := hm.Logger().WithField("key", obfuscateKey(token))
    65  
    66  	// Clean it
    67  	token = stripSignature(token)
    68  
    69  	// Separate out the field values
    70  	fieldValues, err := getFieldValues(token)
    71  	if err != nil {
    72  		logger.WithError(err).Error("Field extraction failed")
    73  		return hm.authorizationError(r)
    74  	}
    75  
    76  	// Generate a signature string
    77  	signatureString, err := generateHMACSignatureStringFromRequest(r, fieldValues.Headers, r.URL.Path)
    78  
    79  	if err != nil {
    80  		logger.WithError(err).WithField("signature_string", signatureString).Error("Signature string generation failed")
    81  		return hm.authorizationError(r)
    82  	}
    83  
    84  	if len(hm.Spec.HmacAllowedAlgorithms) > 0 {
    85  		algorithmAllowed := false
    86  		for _, alg := range hm.Spec.HmacAllowedAlgorithms {
    87  			if alg == fieldValues.Algorthm {
    88  				algorithmAllowed = true
    89  				break
    90  			}
    91  		}
    92  		if !algorithmAllowed {
    93  			logger.WithError(err).WithField("algorithm", fieldValues.Algorthm).Error("Algorithm not supported")
    94  			return hm.authorizationError(r)
    95  		}
    96  	}
    97  
    98  	var secret string
    99  	var rsaKey *rsa.PublicKey
   100  	session := user.SessionState{Mutex: &sync.RWMutex{}}
   101  
   102  	if strings.HasPrefix(fieldValues.Algorthm, "rsa") {
   103  		var certificateId string
   104  
   105  		certificateId, session, err = hm.getRSACertificateIdAndSessionForKeyID(r, fieldValues.KeyID)
   106  		if err != nil {
   107  			logger.WithError(err).WithFields(logrus.Fields{
   108  				"keyID": fieldValues.KeyID,
   109  			}).Error("Failed to fetch session/public key")
   110  			return hm.authorizationError(r)
   111  		}
   112  
   113  		publicKey := CertificateManager.ListRawPublicKey(certificateId)
   114  		if publicKey == nil {
   115  			log.Error("Certificate not found")
   116  			return errors.New("Certificate not found"), http.StatusInternalServerError
   117  		}
   118  		var ok bool
   119  		rsaKey, ok = publicKey.(*rsa.PublicKey)
   120  		if !ok {
   121  			log.Error("Certificate doesn't contain RSA Public key")
   122  			return errors.New("Certificate doesn't contain RSA Public key"), http.StatusInternalServerError
   123  		}
   124  	} else {
   125  		// Get a session for the Key ID
   126  		secret, session, err = hm.getSecretAndSessionForKeyID(r, fieldValues.KeyID)
   127  		if err != nil {
   128  			logger.WithError(err).WithFields(logrus.Fields{
   129  				"keyID": fieldValues.KeyID,
   130  			}).Error("No HMAC secret for this key")
   131  			return hm.authorizationError(r)
   132  		}
   133  	}
   134  	var matchPass bool
   135  
   136  	if strings.HasPrefix(fieldValues.Algorthm, "rsa") {
   137  		matchPass, err = validateRSAEncodedSignature(signatureString, rsaKey, fieldValues.Algorthm, fieldValues.Signature)
   138  		if err != nil {
   139  			logger.WithError(err).Error("Signature validation failed.")
   140  		}
   141  
   142  		if !matchPass {
   143  			isLower, lowerList := hm.hasLowerCaseEscaped(fieldValues.Signature)
   144  			if isLower {
   145  				logger.Debug("--- Detected lower case encoding! ---")
   146  				upperedSignature := hm.replaceWithUpperCase(fieldValues.Signature, lowerList)
   147  				matchPass, err = validateRSAEncodedSignature(signatureString, rsaKey, fieldValues.Algorthm, upperedSignature)
   148  				if err != nil {
   149  					logger.WithError(err).Error("Signature validation failed.")
   150  				}
   151  			}
   152  		}
   153  
   154  		if !matchPass {
   155  			logger.WithFields(logrus.Fields{
   156  				"got": fieldValues.Signature,
   157  			}).Error("Signature string does not match!")
   158  			return hm.authorizationError(r)
   159  		}
   160  	} else {
   161  		// Create a signed string with the secret
   162  		encodedSignature, err := generateHMACEncodedSignature(signatureString, secret, fieldValues.Algorthm)
   163  		if err != nil {
   164  			logger.WithFields(logrus.Fields{
   165  				"error": err,
   166  			}).Error("Failed to validate signature")
   167  			return hm.authorizationError(r)
   168  		}
   169  
   170  		// Compare
   171  		matchPass = encodedSignature == fieldValues.Signature
   172  
   173  		// Check for lower case encoding (.Net issues, again)
   174  		if !matchPass {
   175  			isLower, lowerList := hm.hasLowerCaseEscaped(fieldValues.Signature)
   176  			if isLower {
   177  				logger.Debug("--- Detected lower case encoding! ---")
   178  				upperedSignature := hm.replaceWithUpperCase(fieldValues.Signature, lowerList)
   179  				if encodedSignature == upperedSignature {
   180  					matchPass = true
   181  					encodedSignature = upperedSignature
   182  				}
   183  			}
   184  		}
   185  
   186  		if !matchPass {
   187  			logger.WithFields(logrus.Fields{
   188  				"expected": encodedSignature,
   189  				"got":      fieldValues.Signature,
   190  			}).Error("Signature string does not match!")
   191  			return hm.authorizationError(r)
   192  		}
   193  	}
   194  
   195  	// Check clock skew
   196  	_, dateVal := getDateHeader(r)
   197  	if !hm.checkClockSkew(dateVal) {
   198  		logger.Error("Clock skew outside of acceptable bounds")
   199  		return hm.authorizationError(r)
   200  	}
   201  
   202  	// Set session state on context, we will need it later
   203  	switch hm.Spec.BaseIdentityProvidedBy {
   204  	case apidef.HMACKey, apidef.UnsetAuth:
   205  		ctxSetSession(r, &session, fieldValues.KeyID, false)
   206  		hm.setContextVars(r, fieldValues.KeyID)
   207  	}
   208  
   209  	// Everything seems in order let the request through
   210  	return nil, http.StatusOK
   211  }
   212  
   213  func stripSignature(token string) string {
   214  	token = strings.TrimPrefix(token, "Signature")
   215  	token = strings.TrimPrefix(token, "signature")
   216  	return strings.TrimSpace(token)
   217  }
   218  
   219  func (hm *HTTPSignatureValidationMiddleware) hasLowerCaseEscaped(signature string) (bool, []string) {
   220  	foundList := hm.lowercasePattern.FindAllString(signature, -1)
   221  	return len(foundList) > 0, foundList
   222  }
   223  
   224  func (hm *HTTPSignatureValidationMiddleware) replaceWithUpperCase(originalSignature string, lowercaseList []string) string {
   225  	newSignature := originalSignature
   226  	for _, lStr := range lowercaseList {
   227  		asUpper := strings.ToUpper(lStr)
   228  		newSignature = strings.Replace(newSignature, lStr, asUpper, -1)
   229  	}
   230  
   231  	return newSignature
   232  }
   233  
   234  func (hm *HTTPSignatureValidationMiddleware) setContextVars(r *http.Request, token string) {
   235  	if !hm.Spec.EnableContextVars {
   236  		return
   237  	}
   238  	// Flatten claims and add to context
   239  	if cnt := ctxGetData(r); cnt != nil {
   240  		// Key data
   241  		cnt["token"] = token
   242  		ctxSetData(r, cnt)
   243  	}
   244  }
   245  
   246  func (hm *HTTPSignatureValidationMiddleware) authorizationError(r *http.Request) (error, int) {
   247  	hm.Logger().Info("Authorization field missing or malformed")
   248  	token, _ := hm.getAuthToken(hm.getAuthType(), r)
   249  	AuthFailed(hm, r, token)
   250  
   251  	return errors.New("Authorization field missing, malformed or invalid"), http.StatusBadRequest
   252  }
   253  
   254  func (hm HTTPSignatureValidationMiddleware) checkClockSkew(dateHeaderValue string) bool {
   255  	// Reference layout for parsing time: "Mon Jan 2 15:04:05 MST 2006"
   256  	refDate := "Mon, 02 Jan 2006 15:04:05 MST"
   257  	// Fall back to a numeric timezone, since some environments don't provide a timezone name code
   258  	refDateNumeric := "Mon, 02 Jan 2006 15:04:05 -07"
   259  
   260  	tim, err := time.Parse(refDate, dateHeaderValue)
   261  	if err != nil {
   262  		tim, err = time.Parse(refDateNumeric, dateHeaderValue)
   263  	}
   264  
   265  	if err != nil {
   266  		hm.Logger().WithError(err).WithField("date_string", tim).Error("Date parsing failed")
   267  		return false
   268  	}
   269  
   270  	inSec := tim.UnixNano()
   271  	now := time.Now().UnixNano()
   272  
   273  	diff := now - inSec
   274  
   275  	in_ms := diff / 1000000
   276  
   277  	if hm.Spec.HmacAllowedClockSkew <= 0 {
   278  		return true
   279  	}
   280  
   281  	if math.Abs(float64(in_ms)) > hm.Spec.HmacAllowedClockSkew {
   282  		hm.Logger().Debug("Difference is: ", math.Abs(float64(in_ms)))
   283  		return false
   284  	}
   285  
   286  	return true
   287  }
   288  
   289  type HMACFieldValues struct {
   290  	KeyID     string
   291  	Algorthm  string
   292  	Headers   []string
   293  	Signature string
   294  }
   295  
   296  func (hm *HTTPSignatureValidationMiddleware) getSecretAndSessionForKeyID(r *http.Request, keyId string) (string, user.SessionState, error) {
   297  	session, keyExists := hm.CheckSessionAndIdentityForValidKey(&keyId, r)
   298  	if !keyExists {
   299  		return "", session, errors.New("Key ID does not exist")
   300  	}
   301  
   302  	if session.HmacSecret == "" || !session.HMACEnabled && !session.EnableHTTPSignatureValidation {
   303  		hm.Logger().Info("API Requires HMAC signature, session missing HMACSecret or HMAC not enabled for key")
   304  
   305  		return "", session, errors.New("This key ID is invalid")
   306  	}
   307  
   308  	return session.HmacSecret, session, nil
   309  }
   310  
   311  func (hm *HTTPSignatureValidationMiddleware) getRSACertificateIdAndSessionForKeyID(r *http.Request, keyId string) (string, user.SessionState, error) {
   312  	session, keyExists := hm.CheckSessionAndIdentityForValidKey(&keyId, r)
   313  	if !keyExists {
   314  		return "", session, errors.New("Key ID does not exist")
   315  	}
   316  
   317  	if session.RSACertificateId == "" || !session.EnableHTTPSignatureValidation {
   318  		hm.Logger().Info("API Requires RSA signature, session missing RSA Certificate Id or RSA not enabled for key")
   319  		return "", session, errors.New("This key ID is invalid")
   320  	}
   321  
   322  	return session.RSACertificateId, session, nil
   323  }
   324  
   325  func getDateHeader(r *http.Request) (string, string) {
   326  	auxHeaderVal := r.Header.Get(altHeaderSpec)
   327  	// Prefer aux if present
   328  	if auxHeaderVal != "" {
   329  		log.WithFields(logrus.Fields{
   330  			"prefix": "hmac",
   331  		}).Warning("Using auxiliary header for this request")
   332  		return strings.ToLower(altHeaderSpec), auxHeaderVal
   333  	}
   334  
   335  	dateHeaderVal := r.Header.Get(dateHeaderSpec)
   336  	if dateHeaderVal != "" {
   337  		log.WithFields(logrus.Fields{
   338  			"prefix": "hmac",
   339  		}).Debug("Got date header")
   340  		return strings.ToLower(dateHeaderSpec), dateHeaderVal
   341  	}
   342  
   343  	return "", ""
   344  }
   345  
   346  // parses v which is a string of key1=value1,,key2=value2 ... format and returns
   347  // a map of key:value pairs.
   348  func loadKeyValues(v string) map[string]string {
   349  	s := &scanner.Scanner{}
   350  	s.Init(strings.NewReader(v))
   351  	m := make(map[string]string)
   352  	// the state of the scanner.
   353  	// 0 - key
   354  	// 1 - value
   355  	var mode int
   356  	var key string
   357  	for {
   358  		tok := s.Scan()
   359  		if tok == scanner.EOF {
   360  			break
   361  		}
   362  		text := s.TokenText()
   363  		switch text {
   364  		case "=":
   365  			mode = 1
   366  			continue
   367  		case ",":
   368  			mode = 0
   369  			continue
   370  		default:
   371  			switch mode {
   372  			case 0:
   373  				key = text
   374  				mode = 1
   375  			case 1:
   376  				m[key] = text
   377  				mode = 0
   378  			}
   379  		}
   380  	}
   381  	return m
   382  }
   383  
   384  func getFieldValues(authHeader string) (*HMACFieldValues, error) {
   385  	set := HMACFieldValues{}
   386  	m := loadKeyValues(authHeader)
   387  	for key, value := range m {
   388  		if len(value) > 0 && value[0] == '"' {
   389  			v, err := strconv.Unquote(m[key])
   390  			if err != nil {
   391  				return nil, err
   392  			}
   393  			value = v
   394  		}
   395  		switch strings.ToLower(key) {
   396  		case "keyid":
   397  			set.KeyID = value
   398  		case "algorithm":
   399  			set.Algorthm = value
   400  		case "headers":
   401  			set.Headers = strings.Split(value, " ")
   402  		case "signature":
   403  			set.Signature = value
   404  		default:
   405  			log.WithFields(logrus.Fields{
   406  				"prefix": "hmac",
   407  				"field":  key,
   408  			}).Warning("Invalid header field found")
   409  			return nil, errors.New("Header key is not valid, not in allowed parameter list")
   410  		}
   411  	}
   412  
   413  	// Date is the absolute minimum header set
   414  	if len(set.Headers) == 0 {
   415  		set.Headers = append(set.Headers, "date")
   416  	}
   417  
   418  	return &set, nil
   419  }
   420  
   421  // "Signature keyId="9876",algorithm="hmac-sha1",headers="x-test x-test-2",signature="queryEscape(base64(sig))"")
   422  func generateHMACSignatureStringFromRequest(r *http.Request, headers []string, path string) (string, error) {
   423  	signatureString := ""
   424  	for i, header := range headers {
   425  		loweredHeader := strings.TrimSpace(strings.ToLower(header))
   426  		if loweredHeader == "(request-target)" {
   427  			requestHeaderField := "(request-target): " + strings.ToLower(r.Method) + " " + path
   428  			signatureString += requestHeaderField
   429  		} else {
   430  			// exception for dates and .Net oddness
   431  			headerVal := r.Header.Get(loweredHeader)
   432  			if loweredHeader == "date" {
   433  				loweredHeader, headerVal = getDateHeader(r)
   434  			}
   435  			headerField := strings.TrimSpace(loweredHeader) + ": " + strings.TrimSpace(headerVal)
   436  			signatureString += headerField
   437  		}
   438  
   439  		if i != len(headers)-1 {
   440  			signatureString += "\n"
   441  		}
   442  	}
   443  	log.Debug("Generated sig string: ", signatureString)
   444  	return signatureString, nil
   445  }
   446  
   447  func generateHMACEncodedSignature(signatureString, secret string, algorithm string) (string, error) {
   448  	if secret == "" {
   449  		return "", errors.New("Hmac secret is empty")
   450  	}
   451  
   452  	key := []byte(secret)
   453  
   454  	var hashFunction func() hash.Hash
   455  
   456  	switch algorithm {
   457  	case "hmac-sha256":
   458  		hashFunction = sha256.New
   459  	case "hmac-sha384":
   460  		hashFunction = sha512.New384
   461  	case "hmac-sha512":
   462  		hashFunction = sha512.New
   463  	default:
   464  		hashFunction = sha1.New
   465  	}
   466  
   467  	h := hmac.New(hashFunction, key)
   468  	h.Write([]byte(signatureString))
   469  	encodedString := base64.StdEncoding.EncodeToString(h.Sum(nil))
   470  	return url.QueryEscape(encodedString), nil
   471  }
   472  
   473  func validateRSAEncodedSignature(signatureString string, publicKey *rsa.PublicKey, algorithm string, signature string) (bool, error) {
   474  	var hashFunction hash.Hash
   475  	var hashType crypto.Hash
   476  
   477  	switch algorithm {
   478  	case "rsa-sha256":
   479  		hashFunction = sha256.New()
   480  		hashType = crypto.SHA256
   481  	default:
   482  		hashFunction = sha256.New()
   483  		hashType = crypto.SHA256
   484  	}
   485  	hashFunction.Write([]byte(signatureString))
   486  	hashed := hashFunction.Sum(nil)
   487  
   488  	decodedSignature, err := base64.StdEncoding.DecodeString(signature)
   489  	if err != nil {
   490  		log.Error("Error while base64 decoding signature:", err)
   491  		return false, err
   492  	}
   493  	err = rsa.VerifyPKCS1v15(publicKey, hashType, hashed, decodedSignature)
   494  	if err != nil {
   495  		log.Error("Signature match failed:", err)
   496  		return false, err
   497  	}
   498  
   499  	return true, nil
   500  }