Skip to content

Commit

Permalink
cache ticket auth signature instead of timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
itswisdomagain committed Aug 9, 2019
1 parent 2d84864 commit a7d0048
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 59 deletions.
28 changes: 14 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,20 @@ var runServiceCommand func(string) error
//
// See loadConfig for details on the configuration load process.
type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
LogDir string `long:"logdir" description:"Directory to log output."`
Listen string `long:"listen" description:"Listen for connections on the specified interface/port (default all interfaces port: 9113, testnet: 19113)"`
TestNet bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
MemProfile string `long:"memprofile" description:"Write mem profile to the specified file"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
APISecret string `long:"apisecret" description:"Secret string used to encrypt API tokens."`
TicketChallengeMaxAge int64 `long:"ticketchallengemaxage" description:"Max age (in seconds) for API v3 ticket authentication timestamps. Max allowed value is 1800 (30 minutes)."`
BaseURL string `long:"baseurl" description:"BaseURL to use when sending links via email"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
LogDir string `long:"logdir" description:"Directory to log output."`
Listen string `long:"listen" description:"Listen for connections on the specified interface/port (default all interfaces port: 9113, testnet: 19113)"`
TestNet bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
MemProfile string `long:"memprofile" description:"Write mem profile to the specified file"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
APISecret string `long:"apisecret" description:"Secret string used to encrypt API tokens."`
TicketChallengeMaxAge int64 `long:"ticketchallengemaxage" description:"Max age (in seconds) for API v3 ticket authentication timestamps. Max allowed value is 1800 (30 minutes)."`
BaseURL string `long:"baseurl" description:"BaseURL to use when sending links via email"`
// todo: can `ColdWalletExtPub` and `PoolFees` be read from stakepoold via rpc?
ColdWalletExtPub string `long:"coldwalletextpub" description:"The extended public key for addresses to which voting service user fees are sent."`
ClosePool bool `long:"closepool" description:"Disable user registration actions (sign-ups and submitting addresses)"`
Expand Down
56 changes: 24 additions & 32 deletions v3api/ticketauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package v3api

import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -32,21 +31,38 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress
return
}

// confirm that the timestamp signature is a valid base64 string
decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature)
// Ensure that this signature had not been used in a previous authentication attempt.
if v3Api.processedTicketChallenges.containsChallenge(timestampSignature) {
log.Warnf("disallowed reuse of ticket auth signature %v", timestampSignature)
return
}

authTimestamp, err := strconv.Atoi(timestamp)
if err != nil {
log.Warnf("invalid API v3 signature %s", timestampSignature)
log.Warnf("invalid ticket auth timestamp value %v", timestamp)
return
}

// todo check if ticket belongs to this vsp
// 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 > v3Api.ticketChallengeMaxAge {
log.Warnf("expired ticket auth timestamp value %v", timestamp)
return
}

// 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)
// confirm that the timestamp signature is a valid base64 string
decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature)
if err != nil {
log.Warnf("invalid ticket auth signature %s", timestampSignature)
return
}

// Mark this timestamp signature as used to prevent subsequent reuse.
challengeExpiresIn := v3Api.ticketChallengeMaxAge - timestampDelta
v3Api.processedTicketChallenges.addChallenge(timestampSignature, challengeExpiresIn)

// todo check if ticket belongs to this vsp

// get user wallet address using ticket hash
// todo: may be better to maintain a memory map of tickets-userWalletAddresses
ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo(ticketHash)
Expand Down Expand Up @@ -96,27 +112,3 @@ func getAuthValueFromParam(paramKeyValue, key string) string {
}
return ""
}

func (v3Api *V3API) validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error {
authTimestamp, err := strconv.Atoi(timestampMessage)
if err != nil {
return fmt.Errorf("invalid timestamp value %v: %v", timestampMessage, err)
}

// 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 timestamp value %v", timestampMessage)
}

// Save this timestamp value as used to prevent subsequent reuse.
challengeExpiresIn := ticketChallengeMaxAge - timestampDelta
v3Api.processedTicketChallenges.addChallenge(timestampMessage, challengeExpiresIn)

return nil
}
18 changes: 9 additions & 9 deletions v3api/ticketchallengescache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import (

type ticketChallengesCache struct {
sync.Mutex
data map[string]int64 // [timestamp]expiry
usedSignatures map[string]int64 // [signature]expiry
}

func newTicketChallengesCache() *ticketChallengesCache {
cache := &ticketChallengesCache{
data: make(map[string]int64, 0),
usedSignatures: make(map[string]int64, 0),
}

go func() {
for now := range time.Tick(time.Second) {
cache.Lock()
for timestampChallenge, challengeExpiry := range cache.data {
for usedSignature, challengeExpiry := range cache.usedSignatures {
if now.Unix() > challengeExpiry {
delete(cache.data, timestampChallenge)
delete(cache.usedSignatures, usedSignature)
}
}
cache.Unlock()
Expand All @@ -30,17 +30,17 @@ func newTicketChallengesCache() *ticketChallengesCache {
return cache
}

func (cache *ticketChallengesCache) addChallenge(timestamp string, expiresIn int64) {
func (cache *ticketChallengesCache) addChallenge(signature string, expiresIn int64) {
cache.Lock()
if _, ok := cache.data[timestamp]; !ok {
cache.data[timestamp] = time.Now().Unix() + expiresIn
if _, ok := cache.usedSignatures[signature]; !ok {
cache.usedSignatures[signature] = time.Now().Unix() + expiresIn
}
cache.Unlock()
}

func (cache *ticketChallengesCache) containsChallenge(timestamp string) (exists bool) {
func (cache *ticketChallengesCache) containsChallenge(signatures string) (exists bool) {
cache.Lock()
_, exists = cache.data[timestamp]
_, exists = cache.usedSignatures[signatures]
cache.Unlock()
return
}
8 changes: 4 additions & 4 deletions v3api/v3api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import (
)

type V3API struct {
stakepooldConnMan *stakepooldclient.StakepooldManager
stakepooldConnMan *stakepooldclient.StakepooldManager

ticketChallengeMaxAge int64
ticketChallengeMaxAge int64
processedTicketChallenges *ticketChallengesCache
}

func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API {
return &V3API{
stakepooldConnMan: stakepooldConnMan,
ticketChallengeMaxAge: ticketChallengeMaxAge,
stakepooldConnMan: stakepooldConnMan,
ticketChallengeMaxAge: ticketChallengeMaxAge,
processedTicketChallenges: newTicketChallengesCache(),
}
}

0 comments on commit a7d0048

Please sign in to comment.