Add asymmetric key support for MailServer requests (#1075)

* add Asymmetric Key support for MailServer requests

* remove deprecated notice

* fix linter

* refactoring Whisper config related to MailServer

* fix race condition
This commit is contained in:
Adam Babik 2018-07-04 11:30:57 +02:00 committed by Adrià Cidre
parent 93210061ad
commit 38a60135b2
9 changed files with 340 additions and 140 deletions

View File

@ -28,7 +28,7 @@ func whisperConfig(nodeConfig *params.NodeConfig) (*params.NodeConfig, error) {
return nil, fmt.Errorf("password file: %v", err) return nil, fmt.Errorf("password file: %v", err)
} }
whisperConfig.Password = string(password) whisperConfig.MailServerPassword = string(password)
} }
// firebase configuration // firebase configuration

View File

@ -20,6 +20,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"sync"
"time" "time"
@ -44,7 +45,7 @@ const (
var ( var (
errDirectoryNotProvided = errors.New("data directory not provided") errDirectoryNotProvided = errors.New("data directory not provided")
errPasswordNotProvided = errors.New("password is not specified") errDecryptionMethodNotProvided = errors.New("decryption method is not provided")
// By default go-ethereum/metrics creates dummy metrics that don't register anything. // By default go-ethereum/metrics creates dummy metrics that don't register anything.
// Real metrics are collected only if -metrics flag is set // Real metrics are collected only if -metrics flag is set
requestProcessTimer = metrics.NewRegisteredTimer("mailserver/requestProcessTime", nil) requestProcessTimer = metrics.NewRegisteredTimer("mailserver/requestProcessTime", nil)
@ -85,8 +86,10 @@ type WMailServer struct {
db dbImpl db dbImpl
w *whisper.Whisper w *whisper.Whisper
pow float64 pow float64
key []byte filter *whisper.Filter
limit *limiter
muLimiter sync.RWMutex
limiter *limiter
tick *ticker tick *ticker
} }
@ -116,24 +119,26 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e
return errDirectoryNotProvided return errDirectoryNotProvided
} }
if len(config.Password) == 0 { if len(config.MailServerPassword) == 0 && config.MailServerAsymKey == nil {
return errPasswordNotProvided 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.w = shh
s.pow = config.MinimumPoW s.pow = config.MinimumPoW
if err := s.setupWhisperIdentity(config); err != nil { if err := s.setupRequestMessageDecryptor(config); err != nil {
return err return err
} }
s.setupLimiter(time.Duration(config.MailServerRateLimit) * time.Second) 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 return nil
} }
@ -141,24 +146,34 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e
// limit db cleanup. // limit db cleanup.
func (s *WMailServer) setupLimiter(limit time.Duration) { func (s *WMailServer) setupLimiter(limit time.Duration) {
if limit > 0 { if limit > 0 {
s.limit = newLimiter(limit) s.limiter = newLimiter(limit)
s.setupMailServerCleanup(limit) s.setupMailServerCleanup(limit)
} }
} }
// setupWhisperIdentity setup the whisper identity (symkey) for the current mail // setupRequestMessageDecryptor setup a Whisper filter to decrypt
// server. // incoming Whisper requests.
func (s *WMailServer) setupWhisperIdentity(config *params.WhisperConfig) error { func (s *WMailServer) setupRequestMessageDecryptor(config *params.WhisperConfig) error {
MailServerKeyID, err := s.w.AddSymKeyFromPassword(config.Password) var filter whisper.Filter
if config.MailServerPassword != "" {
keyID, err := s.w.AddSymKeyFromPassword(config.MailServerPassword)
if err != nil { if err != nil {
return fmt.Errorf("create symmetric key: %s", err) return fmt.Errorf("create symmetric key: %v", err)
} }
s.key, err = s.w.GetSymKey(MailServerKeyID) symKey, err := s.w.GetSymKey(keyID)
if err != nil { if err != nil {
return fmt.Errorf("save symmetric key: %s", err) 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.filter = &filter
return nil return nil
} }
@ -168,7 +183,7 @@ func (s *WMailServer) setupMailServerCleanup(period time.Duration) {
if s.tick == nil { if s.tick == nil {
s.tick = &ticker{} 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. // 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 // 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. // allows the query, it will store/update new query time for the current peer.
func (s *WMailServer) exceedsPeerRequests(peer []byte) bool { 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) peerID := string(peer)
if !s.limit.isAllowed(peerID) { if !s.limiter.isAllowed(peerID) {
log.Info("peerID exceeded the number of requests per second") log.Info("peerID exceeded the number of requests per second")
return true return true
} }
s.limit.add(peerID) s.limiter.add(peerID)
} }
return false return false
} }
@ -345,8 +363,7 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope)
return false, 0, 0, nil, 0, nil return false, 0, 0, nil, 0, nil
} }
f := whisper.Filter{KeySym: s.key} decrypted := request.Open(s.filter)
decrypted := request.Open(&f)
if decrypted == nil { if decrypted == nil {
log.Warn("Failed to decrypt p2p request") log.Warn("Failed to decrypt p2p request")
return false, 0, 0, nil, 0, nil return false, 0, 0, nil, 0, nil

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"time" "time"
@ -48,8 +49,6 @@ type ServerTestParams struct {
key *ecdsa.PrivateKey key *ecdsa.PrivateKey
} }
const dataDirPrefix = "whisper-server-test"
func TestMailserverSuite(t *testing.T) { func TestMailserverSuite(t *testing.T) {
suite.Run(t, new(MailserverSuite)) suite.Run(t, new(MailserverSuite))
} }
@ -67,86 +66,159 @@ func (s *MailserverSuite) SetupTest() {
s.shh = whisper.New(&whisper.DefaultConfig) s.shh = whisper.New(&whisper.DefaultConfig)
s.shh.RegisterServer(s.server) s.shh.RegisterServer(s.server)
var err error tmpDir, err := ioutil.TempDir("", "mailserver-test")
s.dataDir, err = ioutil.TempDir("/tmp", dataDirPrefix) s.Require().NoError(err)
if err != nil { s.dataDir = tmpDir
s.FailNow("failed creating tmp data dir", err)
} // 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 = &params.WhisperConfig{ s.config = &params.WhisperConfig{
DataDir: s.dataDir, DataDir: tmpDir,
Password: "pwd", MailServerAsymKeyFile: asymKeyFile,
MailServerRateLimit: 5, MailServerPasswordFile: passwordFile,
} }
} }
func (s *MailserverSuite) TearDownTest() { func (s *MailserverSuite) TearDownTest() {
if s.dataDir != "" { s.Require().NoError(os.RemoveAll(s.config.DataDir))
if err := os.RemoveAll(s.dataDir); err != nil {
fmt.Printf("couldn't remove temporary data dir %s: %v", s.dataDir, err)
}
}
} }
func (s *MailserverSuite) TestInit() { func (s *MailserverSuite) TestInit() {
asymKey, err := crypto.GenerateKey()
s.Require().NoError(err)
testCases := []struct { testCases := []struct {
config params.WhisperConfig config params.WhisperConfig
expectedError error expectedError error
limiterActive bool
info string info string
}{ }{
{ {
config: params.WhisperConfig{DataDir: ""}, config: params.WhisperConfig{DataDir: ""},
expectedError: errDirectoryNotProvided, expectedError: errDirectoryNotProvided,
limiterActive: false, info: "config with empty DataDir",
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",
}, },
{ {
config: params.WhisperConfig{ config: params.WhisperConfig{
DataDir: s.dataDir, DataDir: "/invalid-path",
Password: "pwd", 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, expectedError: nil,
limiterActive: false, info: "config with correct DataDir and Password",
info: "Initializing a mail server with a config with empty DataDir and inactive limiter", },
{
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 { for _, tc := range testCases {
s.T().Run(tc.info, func(*testing.T) { s.T().Run(tc.info, func(*testing.T) {
s.server.limit = nil mailServer := &WMailServer{}
err := s.server.Init(s.shh, &tc.config) shh := whisper.New(&whisper.DefaultConfig)
s.server.tick = nil shh.RegisterServer(mailServer)
s.server.Close()
err := mailServer.Init(shh, &tc.config)
s.Equal(tc.expectedError, err) 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() { func (s *MailserverSuite) TestArchive() {
err := s.server.Init(s.shh, s.config) err := s.config.ReadMailServerPasswordFile()
s.server.tick = nil s.Require().NoError(err)
s.NoError(err)
err = s.server.Init(s.shh, s.config)
s.Require().NoError(err)
defer s.server.Close() defer s.server.Close()
env, err := generateEnvelope(time.Now()) env, err := generateEnvelope(time.Now())
@ -163,15 +235,15 @@ func (s *MailserverSuite) TestArchive() {
} }
func (s *MailserverSuite) TestManageLimits() { 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.False(s.server.exceedsPeerRequests([]byte("peerID")))
s.Equal(1, len(s.server.limit.db)) s.Equal(1, len(s.server.limiter.db))
firstSaved := s.server.limit.db["peerID"] firstSaved := s.server.limiter.db["peerID"]
// second call when limit is not accomplished does not store a new limit // second call when limit is not accomplished does not store a new limit
s.True(s.server.exceedsPeerRequests([]byte("peerID"))) s.True(s.server.exceedsPeerRequests([]byte("peerID")))
s.Equal(1, len(s.server.limit.db)) s.Equal(1, len(s.server.limiter.db))
s.Equal(firstSaved, s.server.limit.db["peerID"]) s.Equal(firstSaved, s.server.limiter.db["peerID"])
} }
func (s *MailserverSuite) TestDBKey() { func (s *MailserverSuite) TestDBKey() {
@ -392,7 +464,11 @@ func (s *MailserverSuite) setupServer(server *WMailServer) {
s.shh = whisper.New(&whisper.DefaultConfig) s.shh = whisper.New(&whisper.DefaultConfig)
s.shh.RegisterServer(server) s.shh.RegisterServer(server)
err := server.Init(s.shh, &params.WhisperConfig{DataDir: s.dataDir, Password: password, MinimumPoW: powRequirement}) err := server.Init(s.shh, &params.WhisperConfig{
DataDir: s.dataDir,
MailServerPassword: password,
MinimumPoW: powRequirement,
})
if err != nil { if err != nil {
s.T().Fatal(err) s.T().Fatal(err)
} }

View File

@ -1,8 +1,12 @@
package mailserver package mailserver
import "time" import (
"sync"
"time"
)
type ticker struct { type ticker struct {
mu sync.RWMutex
timeTicker *time.Ticker timeTicker *time.Ticker
} }
@ -11,14 +15,19 @@ func (t *ticker) run(period time.Duration, fn func()) {
return return
} }
t.timeTicker = time.NewTicker(period) tt := time.NewTicker(period)
t.mu.Lock()
t.timeTicker = tt
t.mu.Unlock()
go func() { go func() {
for range t.timeTicker.C { for range tt.C {
fn() fn()
} }
}() }()
} }
func (t *ticker) stop() { func (t *ticker) stop() {
t.mu.RLock()
t.timeTicker.Stop() t.timeTicker.Stop()
t.mu.RUnlock()
} }

View File

@ -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. // activateShhService configures Whisper and adds it to the given node.
func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb.DB) (err error) { func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb.DB) (err error) {
if config.WhisperConfig == nil || !config.WhisperConfig.Enabled { if config.WhisperConfig == nil || !config.WhisperConfig.Enabled {
@ -223,19 +245,8 @@ func activateShhService(stack *node.Node, config *params.NodeConfig, db *leveldb
// enable mail service // enable mail service
if config.WhisperConfig.EnableMailServer { if config.WhisperConfig.EnableMailServer {
if config.WhisperConfig.Password == "" { if err := registerMailServer(whisperService, config.WhisperConfig); err != nil {
if err := config.WhisperConfig.ReadPasswordFile(); err != nil { return nil, fmt.Errorf("failed to register MailServer: %v", err)
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
} }
} }

View File

@ -2,6 +2,7 @@ package params
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -11,8 +12,10 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/status-im/status-go/static" "github.com/status-im/status-go/static"
) )
@ -85,13 +88,6 @@ type WhisperConfig struct {
// Enabled flag specifies whether protocol is enabled // Enabled flag specifies whether protocol is enabled
Enabled bool 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 should be true if the node should start with an empty bloom filter and not forward messages from other nodes
LightClient bool LightClient bool
@ -105,6 +101,19 @@ type WhisperConfig struct {
// MinimumPoW minimum PoW for Whisper messages // MinimumPoW minimum PoW for Whisper messages
MinimumPoW float64 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 // RateLimit minimum time between queries to mail server per peer
MailServerRateLimit int MailServerRateLimit int
@ -121,13 +130,13 @@ type WhisperConfig struct {
EnableNTPSync bool EnableNTPSync bool
} }
// ReadPasswordFile reads and returns content of the password file // ReadMailServerPasswordFile reads and returns content of the password file
func (c *WhisperConfig) ReadPasswordFile() error { func (c *WhisperConfig) ReadMailServerPasswordFile() error {
if len(c.PasswordFile) == 0 { if len(c.MailServerPasswordFile) == 0 {
return ErrNoPasswordFileValueSet return ErrNoPasswordFileValueSet
} }
password, err := ioutil.ReadFile(c.PasswordFile) password, err := ioutil.ReadFile(c.MailServerPasswordFile)
if err != nil { if err != nil {
return err return err
} }
@ -137,11 +146,17 @@ func (c *WhisperConfig) ReadPasswordFile() error {
return ErrEmptyPasswordFile return ErrEmptyPasswordFile
} }
c.Password = string(password) c.MailServerPassword = string(password)
return nil 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 // String dumps config object as nicely indented JSON
func (c *WhisperConfig) String() string { func (c *WhisperConfig) String() string {
data, _ := json.MarshalIndent(c, "", " ") // nolint: gas data, _ := json.MarshalIndent(c, "", " ") // nolint: gas

View File

@ -28,6 +28,9 @@ var (
ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value") ErrInvalidMailServerPeer = errors.New("invalid mailServerPeer value")
// ErrInvalidSymKeyID is returned when it fails to get a symmetric key. // ErrInvalidSymKeyID is returned when it fails to get a symmetric key.
ErrInvalidSymKeyID = errors.New("invalid symKeyID value") 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. // SymKeyID is an ID of a symmetric key to authenticate to MailServer.
// It's derived from MailServer password. // It's derived from MailServer password.
//
// It's also possible to authenticate request with MailServerPeer
// public key.
SymKeyID string `json:"symKeyID"` SymKeyID string `json:"symKeyID"`
// Timeout is the time to live of the request specified in seconds. // 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) 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) mailServerNode, err := discover.ParseNode(r.MailServerPeer)
if err != nil { if err != nil {
return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err)
} }
symKey, err := shh.GetSymKey(r.SymKeyID) var (
symKey []byte
publicKey *ecdsa.PublicKey
)
if r.SymKeyID != "" {
symKey, err = shh.GetSymKey(r.SymKeyID)
if err != nil { if err != nil {
return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) 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 { if err != nil {
return nil, err return nil, err
} }
@ -175,14 +202,27 @@ func (api *PublicAPI) ConfirmMessagesProcessed(messages []*whisper.Message) erro
// makeEnvelop makes an envelop for a historic messages request. // makeEnvelop makes an envelop for a historic messages request.
// Symmetric key is used to authenticate to MailServer. // Symmetric key is used to authenticate to MailServer.
// PK is the current node ID. // 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{ params := whisper.MessageParams{
PoW: pow, PoW: pow,
Payload: payload, Payload: payload,
KeySym: symKey,
WorkTime: defaultWorkTime, WorkTime: defaultWorkTime,
Src: nodeID, 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(&params) message, err := whisper.NewSentMessage(&params)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/t/helpers"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -141,7 +142,7 @@ func (s *ShhExtSuite) TestWaitMessageExpired() {
} }
} }
func (s *ShhExtSuite) TestRequestMessages() { func (s *ShhExtSuite) TestRequestMessagesErrors() {
var err error var err error
shh := whisper.New(nil) shh := whisper.New(nil)
@ -152,17 +153,14 @@ func (s *ShhExtSuite) TestRequestMessages() {
}, },
}) // in-memory node as no data dir }) // in-memory node as no data dir
s.NoError(err) s.NoError(err)
err = aNode.Register(func(_ *node.ServiceContext) (node.Service, error) { err = aNode.Register(func(*node.ServiceContext) (node.Service, error) {
return shh, nil return shh, nil
}) })
s.NoError(err) s.NoError(err)
err = aNode.Start() err = aNode.Start()
s.NoError(err) s.NoError(err)
defer func() { defer func() { s.NoError(aNode.Stop()) }()
err := aNode.Stop()
s.NoError(err)
}()
mock := newHandlerMock(1) mock := newHandlerMock(1)
service := New(shh, mock, nil, false) service := New(shh, mock, nil, false)
@ -182,6 +180,7 @@ func (s *ShhExtSuite) TestRequestMessages() {
// non-existent symmetric key // non-existent symmetric key
hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ hash, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailServerPeer, MailServerPeer: mailServerPeer,
SymKeyID: "invalid-sym-key-id",
}) })
s.Nil(hash) s.Nil(hash)
s.EqualError(err, "invalid symKeyID value: non-existent key ID") s.EqualError(err, "invalid symKeyID value: non-existent key ID")
@ -193,16 +192,39 @@ func (s *ShhExtSuite) TestRequestMessages() {
MailServerPeer: mailServerPeer, MailServerPeer: mailServerPeer,
SymKeyID: symKeyID, SymKeyID: symKeyID,
}) })
s.Contains(err.Error(), "Could not find peer with ID")
s.Nil(hash) s.Nil(hash)
s.Contains(err.Error(), "Could not find peer with ID")
// from is greater than to // from is greater than to
hash, err = api.RequestMessages(context.TODO(), MessagesRequest{ hash, err = api.RequestMessages(context.TODO(), MessagesRequest{
From: 10, From: 10,
To: 5, To: 5,
}) })
s.Contains(err.Error(), "Query range is invalid: from > to (10 > 5)")
s.Nil(hash) 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 // with a peer acting as a mailserver
// prepare a node first // prepare a node first
@ -214,29 +236,39 @@ func (s *ShhExtSuite) TestRequestMessages() {
}, },
}) // in-memory node as no data dir }) // in-memory node as no data dir
s.NoError(err) 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 return whisper.New(nil), nil
}) })
s.NoError(err) s.NoError(err)
err = mailNode.Start() err = mailNode.Start()
s.NoError(err) s.NoError(err)
defer func() { defer func() { s.NoError(mailNode.Stop()) }()
err := mailNode.Stop()
s.NoError(err)
}()
// add mailPeer as a peer // add mailPeer as a peer
aNode.Server().AddPeer(mailNode.Server().Self()) 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{ hash, err = api.RequestMessages(context.TODO(), MessagesRequest{
MailServerPeer: mailNode.Server().Self().String(), MailServerPeer: mailNode.Server().Self().String(),
SymKeyID: symKeyID, SymKeyID: symKeyID,
}) })
s.NoError(err) s.NoError(err)
s.NotNil(hash) 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)) s.Contains(api.service.tracker.cache, common.BytesToHash(hash))
} }

View File

@ -558,7 +558,7 @@ func (s *WhisperMailboxSuite) startMailboxBackend() (*api.StatusBackend, func())
mailboxConfig.WhisperConfig.Enabled = true mailboxConfig.WhisperConfig.Enabled = true
mailboxConfig.KeyStoreDir = datadir mailboxConfig.KeyStoreDir = datadir
mailboxConfig.WhisperConfig.EnableMailServer = true 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.WhisperConfig.DataDir = filepath.Join(datadir, "data")
mailboxConfig.DataDir = datadir mailboxConfig.DataDir = datadir