whisper: mail server introduced

This commit is contained in:
Vlad 2017-01-16 23:00:28 +01:00
parent b392986c5c
commit 202ac23df2
5 changed files with 235 additions and 63 deletions

168
cmd/wnode/mailserver.go Normal file
View file

@ -0,0 +1,168 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"encoding/binary"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rlp"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
type WMailServer struct {
db *leveldb.DB
w *whisper.Whisper
pow float64
key []byte
}
type DBKey struct {
timestamp uint32
hash common.Hash
raw []byte
}
func NewDbKey(t uint32, h common.Hash) *DBKey {
const sz = common.HashLength + sizeOfInt
var k DBKey
k.timestamp = t
k.hash = h
k.raw = make([]byte, sz)
binary.BigEndian.PutUint32(k.raw, k.timestamp)
copy(k.raw[sizeOfInt:], k.hash[:])
return &k
}
func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) {
var err error
if len(path) == 0 {
utils.Fatalf("DB file is not specified")
}
if len(password) == 0 {
utils.Fatalf("Password is not specified for MailServer")
}
s.db, err = leveldb.OpenFile(path, nil)
if err != nil {
utils.Fatalf("Failed to open DB file: %s", err)
}
s.w = shh
s.pow = pow
err = s.w.AddSymKey(mailServerKeyName, []byte(password))
if err != nil {
utils.Fatalf("Failed to create symmetric key for MailServer: %s", err)
}
s.key = s.w.GetSymKey(mailServerKeyName)
}
func (s *WMailServer) Close() {
if s.db != nil {
s.db.Close()
}
}
func (s *WMailServer) Archive(env *whisper.Envelope) {
key := NewDbKey(env.Expiry-env.TTL, env.Hash())
rawEnvelope, err := rlp.EncodeToBytes(env)
if err != nil {
glog.V(logger.Error).Infof("rlp.EncodeToBytes failed: %s", err)
} else {
err = s.db.Put(key.raw, rawEnvelope, nil)
if err != nil {
glog.V(logger.Error).Infof("Writing to DB failed: %s", err)
}
}
}
func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) {
ok, lower, upper, topic := s.validate(peer, request)
if !ok {
return
}
var err error
var zero common.Hash
var empty whisper.TopicType
kl := NewDbKey(lower, zero)
ku := NewDbKey(upper, zero)
i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil)
defer i.Release()
for i.Next() {
var envelope whisper.Envelope
err = envelope.DecodeBytes(i.Value())
if err != nil {
glog.V(logger.Error).Infof("RLP decoding failed: %s", err)
}
if topic == empty || envelope.Topic == topic {
err = s.w.SendP2PDirect(peer, &envelope)
if err != nil {
glog.V(logger.Error).Infof("Failed to send direct message to peer: %s", err)
return
}
}
}
err = i.Error()
if err != nil {
glog.V(logger.Error).Infof("Level DB iterator error: %s", err)
}
}
func (s *WMailServer) validate(peer *whisper.Peer, request *whisper.Envelope) (bool, uint32, uint32, whisper.TopicType) {
var topic whisper.TopicType
if s.pow > 0.0 && request.PoW() < s.pow {
return false, 0, 0, topic
}
f := whisper.Filter{KeySym: s.key}
decrypted := request.Open(&f)
if decrypted == nil {
glog.V(logger.Warn).Infof("Failed to decrypt p2p request")
return false, 0, 0, topic
}
if len(decrypted.Payload) < sizeOfInt*2 {
glog.V(logger.Warn).Infof("Undersized p2p request")
return false, 0, 0, topic
}
if bytes.Equal(peer.ID(), decrypted.Signature) {
glog.V(logger.Warn).Infof("Wrong signature of p2p request")
return false, 0, 0, topic
}
lower := binary.BigEndian.Uint32(decrypted.Payload[:sizeOfInt])
upper := binary.BigEndian.Uint32(decrypted.Payload[sizeOfInt : sizeOfInt*2])
if len(decrypted.Payload) == sizeOfInt*2+whisper.TopicLength {
topic = whisper.BytesToTopic(decrypted.Payload[sizeOfInt*2 : sizeOfInt*2+whisper.TopicLength])
}
return true, lower, upper, topic
}

View file

@ -43,7 +43,6 @@ import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rlp"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"golang.org/x/crypto/pbkdf2"
)
@ -53,7 +52,7 @@ const sizeOfInt = 4
var (
quitCommand = "~Q"
enodePrefix = "enode://"
mailServerKeyName = "fd8c027e29852ef"
mailServerKeyName = "958e04ab302fb36ad2616a352cbac79dfa08f43d325d0833e07061a9ef8c383d"
)
// singletons
@ -81,7 +80,7 @@ var (
echoMode = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics")
bootstrapMode = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
forwarderMode = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
mailServerMode = flag.Bool("m", false, "mail server mode: delivers expired messages on demand")
mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand")
requestMail = flag.Bool("r", false, "request expired messages from the bootstrap server")
asymmetricMode = flag.Bool("a", false, "use asymmetric encryption")
testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics")
@ -150,7 +149,6 @@ func echo() {
fmt.Printf("mspow = %f \n", *argServerPoW)
fmt.Printf("ip = %s \n", *argIP)
fmt.Printf("salt = %s \n", *argSalt)
fmt.Printf("topic = %x \n", topic)
fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub)))
fmt.Printf("idfile = %s \n", *argIDFile)
fmt.Printf("dbpath = %s \n", *argDBPath)
@ -175,7 +173,7 @@ func initialize() {
if *bootstrapMode {
if len(*argIP) == 0 {
argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30303): ")
argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ")
}
} else {
if len(*argEnode) == 0 {
@ -185,12 +183,13 @@ func initialize() {
peers = append(peers, peer)
}
if *mailServerMode && len(msPassword) == 0 {
if *mailServerMode {
if len(msPassword) == 0 {
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
if err != nil {
utils.Fatalf("Failed to read Mail Server password: %s", err)
}
}
shh = whisper.NewWhisper(&mailServer)
mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW)
} else {
@ -247,18 +246,31 @@ func isKeyValid(k *ecdsa.PublicKey) bool {
func configureNode() {
var err error
var p2pAccept bool
if *forwarderMode {
return
}
if *asymmetricMode && len(*argPub) == 0 {
if *asymmetricMode {
if len(*argPub) == 0 {
s := scanLine("Please enter the peer's public key: ")
pub = crypto.ToECDSAPub(common.FromHex(s))
if !isKeyValid(pub) {
utils.Fatalf("Error: invalid public key")
}
}
}
if *requestMail {
p2pAccept = true
if len(msPassword) == 0 {
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
if err != nil {
utils.Fatalf("Failed to read Mail Server password: %s", err)
}
}
}
if !*asymmetricMode && !*forwarderMode && !*testMode {
pass, err := console.Stdin.PromptPassword("Please enter the password: ")
@ -283,15 +295,14 @@ func configureNode() {
}
}
if *requestMail && len(msPassword) == 0 {
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
if err != nil {
utils.Fatalf("Failed to read Mail Server password: %s", err)
filter := whisper.Filter{
KeySym: symKey,
KeyAsym: asymKey,
Topics: []whisper.TopicType{topic},
AcceptP2P: p2pAccept,
}
}
filter := &whisper.Filter{KeySym: symKey, KeyAsym: asymKey, Topics: []whisper.TopicType{topic}}
filterID = shh.Watch(filter)
filterID = shh.Watch(&filter)
fmt.Printf("Filter is configured for the topic: %x \n", topic)
}
func generateTopic(password, salt []byte) {
@ -451,7 +462,10 @@ func printMessageInfo(msg *whisper.ReceivedMessage) {
func requestExpiredMessagesLoop() {
var key, peerID []byte
var timeLow, timeUpp, t uint32
var timeLow, timeUpp uint32
var t string
var xt, empty whisper.TopicType
err := shh.AddSymKey(mailServerKeyName, []byte(msPassword))
if err != nil {
utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
@ -463,16 +477,23 @@ func requestExpiredMessagesLoop() {
for {
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ")
t = scanUint("Please enter the topic: ")
t = scanLine("Please enter the topic (hexadecimal): ")
if len(t) >= whisper.TopicLength*2 {
x, err := hex.DecodeString(t)
if err != nil {
utils.Fatalf("Failed to parse the topic: %s", err)
}
xt = whisper.BytesToTopic(x)
}
if timeUpp == 0 {
timeUpp = 0xFFFFFFFF
}
data := make([]byte, sizeOfInt*3)
data := make([]byte, sizeOfInt*2+whisper.TopicLength)
binary.BigEndian.PutUint32(data, timeLow)
binary.BigEndian.PutUint32(data[sizeOfInt:], timeUpp)
binary.BigEndian.PutUint32(data[sizeOfInt*2:], t)
if t == 0 {
copy(data[sizeOfInt*2:], xt[:])
if xt == empty {
data = data[:sizeOfInt*2]
}
@ -489,12 +510,7 @@ func requestExpiredMessagesLoop() {
utils.Fatalf("Wrap failed: %s", err)
}
encoded, err := rlp.EncodeToBytes(env)
if err != nil {
utils.Fatalf("RLP encoding failed: %s", err)
}
err = shh.RequestHistoricMessages(peerID, encoded)
err = shh.RequestHistoricMessages(peerID, env)
if err != nil {
utils.Fatalf("Failed to send P2P message: %s", err)
}
@ -526,11 +542,3 @@ func extractIdFromEnode(s string) []byte {
return b
}
// this is a temporary stub, will be expanded later
type WMailServer struct{}
func (s *WMailServer) Archive(env *whisper.Envelope) {}
func (s *WMailServer) DeliverMail(peer *whisper.Peer, data []byte) {}
func (s *WMailServer) Init(w *whisper.Whisper, path string, password string, pow float64) {}
func (s *WMailServer) Close() {}

View file

@ -93,12 +93,12 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error {
// data contains parameters (time frame, payment details, etc.), required
// by the remote email-like server. Whisper is not aware about the data format,
// it will just forward the raw data to the server.
func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.RequestHistoricMessages(peerID, data)
}
//func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error {
// if api.whisper == nil {
// return whisperOffLineErr
// }
// return api.whisper.RequestHistoricMessages(peerID, data)
//}
// HasIdentity checks if the whisper node is configured with the private key
// of the specified public pair.

View file

@ -83,5 +83,5 @@ func (e unknownVersionError) Error() string {
// in order to bypass the expiry checks.
type MailServer interface {
Archive(env *Envelope)
DeliverMail(whisperPeer *Peer, data []byte)
DeliverMail(whisperPeer *Peer, request *Envelope)
}

View file

@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/pbkdf2"
set "gopkg.in/fatih/set.v0"
)
@ -125,13 +124,13 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error {
return nil
}
func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error {
func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error {
p, err := w.getPeer(peerID)
if err != nil {
return err
}
p.trusted = true
return p2p.Send(p.ws, p2pRequestCode, data)
return p2p.Send(p.ws, p2pRequestCode, envelope)
}
func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
@ -361,25 +360,22 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
// therefore might not satisfy the PoW, expiry and other requirements.
// these messages are only accepted from the trusted peer.
if p.trusted {
var envelopes []*Envelope
if err := packet.Decode(&envelopes); err != nil {
var envelope Envelope
if err := packet.Decode(&envelope); err != nil {
glog.V(logger.Warn).Infof("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err)
return fmt.Errorf("garbage received (directMessage)")
}
for _, envelope := range envelopes {
wh.postEvent(envelope, true)
}
wh.postEvent(&envelope, true)
}
case p2pRequestCode:
// Must be processed if mail server is implemented. Otherwise ignore.
if wh.mailServer != nil {
s := rlp.NewStream(packet.Payload, uint64(packet.Size))
data, err := s.Bytes()
if err == nil {
wh.mailServer.DeliverMail(p, data)
} else {
glog.V(logger.Error).Infof("%v: bad requestHistoricMessages received: [%v]", p.peer, err)
var request Envelope
if err := packet.Decode(&request); err != nil {
glog.V(logger.Warn).Infof("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err)
return fmt.Errorf("garbage received (p2p request)")
}
wh.mailServer.DeliverMail(p, &request)
}
default:
// New message types might be implemented in the future versions of Whisper.