github.com/status-im/status-go@v1.1.0/server/pairing/challenge_management.go (about) 1 package pairing 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 11 "github.com/btcsuite/btcutil/base58" 12 "github.com/gorilla/sessions" 13 "go.uber.org/zap" 14 ) 15 16 const ( 17 // Session names 18 sessionChallenge = "challenge" 19 sessionBlocked = "blocked" 20 ) 21 22 type ChallengeError struct { 23 Text string 24 HTTPCode int 25 } 26 27 func (ce *ChallengeError) Error() string { 28 return fmt.Sprintf("%s : %d", ce.Text, ce.HTTPCode) 29 } 30 31 func makeCookieStore() (*sessions.CookieStore, error) { 32 auth := make([]byte, 64) 33 _, err := rand.Read(auth) 34 if err != nil { 35 return nil, err 36 } 37 38 enc := make([]byte, 32) 39 _, err = rand.Read(enc) 40 if err != nil { 41 return nil, err 42 } 43 44 return sessions.NewCookieStore(auth, enc), nil 45 } 46 47 // ChallengeGiver is responsible for generating challenges and checking challenge responses 48 type ChallengeGiver struct { 49 cookieStore *sessions.CookieStore 50 encryptor *PayloadEncryptor 51 logger *zap.Logger 52 authedIP net.IP 53 } 54 55 func NewChallengeGiver(e *PayloadEncryptor, logger *zap.Logger) (*ChallengeGiver, error) { 56 cs, err := makeCookieStore() 57 if err != nil { 58 return nil, err 59 } 60 61 return &ChallengeGiver{ 62 cookieStore: cs, 63 encryptor: e.Renew(), 64 logger: logger, 65 }, nil 66 } 67 68 func (cg *ChallengeGiver) getIP(r *http.Request) (net.IP, error) { 69 h, _, err := net.SplitHostPort(r.RemoteAddr) 70 if err != nil { 71 cg.logger.Error("getIP: h, _, err := net.SplitHostPort(r.RemoteAddr)", zap.Error(err), zap.String("r.RemoteAddr", r.RemoteAddr)) 72 return nil, &ChallengeError{"error", http.StatusInternalServerError} 73 } 74 return net.ParseIP(h), nil 75 } 76 77 func (cg *ChallengeGiver) registerClientIP(r *http.Request) error { 78 IP, err := cg.getIP(r) 79 if err != nil { 80 return err 81 } 82 cg.authedIP = IP 83 return nil 84 } 85 86 func (cg *ChallengeGiver) validateClientIP(r *http.Request) error { 87 // If we haven't registered yet register the IP 88 if cg.authedIP == nil || len(cg.authedIP) == 0 { 89 err := cg.registerClientIP(r) 90 if err != nil { 91 return err 92 } 93 } 94 95 // Then compare the current req RemoteIP with the authed IP 96 IP, err := cg.getIP(r) 97 if err != nil { 98 return err 99 } 100 101 if !cg.authedIP.Equal(IP) { 102 cg.logger.Error( 103 "request RemoteAddr does not match authedIP: expected '%s', received '%s'", 104 zap.String("expected", cg.authedIP.String()), 105 zap.String("received", IP.String()), 106 ) 107 return &ChallengeError{"forbidden", http.StatusForbidden} 108 } 109 return nil 110 } 111 112 func (cg *ChallengeGiver) getSession(r *http.Request) (*sessions.Session, error) { 113 s, err := cg.cookieStore.Get(r, sessionChallenge) 114 if err != nil { 115 cg.logger.Error("checkChallengeResponse: cg.cookieStore.Get(r, sessionChallenge)", zap.Error(err), zap.String("sessionChallenge", sessionChallenge)) 116 return nil, &ChallengeError{"error", http.StatusInternalServerError} 117 } 118 return s, nil 119 } 120 121 func (cg *ChallengeGiver) generateNewChallenge(s *sessions.Session, w http.ResponseWriter, r *http.Request) ([]byte, error) { 122 challenge := make([]byte, 64) 123 _, err := rand.Read(challenge) 124 if err != nil { 125 cg.logger.Error("regenerateNewChallenge: _, err = rand.Read(challenge)", zap.Error(err)) 126 return nil, &ChallengeError{"error", http.StatusInternalServerError} 127 } 128 129 s.Values[sessionChallenge] = challenge 130 err = s.Save(r, w) 131 if err != nil { 132 cg.logger.Error("regenerateNewChallenge: err = s.Save(r, w)", zap.Error(err)) 133 return nil, &ChallengeError{"error", http.StatusInternalServerError} 134 } 135 136 return challenge, nil 137 } 138 139 func (cg *ChallengeGiver) block(s *sessions.Session, w http.ResponseWriter, r *http.Request) error { 140 s.Values[sessionBlocked] = true 141 err := s.Save(r, w) 142 if err != nil { 143 cg.logger.Error("block: err = s.Save(r, w)", zap.Error(err)) 144 return &ChallengeError{"error", http.StatusInternalServerError} 145 } 146 147 return &ChallengeError{"forbidden", http.StatusForbidden} 148 } 149 150 func (cg *ChallengeGiver) checkChallengeResponse(w http.ResponseWriter, r *http.Request) error { 151 err := cg.validateClientIP(r) 152 if err != nil { 153 return err 154 } 155 156 s, err := cg.getSession(r) 157 if err != nil { 158 return err 159 } 160 161 blocked, ok := s.Values[sessionBlocked].(bool) 162 if ok && blocked { 163 return &ChallengeError{"forbidden", http.StatusForbidden} 164 } 165 166 // If the request header doesn't include a challenge don't punish the client, just throw a 403 167 clientChallengeResp := r.Header.Get(sessionChallenge) 168 if clientChallengeResp == "" { 169 return &ChallengeError{"forbidden", http.StatusForbidden} 170 } 171 172 dcr, err := cg.encryptor.decryptPlain(base58.Decode(clientChallengeResp)) 173 if err != nil { 174 cg.logger.Error("checkChallengeResponse: cg.encryptor.decryptPlain(base58.Decode(clientChallengeResp))", zap.Error(err), zap.String("clientChallengeResp", clientChallengeResp)) 175 return &ChallengeError{"error", http.StatusInternalServerError} 176 } 177 178 // If the challenge is not in the session store don't punish the client, just throw a 403 179 challenge, ok := s.Values[sessionChallenge].([]byte) 180 if !ok { 181 return &ChallengeError{"forbidden", http.StatusForbidden} 182 } 183 184 // Only if we have both a challenge in the session store and in the request header 185 // do we entertain blocking the client. Because then we know someone is trying to be sneaky. 186 if !bytes.Equal(dcr, challenge) { 187 return cg.block(s, w, r) 188 } 189 190 // If every is ok, generate a new challenge for the next req 191 _, err = cg.generateNewChallenge(s, w, r) 192 return err 193 } 194 195 func (cg *ChallengeGiver) getChallenge(w http.ResponseWriter, r *http.Request) ([]byte, error) { 196 err := cg.validateClientIP(r) 197 if err != nil { 198 return nil, err 199 } 200 201 s, err := cg.getSession(r) 202 if err != nil { 203 return nil, err 204 } 205 206 challenge, ok := s.Values[sessionChallenge].([]byte) 207 if !ok { 208 challenge, err = cg.generateNewChallenge(s, w, r) 209 if err != nil { 210 return nil, err 211 } 212 } 213 return challenge, nil 214 } 215 216 // ChallengeTaker is responsible for storing and performing server challenges 217 type ChallengeTaker struct { 218 encryptor *PayloadEncryptor 219 serverChallenge []byte 220 } 221 222 func NewChallengeTaker(e *PayloadEncryptor) *ChallengeTaker { 223 return &ChallengeTaker{ 224 encryptor: e.Renew(), 225 } 226 } 227 228 func (ct *ChallengeTaker) SetChallenge(resp *http.Response) error { 229 challenge, err := ioutil.ReadAll(resp.Body) 230 if err != nil { 231 return err 232 } 233 ct.serverChallenge = challenge 234 return nil 235 } 236 237 func (ct *ChallengeTaker) DoChallenge(req *http.Request) error { 238 if ct.serverChallenge != nil { 239 ec, err := ct.encryptor.encryptPlain(ct.serverChallenge) 240 if err != nil { 241 return err 242 } 243 244 req.Header.Set(sessionChallenge, base58.Encode(ec)) 245 } 246 return nil 247 }