From 9b4adfe863dd41d79445164d72210ae97c62ff75 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 17 Jan 2018 22:33:58 +0100 Subject: [PATCH] moved sync out of JWT and added new secret reader --- blacklist.go | 49 +++++++++++++++++++++++++++++++++++-------------- jwt.go | 37 ++++++++++++++++++++++--------------- jwt_test.go | 29 +++++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/blacklist.go b/blacklist.go index 2b9204a..1cb43f7 100644 --- a/blacklist.go +++ b/blacklist.go @@ -2,39 +2,60 @@ package jwt +import "sync" + // Blacklist is the blacklisting storage interface type Blacklist interface { Add(string, int64) Remove(string) Check(string) bool - Map() MapBlacklist + Map() BlacklistMap } -// MapBlacklist implements the Blacklist interface -type MapBlacklist map[string]int64 +// BlacklistMap is the blacklist map structure +type BlacklistMap map[string]int64 -// NewMapBlacklist -func NewMapBlacklist() MapBlacklist { - return make(MapBlacklist) +// MemBlacklist implements the Blacklist interface +type MemBlacklist struct { + // protects list + sync.RWMutex + list BlacklistMap +} + +// NewMemBlacklist +func NewMemBlacklist() *MemBlacklist { + return &MemBlacklist{list: make(BlacklistMap)} } // Add adds a new token signature with expiration time to the blacklist -func (mb MapBlacklist) Add(sig string, exp int64) { - mb[sig] = exp +func (mb MemBlacklist) Add(sig string, exp int64) { + mb.Lock() + mb.list[sig] = exp + mb.Unlock() } // Remove deletes a token signature from the blacklist -func (mb MapBlacklist) Remove(sig string) { - delete(mb, sig) +func (mb MemBlacklist) Remove(sig string) { + mb.Lock() + delete(mb.list, sig) + mb.Unlock() } // Check returns true if a token signature is blacklisted and false otherwise -func (mb MapBlacklist) Check(sig string) (ok bool) { - _, ok = mb[sig] +func (mb MemBlacklist) Check(sig string) (ok bool) { + mb.RLock() + _, ok = mb.list[sig] + mb.RUnlock() return } // Map returns the blacklist in the form of a iterable map structure for cleanup -func (mb MapBlacklist) Map() MapBlacklist { - return mb +func (mb MemBlacklist) Map() (list BlacklistMap) { + list = make(BlacklistMap) + mb.RLock() + for k, v := range mb.list { + list[k] = v + } + mb.RUnlock() + return } diff --git a/jwt.go b/jwt.go index 7d61567..2106f13 100644 --- a/jwt.go +++ b/jwt.go @@ -9,8 +9,8 @@ import ( "encoding/base64" "encoding/json" "errors" + "io" "strings" - "sync" "time" ) @@ -19,11 +19,15 @@ const ( Header = "Authorization" // DefaultExpiry is the default token expiration time DefaultExpiry = time.Hour * 12 + // KeySize is the secret key size + KeySize = 64 - typ = "JWT" - keySize = 64 + typ = "JWT" ) +// DefaultSecretReader is the default secret key generator +var DefaultSecretReader = rand.Reader + var ( ErrNoJWT = errors.New("not a json web token") ErrEmptyToken = errors.New("token is empty") @@ -37,6 +41,7 @@ var ( ErrMissingTokenParts = errors.New("missing token parts") ErrEmptySignature = errors.New("token signature is empty") ErrTokenIsNil = errors.New("token is nil") + ErrInvalidKeySize = errors.New("invalid secret key size") ) // JWT represents the JSON Web Token signing and blacklisting infrastructure @@ -44,8 +49,6 @@ type JWT struct { key []byte expiry time.Duration - // protects list - sync.RWMutex blacklist Blacklist done chan struct{} } @@ -54,12 +57,22 @@ type JWT struct { // If the timeout is less or equal to zero the default expiry (12 hours) is used. // If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens. // Call the Stop() method to exit the goroutine. -func New(expiry time.Duration, blacklist Blacklist) *JWT { +func New(expiry time.Duration, blacklist Blacklist, secret io.Reader) (*JWT, error) { if expiry <= 0 { expiry = DefaultExpiry } - key := make([]byte, keySize) - rand.Read(key) + if secret == nil { + secret = DefaultSecretReader + } + secret = io.LimitReader(secret, KeySize) + key := make([]byte, KeySize) + i, err := secret.Read(key) + if err != nil { + return nil, errors.New("secret reader error: " + err.Error()) + } + if i < KeySize { + return nil, ErrInvalidKeySize + } jwt := &JWT{ key: key, expiry: expiry, @@ -69,7 +82,7 @@ func New(expiry time.Duration, blacklist Blacklist) *JWT { jwt.done = make(chan struct{}) go jwt.clean() } - return jwt + return jwt, nil } func (jwt *JWT) sum(token string, h Hash) []byte { @@ -107,8 +120,6 @@ func (jwt *JWT) Invalidate(t *Token) error { if err := jwt.blacklisted(t.Sig()); err != nil { return err } - jwt.Lock() - defer jwt.Unlock() jwt.blacklist.Add(t.Sig(), exp) return nil } @@ -118,8 +129,6 @@ func (jwt *JWT) blacklisted(sig string) error { if sig == "" { return ErrEmptySignature } - jwt.RLock() - defer jwt.RUnlock() if jwt.blacklist.Check(sig) { return ErrBlacklisted } @@ -137,13 +146,11 @@ func (jwt *JWT) clean() { return } now := time.Now().UTC().Unix() - jwt.Lock() for k, v := range jwt.blacklist.Map() { if now > v { jwt.blacklist.Remove(k) } } - jwt.Unlock() } } diff --git a/jwt_test.go b/jwt_test.go index 089df0d..30cfbfe 100644 --- a/jwt_test.go +++ b/jwt_test.go @@ -3,20 +3,24 @@ package jwt import ( + "bytes" "errors" "testing" "time" ) func TestValidate(t *testing.T) { - jwt := New(time.Second, NewMapBlacklist()) + jwt, err := New(time.Second, NewMemBlacklist(), nil) + if err != nil { + t.Error(err) + } token := NewToken(map[string]interface{}{ "sub": "1234567890", "name": "John Doe", "admin": true, "fizz": "buzz", }, nil) - err := jwt.Sign(token) + err = jwt.Sign(token) if err != nil { t.Error(err) } @@ -57,14 +61,17 @@ func TestValidate(t *testing.T) { } func TestNoBlacklist(t *testing.T) { - jwt := New(time.Second, nil) + jwt, err := New(time.Second, nil, nil) + if err != nil { + t.Error(err) + } token := NewToken(map[string]interface{}{ "sub": "1234567890", "name": "John Doe", "admin": true, "fizz": "buzz", }, nil) - err := jwt.Sign(token) + err = jwt.Sign(token) if err != nil { t.Error(err) } @@ -91,3 +98,17 @@ func TestNoBlacklist(t *testing.T) { t.Error(errors.New("blacklisting should be disabled")) } } + +func TestEmptySecretReader(t *testing.T) { + _, err := New(time.Second, nil, new(bytes.Buffer)) + if err == nil { + t.Error(errors.New("error should be secret reader error")) + } +} + +func TestInvalidSecretReader(t *testing.T) { + _, err := New(time.Second, nil, bytes.NewBufferString("123")) + if err != ErrInvalidKeySize { + t.Error(errors.New("error should be invalid key size")) + } +}