diff options
Diffstat (limited to 'internal/authentication')
| -rw-r--r-- | internal/authentication/cached.go | 106 | ||||
| -rw-r--r-- | internal/authentication/cached_test.go | 35 |
2 files changed, 141 insertions, 0 deletions
diff --git a/internal/authentication/cached.go b/internal/authentication/cached.go new file mode 100644 index 000000000..b84d988bf --- /dev/null +++ b/internal/authentication/cached.go @@ -0,0 +1,106 @@ +package authentication + +import ( + "crypto/hmac" + "crypto/rand" + "fmt" + "hash" + "sync" + "time" +) + +// NewCredentialCacheHMAC creates a new CredentialCacheHMAC with a given hash.Hash func and lifespan. +func NewCredentialCacheHMAC(h func() hash.Hash, lifespan time.Duration) *CredentialCacheHMAC { + secret := make([]byte, h().BlockSize()) + + _, _ = rand.Read(secret) + + return &CredentialCacheHMAC{ + mu: sync.Mutex{}, + hash: hmac.New(h, secret), + lifespan: lifespan, + + values: map[string]CachedCredential{}, + } +} + +// CredentialCacheHMAC implements in-memory credential caching using a HMAC function and effective lifespan. +type CredentialCacheHMAC struct { + mu sync.Mutex + hash hash.Hash + + lifespan time.Duration + + values map[string]CachedCredential +} + +// Valid checks the cache for results for a given username and password in the cache and returns two booleans. The valid +// return value is indicative if the credential cache had an exact match, and the ok return value returns true if a +// current cached value exists within the cache. +func (c *CredentialCacheHMAC) Valid(username, password string) (valid, ok bool) { + c.mu.Lock() + + defer c.mu.Unlock() + + var ( + entry CachedCredential + err error + ) + + if entry, ok = c.values[username]; ok { + if entry.expires.Before(time.Now()) { + delete(c.values, username) + + return false, false + } + } + + var value []byte + + if value, err = c.sum(username, password); err != nil { + return false, false + } + + valid = hmac.Equal(value, entry.value) + + c.hash.Reset() + + return valid, true +} + +func (c *CredentialCacheHMAC) sum(username, password string) (sum []byte, err error) { + defer c.hash.Reset() + + if _, err = c.hash.Write([]byte(password)); err != nil { + return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err) + } + + if _, err = c.hash.Write([]byte(username)); err != nil { + return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err) + } + + return c.hash.Sum(nil), nil +} + +// Put a new credential combination into the cache. +func (c *CredentialCacheHMAC) Put(username, password string) (err error) { + c.mu.Lock() + + defer c.mu.Unlock() + + var value []byte + + if value, err = c.sum(username, password); err != nil { + return err + } + + c.values[username] = CachedCredential{expires: time.Now().Add(c.lifespan), value: value} + + return nil +} + +// CachedCredential is a cached credential which has an expiration and checksum value. +type CachedCredential struct { + expires time.Time + value []byte +} diff --git a/internal/authentication/cached_test.go b/internal/authentication/cached_test.go new file mode 100644 index 000000000..a60fc3b99 --- /dev/null +++ b/internal/authentication/cached_test.go @@ -0,0 +1,35 @@ +package authentication + +import ( + "crypto/sha256" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewCredentialCacheHMAC(t *testing.T) { + cache := NewCredentialCacheHMAC(sha256.New, time.Second*2) + + require.NoError(t, cache.Put("abc", "123")) + + var valid, found bool + + valid, found = cache.Valid("abc", "123") + + assert.True(t, found) + assert.True(t, valid) + + valid, found = cache.Valid("abc", "123") + + assert.True(t, found) + assert.True(t, valid) + + time.Sleep(time.Second * 2) + + valid, found = cache.Valid("abc", "123") + + assert.False(t, found) + assert.False(t, valid) +} |
