diff --git a/cmd/statusd/whispercfg.go b/cmd/statusd/whispercfg.go index 3b16f9e69..2496101fb 100644 --- a/cmd/statusd/whispercfg.go +++ b/cmd/statusd/whispercfg.go @@ -28,7 +28,7 @@ func whisperConfig(nodeConfig *params.NodeConfig) (*params.NodeConfig, error) { return nil, fmt.Errorf("password file: %v", err) } - whisperConfig.Password = string(password) + whisperConfig.MailServerPassword = string(password) } // firebase configuration diff --git a/mailserver/mailserver.go b/mailserver/mailserver.go index 2aff17b48..fbf126cea 100644 --- a/mailserver/mailserver.go +++ b/mailserver/mailserver.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "errors" "fmt" + "sync" "time" @@ -43,8 +44,8 @@ const ( ) var ( - errDirectoryNotProvided = errors.New("data directory not provided") - errPasswordNotProvided = errors.New("password is not specified") + errDirectoryNotProvided = errors.New("data directory not provided") + errDecryptionMethodNotProvided = errors.New("decryption method is not provided") // By default go-ethereum/metrics creates dummy metrics that don't register anything. // Real metrics are collected only if -metrics flag is set requestProcessTimer = metrics.NewRegisteredTimer("mailserver/requestProcessTime", nil) @@ -82,12 +83,14 @@ type dbImpl interface { // WMailServer whisper mailserver. type WMailServer struct { - db dbImpl - w *whisper.Whisper - pow float64 - key []byte - limit *limiter - tick *ticker + db dbImpl + w *whisper.Whisper + pow float64 + filter *whisper.Filter + + muLimiter sync.RWMutex + limiter *limiter + tick *ticker } // DBKey key to be stored on db. @@ -116,24 +119,26 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e return errDirectoryNotProvided } - if len(config.Password) == 0 { - return errPasswordNotProvided + if len(config.MailServerPassword) == 0 && config.MailServerAsymKey == nil { + return errDecryptionMethodNotProvided } - db, err := db.Open(config.DataDir, nil) - if err != nil { - return fmt.Errorf("open DB: %s", err) - } - - s.db = db s.w = shh s.pow = config.MinimumPoW - if err := s.setupWhisperIdentity(config); err != nil { + if err := s.setupRequestMessageDecryptor(config); err != nil { return err } s.setupLimiter(time.Duration(config.MailServerRateLimit) * time.Second) + // Open database in the last step in order not to init with error + // and leave the database open by accident. + database, err := db.Open(config.DataDir, nil) + if err != nil { + return fmt.Errorf("open DB: %s", err) + } + s.db = database + return nil } @@ -141,23 +146,33 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e // limit db cleanup. func (s *WMailServer) setupLimiter(limit time.Duration) { if limit > 0 { - s.limit = newLimiter(limit) + s.limiter = newLimiter(limit) s.setupMailServerCleanup(limit) } } -// setupWhisperIdentity setup the whisper identity (symkey) for the current mail -// server. -func (s *WMailServer) setupWhisperIdentity(config *params.WhisperConfig) error { - MailServerKeyID, err := s.w.AddSymKeyFromPassword(config.Password) - if err != nil { - return fmt.Errorf("create symmetric key: %s", err) +// setupRequestMessageDecryptor setup a Whisper filter to decrypt +// incoming Whisper requests. +func (s *WMailServer) setupRequestMessageDecryptor(config *params.WhisperConfig) error { + var filter whisper.Filter + + if config.MailServerPassword != "" { + keyID, err := s.w.AddSymKeyFromPassword(config.MailServerPassword) + if err != nil { + return fmt.Errorf("create symmetric key: %v", err) + } + + symKey, err := s.w.GetSymKey(keyID) + if err != nil { + return fmt.Errorf("save symmetric key: %v", err) + } + + filter = whisper.Filter{KeySym: symKey} + } else if config.MailServerAsymKey != nil { + filter = whisper.Filter{KeyAsym: config.MailServerAsymKey} } - s.key, err = s.w.GetSymKey(MailServerKeyID) - if err != nil { - return fmt.Errorf("save symmetric key: %s", err) - } + s.filter = &filter return nil } @@ -168,7 +183,7 @@ func (s *WMailServer) setupMailServerCleanup(period time.Duration) { if s.tick == nil { s.tick = &ticker{} } - go s.tick.run(period, s.limit.deleteExpired) + go s.tick.run(period, s.limiter.deleteExpired) } // Close the mailserver and its associated db connection. @@ -244,13 +259,16 @@ func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) // exceedsPeerRequests in case limit its been setup on the current server and limit // allows the query, it will store/update new query time for the current peer. func (s *WMailServer) exceedsPeerRequests(peer []byte) bool { - if s.limit != nil { + s.muLimiter.RLock() + defer s.muLimiter.RUnlock() + + if s.limiter != nil { peerID := string(peer) - if !s.limit.isAllowed(peerID) { + if !s.limiter.isAllowed(peerID) { log.Info("peerID exceeded the number of requests per second") return true } - s.limit.add(peerID) + s.limiter.add(peerID) } return false } @@ -345,8 +363,7 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) return false, 0, 0, nil, 0, nil } - f := whisper.Filter{KeySym: s.key} - decrypted := request.Open(&f) + decrypted := request.Open(s.filter) if decrypted == nil { log.Warn("Failed to decrypt p2p request") return false, 0, 0, nil, 0, nil diff --git a/mailserver/mailserver_test.go b/mailserver/mailserver_test.go index c41798d51..248964501 100644 --- a/mailserver/mailserver_test.go +++ b/mailserver/mailserver_test.go @@ -23,6 +23,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "testing" "time" @@ -48,8 +49,6 @@ type ServerTestParams struct { key *ecdsa.PrivateKey } -const dataDirPrefix = "whisper-server-test" - func TestMailserverSuite(t *testing.T) { suite.Run(t, new(MailserverSuite)) } @@ -67,86 +66,159 @@ func (s *MailserverSuite) SetupTest() { s.shh = whisper.New(&whisper.DefaultConfig) s.shh.RegisterServer(s.server) - var err error - s.dataDir, err = ioutil.TempDir("/tmp", dataDirPrefix) - if err != nil { - s.FailNow("failed creating tmp data dir", err) - } + tmpDir, err := ioutil.TempDir("", "mailserver-test") + s.Require().NoError(err) + s.dataDir = tmpDir + + // required files to validate mail server decryption method + asymKeyFile := filepath.Join(tmpDir, "asymkey") + passwordFile := filepath.Join(tmpDir, "password") + privateKey, err := crypto.GenerateKey() + s.Require().NoError(err) + err = crypto.SaveECDSA(asymKeyFile, privateKey) + s.Require().NoError(err) + err = ioutil.WriteFile(passwordFile, []byte("testpassword"), os.ModePerm) + s.Require().NoError(err) s.config = ¶ms.WhisperConfig{ - DataDir: s.dataDir, - Password: "pwd", - MailServerRateLimit: 5, + DataDir: tmpDir, + MailServerAsymKeyFile: asymKeyFile, + MailServerPasswordFile: passwordFile, } } func (s *MailserverSuite) TearDownTest() { - if s.dataDir != "" { - if err := os.RemoveAll(s.dataDir); err != nil { - fmt.Printf("couldn't remove temporary data dir %s: %v", s.dataDir, err) - } - } - + s.Require().NoError(os.RemoveAll(s.config.DataDir)) } func (s *MailserverSuite) TestInit() { + asymKey, err := crypto.GenerateKey() + s.Require().NoError(err) + testCases := []struct { config params.WhisperConfig expectedError error - limiterActive bool info string }{ { config: params.WhisperConfig{DataDir: ""}, expectedError: errDirectoryNotProvided, - limiterActive: false, - info: "Initializing a mail server with a config with empty DataDir", - }, - { - config: params.WhisperConfig{DataDir: s.dataDir, Password: ""}, - expectedError: errPasswordNotProvided, - limiterActive: false, - info: "Initializing a mail server with a config with an empty password", - }, - { - config: params.WhisperConfig{DataDir: "/invalid-path", Password: "pwd"}, - expectedError: errors.New("open DB: mkdir /invalid-path: permission denied"), - limiterActive: false, - info: "Initializing a mail server with a config with an unexisting DataDir", - }, - { - config: *s.config, - expectedError: nil, - limiterActive: true, - info: "Initializing a mail server with a config with correct config and active limiter", + info: "config with empty DataDir", }, { config: params.WhisperConfig{ - DataDir: s.dataDir, - Password: "pwd", + DataDir: "/invalid-path", + MailServerPassword: "pwd", + }, + expectedError: errors.New("open DB: mkdir /invalid-path: permission denied"), + info: "config with an unexisting DataDir", + }, + { + config: params.WhisperConfig{ + DataDir: s.config.DataDir, + MailServerPassword: "", + MailServerAsymKey: nil, + }, + expectedError: errDecryptionMethodNotProvided, + info: "config with an empty password and empty asym key", + }, + { + config: params.WhisperConfig{ + DataDir: s.config.DataDir, + MailServerPassword: "pwd", }, expectedError: nil, - limiterActive: false, - info: "Initializing a mail server with a config with empty DataDir and inactive limiter", + info: "config with correct DataDir and Password", + }, + { + config: params.WhisperConfig{ + DataDir: s.config.DataDir, + MailServerAsymKey: asymKey, + }, + expectedError: nil, + info: "config with correct DataDir and AsymKey", + }, + { + config: params.WhisperConfig{ + DataDir: s.config.DataDir, + MailServerAsymKey: asymKey, + MailServerPassword: "pwd", + }, + expectedError: nil, + info: "config with both asym key and password", + }, + { + config: params.WhisperConfig{ + DataDir: s.config.DataDir, + MailServerPassword: "pwd", + MailServerRateLimit: 5, + }, + expectedError: nil, + info: "config with rate limit", }, } for _, tc := range testCases { s.T().Run(tc.info, func(*testing.T) { - s.server.limit = nil - err := s.server.Init(s.shh, &tc.config) - s.server.tick = nil - s.server.Close() + mailServer := &WMailServer{} + shh := whisper.New(&whisper.DefaultConfig) + shh.RegisterServer(mailServer) + + err := mailServer.Init(shh, &tc.config) s.Equal(tc.expectedError, err) - s.Equal(tc.limiterActive, (s.server.limit != nil)) + defer mailServer.Close() + + // db should be open only if there was no error + if tc.expectedError == nil { + s.NotNil(mailServer.db) + } else { + s.Nil(mailServer.db) + } + + if tc.config.MailServerRateLimit > 0 { + s.NotNil(mailServer.limiter) + } }) } } +func (s *MailserverSuite) TestSetupRequestMessageDecryptor() { + // without configured Password and AsymKey + config := *s.config + s.Error(errDecryptionMethodNotProvided, s.server.Init(s.shh, &config)) + + // Password should work ok + config = *s.config + s.NoError(config.ReadMailServerPasswordFile()) + s.NoError(s.server.Init(s.shh, &config)) + s.NotNil(s.server.filter.KeySym) + s.Nil(s.server.filter.KeyAsym) + s.server.Close() + + // AsymKey can also be used + config = *s.config + s.NoError(config.ReadMailServerAsymKeyFile()) + s.NoError(s.server.Init(s.shh, &config)) + s.Nil(s.server.filter.KeySym) // important: symmetric key should be nil + s.Equal(config.MailServerAsymKey, s.server.filter.KeyAsym) + s.server.Close() + + // when both Password and AsymKey are set, Password has a preference + config = *s.config + s.NoError(config.ReadMailServerPasswordFile()) + s.NoError(config.ReadMailServerAsymKeyFile()) + s.NoError(s.server.Init(s.shh, &config)) + s.NotNil(s.server.filter.KeySym) + s.Nil(s.server.filter.KeyAsym) + s.server.Close() +} + func (s *MailserverSuite) TestArchive() { - err := s.server.Init(s.shh, s.config) - s.server.tick = nil - s.NoError(err) + err := s.config.ReadMailServerPasswordFile() + s.Require().NoError(err) + + err = s.server.Init(s.shh, s.config) + s.Require().NoError(err) defer s.server.Close() env, err := generateEnvelope(time.Now()) @@ -163,15 +235,15 @@ func (s *MailserverSuite) TestArchive() { } func (s *MailserverSuite) TestManageLimits() { - s.server.limit = newLimiter(time.Duration(5) * time.Millisecond) + s.server.limiter = newLimiter(time.Duration(5) * time.Millisecond) s.False(s.server.exceedsPeerRequests([]byte("peerID"))) - s.Equal(1, len(s.server.limit.db)) - firstSaved := s.server.limit.db["peerID"] + s.Equal(1, len(s.server.limiter.db)) + firstSaved := s.server.limiter.db["peerID"] // second call when limit is not accomplished does not store a new limit s.True(s.server.exceedsPeerRequests([]byte("peerID"))) - s.Equal(1, len(s.server.limit.db)) - s.Equal(firstSaved, s.server.limit.db["peerID"]) + s.Equal(1, len(s.server.limiter.db)) + s.Equal(firstSaved, s.server.limiter.db["peerID"]) } func (s *MailserverSuite) TestDBKey() { @@ -392,7 +464,11 @@ func (s *MailserverSuite) setupServer(server *WMailServer) { s.shh = whisper.New(&whisper.DefaultConfig) s.shh.RegisterServer(server) - err := server.Init(s.shh, ¶ms.WhisperConfig{DataDir: s.dataDir, Password: password, MinimumPoW: powRequirement}) + err := server.Init(s.shh, ¶ms.WhisperConfig{ + DataDir: s.dataDir, + MailServerPassword: password, + MinimumPoW: powRequirement, + }) if err != nil { s.T().Fatal(err) } diff --git a/mailserver/ticker.go b/mailserver/ticker.go index babf68ca9..4a39d3d53 100644 --- a/mailserver/ticker.go +++ b/mailserver/ticker.go @@ -1,8 +1,12 @@ package mailserver -import "time" +import ( + "sync" + "time" +) type ticker struct { + mu sync.RWMutex timeTicker *time.Ticker } @@ -11,14 +15,19 @@ func (t *ticker) run(period time.Duration, fn func()) { return } - t.timeTicker = time.NewTicker(period) + tt := time.NewTicker(period) + t.mu.Lock() + t.timeTicker = tt + t.mu.Unlock() go func() { - for range t.timeTicker.C { + for range tt.C { fn() } }() } func (t *ticker) stop() { + t.mu.RLock() t.timeTicker.Stop() + t.mu.RUnlock() } diff --git a/node/node.go b/node/node.go index f8d692d92..907cd20e3 100644 --- a/node/node.go +++ b/node/node.go @@ -189,6 +189,28 @@ func activateStatusService(stack *node.Node, config *params.NodeConfig) error { }) } +func registerMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) { + // if the Password is already set, do not override it + if config.MailServerPassword == "" && config.MailServerPasswordFile != "" { + err = config.ReadMailServerPasswordFile() + if err != nil { + return + } + } + // similarly, do not override already configured AsymKey + if config.MailServerAsymKey == nil && config.MailServerAsymKeyFile != "" { + err = config.ReadMailServerAsymKeyFile() + if err != nil { + return + } + } + + var mailServer mailserver.WMailServer + whisperService.RegisterServer(&mailServer) + + return mailServer.Init(whisperService, config) +} + // activateShhService configures Whisper and adds it to the given node. func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb.DB) (err error) { if config.WhisperConfig == nil || !config.WhisperConfig.Enabled { @@ -223,19 +245,8 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb // enable mail service if config.WhisperConfig.EnableMailServer { - if config.WhisperConfig.Password == "" { - if err := config.WhisperConfig.ReadPasswordFile(); err != nil { - return nil, err - } - } - - logger.Info("Register MailServer") - - var mailServer mailserver.WMailServer - whisperService.RegisterServer(&mailServer) - err := mailServer.Init(whisperService, config.WhisperConfig) - if err != nil { - return nil, err + if err := registerMailServer(whisperService, config.WhisperConfig); err != nil { + return nil, fmt.Errorf("failed to register MailServer: %v", err) } } diff --git a/params/config.go b/params/config.go index 9c3b1570d..83a9224c3 100644 --- a/params/config.go +++ b/params/config.go @@ -2,6 +2,7 @@ package params import ( "bytes" + "crypto/ecdsa" "encoding/json" "errors" "fmt" @@ -11,8 +12,10 @@ import ( "strings" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/status-im/status-go/static" ) @@ -85,13 +88,6 @@ type WhisperConfig struct { // Enabled flag specifies whether protocol is enabled Enabled bool - // PasswordFile contains a password for symmetric encryption with MailServer. - PasswordFile string - - // Password for symmetric encryption with MailServer. - // (if no account file selected, then this password is used for symmetric encryption). - Password string - // LightClient should be true if the node should start with an empty bloom filter and not forward messages from other nodes LightClient bool @@ -105,6 +101,19 @@ type WhisperConfig struct { // MinimumPoW minimum PoW for Whisper messages MinimumPoW float64 + // MailServerPasswordFile contains a password for symmetric encryption with MailServer. + MailServerPasswordFile string + + // MailServerPassword for symmetric encryption with MailServer. + // (if no account file selected, then this password is used for symmetric encryption). + MailServerPassword string + + // MailServerAsymKeyFile is a file with an asymmetric key to decrypt messages sent to MailServer. + MailServerAsymKeyFile string + + // MailServerAsymKey is an asymmetric key to decrypt messages sent to MailServer. + MailServerAsymKey *ecdsa.PrivateKey + // RateLimit minimum time between queries to mail server per peer MailServerRateLimit int @@ -121,13 +130,13 @@ type WhisperConfig struct { EnableNTPSync bool } -// ReadPasswordFile reads and returns content of the password file -func (c *WhisperConfig) ReadPasswordFile() error { - if len(c.PasswordFile) == 0 { +// ReadMailServerPasswordFile reads and returns content of the password file +func (c *WhisperConfig) ReadMailServerPasswordFile() error { + if len(c.MailServerPasswordFile) == 0 { return ErrNoPasswordFileValueSet } - password, err := ioutil.ReadFile(c.PasswordFile) + password, err := ioutil.ReadFile(c.MailServerPasswordFile) if err != nil { return err } @@ -137,11 +146,17 @@ func (c *WhisperConfig) ReadPasswordFile() error { return ErrEmptyPasswordFile } - c.Password = string(password) + c.MailServerPassword = string(password) return nil } +// ReadMailServerAsymKeyFile reads and returns a private key from a given file. +func (c *WhisperConfig) ReadMailServerAsymKeyFile() (err error) { + c.MailServerAsymKey, err = crypto.LoadECDSA(c.MailServerAsymKeyFile) + return +} + // String dumps config object as nicely indented JSON func (c *WhisperConfig) String() string { data, _ := json.MarshalIndent(c, "", " ") // nolint: gas diff --git a/services/shhext/api.go b/services/shhext/api.go index 2616cd7a6..f95c026a3 100644 --- a/services/shhext/api.go +++ b/services/shhext/api.go @@ -28,6 +28,9 @@ var ( ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value") // ErrInvalidSymKeyID is returned when it fails to get a symmetric key. ErrInvalidSymKeyID = errors.New("invalid symKeyID value") + // ErrInvalidPublicKey is returned when public key can't be extracted + // from MailServer's nodeID. + ErrInvalidPublicKey = errors.New("can't extract public key") ) // ----- @@ -59,6 +62,9 @@ type MessagesRequest struct { // SymKeyID is an ID of a symmetric key to authenticate to MailServer. // It's derived from MailServer password. + // + // It's also possible to authenticate request with MailServerPeer + // public key. SymKeyID string `json:"symKeyID"` // Timeout is the time to live of the request specified in seconds. @@ -128,17 +134,38 @@ func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (hex return nil, fmt.Errorf("Query range is invalid: from > to (%d > %d)", r.From, r.To) } + var err error + mailServerNode, err := discover.ParseNode(r.MailServerPeer) if err != nil { return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) } - symKey, err := shh.GetSymKey(r.SymKeyID) - if err != nil { - return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) + var ( + symKey []byte + publicKey *ecdsa.PublicKey + ) + + if r.SymKeyID != "" { + symKey, err = shh.GetSymKey(r.SymKeyID) + if err != nil { + return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) + } + } else { + publicKey, err = mailServerNode.ID.Pubkey() + if err != nil { + return nil, fmt.Errorf("%v: %v", ErrInvalidPublicKey, err) + } } - envelope, err := makeEnvelop(makePayload(r), symKey, api.service.nodeID, shh.MinPow(), now) + envelope, err := makeEnvelop( + makePayload(r), + symKey, + publicKey, + api.service.nodeID, + shh.MinPow(), + now, + ) if err != nil { return nil, err } @@ -175,14 +202,27 @@ func (api *PublicAPI) ConfirmMessagesProcessed(messages []*whisper.Message) erro // makeEnvelop makes an envelop for a historic messages request. // Symmetric key is used to authenticate to MailServer. // PK is the current node ID. -func makeEnvelop(payload []byte, symKey []byte, nodeID *ecdsa.PrivateKey, pow float64, now time.Time) (*whisper.Envelope, error) { +func makeEnvelop( + payload []byte, + symKey []byte, + publicKey *ecdsa.PublicKey, + nodeID *ecdsa.PrivateKey, + pow float64, + now time.Time, +) (*whisper.Envelope, error) { params := whisper.MessageParams{ PoW: pow, Payload: payload, - KeySym: symKey, WorkTime: defaultWorkTime, Src: nodeID, } + // Either symKey or public key is required. + // This condition is verified in `message.Wrap()` method. + if len(symKey) > 0 { + params.KeySym = symKey + } else if publicKey != nil { + params.Dst = publicKey + } message, err := whisper.NewSentMessage(¶ms) if err != nil { return nil, err diff --git a/services/shhext/service_test.go b/services/shhext/service_test.go index cf73e67c9..87165f7a6 100644 --- a/services/shhext/service_test.go +++ b/services/shhext/service_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" + "github.com/status-im/status-go/t/helpers" "github.com/stretchr/testify/suite" ) @@ -141,7 +142,7 @@ func (s *ShhExtSuite) TestWaitMessageExpired() { } } -func (s *ShhExtSuite) TestRequestMessages() { +func (s *ShhExtSuite) TestRequestMessagesErrors() { var err error shh := whisper.New(nil) @@ -152,17 +153,14 @@ func (s *ShhExtSuite) TestRequestMessages() { }, }) // in-memory node as no data dir s.NoError(err) - err = aNode.Register(func(_ *node.ServiceContext) (node.Service, error) { + err = aNode.Register(func(*node.ServiceContext) (node.Service, error) { return shh, nil }) s.NoError(err) err = aNode.Start() s.NoError(err) - defer func() { - err := aNode.Stop() - s.NoError(err) - }() + defer func() { s.NoError(aNode.Stop()) }() mock := newHandlerMock(1) service := New(shh, mock, nil, false) @@ -182,6 +180,7 @@ func (s *ShhExtSuite) TestRequestMessages() { // non-existent symmetric key hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ MailServerPeer: mailServerPeer, + SymKeyID: "invalid-sym-key-id", }) s.Nil(hash) s.EqualError(err, "invalid symKeyID value: non-existent key ID") @@ -193,16 +192,39 @@ func (s *ShhExtSuite) TestRequestMessages() { MailServerPeer: mailServerPeer, SymKeyID: symKeyID, }) - s.Contains(err.Error(), "Could not find peer with ID") s.Nil(hash) + s.Contains(err.Error(), "Could not find peer with ID") // from is greater than to hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ From: 10, To: 5, }) - s.Contains(err.Error(), "Query range is invalid: from > to (10 > 5)") s.Nil(hash) + s.Contains(err.Error(), "Query range is invalid: from > to (10 > 5)") +} + +func (s *ShhExtSuite) TestRequestMessagesSuccess() { + var err error + + shh := whisper.New(nil) + aNode, err := node.New(&node.Config{ + P2P: p2p.Config{ + MaxPeers: math.MaxInt32, + NoDiscovery: true, + }, + }) // in-memory node as no data dir + s.NoError(err) + err = aNode.Register(func(*node.ServiceContext) (node.Service, error) { return shh, nil }) + s.NoError(err) + + err = aNode.Start() + s.NoError(err) + defer func() { err := aNode.Stop(); s.NoError(err) }() + + mock := newHandlerMock(1) + service := New(shh, mock, nil, false) + api := NewPublicAPI(service) // with a peer acting as a mailserver // prepare a node first @@ -214,29 +236,39 @@ func (s *ShhExtSuite) TestRequestMessages() { }, }) // in-memory node as no data dir s.NoError(err) - err = mailNode.Register(func(_ *node.ServiceContext) (node.Service, error) { + err = mailNode.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(nil), nil }) s.NoError(err) err = mailNode.Start() s.NoError(err) - defer func() { - err := mailNode.Stop() - s.NoError(err) - }() + defer func() { s.NoError(mailNode.Stop()) }() // add mailPeer as a peer aNode.Server().AddPeer(mailNode.Server().Self()) - time.Sleep(time.Second) // wait for the peer to be added + waitErr := helpers.WaitForPeerAsync(aNode.Server(), mailNode.Server().Self().String(), p2p.PeerEventTypeAdd, time.Second) + s.NoError(<-waitErr) - // send a request + var hash []byte + + // send a request with a symmetric key + symKeyID, symKeyErr := shh.AddSymKeyFromPassword("some-pass") + s.NoError(symKeyErr) hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ MailServerPeer: mailNode.Server().Self().String(), SymKeyID: symKeyID, }) s.NoError(err) s.NotNil(hash) + s.Contains(api.service.tracker.cache, common.BytesToHash(hash)) + // Send a request without a symmetric key. In this case, + // a public key extracted from MailServerPeer will be used. + hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ + MailServerPeer: mailNode.Server().Self().String(), + }) + s.NoError(err) + s.NotNil(hash) s.Contains(api.service.tracker.cache, common.BytesToHash(hash)) } diff --git a/t/e2e/whisper/whisper_mailbox_test.go b/t/e2e/whisper/whisper_mailbox_test.go index adfe64645..d22e7907b 100644 --- a/t/e2e/whisper/whisper_mailbox_test.go +++ b/t/e2e/whisper/whisper_mailbox_test.go @@ -558,7 +558,7 @@ func (s *WhisperMailboxSuite) startMailboxBackend() (*api.StatusBackend, func()) mailboxConfig.WhisperConfig.Enabled = true mailboxConfig.KeyStoreDir = datadir mailboxConfig.WhisperConfig.EnableMailServer = true - mailboxConfig.WhisperConfig.PasswordFile = filepath.Join(RootDir, "/static/keys/wnodepassword") + mailboxConfig.WhisperConfig.MailServerPasswordFile = filepath.Join(RootDir, "/static/keys/wnodepassword") mailboxConfig.WhisperConfig.DataDir = filepath.Join(datadir, "data") mailboxConfig.DataDir = datadir