2023-01-06 12:21:14 +00:00
|
|
|
package pairing
|
2022-06-10 15:32:15 +00:00
|
|
|
|
|
|
|
import (
|
2022-08-07 22:14:33 +00:00
|
|
|
"crypto/ecdsa"
|
2022-08-19 12:45:50 +00:00
|
|
|
"crypto/rand"
|
2023-01-06 12:21:14 +00:00
|
|
|
"encoding/hex"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2022-10-20 15:14:09 +00:00
|
|
|
"regexp"
|
2022-06-10 15:32:15 +00:00
|
|
|
"testing"
|
2023-01-06 12:21:14 +00:00
|
|
|
"time"
|
2022-06-10 15:32:15 +00:00
|
|
|
|
2023-01-06 12:21:14 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2022-06-10 15:32:15 +00:00
|
|
|
"github.com/stretchr/testify/suite"
|
2023-01-06 12:21:14 +00:00
|
|
|
|
|
|
|
"github.com/status-im/status-go/server"
|
2022-06-10 15:32:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestPairingServerSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(PairingServerSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
type PairingServerSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
TestPairingServerComponents
|
|
|
|
}
|
|
|
|
|
2022-10-12 10:00:14 +00:00
|
|
|
func (s *PairingServerSuite) SetupTest() {
|
2022-06-10 15:32:15 +00:00
|
|
|
s.SetupPairingServerComponents(s.T())
|
|
|
|
}
|
|
|
|
|
2022-10-20 15:14:09 +00:00
|
|
|
func (s *PairingServerSuite) TestMultiBackgroundForeground() {
|
|
|
|
err := s.PS.Start()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToForeground()
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToForeground()
|
|
|
|
s.PS.ToForeground()
|
2023-01-13 17:12:46 +00:00
|
|
|
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple
|
2022-10-20 15:14:09 +00:00
|
|
|
}
|
|
|
|
|
2023-02-15 15:50:30 +00:00
|
|
|
func (s *PairingServerSuite) TestMultiTimeout() {
|
|
|
|
s.PS.SetTimeout(20)
|
|
|
|
|
|
|
|
err := s.PS.Start()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToForeground()
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToBackground()
|
|
|
|
s.PS.ToForeground()
|
|
|
|
s.PS.ToForeground()
|
|
|
|
|
|
|
|
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple
|
|
|
|
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToBackground()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToForeground()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToBackground()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToBackground()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToForeground()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
|
|
s.PS.ToForeground()
|
|
|
|
|
|
|
|
// Wait for timeout to expire
|
|
|
|
time.Sleep(40 * time.Millisecond)
|
|
|
|
s.Require().False(s.PS.IsRunning())
|
|
|
|
}
|
|
|
|
|
2022-06-10 15:32:15 +00:00
|
|
|
func (s *PairingServerSuite) TestPairingServer_StartPairing() {
|
2022-07-01 15:37:53 +00:00
|
|
|
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
|
2022-08-07 22:14:33 +00:00
|
|
|
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
2022-07-01 15:37:53 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
s.PS.PayloadManager = pm
|
|
|
|
|
2022-06-10 15:32:15 +00:00
|
|
|
modes := []Mode{
|
|
|
|
Receiving,
|
|
|
|
Sending,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range modes {
|
|
|
|
s.PS.mode = m
|
|
|
|
|
|
|
|
err = s.PS.StartPairing()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
cp, err := s.PS.MakeConnectionParams()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2022-08-07 22:14:33 +00:00
|
|
|
qr := cp.ToString()
|
2022-06-10 15:32:15 +00:00
|
|
|
|
|
|
|
// Client reads QR code and parses the connection string
|
|
|
|
ccp := new(ConnectionParams)
|
|
|
|
err = ccp.FromString(qr)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2023-01-06 12:21:14 +00:00
|
|
|
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
|
|
|
|
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
|
|
|
|
})
|
2022-06-10 15:32:15 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2022-08-07 22:14:33 +00:00
|
|
|
// Compare cert values
|
|
|
|
cert := c.serverCert
|
2023-01-06 12:21:14 +00:00
|
|
|
cl := s.PS.GetCert().Leaf
|
2022-08-07 22:14:33 +00:00
|
|
|
s.Require().Equal(cl.Signature, cert.Signature)
|
|
|
|
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X))
|
|
|
|
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y))
|
|
|
|
s.Require().Equal(cl.Version, cert.Version)
|
|
|
|
s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber))
|
|
|
|
s.Require().Exactly(cl.NotBefore, cert.NotBefore)
|
|
|
|
s.Require().Exactly(cl.NotAfter, cert.NotAfter)
|
|
|
|
s.Require().Exactly(cl.IPAddresses, cert.IPAddresses)
|
2022-08-06 13:26:16 +00:00
|
|
|
|
2022-07-01 15:37:53 +00:00
|
|
|
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
|
2022-08-07 22:14:33 +00:00
|
|
|
c.PayloadManager, err = NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
2022-07-01 15:37:53 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2022-06-10 15:32:15 +00:00
|
|
|
err = c.PairAccount()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2022-06-10 23:03:16 +00:00
|
|
|
switch m {
|
|
|
|
case Receiving:
|
2022-08-19 12:45:50 +00:00
|
|
|
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain, s.PS.Received())
|
|
|
|
s.Require().Equal(s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
|
2022-07-05 05:40:43 +00:00
|
|
|
s.Require().Nil(s.PS.ToSend())
|
|
|
|
s.Require().Nil(c.Received())
|
2022-06-10 23:03:16 +00:00
|
|
|
case Sending:
|
2022-08-19 12:45:50 +00:00
|
|
|
s.Require().Equal(c.Received(), s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain)
|
|
|
|
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
|
2022-07-05 05:40:43 +00:00
|
|
|
s.Require().Nil(c.ToSend())
|
|
|
|
s.Require().Nil(s.PS.Received())
|
2022-06-10 23:03:16 +00:00
|
|
|
}
|
2022-06-10 15:32:15 +00:00
|
|
|
|
2022-06-29 15:21:22 +00:00
|
|
|
// Reset the server's PayloadEncryptionManager
|
2022-07-05 05:40:43 +00:00
|
|
|
s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).ResetPayload()
|
2022-10-12 10:00:14 +00:00
|
|
|
s.PS.ResetPort()
|
2022-06-10 15:32:15 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-19 12:45:50 +00:00
|
|
|
|
2023-01-06 12:21:14 +00:00
|
|
|
func (s *PairingServerSuite) sendingSetup() *Client {
|
2022-08-19 12:45:50 +00:00
|
|
|
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
|
|
|
|
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.PS.PayloadManager = pm
|
|
|
|
s.PS.mode = Sending
|
|
|
|
|
|
|
|
err = s.PS.StartPairing()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
cp, err := s.PS.MakeConnectionParams()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
qr := cp.ToString()
|
|
|
|
|
|
|
|
// Client reads QR code and parses the connection string
|
|
|
|
ccp := new(ConnectionParams)
|
|
|
|
err = ccp.FromString(qr)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2023-01-06 12:21:14 +00:00
|
|
|
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
|
|
|
|
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
|
|
|
|
})
|
2022-08-19 12:45:50 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
|
|
|
|
c.PayloadManager, err = NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware() {
|
|
|
|
c := s.sendingSetup()
|
|
|
|
|
|
|
|
// Attempt to get the private key data, this should fail because there is no challenge
|
|
|
|
err := c.receiveAccountData()
|
|
|
|
s.Require().Error(err)
|
|
|
|
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
|
|
|
|
|
|
|
|
err = c.getChallenge()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
challenge := c.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
|
|
|
|
err = c.getChallenge()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Equal(challenge, c.serverChallenge)
|
|
|
|
|
|
|
|
// receiving account data should now work.
|
|
|
|
err = c.receiveAccountData()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_block() {
|
|
|
|
c := s.sendingSetup()
|
|
|
|
|
|
|
|
// Attempt to get the private key data, this should fail because there is no challenge
|
|
|
|
err := c.receiveAccountData()
|
|
|
|
s.Require().Error(err)
|
|
|
|
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
|
|
|
|
|
|
|
|
// Get the challenge
|
|
|
|
err = c.getChallenge()
|
|
|
|
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)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Attempt again to get the account data, should fail
|
|
|
|
// behind the scenes the server will block the session if the client fails the challenge. There is no forgiveness!
|
|
|
|
err = c.receiveAccountData()
|
|
|
|
s.Require().Error(err)
|
|
|
|
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
|
|
|
|
|
|
|
|
// Get the real challenge
|
|
|
|
err = c.getChallenge()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Attempt to get the account data, should fail because the client is now blocked.
|
|
|
|
err = c.receiveAccountData()
|
|
|
|
s.Require().Error(err)
|
|
|
|
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
|
|
|
|
}
|
2023-01-06 12:21:14 +00:00
|
|
|
|
|
|
|
func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
say, ok := r.URL.Query()["say"]
|
|
|
|
if !ok || len(say) == 0 {
|
|
|
|
say = append(say, "nothing")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := w.Write([]byte("Hello I like to be a tls server. You said: `" + say[0] + "` " + time.Now().String()))
|
|
|
|
if err != nil {
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeThingToSay() (string, error) {
|
|
|
|
b := make([]byte, 32)
|
|
|
|
_, err := rand.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return hex.EncodeToString(b), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() {
|
|
|
|
s.PS.mode = Sending
|
|
|
|
s.PS.SetHandlers(server.HandlerPatternMap{"/hello": testHandler(s.T())})
|
|
|
|
|
|
|
|
err := s.PS.Start()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
// Give time for the sever to be ready, hacky I know, I'll iron this out
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
|
|
|
// Server generates a QR code connection string
|
|
|
|
cp, err := s.PS.MakeConnectionParams()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
qr := cp.ToString()
|
|
|
|
|
|
|
|
// Client reads QR code and parses the connection string
|
|
|
|
ccp := new(ConnectionParams)
|
|
|
|
err = ccp.FromString(qr)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
c, err := NewPairingClient(nil, ccp, &AccountPayloadManagerConfig{
|
|
|
|
PayloadSourceConfig: &PayloadSourceConfig{KeystorePath: ""},
|
|
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
thing, err := makeThingToSay()
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
response, err := c.Get(c.baseAddress.String() + "/hello?say=" + thing)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
|
|
|
content, err := ioutil.ReadAll(response.Body)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Equal("Hello I like to be a tls server. You said: `"+thing+"`", string(content[:109]))
|
|
|
|
}
|