Skip to content

Commit

Permalink
HMAC now works in Tyk, even follows the draft spec
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Buhr committed Jul 25, 2014
1 parent f65db5e commit e9e36cd
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 22 deletions.
40 changes: 18 additions & 22 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,31 +290,27 @@ func createKeyHandler(w http.ResponseWriter, r *http.Request) {
} else {
newKey := authManager.GenerateAuthKey(newSession.OrgID)

// If we have enabled HMAC checking for keys, we need to generate a secret for the client to use
if newSession.HMACEnabled {
newSession.HmacSecret = authManager.GenerateHMACSecret()
}

authManager.UpdateSession(newKey, newSession)
responseObj.Action = "create"
responseObj.Key = newKey
responseObj.Status = "ok"

responseMessage, err = json.Marshal(&responseObj)

if err != nil {
code = 400
log.Error("Couldn't decode body")
log.Error("Marshalling failed")
log.Error(err)
responseMessage = createError("Request malformed")

responseMessage = []byte(E_SYSTEM_ERROR)
code = 500
} else {
authManager.UpdateSession(newKey, newSession)
responseObj.Action = "create"
responseObj.Key = newKey
responseObj.Status = "ok"

responseMessage, err = json.Marshal(&responseObj)

if err != nil {
log.Error("Marshalling failed")
log.Error(err)
responseMessage = []byte(E_SYSTEM_ERROR)
code = 500
} else {
log.WithFields(logrus.Fields{
"key": newKey,
}).Info("Generated new key - success.")
}

log.WithFields(logrus.Fields{
"key": newKey,
}).Info("Generated new key - success.")
}
}

Expand Down
1 change: 1 addition & 0 deletions api_definition_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type APIDefinition struct {
Location string `bson:"location" json:"location"`
Key string `bson:"key" json:"key"`
} `bson:"definition" json:"definition"`
EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
VersionData struct {
NotVersioned bool `bson:"not_versioned" json:"not_versioned"`
Versions map[string]VersionInfo `bson:"versions" json:"versions"`
Expand Down
11 changes: 11 additions & 0 deletions auth_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"
"github.com/nu7hatch/gouuid"
"strings"
"encoding/base64"
)

// AuthorisationHandler is used to validate a session key,
Expand Down Expand Up @@ -91,3 +92,13 @@ func (b AuthorisationManager) GenerateAuthKey(OrgID string) string {

return newAuthKey
}

// GenerateHMACSecret is a utility function for generating new auth keys. Returns the storage key name and the actual key
func (b AuthorisationManager) GenerateHMACSecret() string {
u5, _ := uuid.NewV4()
cleanSting := strings.Replace(u5.String(), "-", "", -1)
newSecret := base64.StdEncoding.EncodeToString([]byte(cleanSting))


return newSecret
}
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ func loadApps(APISpecs []APISpec, Muxer *http.ServeMux) {
} else if spec.APIDefinition.UseBasicAuth {
// Basic Auth
keyCheck = BasicAuthKeyIsValid{tykMiddleware}.New()
} else if spec.EnableSignatureChecking {
// HMAC Auth
keyCheck = HMACMiddleware{tykMiddleware}.New()
} else {
// Auth key
keyCheck = KeyExists{tykMiddleware}.New()
Expand Down
236 changes: 236 additions & 0 deletions middleware_check_HMAC_signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package main

import "net/http"

import (
"github.com/Sirupsen/logrus"
"github.com/gorilla/context"
"strings"
"net/url"
"sort"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
)

// Test key: 53ac07777cbb8c2d530000021a42331a43bd45555d5c923bdb36fc8a

const DateHeaderSpec string = "X-Date"

// HMACMiddleware will check if the request has a signature, and if the request is allowed through
type HMACMiddleware struct {
TykMiddleware
}

func (hm HMACMiddleware) authorizationError(w http.ResponseWriter, r *http.Request) {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": r.RemoteAddr,
}).Info("Authorisation field missing or malformed")

handler := ErrorHandler{hm.TykMiddleware}
handler.HandleError(w, r, "Authorisation field missing, malformed or invalid", 400)
}

// New creates a new HttpHandler for the alice middleware package, key implementation is here https://web-payments.org/specs/ED/http-signatures/2014-02-01/
func (hm HMACMiddleware) New() func(http.Handler) http.Handler {
aliceHandler := func(h http.Handler) http.Handler {
thisHandler := func(w http.ResponseWriter, r *http.Request) {

log.Debug("HMAC middleware activated")

authHeaderValue := r.Header.Get("Authorization")
if authHeaderValue == "" {
hm.authorizationError(w, r)
return
}

log.Debug("Got auth header")

if r.Header.Get(DateHeaderSpec) == "" {
log.Debug("Date missing")
hm.authorizationError(w, r)
return
}

log.Debug("Got date")

// Extract the keyId:
splitTypes := strings.Split(authHeaderValue, " ")
if len(splitTypes) != 2 {
hm.authorizationError(w, r)
return
}

log.Debug("Found two fields")

if strings.ToLower(splitTypes[0]) != "signature" {
hm.authorizationError(w, r)
return
}

log.Debug("Found signature value field")

splitValues := strings.Split(splitTypes[1], ",")
if len(splitValues) != 3 {
log.Debug("Comma length is wrong - got: ", splitValues)
hm.authorizationError(w, r)
return
}

log.Debug("Found 2 commas - getting elements of signature")

// extract the keyId, algorithm and signature
keyId := ""
algorithm := ""
signature := ""
for _, v := range(splitValues) {
splitKeyValuePair := strings.Split(v, "=")
if len(splitKeyValuePair) != 2 {
hm.authorizationError(w, r)
log.Debug("Equals length is wrong - got: ", splitKeyValuePair)
return
}
if strings.ToLower(splitKeyValuePair[0]) == "keyid" {
keyId = strings.Trim(splitKeyValuePair[1], "\"")
}
if strings.ToLower(splitKeyValuePair[0]) == "algorithm" {
algorithm = strings.Trim(splitKeyValuePair[1], "\"")
}
if strings.ToLower(splitKeyValuePair[0]) == "signature" {
signature = strings.Trim(splitKeyValuePair[1], "\"")
}
}

log.Debug("Extracted values... checking validity")

// None may be empty
if keyId == "" || algorithm == "" || signature == "" {
hm.authorizationError(w, r)
return
}

log.Debug("Key is valid: ", keyId)
log.Debug("algo is valid: ", algorithm)
log.Debug("signature isn't empty: ", signature)

// Check if API key valid
keyExists, thisSessionState := authManager.IsKeyAuthorised(keyId)
if !keyExists {
hm.authorizationError(w, r)
return
}

log.Debug("Found key in session store")

// Set session state on context, we will need it later
context.Set(r, SessionData, thisSessionState)
context.Set(r, AuthHeaderValue, keyId)

if thisSessionState.HmacSecret == "" || thisSessionState.HMACEnabled == false {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": r.RemoteAddr,
}).Info("API Requires HMAC signature, session missing HMACSecret or HMAC not enabled for key")

handler := ErrorHandler{hm.TykMiddleware}
handler.HandleError(w, r, "This key is invalid", 400)
return
}

log.Debug("Sessionstate is HMAC enabled")

ourSignature := hm.generateSignatureFromRequest(r, thisSessionState.HmacSecret)
log.Debug("Our Signature: ", ourSignature)

compareTo, err := url.QueryUnescape(signature)

if err != nil {
hm.authorizationError(w, r)
return
}

log.Debug("Request Signature: ", compareTo)
if ourSignature != compareTo {
log.WithFields(logrus.Fields{
"path": r.URL.Path,
"origin": r.RemoteAddr,
}).Info("Request signature is invalid")

handler := ErrorHandler{hm.TykMiddleware}
handler.HandleError(w, r, "Request signature is invalid", 400)
return
}

log.Debug("Signature matches")

// Everything seems in order let the request through
h.ServeHTTP(w, r)
}
return http.HandlerFunc(thisHandler)
}
return aliceHandler
}

func (hm HMACMiddleware) parseFormParams(values url.Values) string {
kvValues := map[string]string{}
keys := []string{}

log.Debug("Parsing header values")

for k, v := range(values) {
log.Debug("Form parser - processing key: ", k)
log.Debug("Form parser - processing value: ", v)
encodedKey := url.QueryEscape(k)
encodedVals := []string{}
for _, raw_value := range(v) {
encodedVals = append(encodedVals, url.QueryEscape(raw_value))
}
joined_vals := strings.Join(encodedVals, "|")
kvPair := encodedKey + "=" + joined_vals
kvValues[k] = kvPair
keys = append(keys, k)
}

// sort the keys in alphabetical order
sort.Strings(keys)
sortedKvs := []string{}

// Put the prepared key value params in order according to above sort
for _, sk := range(keys) {
sortedKvs = append(sortedKvs, kvValues[sk])
}

// Join the kv's up as per spec
prepared_params := strings.Join(sortedKvs, "&")

return prepared_params
}

// Generates our signature - based on: https://web-payments.org/specs/ED/http-signatures/2014-02-01/#page-3 HMAC signing
func (hm HMACMiddleware) generateSignatureFromRequest(r *http.Request, secret string) string {
//method := strings.ToUpper(r.Method)
//base_url := url.QueryEscape(r.URL.RequestURI())

date_header := url.QueryEscape(r.Header.Get(DateHeaderSpec))

// Not using form params for now, just date string
//params := url.QueryEscape(hm.parseFormParams(r.Form))

// Prep the signature string
signatureString := strings.ToLower(DateHeaderSpec) + ":" + date_header

log.Debug("Signature string before encoding: ", signatureString)

// Encode it
key := []byte(secret)
h := hmac.New(sha1.New, key)
h.Write([]byte(signatureString))

encodedString := base64.StdEncoding.EncodeToString(h.Sum(nil))
log.Debug("Encoded signature string: ", encodedString)
log.Debug("URL Encoded: ", url.QueryEscape(encodedString))

// Return as base64
return encodedString
}
2 changes: 2 additions & 0 deletions session_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type SessionState struct {
BasicAuthData struct {
Password string `json:"password"`
} `json:"basic_auth_data"`
HMACEnabled bool `json:"hmac_enabled"`
HmacSecret string `json:"hmac_string"`
}

// SessionLimiter is the rate limiter for the API, use ForwardMessage() to
Expand Down

0 comments on commit e9e36cd

Please sign in to comment.