mirror of https://github.com/status-im/go-waku.git
140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
package relay
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/hash"
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
|
"github.com/waku-org/go-waku/waku/v2/timesource"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func msgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
|
|
timestampBytes := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(timestampBytes, uint64(msg.GetTimestamp()))
|
|
|
|
var ephemeralByte byte
|
|
if msg.GetEphemeral() {
|
|
ephemeralByte = 1
|
|
}
|
|
|
|
return hash.SHA256(
|
|
[]byte(pubSubTopic),
|
|
msg.Payload,
|
|
[]byte(msg.ContentTopic),
|
|
timestampBytes,
|
|
[]byte{ephemeralByte},
|
|
)
|
|
}
|
|
|
|
type validatorFn = func(ctx context.Context, msg *pb.WakuMessage, topic string) bool
|
|
|
|
func (w *WakuRelay) RegisterDefaultValidator(fn validatorFn) {
|
|
w.topicValidatorMutex.Lock()
|
|
defer w.topicValidatorMutex.Unlock()
|
|
w.defaultTopicValidators = append(w.defaultTopicValidators, fn)
|
|
}
|
|
|
|
func (w *WakuRelay) RegisterTopicValidator(topic string, fn validatorFn) {
|
|
w.topicValidatorMutex.Lock()
|
|
defer w.topicValidatorMutex.Unlock()
|
|
|
|
w.topicValidators[topic] = append(w.topicValidators[topic], fn)
|
|
}
|
|
|
|
func (w *WakuRelay) RemoveTopicValidator(topic string) {
|
|
w.topicValidatorMutex.Lock()
|
|
defer w.topicValidatorMutex.Unlock()
|
|
|
|
delete(w.topicValidators, topic)
|
|
}
|
|
|
|
func (w *WakuRelay) topicValidator(topic string) func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
|
|
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
|
|
msg, err := pb.Unmarshal(message.Data)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
w.topicValidatorMutex.RLock()
|
|
validators := w.topicValidators[topic]
|
|
validators = append(validators, w.defaultTopicValidators...)
|
|
w.topicValidatorMutex.RUnlock()
|
|
exists := len(validators) > 0
|
|
|
|
if exists {
|
|
for _, v := range validators {
|
|
if !v(ctx, msg, topic) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
// AddSignedTopicValidator registers a gossipsub validator for a topic which will check that messages Meta field contains a valid ECDSA signature for the specified pubsub topic. This is used as a DoS prevention mechanism
|
|
func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.PublicKey) error {
|
|
w.log.Info("adding validator to signed topic", zap.String("topic", topic), zap.String("publicKey", hex.EncodeToString(elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y))))
|
|
|
|
fn := signedTopicBuilder(w.timesource, publicKey)
|
|
|
|
w.RegisterTopicValidator(topic, fn)
|
|
|
|
if !w.IsSubscribed(topic) {
|
|
w.log.Warn("relay is not subscribed to signed topic", zap.String("topic", topic))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
const messageWindowDuration = time.Minute * 5
|
|
|
|
func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool {
|
|
if msg.GetTimestamp() == 0 {
|
|
return false
|
|
}
|
|
|
|
now := t.Now()
|
|
msgTime := time.Unix(0, msg.GetTimestamp())
|
|
|
|
return now.Sub(msgTime).Abs() <= messageWindowDuration
|
|
}
|
|
|
|
func signedTopicBuilder(t timesource.Timesource, publicKey *ecdsa.PublicKey) validatorFn {
|
|
publicKeyBytes := crypto.FromECDSAPub(publicKey)
|
|
return func(ctx context.Context, msg *pb.WakuMessage, topic string) bool {
|
|
if !withinTimeWindow(t, msg) {
|
|
return false
|
|
}
|
|
|
|
msgHash := msgHash(topic, msg)
|
|
signature := msg.Meta
|
|
|
|
return secp256k1.VerifySignature(publicKeyBytes, msgHash, signature)
|
|
}
|
|
}
|
|
|
|
// SignMessage adds an ECDSA signature to a WakuMessage as an opt-in mechanism for DoS prevention
|
|
func SignMessage(privKey *ecdsa.PrivateKey, msg *pb.WakuMessage, pubsubTopic string) error {
|
|
msgHash := msgHash(pubsubTopic, msg)
|
|
sign, err := secp256k1.Sign(msgHash, crypto.FromECDSA(privKey))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg.Meta = sign[0:64] // Remove V
|
|
return nil
|
|
}
|