Skip to content

Commit

Permalink
fix: add username hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
shaj13 committed Feb 4, 2021
1 parent 1f53240 commit 324c111
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 19 deletions.
38 changes: 21 additions & 17 deletions auth/strategies/basic/cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,41 @@ import (
"net/http"

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

// ExtensionKey represents a key for the password in info extensions.
// Typically used when basic strategy cache the authentication decisions.
const ExtensionKey = "x-go-guardian-basic-password"

// NewCached return new auth.Strategy.
// The returned strategy, caches the invocation result of authenticate function.
func NewCached(f AuthenticateFunc, cache auth.Cache, opts ...auth.Option) auth.Strategy {
cb := new(cachedBasic)
cb.fn = f
cb.cache = cache
cb.comparator = plainText{}
cb.hasher = internal.PlainTextHasher{}
for _, opt := range opts {
opt.Apply(cb)
}
return New(cb.authenticate, opts...)
}

type cachedBasic struct {
fn AuthenticateFunc
comparator Comparator
cache auth.Cache
hasher internal.Hasher
}

func (c *cachedBasic) authenticate(ctx context.Context, r *http.Request, userName, pass string) (auth.Info, error) { // nolint:lll
hash := c.hasher.Hash(userName)
v, ok := c.cache.Load(userName)

// if info not found invoke user authenticate function
if !ok {
return c.authenticatAndHash(ctx, r, userName, pass)
return c.authenticatAndHash(ctx, r, hash, userName, pass)
}

if _, ok := v.(auth.Info); !ok {
Expand All @@ -33,34 +50,21 @@ func (c *cachedBasic) authenticate(ctx context.Context, r *http.Request, userNam
ext := info.GetExtensions()

if !ext.Has(ExtensionKey) {
return c.authenticatAndHash(ctx, r, userName, pass)
return c.authenticatAndHash(ctx, r, hash, userName, pass)
}

return info, c.comparator.Compare(ext.Get(ExtensionKey), pass)
}

func (c *cachedBasic) authenticatAndHash(ctx context.Context, r *http.Request, userName, pass string) (auth.Info, error) { //nolint:lll
func (c *cachedBasic) authenticatAndHash(ctx context.Context, r *http.Request, hash string, userName, pass string) (auth.Info, error) { //nolint:lll
info, err := c.fn(ctx, r, userName, pass)
if err != nil {
return nil, err
}

hashedPass, _ := c.comparator.Hash(pass)
info.GetExtensions().Set(ExtensionKey, hashedPass)
c.cache.Store(userName, info)
c.cache.Store(hash, info)

return info, nil
}

// NewCached return new auth.Strategy.
// The returned strategy, caches the invocation result of authenticate function.
func NewCached(f AuthenticateFunc, cache auth.Cache, opts ...auth.Option) auth.Strategy {
cb := new(cachedBasic)
cb.fn = f
cb.cache = cache
cb.comparator = plainText{}
for _, opt := range opts {
opt.Apply(cb)
}
return New(cb.authenticate, opts...)
}
6 changes: 6 additions & 0 deletions auth/strategies/basic/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ func ExampleSetHash() {
basic.NewCached(exampleAuthFunc, cache, opt)
}

func ExampleSetUserNameHash() {
opt := basic.SetUserNameHash(crypto.SHA256, []byte("<32 byte key>")) // import _ crypto/sha256
cache := libcache.LRU.New(1)
basic.NewCached(exampleAuthFunc, cache, opt)
}

func exampleAuthFunc(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
// here connect to db or any other service to fetch user and validate it.
if userName == "test" && password == "test" {
Expand Down
20 changes: 18 additions & 2 deletions auth/strategies/basic/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import (
"crypto"

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

// SetHash set the hashing algorithm to hash the user password.
// SetHash apply password hashing using h,
// SetHash only used when caching the auth decision,
// to mitigates brute force attacks.
func SetHash(h crypto.Hash) auth.Option {
b := basicHashing{h}
return SetComparator(b)
}

// SetComparator set password comparator.
// SetComparator set password comparator,
// to be used when caching the auth decision.
func SetComparator(c Comparator) auth.Option {
return auth.OptionFunc(func(v interface{}) {
if v, ok := v.(*cachedBasic); ok {
Expand All @@ -29,3 +33,15 @@ func SetParser(p Parser) auth.Option {
}
})
}

// SetUserNameHash apply username hashing based on HMAC with h and key,
// SetUserNameHash only used when caching the auth decision,
// to prevent precomputation and length extension attacks,
// and to mitigates hash map DOS attacks via collisions.
func SetUserNameHash(h crypto.Hash, key []byte) auth.Option {
return auth.OptionFunc(func(v interface{}) {
if v, ok := v.(*cachedBasic); ok {
v.hasher = internal.NewHMACHasher(h, key)
}
})
}

0 comments on commit 324c111

Please sign in to comment.