Skip to content

Commit

Permalink
fix: add hash option to token strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
shaj13 committed Feb 3, 2021
1 parent 3a83a99 commit f738017
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 12 deletions.
47 changes: 47 additions & 0 deletions auth/internal/hasher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package internal

import (
"crypto"
"crypto/hmac"
"encoding/base64"
"hash"
"sync"
)

// Hasher represents a hash generator.
type Hasher interface {
Hash(string) string
}

// PlainTextHasher implements the hasher interface and return input as is without hashing it.
type PlainTextHasher struct{}

// Hash return str as is without hashing it.
func (p PlainTextHasher) Hash(str string) string { return str }

// HMACHasher implements the hasher interface and hash input using HMAC hashing alg.
type HMACHasher struct {
p *sync.Pool
}

// Hash str and return output as base64.
func (hm HMACHasher) Hash(str string) string {
h := hm.p.Get().(hash.Hash)
if _, err := h.Write([]byte(str)); err != nil {
// Write() on hash never fails
panic(err)
}

return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// NewHMACHasher return new hmac hasher instance.
func NewHMACHasher(h crypto.Hash, key []byte) *HMACHasher {
return &HMACHasher{
p: &sync.Pool{
New: func() interface{} {
return hmac.New(h.New, key)
},
},
}
}
22 changes: 16 additions & 6 deletions auth/strategies/token/cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/shaj13/go-guardian/v2/auth"
"github.com/shaj13/go-guardian/v2/auth/internal"
)

// AuthenticateFunc declare function signature to authenticate request using token.
Expand All @@ -23,6 +24,7 @@ func New(fn AuthenticateFunc, c auth.Cache, opts ...auth.Option) auth.Strategy {
cache: c,
typ: Bearer,
parser: AuthorizationParser(string(Bearer)),
h: internal.PlainTextHasher{},
}

for _, opt := range opts {
Expand All @@ -38,6 +40,7 @@ type cachedToken struct {
typ Type
cache auth.Cache
authFunc AuthenticateFunc
h internal.Hasher
}

func (c *cachedToken) Authenticate(ctx context.Context, r *http.Request) (auth.Info, error) {
Expand All @@ -46,7 +49,8 @@ func (c *cachedToken) Authenticate(ctx context.Context, r *http.Request) (auth.I
return nil, err
}

i, ok := c.cache.Load(token)
hash := c.h.Hash(token)
i, ok := c.cache.Load(hash)

// if token not found invoke user authenticate function
if !ok {
Expand All @@ -55,7 +59,7 @@ func (c *cachedToken) Authenticate(ctx context.Context, r *http.Request) (auth.I
if err != nil {
return nil, err
}
c.cache.StoreWithTTL(token, i, time.Until(t))
c.cache.StoreWithTTL(hash, i, time.Until(t))
}

info, ok := i.(auth.Info)
Expand All @@ -72,13 +76,19 @@ func (c *cachedToken) Authenticate(ctx context.Context, r *http.Request) (auth.I
}

func (c *cachedToken) Append(token interface{}, info auth.Info) error {
c.cache.Store(token, info)
return nil
if str, ok := token.(string); ok {
hash := c.h.Hash(str)
c.cache.Store(hash, info)
}
return auth.NewTypeError("strategies/token:", "str", token)
}

func (c *cachedToken) Revoke(token interface{}) error {
c.cache.Delete(token)
return nil
if str, ok := token.(string); ok {
hash := c.h.Hash(str)
c.cache.Delete(hash)
}
return auth.NewTypeError("strategies/token:", "str", token)
}

// NoOpAuthenticate implements AuthenticateFunc, it return nil, time.Time{}, ErrNOOP,
Expand Down
6 changes: 5 additions & 1 deletion auth/strategies/token/cached_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/shaj13/go-guardian/v2/auth"
"github.com/shaj13/go-guardian/v2/auth/internal"
)

func TestNewCahced(t *testing.T) {
Expand Down Expand Up @@ -64,7 +65,10 @@ func TestNewCahced(t *testing.T) {

func TestCahcedTokenAppend(t *testing.T) {
cache := libcache.LRU.New(0)
strategy := &cachedToken{cache: cache}
strategy := &cachedToken{
cache: cache,
h: internal.PlainTextHasher{},
}
info := auth.NewDefaultUser("1", "2", nil, nil)
strategy.Append("test-append", info)
cachedInfo, ok := cache.Load("test-append")
Expand Down
23 changes: 18 additions & 5 deletions auth/strategies/token/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (
"sync"

"github.com/shaj13/go-guardian/v2/auth"
"github.com/shaj13/go-guardian/v2/auth/internal"
)

// Static implements auth.Strategy and define a synchronized map honor all predefined bearer tokens.
type static struct {
mu *sync.Mutex
tokens map[string]auth.Info
h internal.Hasher
ttype Type
verify verify
parser Parser
Expand Down Expand Up @@ -50,16 +52,22 @@ func (s *static) Authenticate(ctx context.Context, r *http.Request) (auth.Info,
func (s *static) Append(token interface{}, info auth.Info) error {
s.mu.Lock()
defer s.mu.Unlock()
s.tokens[token.(string)] = info
return nil
if str, ok := token.(string); ok {
hash := s.h.Hash(str)
s.tokens[hash] = info
}
return auth.NewTypeError("strategies/token:", "str", token)
}

// Revoke delete token from static store.
func (s *static) Revoke(token interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.tokens, token.(string))
return nil
if str, ok := token.(string); ok {
hash := s.h.Hash(str)
delete(s.tokens, hash)
}
return auth.NewTypeError("strategies/token:", "str", token)
}

// NewStaticFromFile returns static auth.Strategy, populated from a CSV file.
Expand Down Expand Up @@ -135,7 +143,8 @@ func NewStaticFromFile(path string, opts ...auth.Option) (auth.Strategy, error)
// NewStatic returns static auth.Strategy, populated from a map.
func NewStatic(tokens map[string]auth.Info, opts ...auth.Option) auth.Strategy {
static := &static{
tokens: tokens,
tokens: make(map[string]auth.Info, len(tokens)),
h: internal.PlainTextHasher{},
verify: func(_ context.Context, _ *http.Request, _ auth.Info, _ string) error {
return nil
},
Expand All @@ -148,5 +157,9 @@ func NewStatic(tokens map[string]auth.Info, opts ...auth.Option) auth.Strategy {
opt.Apply(static)
}

for k, v := range tokens {
_ = static.Append(k, v)
}

return static
}
17 changes: 17 additions & 0 deletions auth/strategies/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ package token

import (
"context"
"crypto"
"errors"
"net/http"

"github.com/shaj13/go-guardian/v2/auth"
"github.com/shaj13/go-guardian/v2/auth/internal"
)

var (
// ErrTokenScopes is returned by token scopes verification when,
// token scopes do not grant access to the requested resource.
ErrTokenScopes = errors.New("strategies/token: The access token scopes do not grant access to the requested resource")

// ErrInvalidToken indicate a hit of an invalid token format.
// And it's returned by Token Parser.
ErrInvalidToken = errors.New("strategies/token: Invalid token")
Expand Down Expand Up @@ -80,3 +83,17 @@ func SetScopes(scopes ...Scope) auth.Option {
}
})
}

// SetHash apply token hashing based on HMAC with h and key,
// To prevent precomputation and length extension attacks,
// and to mitigates hash map DOS attacks via collisions.
func SetHash(h crypto.Hash, key []byte) auth.Option {
return auth.OptionFunc(func(v interface{}) {
switch v := v.(type) {
case *static:
v.h = internal.NewHMACHasher(h, key)
case *cachedToken:
v.h = internal.NewHMACHasher(h, key)
}
})
}
14 changes: 14 additions & 0 deletions auth/strategies/token/token_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package token

import (
"crypto"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -47,3 +48,16 @@ func TestSetScopes(t *testing.T) {
assert.True(t, cached.verify != nil)
assert.True(t, static.verify != nil)
}

func TestSetHash(t *testing.T) {
const token = "token"
cached := new(cachedToken)
static := new(static)
opt := SetHash(crypto.SHA256, []byte("key"))
opt.Apply(cached)
opt.Apply(static)
assert.True(t, cached.h != nil)
assert.NotEqual(t, token, cached.h.Hash(token))
assert.True(t, static.h != nil)
assert.NotEqual(t, token, static.h.Hash(token))
}

0 comments on commit f738017

Please sign in to comment.