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  }