Commit fc2ad88c authored by ale's avatar ale

Add file that was missing in previous commit

Plus a couple of lint style fixes.
parent 8b1e42ef
Pipeline #1175 passed with stages
in 1 minute and 11 seconds
......@@ -35,7 +35,7 @@ type fileBackend struct {
}
func loadUsersFile(path string) (map[string]fileBackendUser, error) {
data, err := ioutil.ReadFile(path)
data, err := ioutil.ReadFile(path) // #nosec
if err != nil {
return nil, err
}
......
......@@ -157,12 +157,9 @@ func decodeAppSpecificPassword(enc string) (*AppSpecificPassword, error) {
}, nil
}
func decodeU2FRegistration(enc string) (u2f.Registration, error) {
var reg u2f.Registration
if err := reg.UnmarshalBinary([]byte(enc)); err != nil {
return reg, err
}
return reg, nil
func decodeU2FRegistration(enc string) (reg u2f.Registration, err error) {
err = reg.UnmarshalBinary([]byte(enc))
return
}
func decodeU2FRegistrationList(encRegs []string) []u2f.Registration {
......
package server
import (
"bytes"
"encoding/gob"
"errors"
"sync"
"time"
"github.com/bradfitz/gomemcache/memcache"
"github.com/tstranex/u2f"
)
var (
u2fClientMaxIdleConns = 5
u2fClientTimeout = 500 * time.Millisecond
u2fCacheExpirationSeconds int32 = 300
)
func init() {
gob.Register(&u2f.Challenge{})
}
// A memcache-backed implementation of the short-term U2F challenge
// storage.
//
// Data should only be consistent over a short period of time, and the
// worst case scenario (challenge can't be retrieved) will simply
// cause the user to retry, so we do not need a strong consistency
// strategy: we simply fan out all reads and writes to all memcache
// servers in parallel.
//
type memcacheU2FStorage struct {
caches []*memcache.Client
}
func newMemcacheU2FStorage(servers []string) *memcacheU2FStorage {
var m memcacheU2FStorage
for _, s := range servers {
c := memcache.New(s)
c.Timeout = u2fClientTimeout
c.MaxIdleConns = u2fClientMaxIdleConns
m.caches = append(m.caches, c)
}
return &m
}
func (m *memcacheU2FStorage) SetUserChallenge(user string, chal *u2f.Challenge) error {
data, err := serializeU2FChallenge(chal)
if err != nil {
return err
}
item := &memcache.Item{
Key: u2fChallengeKey(user),
Value: data,
Expiration: u2fCacheExpirationSeconds,
}
// Write to the memcache servers. At least one write must succeed.
ch := make(chan error, len(m.caches))
defer close(ch)
for _, c := range m.caches {
go func(c *memcache.Client) {
ch <- c.Set(item)
}(c)
}
var ok bool
for i := 0; i < len(m.caches); i++ {
if err := <-ch; err == nil {
ok = true
}
}
if !ok {
return errors.New("all memcache servers failed")
}
return nil
}
func (m *memcacheU2FStorage) GetUserChallenge(user string) (*u2f.Challenge, bool) {
// Run all reads in parallel, return the first non-error result.
//
// This would be better if the memcache API took a Context, so
// we could cancel all pending calls as soon as a result is
// received. This way, we keep them running in the background,
// ignore their results, and fire a goroutine to avoid leaking
// the result channel.
ch := make(chan *u2f.Challenge, 1)
var wg sync.WaitGroup
for _, c := range m.caches {
wg.Add(1)
go func(c *memcache.Client) {
defer wg.Done()
item, err := c.Get(u2fChallengeKey(user))
if err != nil {
return
}
chal, _ := deserializeU2FChallenge(item.Value) // nolint
select {
case ch <- chal:
default:
}
}(c)
}
go func() {
wg.Wait()
close(ch)
}()
chal := <-ch
if chal == nil {
return nil, false
}
return chal, true
}
func u2fChallengeKey(user string) string {
return "u2f/" + user
}
func serializeU2FChallenge(chal *u2f.Challenge) ([]byte, error) {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(chal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func deserializeU2FChallenge(data []byte) (*u2f.Challenge, error) {
var chal u2f.Challenge
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&chal); err != nil {
return nil, err
}
return &chal, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment