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:
parent
93210061ad
commit
38a60135b2
|
@ -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
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"time"
|
||||
|
||||
|
@ -44,7 +45,7 @@ const (
|
|||
|
||||
var (
|
||||
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.
|
||||
// Real metrics are collected only if -metrics flag is set
|
||||
requestProcessTimer = metrics.NewRegisteredTimer("mailserver/requestProcessTime", nil)
|
||||
|
@ -85,8 +86,10 @@ type WMailServer struct {
|
|||
db dbImpl
|
||||
w *whisper.Whisper
|
||||
pow float64
|
||||
key []byte
|
||||
limit *limiter
|
||||
filter *whisper.Filter
|
||||
|
||||
muLimiter sync.RWMutex
|
||||
limiter *limiter
|
||||
tick *ticker
|
||||
}
|
||||
|
||||
|
@ -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,24 +146,34 @@ 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)
|
||||
// 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: %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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
37
node/node.go
37
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue