github.com/status-im/status-go@v1.1.0/server/pairing/server_pairing_test.go (about) 1 package pairing 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/rand" 6 "encoding/hex" 7 "io/ioutil" 8 "net/http" 9 "regexp" 10 "testing" 11 "time" 12 13 "github.com/google/uuid" 14 "github.com/stretchr/testify/require" 15 "github.com/stretchr/testify/suite" 16 17 "github.com/status-im/status-go/server" 18 ) 19 20 func TestPairingServerSuite(t *testing.T) { 21 suite.Run(t, new(PairingServerSuite)) 22 } 23 24 type PairingServerSuite struct { 25 suite.Suite 26 TestPairingServerComponents 27 } 28 29 func (s *PairingServerSuite) SetupTest() { 30 s.SetupPairingServerComponents(s.T()) 31 } 32 33 func (s *PairingServerSuite) TestMultiBackgroundForeground() { 34 err := s.SS.Start() 35 s.Require().NoError(err) 36 s.SS.ToBackground() 37 s.SS.ToForeground() 38 s.SS.ToBackground() 39 s.SS.ToBackground() 40 s.SS.ToForeground() 41 s.SS.ToForeground() 42 s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.SS.MakeBaseURL().String()) // nolint: gosimple 43 } 44 45 func (s *PairingServerSuite) TestMultiTimeout() { 46 s.SS.SetTimeout(20) 47 48 err := s.SS.Start() 49 s.Require().NoError(err) 50 51 s.SS.ToBackground() 52 s.SS.ToForeground() 53 s.SS.ToBackground() 54 s.SS.ToBackground() 55 s.SS.ToForeground() 56 s.SS.ToForeground() 57 58 s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.SS.MakeBaseURL().String()) // nolint: gosimple 59 60 time.Sleep(7 * time.Millisecond) 61 s.SS.ToBackground() 62 time.Sleep(7 * time.Millisecond) 63 s.SS.ToForeground() 64 time.Sleep(7 * time.Millisecond) 65 s.SS.ToBackground() 66 time.Sleep(7 * time.Millisecond) 67 s.SS.ToBackground() 68 time.Sleep(7 * time.Millisecond) 69 s.SS.ToForeground() 70 time.Sleep(7 * time.Millisecond) 71 s.SS.ToForeground() 72 73 // Wait for timeout to expire 74 time.Sleep(40 * time.Millisecond) 75 s.Require().False(s.SS.IsRunning()) 76 } 77 78 // TestPairingServer_StartPairingSend tests that a Server can send data to a ReceiverClient 79 func (s *PairingServerSuite) TestPairingServer_StartPairingSend() { 80 // Replace PairingServer.accountMounter with a MockPayloadMounter 81 pm := NewMockPayloadMounter(s.EphemeralAES) 82 s.SS.accountMounter = pm 83 84 err := s.SS.startSendingData() 85 s.Require().NoError(err) 86 87 cp, err := s.SS.MakeConnectionParams() 88 s.Require().NoError(err) 89 90 qr := cp.ToString() 91 92 // Client reads QR code and parses the connection string 93 ccp := new(ConnectionParams) 94 err = ccp.FromString(qr) 95 s.Require().NoError(err) 96 97 c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig()) 98 s.Require().NoError(err) 99 100 // Compare cert values 101 cert := c.serverCert 102 cl := s.SS.GetCert().Leaf 103 s.Require().Equal(cl.Signature, cert.Signature) 104 s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X)) 105 s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y)) 106 s.Require().Equal(cl.Version, cert.Version) 107 s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber)) 108 s.Require().Exactly(cl.NotBefore, cert.NotBefore) 109 s.Require().Exactly(cl.NotAfter, cert.NotAfter) 110 s.Require().Exactly(cl.IPAddresses, cert.IPAddresses) 111 112 // Replace ReceivingClient.accountReceiver with a MockPayloadReceiver 113 c.accountReceiver = NewMockPayloadReceiver(s.EphemeralAES) 114 115 err = c.getChallenge() 116 s.Require().NoError(err) 117 err = c.receiveAccountData() 118 s.Require().NoError(err) 119 120 s.Require().Equal(c.accountReceiver.Received(), s.SS.accountMounter.(*MockPayloadMounter).encryptor.payload.plain) 121 s.Require().Equal(c.accountReceiver.(*MockPayloadReceiver).encryptor.payload.encrypted, s.SS.accountMounter.(*MockPayloadMounter).encryptor.payload.encrypted) 122 } 123 124 // TestPairingServer_StartPairingReceive tests that a Server can receive data to a SenderClient 125 func (s *PairingServerSuite) TestPairingServer_StartPairingReceive() { 126 // Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager 127 pm := NewMockPayloadReceiver(s.EphemeralAES) 128 s.RS.accountReceiver = pm 129 130 err := s.RS.startReceivingData() 131 s.Require().NoError(err) 132 133 cp, err := s.RS.MakeConnectionParams() 134 s.Require().NoError(err) 135 136 qr := cp.ToString() 137 138 // Client reads QR code and parses the connection string 139 ccp := new(ConnectionParams) 140 err = ccp.FromString(qr) 141 s.Require().NoError(err) 142 143 c, err := NewSenderClient(nil, ccp, &SenderClientConfig{SenderConfig: &SenderConfig{}, ClientConfig: &ClientConfig{}}) 144 s.Require().NoError(err) 145 146 // Compare cert values 147 cert := c.serverCert 148 cl := s.RS.GetCert().Leaf 149 s.Require().Equal(cl.Signature, cert.Signature) 150 s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X)) 151 s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y)) 152 s.Require().Equal(cl.Version, cert.Version) 153 s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber)) 154 s.Require().Exactly(cl.NotBefore, cert.NotBefore) 155 s.Require().Exactly(cl.NotAfter, cert.NotAfter) 156 s.Require().Exactly(cl.IPAddresses, cert.IPAddresses) 157 158 // Replace SendingClient.accountMounter with a MockPayloadMounter 159 c.accountMounter = NewMockPayloadMounter(s.EphemeralAES) 160 s.Require().NoError(err) 161 162 err = c.sendAccountData() 163 s.Require().NoError(err) 164 165 s.Require().Equal(c.accountMounter.(*MockPayloadMounter).encryptor.payload.plain, s.RS.accountReceiver.Received()) 166 s.Require().Equal(s.RS.accountReceiver.(*MockPayloadReceiver).encryptor.getEncrypted(), c.accountMounter.(*MockPayloadMounter).encryptor.payload.encrypted) 167 } 168 169 func (s *PairingServerSuite) sendingSetup() *ReceiverClient { 170 // Replace PairingServer.PayloadManager with a MockPayloadReceiver 171 pm := NewMockPayloadMounter(s.EphemeralAES) 172 s.SS.accountMounter = pm 173 174 err := s.SS.startSendingData() 175 s.Require().NoError(err) 176 177 cp, err := s.SS.MakeConnectionParams() 178 s.Require().NoError(err) 179 180 qr := cp.ToString() 181 182 // Client reads QR code and parses the connection string 183 ccp := new(ConnectionParams) 184 err = ccp.FromString(qr) 185 s.Require().NoError(err) 186 187 c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig()) 188 s.Require().NoError(err) 189 190 // Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager 191 c.accountReceiver = NewMockPayloadReceiver(s.EphemeralAES) 192 s.Require().NoError(err) 193 194 return c 195 } 196 197 func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware() { 198 c := s.sendingSetup() 199 200 // Attempt to get the private key data, this should fail because there is no challenge 201 err := c.receiveAccountData() 202 s.Require().Error(err) 203 s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error()) 204 205 err = c.getChallenge() 206 s.Require().NoError(err) 207 challenge := c.challengeTaker.serverChallenge 208 209 // This is NOT a mistake! Call c.getChallenge() twice to check that the client gets the same challenge 210 // the server will only generate 1 challenge until the challenge is successfully completed 211 err = c.getChallenge() 212 s.Require().NoError(err) 213 s.Require().Equal(challenge, c.challengeTaker.serverChallenge) 214 215 // receiving account data should now work. 216 err = c.receiveAccountData() 217 s.Require().NoError(err) 218 219 // After a successful challenge the challenge should change 220 err = c.getChallenge() 221 s.Require().NoError(err) 222 s.Require().NotEqual(challenge, c.challengeTaker.serverChallenge) 223 224 // Unlock the MockPayloadMounter to allow the test. Don't do this ordinarily 225 s.SS.accountMounter.(*MockPayloadMounter).encryptor.payload.locked = false 226 227 // receiving account data again using the new challenge 228 err = c.receiveAccountData() 229 s.Require().NoError(err) 230 } 231 232 func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_block() { 233 c := s.sendingSetup() 234 235 // Attempt to get the private key data, this should fail because there is no challenge 236 err := c.receiveAccountData() 237 s.Require().Error(err) 238 s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error()) 239 240 // Get the challenge 241 err = c.getChallenge() 242 s.Require().NoError(err) 243 244 // Simulate encrypting with a dodgy key, write some nonsense to the challenge field 245 c.challengeTaker.serverChallenge = make([]byte, 64) 246 _, err = rand.Read(c.challengeTaker.serverChallenge) 247 s.Require().NoError(err) 248 249 // Attempt again to get the account data, should fail 250 // behind the scenes the server will block the session if the client fails the challenge. There is no forgiveness! 251 err = c.receiveAccountData() 252 s.Require().Error(err) 253 s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error()) 254 255 // Get the real challenge 256 err = c.getChallenge() 257 s.Require().NoError(err) 258 259 // Attempt to get the account data, should fail because the client is now blocked. 260 err = c.receiveAccountData() 261 s.Require().Error(err) 262 s.Require().Equal("[client] status not ok when receiving account data, received '403 Forbidden'", err.Error()) 263 } 264 265 func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) { 266 return func(w http.ResponseWriter, r *http.Request) { 267 say, ok := r.URL.Query()["say"] 268 if !ok || len(say) == 0 { 269 say = append(say, "nothing") 270 } 271 272 _, err := w.Write([]byte("Hello I like to be a tls server. You said: `" + say[0] + "` " + time.Now().String())) 273 if err != nil { 274 require.NoError(t, err) 275 } 276 } 277 } 278 279 func makeThingToSay() (string, error) { 280 b := make([]byte, 32) 281 _, err := rand.Read(b) 282 if err != nil { 283 return "", err 284 } 285 286 return hex.EncodeToString(b), nil 287 } 288 289 func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() { 290 s.SS.SetHandlers(server.HandlerPatternMap{"/hello": testHandler(s.T())}) 291 292 err := s.SS.Start() 293 s.Require().NoError(err) 294 295 // Give time for the sever to be ready, hacky I know, I'll iron this out 296 time.Sleep(100 * time.Millisecond) 297 298 // Server generates a QR code connection string 299 cp, err := s.SS.MakeConnectionParams() 300 s.Require().NoError(err) 301 302 qr := cp.ToString() 303 304 // Client reads QR code and parses the connection string 305 ccp := new(ConnectionParams) 306 err = ccp.FromString(qr) 307 s.Require().NoError(err) 308 309 c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig()) 310 s.Require().NoError(err) 311 312 thing, err := makeThingToSay() 313 s.Require().NoError(err) 314 315 response, err := c.Get(c.baseAddress.String() + "/hello?say=" + thing) 316 s.Require().NoError(err) 317 318 defer response.Body.Close() 319 320 content, err := ioutil.ReadAll(response.Body) 321 s.Require().NoError(err) 322 s.Require().Equal("Hello I like to be a tls server. You said: `"+thing+"`", string(content[:109])) 323 } 324 325 func (s *PairingServerSuite) TestFromStringInstallationID() { 326 pm := NewMockPayloadMounter(s.EphemeralAES) 327 s.SS.accountMounter = pm 328 329 err := s.SS.startSendingData() 330 s.Require().NoError(err) 331 332 // Server generates a QR code connection string 333 cp, err := s.SS.MakeConnectionParams() 334 s.Require().NoError(err) 335 336 installationID := uuid.New().String() 337 cp.installationID = installationID 338 qr := cp.ToString() 339 340 // Client reads QR code and parses the connection string 341 ccp := new(ConnectionParams) 342 err = ccp.FromString(qr) 343 s.Require().NoError(err) 344 345 s.Require().Equal(installationID, ccp.installationID) 346 } 347 348 func (s *PairingServerSuite) TestFromStringForwardCompatibility() { 349 pm := NewMockPayloadMounter(s.EphemeralAES) 350 s.SS.accountMounter = pm 351 352 err := s.SS.startSendingData() 353 s.Require().NoError(err) 354 355 // Server generates a QR code connection string 356 cp, err := s.SS.MakeConnectionParams() 357 s.Require().NoError(err) 358 359 installationID := uuid.New().String() 360 cp.installationID = installationID 361 qr := cp.ToString() 362 363 qr += ":for-gods-sake-this-should-not-break:anything" 364 365 // Client reads QR code and parses the connection string 366 ccp := new(ConnectionParams) 367 err = ccp.FromString(qr) 368 s.Require().NoError(err) 369 s.Require().NotEmpty(ccp.netIPs) 370 }