From 2d84864398293dc50cc9d2434a17ad2ac4d64e8e Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 22:37:30 +0100 Subject: [PATCH] track timestamp usage in ticket auth routine --- v3api/ticketauth.go | 19 +++++++++----- v3api/ticketchallengescache.go | 46 ++++++++++++++++++++++++++++++++++ v3api/v3api.go | 7 +++++- 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 v3api/ticketchallengescache.go diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index cdced0cd..305f6831 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -41,8 +41,8 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress // todo check if ticket belongs to this vsp - // check if timestamp is not yet expired - if err := validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { + // check if timestamp is not yet expired and has not been used previously + if err := v3Api.validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { log.Warnf("ticket auth timestamp failed validation: %v", err) return } @@ -97,19 +97,26 @@ func getAuthValueFromParam(paramKeyValue, key string) string { return "" } -func validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { +func (v3Api *V3API) validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { authTimestamp, err := strconv.Atoi(timestampMessage) if err != nil { - return fmt.Errorf("invalid v3 auth request timestamp %v: %v", timestampMessage, err) + return fmt.Errorf("invalid timestamp value %v: %v", timestampMessage, err) } - // todo ensure that timestamp had not been used in a previous authentication attempt + // Ensure that timestamp had not been used in a previous authentication attempt. + if v3Api.processedTicketChallenges.containsChallenge(timestampMessage) { + return fmt.Errorf("disallowed reuse of timestamp value %v", timestampMessage) + } // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) if timestampDelta < 0 || timestampDelta > ticketChallengeMaxAge { - return fmt.Errorf("expired v3 auth request timestamp %v compared to %v", timestampMessage, time.Now().Unix()) + return fmt.Errorf("expired timestamp value %v", timestampMessage) } + // Save this timestamp value as used to prevent subsequent reuse. + challengeExpiresIn := ticketChallengeMaxAge - timestampDelta + v3Api.processedTicketChallenges.addChallenge(timestampMessage, challengeExpiresIn) + return nil } diff --git a/v3api/ticketchallengescache.go b/v3api/ticketchallengescache.go new file mode 100644 index 00000000..e4c1f3d6 --- /dev/null +++ b/v3api/ticketchallengescache.go @@ -0,0 +1,46 @@ +package v3api + +import ( + "sync" + "time" +) + +type ticketChallengesCache struct { + sync.Mutex + data map[string]int64 // [timestamp]expiry +} + +func newTicketChallengesCache() *ticketChallengesCache { + cache := &ticketChallengesCache{ + data: make(map[string]int64, 0), + } + + go func() { + for now := range time.Tick(time.Second) { + cache.Lock() + for timestampChallenge, challengeExpiry := range cache.data { + if now.Unix() > challengeExpiry { + delete(cache.data, timestampChallenge) + } + } + cache.Unlock() + } + }() + + return cache +} + +func (cache *ticketChallengesCache) addChallenge(timestamp string, expiresIn int64) { + cache.Lock() + if _, ok := cache.data[timestamp]; !ok { + cache.data[timestamp] = time.Now().Unix() + expiresIn + } + cache.Unlock() +} + +func (cache *ticketChallengesCache) containsChallenge(timestamp string) (exists bool) { + cache.Lock() + _, exists = cache.data[timestamp] + cache.Unlock() + return +} diff --git a/v3api/v3api.go b/v3api/v3api.go index 78f343a7..696f1726 100644 --- a/v3api/v3api.go +++ b/v3api/v3api.go @@ -1,15 +1,20 @@ package v3api -import "github.com/decred/dcrstakepool/stakepooldclient" +import ( + "github.com/decred/dcrstakepool/stakepooldclient" +) type V3API struct { stakepooldConnMan *stakepooldclient.StakepooldManager + ticketChallengeMaxAge int64 + processedTicketChallenges *ticketChallengesCache } func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API { return &V3API{ stakepooldConnMan: stakepooldConnMan, ticketChallengeMaxAge: ticketChallengeMaxAge, + processedTicketChallenges: newTicketChallengesCache(), } }