Implemented and integrated challenge update after every successful challenge

This commit is contained in:
Samuel Hawksby-Robinson 2023-03-20 18:39:28 +00:00
parent 4019689df1
commit 79d8094dc2
4 changed files with 82 additions and 44 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/rand"
"fmt"
"io/ioutil"
"net/http"
"github.com/btcsuite/btcutil/base58"
@ -11,6 +12,12 @@ import (
"go.uber.org/zap"
)
const (
// Session names
sessionChallenge = "challenge"
sessionBlocked = "blocked"
)
type ChallengeError struct {
Text string
HttpCode int
@ -56,13 +63,50 @@ func NewChallengeGiver(e *PayloadEncryptor, logger *zap.Logger) (*ChallengeGiver
}, nil
}
func (cg *ChallengeGiver) checkChallengeResponse(w http.ResponseWriter, r *http.Request) *ChallengeError {
func (cg *ChallengeGiver) getSession(r *http.Request) (*sessions.Session, *ChallengeError) {
s, err := cg.cookieStore.Get(r, sessionChallenge)
if err != nil {
cg.logger.Error("checkChallengeResponse: cg.cookieStore.Get(r, sessionChallenge)", zap.Error(err), zap.String("sessionChallenge", sessionChallenge))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
}
return s, nil
}
func (cg *ChallengeGiver) generateNewChallenge(s *sessions.Session, w http.ResponseWriter, r *http.Request) ([]byte, *ChallengeError) {
challenge := make([]byte, 64)
_, err := rand.Read(challenge)
if err != nil {
cg.logger.Error("regenerateNewChallenge: _, err = rand.Read(challenge)", zap.Error(err))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
}
s.Values[sessionChallenge] = challenge
err = s.Save(r, w)
if err != nil {
cg.logger.Error("regenerateNewChallenge: err = s.Save(r, w)", zap.Error(err))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
}
return challenge, nil
}
func (cg *ChallengeGiver) block(s *sessions.Session, w http.ResponseWriter, r *http.Request) *ChallengeError {
s.Values[sessionBlocked] = true
err := s.Save(r, w)
if err != nil {
cg.logger.Error("block: err = s.Save(r, w)", zap.Error(err))
return &ChallengeError{"error", http.StatusInternalServerError}
}
return &ChallengeError{"forbidden", http.StatusForbidden}
}
func (cg *ChallengeGiver) checkChallengeResponse(w http.ResponseWriter, r *http.Request) *ChallengeError {
s, ce := cg.getSession(r)
if ce != nil {
return ce
}
blocked, ok := s.Values[sessionBlocked].(bool)
if ok && blocked {
return &ChallengeError{"forbidden", http.StatusForbidden}
@ -89,40 +133,23 @@ func (cg *ChallengeGiver) checkChallengeResponse(w http.ResponseWriter, r *http.
// Only if we have both a challenge in the session store and in the request header
// do we entertain blocking the client. Because then we know someone is trying to be sneaky.
if !bytes.Equal(c, challenge) {
s.Values[sessionBlocked] = true
err = s.Save(r, w)
if err != nil {
cg.logger.Error("checkChallengeResponse: err = s.Save(r, w)", zap.Error(err))
return &ChallengeError{"error", http.StatusInternalServerError}
}
return &ChallengeError{"forbidden", http.StatusForbidden}
return cg.block(s, w, r)
}
return nil
// If every is ok, regenerate the challenge
_, ce = cg.generateNewChallenge(s, w, r)
return ce
}
func (cg *ChallengeGiver) getChallenge(w http.ResponseWriter, r *http.Request) ([]byte, *ChallengeError) {
s, err := cg.cookieStore.Get(r, sessionChallenge)
if err != nil {
cg.logger.Error("getChallenge: hs.cookieStore.Get(r, sessionChallenge)", zap.Error(err))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
s, ce := cg.getSession(r)
if ce != nil {
return nil, ce
}
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
challenge = make([]byte, 64)
_, err = rand.Read(challenge)
if err != nil {
cg.logger.Error("getChallenge: _, err = rand.Read(challenge)", zap.Error(err))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
}
s.Values[sessionChallenge] = challenge
err = s.Save(r, w)
if err != nil {
cg.logger.Error("getChallenge: err = s.Save(r, w)", zap.Error(err))
return nil, &ChallengeError{"error", http.StatusInternalServerError}
}
challenge, ce = cg.generateNewChallenge(s, w, r)
}
return challenge, nil
}
@ -139,8 +166,13 @@ func NewChallengeTaker(e *PayloadEncryptor) *ChallengeTaker {
}
}
func (ct *ChallengeTaker) SetChallenge(challenge []byte) {
func (ct *ChallengeTaker) SetChallenge(resp *http.Response) error {
challenge, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
ct.serverChallenge = challenge
return nil
}
func (ct *ChallengeTaker) DoChallenge(req *http.Request) error {

View File

@ -8,7 +8,6 @@ import (
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
@ -95,12 +94,7 @@ func (c *BaseClient) getChallenge() error {
return fmt.Errorf("[client] status not ok when getting challenge, received '%s'", resp.Status)
}
challenge, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
c.challengeTaker.SetChallenge(challenge)
return nil
return c.challengeTaker.SetChallenge(resp)
}
/*
@ -445,6 +439,7 @@ func StartUpReceivingClient(backend *api.GethStatusBackend, cs, configJSON strin
if err != nil {
return err
}
err = c.getChallenge()
if err != nil {
return err
@ -453,9 +448,19 @@ func StartUpReceivingClient(backend *api.GethStatusBackend, cs, configJSON strin
if err != nil {
return err
}
err = c.getChallenge()
if err != nil {
return err
}
err = c.receiveSyncDeviceData()
if err != nil {
return err
}
err = c.getChallenge()
if err != nil {
return err
}
return c.sendInstallationData()
}

View File

@ -19,10 +19,6 @@ const (
pairingReceiveSyncDevice = pairingBase + "/receiveSyncDevice"
pairingSendInstallation = pairingBase + "/sendInstallation"
pairingReceiveInstallation = pairingBase + "/receiveInstallation"
// Session names
sessionChallenge = "challenge"
sessionBlocked = "blocked"
)
// Account handling

View File

@ -207,17 +207,22 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware(
err = c.getChallenge()
s.Require().NoError(err)
challenge := c.serverChallenge
challenge := c.challengeTaker.serverChallenge
// This is NOT a mistake! Call c.getChallenge() twice to check that the client gets the same challenge
// the server will only generate 1 challenge per session per connection
// the server will only generate 1 challenge until the challenge is successfully completed
err = c.getChallenge()
s.Require().NoError(err)
s.Require().Equal(challenge, c.serverChallenge)
s.Require().Equal(challenge, c.challengeTaker.serverChallenge)
// receiving account data should now work.
err = c.receiveAccountData()
s.Require().NoError(err)
// After a successful challenge the challenge should change
err = c.getChallenge()
s.Require().NoError(err)
s.Require().NotEqual(challenge, c.challengeTaker.serverChallenge)
}
func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_block() {
@ -233,8 +238,8 @@ func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_
s.Require().NoError(err)
// Simulate encrypting with a dodgy key, write some nonsense to the challenge field
c.serverChallenge = make([]byte, 64)
_, err = rand.Read(c.serverChallenge)
c.challengeTaker.serverChallenge = make([]byte, 64)
_, err = rand.Read(c.challengeTaker.serverChallenge)
s.Require().NoError(err)
// Attempt again to get the account data, should fail