Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify the sender of Rollcall create/open/close #1778

Merged
merged 3 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions be1-go/channel/lao/lao.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ func (c *Channel) processRollCallCreate(msg message.Message, msgData interface{}
return xerrors.Errorf("invalid roll_call#create message: %v", err)
}

// check that the message was from an organizer
err = c.checkIsFromOrganizer(msg)
if err != nil {
return err
}

// Check that the ProposedEnd is greater than the ProposedStart
if data.ProposedStart > data.ProposedEnd {
return answer.NewErrorf(-4, "The field `proposed_start` is greater than the field "+
Expand Down Expand Up @@ -391,6 +397,12 @@ func (c *Channel) processRollCallOpen(msg message.Message, msgData interface{},
return xerrors.Errorf("invalid roll_call#open message: %v", err)
}

// check that the message was from an organizer
err = c.checkIsFromOrganizer(msg)
if err != nil {
return err
}

if !c.rollCall.checkPrevID([]byte(rollCallOpen.Opens)) {
return answer.NewError(-1, "The field `opens` does not correspond to the id of "+
"the previous roll call message")
Expand All @@ -417,6 +429,12 @@ func (c *Channel) processRollCallClose(msg message.Message, msgData interface{},
return xerrors.Errorf("invalid roll_call#close message: %v", err)
}

// check that the message was from an organizer
err = c.checkIsFromOrganizer(msg)
if err != nil {
return err
}

if c.rollCall.state != Open {
return answer.NewError(-1, "The roll call cannot be closed since it's not open")
}
Expand Down Expand Up @@ -451,21 +469,10 @@ func (c *Channel) processElectionObject(msg message.Message, msgData interface{}
return xerrors.Errorf("message %v isn't a election#setup message", msgData)
}

senderBuf, err := base64.URLEncoding.DecodeString(msg.Sender)
// check that the message was from an organizer
err := c.checkIsFromOrganizer(msg)
if err != nil {
return xerrors.Errorf(keyDecodeError, err)
}

// Check if the sender of election creation message is the organizer
senderPoint := crypto.Suite.Point()
err = senderPoint.UnmarshalBinary(senderBuf)
if err != nil {
return answer.NewErrorf(-4, keyUnmarshalError, err)
}

if !c.organizerPubKey.Equal(senderPoint) {
return answer.NewErrorf(-5, "Sender key does not match the "+
"organizer's one: %s != %s", senderPoint, c.organizerPubKey)
return err
}

var electionSetup messagedata.ElectionSetup
Expand Down Expand Up @@ -768,3 +775,28 @@ func (c *Channel) extractLaoID() string {
func (r *rollCall) checkPrevID(prevID []byte) bool {
return string(prevID) == r.id
}

// checkIsFromOrganizer is a helper method which validates that the message's
// sender is the organizer. Return an error if it failed or if it's false,
// return nil if it was from the organizer.
func (c *Channel) checkIsFromOrganizer(msg message.Message) error {
senderBuf, err := base64.URLEncoding.DecodeString(msg.Sender)
if err != nil {
return xerrors.Errorf(keyDecodeError, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this is built using xerrors.Errorf not as an amswer.NewErrorf like the other errors returned ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was because this function was created using some existing code.
After reading answer/error.go, I just found that there are some functions with the error code already inside,
such as answer.NewInvalidMessageFieldError and answer.NewAccessDeniedError.

}

senderPoint := crypto.Suite.Point()

err = senderPoint.UnmarshalBinary(senderBuf)
if err != nil {
return answer.NewErrorf(-4, keyUnmarshalError, senderBuf)
}

if !c.organizerPubKey.Equal(senderPoint) {
return answer.NewErrorf(-5,
"sender key %v does not match organizer key %v",
senderPoint, c.organizerPubKey)
}

return nil
}
209 changes: 203 additions & 6 deletions be1-go/channel/lao/lao_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"go.dedis.ch/kyber/v3/sign/schnorr"
"io"
"os"
"path/filepath"
Expand All @@ -16,6 +17,7 @@ import (
"popstellar/message/query/method/message"
"popstellar/network/socket"
"popstellar/validation"
"strconv"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -488,6 +490,82 @@ func TestBaseChannel_SimulateRollCall(t *testing.T) {
require.NoError(t, channel.Publish(messageClosePub, nil))
}

func TestLAOChannel_Rollcall_Creation_Not_Organizer(t *testing.T) {
keypair := generateKeyPair(t)

fakeHub, err := NewFakeHub("", keypair.public, nolog, nil)
require.NoError(t, err)

m := message.Message{MessageID: "0"}
channel, err := NewChannel(sampleLao, fakeHub, m, nolog, keypair.public, nil)
require.NoError(t, err)

// Publish a rollcall create message with a different key than the
// organizer, an error is expected
err = channel.Publish(sampleRollCallCreatePublish, nil)
require.Error(t, err)
}

func TestLAOChannel_Rollcall_Open_Not_Organizer(t *testing.T) {
keypairOrg := generateKeyPair(t)
keypairOther := generateKeyPair(t)

fakeHub, err := NewFakeHub("", keypairOrg.public, nolog, nil)
require.NoError(t, err)

laoId := messagedata.Hash(base64.URLEncoding.EncodeToString(keypairOrg.
publicBuf), strconv.FormatInt(time.Now().Unix(), 10), "Lao 1")
laoChannel := "/root/" + laoId

m := message.Message{MessageID: "0"}
channel, err := NewChannel(laoChannel, fakeHub, m, nolog, keypairOrg.public, nil)
require.NoError(t, err)

rollcallCreate, rollcallId := createRollCallCreate(t, keypairOrg, laoId)

err = channel.Publish(rollcallCreate, nil)
require.NoError(t, err)

rollcallOpen := createRollCallOpen(t, keypairOther, laoId, rollcallId)

// Publish a rollcall open message with a different key than the
// organizer, an error is expected
err = channel.Publish(rollcallOpen, nil)
require.Error(t, err)
}

func TestLAOChannel_Rollcall_Close_Not_Organizer(t *testing.T) {
keypairOrg := generateKeyPair(t)
keypairOther := generateKeyPair(t)

fakeHub, err := NewFakeHub("", keypairOrg.public, nolog, nil)
require.NoError(t, err)

laoId := messagedata.Hash(base64.URLEncoding.EncodeToString(keypairOrg.
publicBuf), strconv.FormatInt(time.Now().Unix(), 10), "Lao 1")
laoChannel := "/root/" + laoId

m := message.Message{MessageID: "0"}
channel, err := NewChannel(laoChannel, fakeHub, m, nolog, keypairOrg.public, nil)
require.NoError(t, err)

rollcallCreate, rollcallId := createRollCallCreate(t, keypairOrg, laoId)

err = channel.Publish(rollcallCreate, nil)
require.NoError(t, err)

rollcallOpen := createRollCallOpen(t, keypairOrg, laoId, rollcallId)
err = channel.Publish(rollcallOpen, nil)
require.NoError(t, err)

rollcallClose := createRollCallClose(t, keypairOther, laoId, rollcallId)

// Publish a rollcall close message with a different key than the
// organizer, an error is expected
err = channel.Publish(rollcallClose, nil)
require.Error(t, err)
}

func TestLAOChannel_Election_Creation(t *testing.T) {
keypair := generateKeyPair(t)
publicKey64 := base64.URLEncoding.EncodeToString(keypair.publicBuf)
Expand Down Expand Up @@ -572,13 +650,14 @@ func TestLAOChannel_Sends_Greeting(t *testing.T) {
}

func Test_LAOChannel_Witness_Message(t *testing.T) {
keypair := generateKeyPair(t)
fakeHub, err := NewFakeHub("", keypair.public, nolog, nil)
organizerPk := getPublicKeyPoint(t, organizerPublicKey)

fakeHub, err := NewFakeHub("", organizerPk, nolog, nil)
require.NoError(t, err)

// Create new Lao channel
m := message.Message{MessageID: "0"}
channel, err := NewChannel(sampleLao, fakeHub, m, nolog, keypair.public, nil)
channel, err := NewChannel(sampleLao, fakeHub, m, nolog, organizerPk, nil)
require.NoError(t, err)

// Publish roll_call_create message
Expand All @@ -593,13 +672,14 @@ func Test_LAOChannel_Witness_Message(t *testing.T) {
}

func Test_LAOChannel_Witness_Message_Not_Received_Yet(t *testing.T) {
keypair := generateKeyPair(t)
fakeHub, err := NewFakeHub("", keypair.public, nolog, nil)
organizerPk := getPublicKeyPoint(t, organizerPublicKey)

fakeHub, err := NewFakeHub("", organizerPk, nolog, nil)
require.NoError(t, err)

// Create new Lao channel
m := message.Message{MessageID: "0"}
channel, err := NewChannel(sampleLao, fakeHub, m, nolog, keypair.public, nil)
channel, err := NewChannel(sampleLao, fakeHub, m, nolog, organizerPk, nil)
require.NoError(t, err)

// Publish witness message and catchup on channel to get the message back
Expand Down Expand Up @@ -803,6 +883,19 @@ func (f *fakeSocket) ID() string {
var sampleLao = "/root/QNNTcGQk-rnehNjgizdzi9IT1nIlmXsOXy1BCWsNaVE="
var organizerPublicKey = "A2nPAZfsvBRPb5uOb1_hUVuAKt5YKPRZdiFq1g0TLr0="

// getPublicKeyPoint convert a base64 encoded public key to a Kyber.Point
func getPublicKeyPoint(t *testing.T, publicKeyBase64 string) kyber.Point {
keyBuf, err := base64.URLEncoding.DecodeString(publicKeyBase64)
require.NoError(t, err)

senderPk := crypto.Suite.Point()

err = senderPk.UnmarshalBinary(keyBuf)
require.NoError(t, err)

return senderPk
}

var sampleRollCallCreate = message.Message{
Data: "eyJjcmVhdGlvbiI6MTY4NDI1OTU4MSwiZGVzY3JpcHRpb24iOiIiLCJpZCI6IktxLV9CbUJUZTFEWnFjSXEzU2pOcklzdHAzTFdCM0N6VFhoOVpBaHctUUU9IiwibG9jYXRpb24iOiJ0ZSI" +
"sIm5hbWUiOiJ0ZSIsInByb3Bvc2VkX2VuZCI6MTY4NDI2MzEyMCwicHJvcG9zZWRfc3RhcnQiOjE2ODQyNTk1ODEsIm9iamVjdCI6InJvbGxfY2FsbCIsImFjdGlvbiI6ImNyZWF0ZSJ9",
Expand Down Expand Up @@ -851,3 +944,107 @@ var sampleWitnessMessagePublish = method.Publish{
Message: sampleWitnessMessage,
},
}

// createPublish is a helper function that create a Publish message
// containing a message data with valid signature and ids
func createPublish(t *testing.T, sender keypair, laoId string,
data []byte) method.Publish {

data64 := base64.URLEncoding.EncodeToString(data)
senderPk := base64.URLEncoding.EncodeToString(sender.publicBuf)
signature, err := schnorr.Sign(suite, sender.private, data)
require.NoError(t, err)

msg := message.Message{
Data: data64,
Sender: senderPk,
Signature: base64.URLEncoding.EncodeToString(signature),
MessageID: messagedata.Hash(data64, senderPk),
WitnessSignatures: nil,
}

publishMsg := method.Publish{
Base: query.Base{
JSONRPCBase: jsonrpc.JSONRPCBase{
JSONRPC: "2.0",
},
Method: "publish",
},

Params: struct {
Channel string `json:"channel"`
Message message.Message `json:"message"`
}{
Channel: "/root/" + laoId,
Message: msg,
},
}

return publishMsg
}

func createRollCallCreate(t *testing.T, sender keypair,
laoId string) (method.Publish, string) {

now := time.Now().Unix()
rollcallName := "Roll Call"
rollcallId := messagedata.Hash("R", laoId, strconv.FormatInt(now, 10), rollcallName)
rollcallCreate, err := json.Marshal(messagedata.RollCallCreate{
Object: "roll_call",
Action: "create",
ID: rollcallId,
Name: rollcallName,
Creation: now,
ProposedStart: now,
ProposedEnd: now + 1000,
Location: "EPFL",
Description: "",
})
require.NoError(t, err)

rollcallCreatePublish := createPublish(t, sender, laoId, rollcallCreate)

return rollcallCreatePublish, rollcallId
}

func createRollCallOpen(t *testing.T, sender keypair,
laoId string, rollcallId string) method.Publish {

openAt := time.Now().Unix()
updateId := messagedata.Hash("R", laoId, rollcallId, strconv.FormatInt(openAt, 10))

rollcallOpen, err := json.Marshal(messagedata.RollCallOpen{
Object: "roll_call",
Action: "open",
UpdateID: updateId,
Opens: rollcallId,
OpenedAt: openAt,
})
require.NoError(t, err)

rollcallOpenPublish := createPublish(t, sender, laoId, rollcallOpen)

return rollcallOpenPublish
}

func createRollCallClose(t *testing.T, sender keypair,
laoId string, openId string) method.Publish {

closeAt := time.Now().Unix()
updateId := messagedata.Hash("R", laoId, openId, strconv.FormatInt(closeAt, 10))
attendees := []string{base64.URLEncoding.EncodeToString(sender.publicBuf)}

rollcallClose, err := json.Marshal(messagedata.RollCallClose{
Object: "roll_call",
Action: "close",
UpdateID: updateId,
Closes: openId,
ClosedAt: closeAt,
Attendees: attendees,
})
require.NoError(t, err)

rollcallClosePublish := createPublish(t, sender, laoId, rollcallClose)

return rollcallClosePublish
}
Loading