mirror of https://github.com/status-im/go-waku.git
feat: validate message timestamp in signed topic validator
This commit is contained in:
parent
8fafc367eb
commit
231469b441
|
@ -4,7 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
|
@ -13,19 +15,46 @@ import (
|
||||||
"github.com/libp2p/go-libp2p/core/peer"
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
"github.com/waku-org/go-waku/waku/v2/hash"
|
"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/protocol/pb"
|
||||||
|
"github.com/waku-org/go-waku/waku/v2/timesource"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
proto "google.golang.org/protobuf/proto"
|
proto "google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Application level message hash
|
// Application level message hash
|
||||||
func MsgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
|
func MsgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
|
||||||
// TODO: Other fields?
|
timestampBytes := make([]byte, 8)
|
||||||
return hash.SHA256([]byte(pubSubTopic), msg.Payload, []byte(msg.ContentTopic))
|
binary.LittleEndian.PutUint64(timestampBytes, uint64(msg.Timestamp))
|
||||||
|
|
||||||
|
var ephemeralByte byte
|
||||||
|
if msg.Ephemeral {
|
||||||
|
ephemeralByte = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash.SHA256(
|
||||||
|
[]byte(pubSubTopic),
|
||||||
|
msg.Payload,
|
||||||
|
[]byte(msg.ContentTopic),
|
||||||
|
timestampBytes,
|
||||||
|
[]byte{ephemeralByte},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageWindowDuration = time.Minute * 5
|
||||||
|
|
||||||
|
func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool {
|
||||||
|
if msg.Timestamp == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
now := t.Now()
|
||||||
|
msgTime := time.Unix(0, msg.Timestamp)
|
||||||
|
|
||||||
|
return now.Sub(msgTime).Abs() <= MessageWindowDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool
|
type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool
|
||||||
|
|
||||||
func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn {
|
func validatorFnBuilder(t timesource.Timesource, topic string, publicKey *ecdsa.PublicKey) validatorFn {
|
||||||
pubkBytes := crypto.FromECDSAPub(publicKey)
|
pubkBytes := crypto.FromECDSAPub(publicKey)
|
||||||
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
|
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
|
||||||
msg := new(pb.WakuMessage)
|
msg := new(pb.WakuMessage)
|
||||||
|
@ -34,6 +63,10 @@ func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !withinTimeWindow(t, msg) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
msgHash := MsgHash(topic, msg)
|
msgHash := MsgHash(topic, msg)
|
||||||
signature := msg.Meta
|
signature := msg.Meta
|
||||||
|
|
||||||
|
@ -43,7 +76,7 @@ func validatorFnBuilder(topic string, publicKey *ecdsa.PublicKey) validatorFn {
|
||||||
|
|
||||||
func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.PublicKey) error {
|
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))))
|
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))))
|
||||||
err := w.pubsub.RegisterTopicValidator(topic, validatorFnBuilder(topic, publicKey))
|
err := w.pubsub.RegisterTopicValidator(topic, validatorFnBuilder(w.timesource, topic, publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,38 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||||
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
|
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
||||||
"github.com/waku-org/go-waku/waku/v2/timesource"
|
|
||||||
"github.com/waku-org/go-waku/waku/v2/utils"
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FakeTimesource struct {
|
||||||
|
now time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeTimesource(now time.Time) FakeTimesource {
|
||||||
|
return FakeTimesource{now}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeTimesource) Start(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeTimesource) Stop() {}
|
||||||
|
|
||||||
|
func (f FakeTimesource) Now() time.Time {
|
||||||
|
return f.now
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timesource interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
func TestMsgHash(t *testing.T) {
|
func TestMsgHash(t *testing.T) {
|
||||||
privKeyB, _ := hex.DecodeString("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6")
|
privKeyB, _ := hex.DecodeString("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6")
|
||||||
prvKey, _ := crypto.ToECDSA(privKeyB)
|
prvKey, _ := crypto.ToECDSA(privKeyB)
|
||||||
|
@ -23,31 +44,52 @@ func TestMsgHash(t *testing.T) {
|
||||||
payload, _ := hex.DecodeString("1A12E077D0E89F9CAC11FBBB6A676C86120B5AD3E248B1F180E98F15EE43D2DFCF62F00C92737B2FF6F59B3ABA02773314B991C41DC19ADB0AD8C17C8E26757B")
|
payload, _ := hex.DecodeString("1A12E077D0E89F9CAC11FBBB6A676C86120B5AD3E248B1F180E98F15EE43D2DFCF62F00C92737B2FF6F59B3ABA02773314B991C41DC19ADB0AD8C17C8E26757B")
|
||||||
contentTopic := "content-topic"
|
contentTopic := "content-topic"
|
||||||
pubsubTopic := "pubsub-topic"
|
pubsubTopic := "pubsub-topic"
|
||||||
|
ephemeral := true
|
||||||
|
timestamp := time.Unix(0, 1683208172339052800)
|
||||||
|
|
||||||
msg := &pb.WakuMessage{
|
msg := &pb.WakuMessage{
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
ContentTopic: contentTopic,
|
ContentTopic: contentTopic,
|
||||||
Timestamp: utils.GetUnixEpoch(timesource.NewDefaultClock()),
|
Timestamp: timestamp.UnixNano(),
|
||||||
|
Ephemeral: ephemeral,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := SignMessage(prvKey, pubsubTopic, msg)
|
err := SignMessage(prvKey, pubsubTopic, msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expectedSignature, _ := hex.DecodeString("B139487797A242291E0DD3F689777E559FB749D565D55FF202C18E24F21312A555043437B4F808BB0D21D542D703873DC712D76A3BAF1C5C8FF754210D894AD4")
|
expectedSignature, _ := hex.DecodeString("127FA211B2514F0E974A055392946DC1A14052182A6ABEFB8A6CD7C51DA1BF2E40595D28EF1A9488797C297EED3AAC45430005FB3A7F037BDD9FC4BD99F59E63")
|
||||||
require.True(t, bytes.Equal(expectedSignature, msg.Meta))
|
require.True(t, bytes.Equal(expectedSignature, msg.Meta))
|
||||||
|
|
||||||
msgData, _ := proto.Marshal(msg)
|
msgData, _ := proto.Marshal(msg)
|
||||||
|
|
||||||
expectedMessageHash, _ := hex.DecodeString("0914369D6D0C13783A8E86409FE42C68D8E8296456B9A9468C845006BCE5B9B2")
|
expectedMessageHash, _ := hex.DecodeString("662F8C20A335F170BD60ABC1F02AD66F0C6A6EE285DA2A53C95259E7937C0AE9")
|
||||||
messageHash := MsgHash(pubsubTopic, msg)
|
messageHash := MsgHash(pubsubTopic, msg)
|
||||||
require.True(t, bytes.Equal(expectedMessageHash, messageHash))
|
require.True(t, bytes.Equal(expectedMessageHash, messageHash))
|
||||||
|
|
||||||
myValidator := validatorFnBuilder(pubsubTopic, &prvKey.PublicKey)
|
myValidator := validatorFnBuilder(NewFakeTimesource(timestamp), pubsubTopic, &prvKey.PublicKey)
|
||||||
|
|
||||||
result := myValidator(context.Background(), "", &pubsub.Message{
|
result := myValidator(context.Background(), "", &pubsub.Message{
|
||||||
Message: &pubsub_pb.Message{
|
Message: &pubsub_pb.Message{
|
||||||
Data: msgData,
|
Data: msgData,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.True(t, result)
|
require.True(t, result)
|
||||||
|
|
||||||
|
// Exceed 5m window in both directions
|
||||||
|
now5m1sInPast := timestamp.Add(-5 * time.Minute).Add(-1 * time.Minute)
|
||||||
|
myValidator = validatorFnBuilder(NewFakeTimesource(now5m1sInPast), pubsubTopic, &prvKey.PublicKey)
|
||||||
|
result = myValidator(context.Background(), "", &pubsub.Message{
|
||||||
|
Message: &pubsub_pb.Message{
|
||||||
|
Data: msgData,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.False(t, result)
|
||||||
|
|
||||||
|
now5m1sInFuture := timestamp.Add(5 * time.Minute).Add(1 * time.Minute)
|
||||||
|
myValidator = validatorFnBuilder(NewFakeTimesource(now5m1sInFuture), pubsubTopic, &prvKey.PublicKey)
|
||||||
|
result = myValidator(context.Background(), "", &pubsub.Message{
|
||||||
|
Message: &pubsub_pb.Message{
|
||||||
|
Data: msgData,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.False(t, result)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue