From fbee9cb845a32b37cb45b29430517d4c3e4997ca Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 17 Apr 2024 13:17:18 +0200 Subject: [PATCH 01/48] Add federation messages --- .../messagedata/federation_challenge.go | 32 +++++++++++++++++++ .../messagedata/federation_expect.json.go | 27 ++++++++++++++++ be1-go/message/messagedata/federation_init.go | 29 +++++++++++++++++ .../federation_request_challenge.go | 25 +++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 be1-go/message/messagedata/federation_challenge.go create mode 100644 be1-go/message/messagedata/federation_expect.json.go create mode 100644 be1-go/message/messagedata/federation_init.go create mode 100644 be1-go/message/messagedata/federation_request_challenge.go diff --git a/be1-go/message/messagedata/federation_challenge.go b/be1-go/message/messagedata/federation_challenge.go new file mode 100644 index 0000000000..af9909651f --- /dev/null +++ b/be1-go/message/messagedata/federation_challenge.go @@ -0,0 +1,32 @@ +package messagedata + +// FederationChallenge defines a message data +type FederationChallenge struct { + Object string `json:"object"` + Action string `json:"action"` + + // Value is a 32 bytes array encoded in hexadecimal + Value string `json:"value"` + // Timestamp is a Unix timestamp + Timestamp int64 `json:"timestamp"` +} + +type Challenge struct { + Value string `json:"value"` + ValidUntil int64 `json:"valid_until"` +} + +// GetObject implements MessageData +func (FederationChallenge) GetObject() string { + return FederationObject +} + +// GetAction implements MessageData +func (FederationChallenge) GetAction() string { + return FederationActionChallenge +} + +// NewEmpty implements MessageData +func (FederationChallenge) NewEmpty() MessageData { + return &FederationChallenge{} +} diff --git a/be1-go/message/messagedata/federation_expect.json.go b/be1-go/message/messagedata/federation_expect.json.go new file mode 100644 index 0000000000..f56bb4023d --- /dev/null +++ b/be1-go/message/messagedata/federation_expect.json.go @@ -0,0 +1,27 @@ +package messagedata + +// FederationExpect defines a message data +type FederationExpect struct { + Object string `json:"object"` + Action string `json:"action"` + + LaoId string `json:"lao_id"` + ServerAddress string `json:"server_address"` + PublicKey string `json:"public_key"` + Challenge Challenge `json:"challenge"` +} + +// GetObject implements MessageData +func (FederationExpect) GetObject() string { + return FederationObject +} + +// GetAction implements MessageData +func (FederationExpect) GetAction() string { + return FederationActionExpect +} + +// NewEmpty implements MessageData +func (FederationExpect) NewEmpty() MessageData { + return &FederationExpect{} +} diff --git a/be1-go/message/messagedata/federation_init.go b/be1-go/message/messagedata/federation_init.go new file mode 100644 index 0000000000..3e013fc6db --- /dev/null +++ b/be1-go/message/messagedata/federation_init.go @@ -0,0 +1,29 @@ +package messagedata + +import "popstellar/message/query/method/message" + +// FederationInit defines a message data +type FederationInit struct { + Object string `json:"object"` + Action string `json:"action"` + + LaoId string `json:"lao_id"` + ServerAddress string `json:"server_address"` + PublicKey string `json:"public_key"` + ChallengeMsg message.Message `json:"challenge"` +} + +// GetObject implements MessageData +func (FederationInit) GetObject() string { + return FederationObject +} + +// GetAction implements MessageData +func (FederationInit) GetAction() string { + return FederationActionInit +} + +// NewEmpty implements MessageData +func (FederationInit) NewEmpty() MessageData { + return &FederationInit{} +} diff --git a/be1-go/message/messagedata/federation_request_challenge.go b/be1-go/message/messagedata/federation_request_challenge.go new file mode 100644 index 0000000000..f14eadfc64 --- /dev/null +++ b/be1-go/message/messagedata/federation_request_challenge.go @@ -0,0 +1,25 @@ +package messagedata + +// FederationRequestChallenge defines a message data +type FederationRequestChallenge struct { + Object string `json:"object"` + Action string `json:"action"` + + // Timestamp is a Unix timestamp + Timestamp int64 `json:"timestamp"` +} + +// GetObject implements MessageData +func (FederationRequestChallenge) GetObject() string { + return FederationObject +} + +// GetAction implements MessageData +func (FederationRequestChallenge) GetAction() string { + return FederationActionRequestChallenge +} + +// NewEmpty implements MessageData +func (FederationRequestChallenge) NewEmpty() MessageData { + return &FederationRequestChallenge{} +} From d748583e19863fb3759796ef4edf2875c209e59a Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 17 Apr 2024 13:45:44 +0200 Subject: [PATCH 02/48] Add federation channel --- be1-go/channel/channel.go | 1 + be1-go/channel/federation/federation.go | 359 ++++++++++++++++++++++++ be1-go/channel/lao/lao.go | 12 +- be1-go/hub/standard_hub/standard_hub.go | 23 ++ be1-go/message/messagedata/mod.go | 6 + 5 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 be1-go/channel/federation/federation.go diff --git a/be1-go/channel/channel.go b/be1-go/channel/channel.go index 6e5e479201..98448a0cc6 100644 --- a/be1-go/channel/channel.go +++ b/be1-go/channel/channel.go @@ -102,6 +102,7 @@ type HubFunctionalities interface { NotifyWitnessMessage(messageId string, publicKey string, signature string) GetClientServerAddress() string GetPeersInfo() []method.ServerInfo + ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) } // Broadcastable defines a channel that can broadcast diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go new file mode 100644 index 0000000000..cbb739ead5 --- /dev/null +++ b/be1-go/channel/federation/federation.go @@ -0,0 +1,359 @@ +package federation + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/rs/zerolog" + "golang.org/x/exp/rand" + "golang.org/x/xerrors" + "popstellar/channel" + "popstellar/channel/registry" + "popstellar/inbox" + jsonrpc "popstellar/message" + "popstellar/message/answer" + "popstellar/message/messagedata" + "popstellar/message/query" + "popstellar/message/query/method" + "popstellar/message/query/method/message" + "popstellar/network/socket" + "popstellar/validation" + "strconv" + "time" +) + +const ( + msgID = "msg id" +) + +type State int + +const ( + None State = iota + 1 + ExpectConnect + Initiating + WaitResult + Connected +) + +// Channel is used to handle federation messages. +type Channel struct { + sockets channel.Sockets + inbox *inbox.Inbox + channelID string + hub channel.HubFunctionalities + log zerolog.Logger + registry registry.MessageRegistry + + localOrganizerPk string + remoteOrganizerPk string + + remoteServer *socket.ClientSocket + + challenge messagedata.Challenge + + state State +} + +// NewChannel returns a new initialized federation channel +func NewChannel(channelID string, hub channel.HubFunctionalities, + log zerolog.Logger, organizerPk string) channel.Channel { + box := inbox.NewInbox(channelID) + log = log.With().Str("channel", "federation").Logger() + + newChannel := &Channel{ + sockets: channel.NewSockets(), + inbox: box, + channelID: channelID, + hub: hub, + log: log, + localOrganizerPk: organizerPk, + state: None, + } + + newChannel.registry = newChannel.NewFederationRegistry() + + return newChannel +} + +// Subscribe is used to handle a subscribe message from the client. +func (c *Channel) Subscribe(socket socket.Socket, msg method.Subscribe) error { + c.log.Info().Str(msgID, strconv.Itoa(msg.ID)).Msg("received a subscribe") + c.sockets.Upsert(socket) + + return nil +} + +// Unsubscribe is used to handle an unsubscribe message. +func (c *Channel) Unsubscribe(socketID string, msg method.Unsubscribe) error { + c.log.Info().Str(msgID, strconv.Itoa(msg.ID)).Msg("received an unsubscribe") + + ok := c.sockets.Delete(socketID) + if !ok { + return answer.NewError(-2, "client is not subscribed to this channel") + } + + return nil +} + +// Publish is used to handle publish messages in the federation channel. +func (c *Channel) Publish(publish method.Publish, socket socket.Socket) error { + c.log.Info(). + Str(msgID, strconv.Itoa(publish.ID)). + Msg("received a publish") + + err := c.verifyMessage(publish.Params.Message) + if err != nil { + return xerrors.Errorf("failed to verify publish message: %v", err) + } + + err = c.handleMessage(publish.Params.Message, socket) + if err != nil { + return xerrors.Errorf("failed to handle a publish message: %v", err) + } + + return nil +} + +// Catchup is used to handle a catchup message. +func (c *Channel) Catchup(catchup method.Catchup) []message.Message { + c.log.Info().Str(msgID, strconv.Itoa(catchup.ID)).Msg("received a catchup") + + return c.inbox.GetSortedMessages() +} + +// Broadcast is used to handle a broadcast message. +func (c *Channel) Broadcast(broadcast method.Broadcast, socket socket.Socket) error { + return answer.NewInvalidActionError("broadcast is not supported") +} + +// NewFederationRegistry creates a new registry for the federation channel +func (c *Channel) NewFederationRegistry() registry.MessageRegistry { + registry := registry.NewMessageRegistry() + + registry.Register(messagedata.FederationRequestChallenge{}, c.processChallengeRequest) + registry.Register(messagedata.FederationExpect{}, c.processFederationExpect) + registry.Register(messagedata.FederationInit{}, c.processFederationInit) + registry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) + + return registry +} + +func (c *Channel) handleMessage(msg message.Message, + socket socket.Socket) error { + err := c.registry.Process(msg, socket) + if err != nil { + return xerrors.Errorf("failed to process message: %v", err) + } + + return nil +} + +// verifyMessage checks if a message in a Publish or Broadcast method is valid +func (c *Channel) verifyMessage(msg message.Message) error { + jsonData, err := base64.URLEncoding.DecodeString(msg.Data) + if err != nil { + return xerrors.Errorf("failed to decode message data: %v", err) + } + + // Verify the data + err = c.hub.GetSchemaValidator().VerifyJSON(jsonData, validation.Data) + if err != nil { + return xerrors.Errorf("failed to verify json schema: %w", err) + } + + // Check if the message already exists + if _, ok := c.inbox.GetMessage(msg.MessageID); ok { + return answer.NewError(-3, "message already exists") + } + + return nil +} + +func (c *Channel) processFederationInit(msg message.Message, + msgData interface{}, s socket.Socket) error { + + _, ok := msgData.(messagedata.FederationInit) + if !ok { + return xerrors.Errorf("message %v is not a federation#init message", + msgData) + } + + // check if it is from the local organizer + if c.localOrganizerPk != msg.Sender { + return answer.NewAccessDeniedError( + "Only local organizer is allowed to send federation#init") + } + + if c.state != None { + return answer.NewInternalServerError("The current state is %v", c.state) + } + c.state = Initiating + + var federationInit messagedata.FederationInit + + err := msg.UnmarshalData(&federationInit) + if err != nil { + c.state = None + return xerrors.Errorf("failed to unmarshal FederationInit data: %v", err) + } + + c.remoteServer, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) + if err != nil { + c.state = None + return answer.NewInternalServerError( + "failed to connect to server %v: %v", + federationInit.ServerAddress, err) + } + + // send the challenge to the other server + remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + challengePublish := 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: remoteChannel, + Message: federationInit.ChallengeMsg, + }, + } + + buf, err := json.Marshal(challengePublish) + if err != nil { + c.state = None + return xerrors.Errorf("failed to marshal challenge: %v", err) + } + + c.remoteServer.Send(buf) + + return nil +} + +func (c *Channel) processFederationExpect(msg message.Message, + msgData interface{}, s socket.Socket) error { + + _, ok := msgData.(messagedata.FederationExpect) + if !ok { + return xerrors.Errorf("message %v is not a federation#expect message", + msgData) + } + + // check if it is from the local organizer + if c.localOrganizerPk != msg.Sender { + return answer.NewAccessDeniedError( + "Only local organizer is allowed to send federation#expect") + } + + if c.state != None { + return answer.NewInternalServerError("The current state is %v", c.state) + } + + var federationExpect messagedata.FederationExpect + + err := msg.UnmarshalData(&federationExpect) + if err != nil { + return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) + } + + c.state = ExpectConnect + c.remoteOrganizerPk = federationExpect.PublicKey + + return nil +} + +func (c *Channel) processFederationChallenge(msg message.Message, + msgData interface{}, s socket.Socket) error { + + _, ok := msgData.(messagedata.FederationChallenge) + if !ok { + return xerrors.Errorf( + "message %v is not a federation#challenge message", msgData) + } + + // check if it is from the remote organizer + if c.remoteOrganizerPk != msg.Sender { + return answer.NewAccessDeniedError( + "Only remote organizer is allowed to send federation#challenge") + } + + if c.state != ExpectConnect { + return answer.NewInternalServerError("The current state is %v", c.state) + } + + var federationChallenge messagedata.FederationChallenge + + err := msg.UnmarshalData(&federationChallenge) + if err != nil { + return xerrors.Errorf( + "failed to unmarshal federationChallenge data: %v", err) + } + + if c.challenge.Value != federationChallenge.Value { + return answer.NewAccessDeniedError("Invalid challenge %v", + federationChallenge.Value) + } + + if c.challenge.ValidUntil < time.Now().Unix() { + return answer.NewAccessDeniedError("This challenge has expired: %v", + federationChallenge) + } + + c.state = Connected + + return nil +} + +func (c *Channel) processChallengeRequest(msg message.Message, + msgData interface{}, s socket.Socket) error { + + _, ok := msgData.(messagedata.FederationRequestChallenge) + if !ok { + return xerrors.Errorf( + "message %v is not a federation#request_challenge message", msgData) + } + + // check if it is from the local organizer + if c.localOrganizerPk != msg.Sender { + return answer.NewAccessDeniedError( + "Only local organizer is allowed to send federation#request_challenge") + } + + if c.state != None && c.state != ExpectConnect { + return answer.NewInternalServerError("The current state is %v", c.state) + } + + var federationRequestChallenge messagedata.FederationRequestChallenge + + err := msg.UnmarshalData(&federationRequestChallenge) + if err != nil { + return xerrors.Errorf( + "failed to unmarshal federationRequestChallenge data: %v", err) + } + + randomBytes := make([]byte, 32) + _, err = rand.Read(randomBytes) + if err != nil { + return answer.NewInternalServerError("Failed to generate random bytes: %v", err) + } + challengeValue := hex.EncodeToString(randomBytes) + expirationTime := time.Now().Add(time.Minute * 5).Unix() + + c.challenge = messagedata.Challenge{ + Value: challengeValue, + ValidUntil: expirationTime, + } + c.state = None + + // send back the challenge directly to the organizer only + s.Send(nil) //challengeMsg) + + return nil +} diff --git a/be1-go/channel/lao/lao.go b/be1-go/channel/lao/lao.go index 35a13c475f..f61cb6df43 100644 --- a/be1-go/channel/lao/lao.go +++ b/be1-go/channel/lao/lao.go @@ -11,6 +11,7 @@ import ( "popstellar/channel/coin" "popstellar/channel/consensus" "popstellar/channel/election" + "popstellar/channel/federation" "popstellar/channel/generalChirping" "popstellar/channel/reaction" "popstellar/channel/registry" @@ -122,6 +123,15 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, msg message.Me consensusCh := consensus.NewChannel(consensusPath, hub, log, organizerPubKey) hub.NotifyNewChannel(consensusPath, consensusCh, socket) + federationPath := fmt.Sprintf("%s/federation", channelID) + organizerPkBytes, err := organizerPubKey.MarshalBinary() + if err != nil { + return nil, xerrors.Errorf("failed to encode organizer key: %v", err) + } + organizerPk := base64.URLEncoding.EncodeToString(organizerPkBytes) + federationCh := federation.NewChannel(federationPath, hub, log, organizerPk) + hub.NotifyNewChannel(federationPath, federationCh, socket) + newChannel := &Channel{ channelID: channelID, sockets: channel.NewSockets(), @@ -138,7 +148,7 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, msg message.Me newChannel.registry = newChannel.NewLAORegistry() - err := newChannel.createAndSendLAOGreet() + err = newChannel.createAndSendLAOGreet() if err != nil { return nil, xerrors.Errorf("failed to send the greeting message: %v", err) } diff --git a/be1-go/hub/standard_hub/standard_hub.go b/be1-go/hub/standard_hub/standard_hub.go index bb08024c7a..dd9fce48c1 100644 --- a/be1-go/hub/standard_hub/standard_hub.go +++ b/be1-go/hub/standard_hub/standard_hub.go @@ -4,6 +4,8 @@ import ( "context" "encoding/base64" "encoding/json" + "github.com/gorilla/websocket" + "popstellar" "popstellar/channel" "popstellar/crypto" state "popstellar/hub/standard_hub/hub_state" @@ -598,6 +600,27 @@ func (h *Hub) GetPeersInfo() []method.ServerInfo { return h.peers.GetAllPeersInfo() } +func (h *Hub) ConnectToServerAsClient(serverAddress string) ( + *socket.ClientSocket, error) { + ws, _, err := websocket.DefaultDialer.Dial(serverAddress, nil) + if err != nil { + return nil, err + } + + wg := &sync.WaitGroup{} + done := make(chan struct{}) + log := popstellar.Logger + + client := socket.NewClientSocket(h.Receiver(), h.OnSocketClose(), ws, wg, + done, log) + wg.Add(2) + + go client.WritePump() + go client.ReadPump() + + return client, nil +} + func generateKeys() (kyber.Point, kyber.Scalar) { secret := suite.Scalar().Pick(suite.RandomStream()) point := suite.Point().Mul(secret, nil) diff --git a/be1-go/message/messagedata/mod.go b/be1-go/message/messagedata/mod.go index e5ca5fced2..14c1977112 100644 --- a/be1-go/message/messagedata/mod.go +++ b/be1-go/message/messagedata/mod.go @@ -49,6 +49,12 @@ const ( VoteActionCastVote = "cast_vote" VoteActionWriteIn = "write_in" + FederationObject = "federation" + FederationActionRequestChallenge = "request_challenge" + FederationActionChallenge = "challenge" + FederationActionInit = "init" + FederationActionExpect = "expect" + ChirpObject = "chirp" ChirpActionAdd = "add" ChirpActionDelete = "delete" From f759a06ab14a7065450e66f709c7e124136b89a1 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Thu, 18 Apr 2024 20:16:22 +0200 Subject: [PATCH 03/48] change FederationInit.challenge type to a base64 encoded message containing a FederationChallenge --- .../authentication/authentication_test.go | 4 ++ be1-go/channel/chirp/chirp_test.go | 4 ++ be1-go/channel/coin/coin_test.go | 4 ++ be1-go/channel/consensus/consensus_test.go | 4 ++ be1-go/channel/election/election_test.go | 4 ++ be1-go/channel/federation/federation.go | 72 ++++++++++++++++++- .../generalChirping/generalChirping_test.go | 4 ++ be1-go/channel/lao/lao_test.go | 4 ++ be1-go/channel/reaction/reaction_test.go | 4 ++ be1-go/message/messagedata/federation_init.go | 10 ++- .../federation_init/federation_init.json | 5 +- .../message/data/dataFederationInit.json | 19 +---- 12 files changed, 109 insertions(+), 29 deletions(-) diff --git a/be1-go/channel/authentication/authentication_test.go b/be1-go/channel/authentication/authentication_test.go index d184a8a4af..bee43300d8 100644 --- a/be1-go/channel/authentication/authentication_test.go +++ b/be1-go/channel/authentication/authentication_test.go @@ -300,6 +300,10 @@ func (h *fakeHub) SendAndHandleMessage(_ method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, diff --git a/be1-go/channel/chirp/chirp_test.go b/be1-go/channel/chirp/chirp_test.go index 830d7193d9..1267a406e4 100644 --- a/be1-go/channel/chirp/chirp_test.go +++ b/be1-go/channel/chirp/chirp_test.go @@ -644,6 +644,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/coin/coin_test.go b/be1-go/channel/coin/coin_test.go index cd8f5e4523..ca6c0c7f6d 100644 --- a/be1-go/channel/coin/coin_test.go +++ b/be1-go/channel/coin/coin_test.go @@ -829,6 +829,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/consensus/consensus_test.go b/be1-go/channel/consensus/consensus_test.go index 5f9ebd8719..42711a918a 100644 --- a/be1-go/channel/consensus/consensus_test.go +++ b/be1-go/channel/consensus/consensus_test.go @@ -2042,6 +2042,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + func (h *fakeHub) NotifyNewChannel(channelID string, channel channel.Channel, socket socket.Socket) {} // fakeSocket is a fake implementation of a socket diff --git a/be1-go/channel/election/election_test.go b/be1-go/channel/election/election_test.go index 8ede712942..31df432c10 100644 --- a/be1-go/channel/election/election_test.go +++ b/be1-go/channel/election/election_test.go @@ -777,6 +777,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index cbb739ead5..76366b9403 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -1,12 +1,12 @@ package federation import ( + "crypto/rand" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "github.com/rs/zerolog" - "golang.org/x/exp/rand" "golang.org/x/xerrors" "popstellar/channel" "popstellar/channel/registry" @@ -209,6 +209,17 @@ func (c *Channel) processFederationInit(msg message.Message, // send the challenge to the other server remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + challengeMsgBase64, err := base64.URLEncoding.DecodeString(federationInit.ChallengeMsg) + if err != nil { + return xerrors.Errorf("failed to decode challenge base64: %v", err) + } + + var challengeMsg message.Message + err = challengeMsg.UnmarshalData(&challengeMsgBase64) + if err != nil { + return xerrors.Errorf("failed to unmarshal message: %v", err) + } + challengePublish := method.Publish{ Base: query.Base{ JSONRPCBase: jsonrpc.JSONRPCBase{ @@ -222,7 +233,7 @@ func (c *Channel) processFederationInit(msg message.Message, Message message.Message `json:"message"` }{ Channel: remoteChannel, - Message: federationInit.ChallengeMsg, + Message: challengeMsg, }, } @@ -352,8 +363,63 @@ func (c *Channel) processChallengeRequest(msg message.Message, } c.state = None + federationChallenge := messagedata.FederationChallenge{ + Object: "federation", + Action: "challenge", + Value: c.challenge.Value, + Timestamp: c.challenge.ValidUntil, + } + + challengeData, err := json.Marshal(federationChallenge) + if err != nil { + return xerrors.Errorf( + "failed to marshal federationChallenge data: %v", err) + } + data := base64.URLEncoding.EncodeToString(challengeData) + + senderBytes, err := c.hub.GetPubKeyServ().MarshalBinary() + if err != nil { + return xerrors.Errorf("failed to marshal server public key: %v", err) + } + sender := base64.URLEncoding.EncodeToString(senderBytes) + + signatureBytes, err := c.hub.Sign(challengeData) + if err != nil { + return xerrors.Errorf("failed to sign message: %v", err) + } + signature := base64.URLEncoding.EncodeToString(signatureBytes) + + challengeMsg := message.Message{ + Data: data, + Sender: sender, + Signature: signature, + MessageID: messagedata.Hash(data, signature), + WitnessSignatures: []message.WitnessSignature{}, + } + + rpcMessage := method.Broadcast{ + Base: query.Base{ + JSONRPCBase: jsonrpc.JSONRPCBase{ + JSONRPC: "2.0", + }, + Method: "broadcast", + }, + Params: struct { + Channel string `json:"channel"` + Message message.Message `json:"message"` + }{ + c.channelID, + challengeMsg, + }, + } + + buf, err := json.Marshal(&rpcMessage) + if err != nil { + return xerrors.Errorf("failed to marshal broadcast query: %v", err) + } + // send back the challenge directly to the organizer only - s.Send(nil) //challengeMsg) + s.Send(buf) return nil } diff --git a/be1-go/channel/generalChirping/generalChirping_test.go b/be1-go/channel/generalChirping/generalChirping_test.go index e70ba74ac9..280b1dd234 100644 --- a/be1-go/channel/generalChirping/generalChirping_test.go +++ b/be1-go/channel/generalChirping/generalChirping_test.go @@ -296,6 +296,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/lao/lao_test.go b/be1-go/channel/lao/lao_test.go index 2d7b76ff3c..088afc39ab 100644 --- a/be1-go/channel/lao/lao_test.go +++ b/be1-go/channel/lao/lao_test.go @@ -836,6 +836,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a Socket // // - implements socket.Socket diff --git a/be1-go/channel/reaction/reaction_test.go b/be1-go/channel/reaction/reaction_test.go index f895673b6b..cc3cec3d0d 100644 --- a/be1-go/channel/reaction/reaction_test.go +++ b/be1-go/channel/reaction/reaction_test.go @@ -688,6 +688,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/message/messagedata/federation_init.go b/be1-go/message/messagedata/federation_init.go index 3e013fc6db..e214f72ec0 100644 --- a/be1-go/message/messagedata/federation_init.go +++ b/be1-go/message/messagedata/federation_init.go @@ -1,16 +1,14 @@ package messagedata -import "popstellar/message/query/method/message" - // FederationInit defines a message data type FederationInit struct { Object string `json:"object"` Action string `json:"action"` - LaoId string `json:"lao_id"` - ServerAddress string `json:"server_address"` - PublicKey string `json:"public_key"` - ChallengeMsg message.Message `json:"challenge"` + LaoId string `json:"lao_id"` + ServerAddress string `json:"server_address"` + PublicKey string `json:"public_key"` + ChallengeMsg string `json:"challenge"` } // GetObject implements MessageData diff --git a/protocol/examples/messageData/federation_init/federation_init.json b/protocol/examples/messageData/federation_init/federation_init.json index 23e46f26bd..6d50160d62 100644 --- a/protocol/examples/messageData/federation_init/federation_init.json +++ b/protocol/examples/messageData/federation_init/federation_init.json @@ -4,8 +4,5 @@ "lao_id": "fzJSZjKf-2cbXH7kds9H8NORuuFIRLkevJlN7qQemjo=", "public_key": "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "server_address": "wss://epfl.ch:9000/server", - "challenge": { - "value": "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4", - "valid_until": 1712854874 - } + "challenge": "ewogICAgICAgICJEYXRhIjogImV5SnZZbXBsWTNRaU9pSm1aV1JsY21GMGFXOXVJaXdpWVdOMGFXOXVJam9pWTJoaGJHeGxibWRsSWl3aWRtRnNkV1VpT2lKbFltRXpaVEkwWldaalpEQmlOVE5tWVRZNU9UQTRZbUZrTldReFkySTJPVGxrTnprNE1HUTVNekV3T1dSaE1HSXlZbVprTlRBek4yTXlZemc1WldVd0lpd2lkR2x0WlhOMFlXMXdJam94TnpFek16ZzFOVFk0ZlE9PSIsCiAgICAgICAgIlNlbmRlciI6ICJ6WGd6UWFhX05wVWUtdjBaa180cThrMTg0b2hRNW5UUWhCREtnbmNIenE0PSIsCiAgICAgICAgIlNpZ25hdHVyZSI6ICJCSUxZd1lrVDV0T0JMNHJDRDd5dmhCa2hBWXFSWE9JM2FqUTJ1SjFnQWstZzZuUmMzOHZNTW5sSFNodU5DUTNkUUZYWVpQbjM3Y0NGZWxoV0dqWThCZz09IiwKICAgICAgICAiTWVzc2FnZUlEIjogInNEX1BkcnlCdU9yMTRfNjVoOEwtZTFsemRRcERXeFVBbmd0dTF1d3FnRUk9IiwKICAgICAgICAiV2l0bmVzc1NpZ25hdHVyZXMiOiBbXQogICAgfQ==" } \ No newline at end of file diff --git a/protocol/query/method/message/data/dataFederationInit.json b/protocol/query/method/message/data/dataFederationInit.json index f391407681..5b29752b20 100644 --- a/protocol/query/method/message/data/dataFederationInit.json +++ b/protocol/query/method/message/data/dataFederationInit.json @@ -26,22 +26,9 @@ "$comment": "public key of the remote organizer" }, "challenge": { - "type": "object", - "properties": { - "value": { - "type": "string", - "contentEncoding": "hex", - "pattern": "^[0-9a-fA-F]{64}$", - "$comment": "A 32 bytes array encoded in hexadecimal" - }, - "valid_until": { - "type": "integer", - "description": "[Timestamp] of the expiration time", - "minimum": 0 - } - }, - "additionalProperties": false, - "required": ["value", "valid_until"] + "type": "string", + "contentEncoding": "base64", + "$comment": "message/message containing a FederationChallenge data" } }, "additionalProperties": false, From 5978ac20a280ca5d082b530469e575a82e3fdb49 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Thu, 18 Apr 2024 20:31:25 +0200 Subject: [PATCH 04/48] Store socket of remote server after challenge --- be1-go/channel/federation/federation.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index 76366b9403..e0ccf91f03 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -49,7 +49,7 @@ type Channel struct { localOrganizerPk string remoteOrganizerPk string - remoteServer *socket.ClientSocket + remoteServer socket.Socket challenge messagedata.Challenge @@ -318,6 +318,9 @@ func (c *Channel) processFederationChallenge(msg message.Message, } c.state = Connected + c.remoteServer = s + // Send Federation result to S1 + // c.remoteServer.Send(...) return nil } From afe91c4ac1529f5652b43cc25753f4a7ef05df25 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 23 Apr 2024 21:27:14 +0200 Subject: [PATCH 05/48] update json schema --- be1-go/channel/federation/federation.go | 20 ++----- be1-go/message/messagedata/federation_init.go | 10 ++-- .../method/message => test}/message_test.go | 15 ++--- be1-go/validation/schema_validator.go | 8 +++ .../federation_init/federation_init.json | 8 ++- .../data/dataFederationConnExpect.json | 54 ------------------ .../message/data/dataFederationConnInit.json | 55 ------------------- .../message/data/dataFederationInit.json | 3 +- 8 files changed, 36 insertions(+), 137 deletions(-) rename be1-go/message/{query/method/message => test}/message_test.go (77%) delete mode 100644 protocol/query/method/message/data/dataFederationConnExpect.json delete mode 100644 protocol/query/method/message/data/dataFederationConnInit.json diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index e0ccf91f03..ea0330ff02 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -49,7 +49,8 @@ type Channel struct { localOrganizerPk string remoteOrganizerPk string - remoteServer socket.Socket + remoteChannel string + remoteServer socket.Socket challenge messagedata.Challenge @@ -208,17 +209,7 @@ func (c *Channel) processFederationInit(msg message.Message, } // send the challenge to the other server - remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - challengeMsgBase64, err := base64.URLEncoding.DecodeString(federationInit.ChallengeMsg) - if err != nil { - return xerrors.Errorf("failed to decode challenge base64: %v", err) - } - - var challengeMsg message.Message - err = challengeMsg.UnmarshalData(&challengeMsgBase64) - if err != nil { - return xerrors.Errorf("failed to unmarshal message: %v", err) - } + c.remoteChannel = fmt.Sprintf("/root/%s/federation", federationInit.LaoId) challengePublish := method.Publish{ Base: query.Base{ @@ -232,8 +223,8 @@ func (c *Channel) processFederationInit(msg message.Message, Channel string `json:"channel"` Message message.Message `json:"message"` }{ - Channel: remoteChannel, - Message: challengeMsg, + Channel: c.remoteChannel, + Message: federationInit.ChallengeMsg, }, } @@ -276,6 +267,7 @@ func (c *Channel) processFederationExpect(msg message.Message, c.state = ExpectConnect c.remoteOrganizerPk = federationExpect.PublicKey + c.remoteChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) return nil } diff --git a/be1-go/message/messagedata/federation_init.go b/be1-go/message/messagedata/federation_init.go index e214f72ec0..3e013fc6db 100644 --- a/be1-go/message/messagedata/federation_init.go +++ b/be1-go/message/messagedata/federation_init.go @@ -1,14 +1,16 @@ package messagedata +import "popstellar/message/query/method/message" + // FederationInit defines a message data type FederationInit struct { Object string `json:"object"` Action string `json:"action"` - LaoId string `json:"lao_id"` - ServerAddress string `json:"server_address"` - PublicKey string `json:"public_key"` - ChallengeMsg string `json:"challenge"` + LaoId string `json:"lao_id"` + ServerAddress string `json:"server_address"` + PublicKey string `json:"public_key"` + ChallengeMsg message.Message `json:"challenge"` } // GetObject implements MessageData diff --git a/be1-go/message/query/method/message/message_test.go b/be1-go/message/test/message_test.go similarity index 77% rename from be1-go/message/query/method/message/message_test.go rename to be1-go/message/test/message_test.go index 8100573410..be13e1d173 100644 --- a/be1-go/message/query/method/message/message_test.go +++ b/be1-go/message/test/message_test.go @@ -1,17 +1,18 @@ -package message +package test import ( "encoding/base64" "os" "path/filepath" "popstellar/message/messagedata" + "popstellar/message/query/method/message" "testing" "github.com/stretchr/testify/require" ) func Test_UnmarshalData(t *testing.T) { - messageDataPath := filepath.Join("..", "..", "..", "..", "..", "protocol", + messageDataPath := filepath.Join("..", "..", "..", "protocol", "examples", "messageData", "lao_create", "lao_create.json") messageDataBuf, err := os.ReadFile(messageDataPath) @@ -20,23 +21,23 @@ func Test_UnmarshalData(t *testing.T) { laoCreate := messagedata.LaoCreate{} electionSetup := messagedata.ElectionSetup{} - message := Message{ + msg := message.Message{ Data: string(messageDataBuf), } - err = message.UnmarshalData(&laoCreate) + err = msg.UnmarshalData(&laoCreate) require.Error(t, err) - err = message.UnmarshalData(&electionSetup) + err = msg.UnmarshalData(&electionSetup) require.Error(t, err) messageData := base64.URLEncoding.EncodeToString(messageDataBuf) - message = Message{ + msg = message.Message{ Data: messageData, } - err = message.UnmarshalData(&laoCreate) + err = msg.UnmarshalData(&laoCreate) require.NoError(t, err) require.Equal(t, "lao", laoCreate.Object) diff --git a/be1-go/validation/schema_validator.go b/be1-go/validation/schema_validator.go index 273088d0a2..4919fd3378 100644 --- a/be1-go/validation/schema_validator.go +++ b/be1-go/validation/schema_validator.go @@ -49,6 +49,7 @@ func init() { // Override the defaults for loading files and decoding base64 encoded data jsonschema.Loaders["file"] = loadFileURL jsonschema.Decoders["base64"] = base64.URLEncoding.DecodeString + jsonschema.Loaders["https"] = loadHttpURL } // VerifyJSON verifies that the `msg` follow the schema protocol of name @@ -137,3 +138,10 @@ func loadFileURL(s string) (io.ReadCloser, error) { return protocolFS.Open(path) } + +func loadHttpURL(s string) (io.ReadCloser, error) { + // If for example a message data use a $ref to ../message.json, + // it will try to load using the baseURL => replace path, + // so that only local file is loaded. + return loadFileURL(strings.ReplaceAll(s, baseURL, "file://")) +} diff --git a/protocol/examples/messageData/federation_init/federation_init.json b/protocol/examples/messageData/federation_init/federation_init.json index 6d50160d62..a99fc4b757 100644 --- a/protocol/examples/messageData/federation_init/federation_init.json +++ b/protocol/examples/messageData/federation_init/federation_init.json @@ -4,5 +4,11 @@ "lao_id": "fzJSZjKf-2cbXH7kds9H8NORuuFIRLkevJlN7qQemjo=", "public_key": "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "server_address": "wss://epfl.ch:9000/server", - "challenge": "ewogICAgICAgICJEYXRhIjogImV5SnZZbXBsWTNRaU9pSm1aV1JsY21GMGFXOXVJaXdpWVdOMGFXOXVJam9pWTJoaGJHeGxibWRsSWl3aWRtRnNkV1VpT2lKbFltRXpaVEkwWldaalpEQmlOVE5tWVRZNU9UQTRZbUZrTldReFkySTJPVGxrTnprNE1HUTVNekV3T1dSaE1HSXlZbVprTlRBek4yTXlZemc1WldVd0lpd2lkR2x0WlhOMFlXMXdJam94TnpFek16ZzFOVFk0ZlE9PSIsCiAgICAgICAgIlNlbmRlciI6ICJ6WGd6UWFhX05wVWUtdjBaa180cThrMTg0b2hRNW5UUWhCREtnbmNIenE0PSIsCiAgICAgICAgIlNpZ25hdHVyZSI6ICJCSUxZd1lrVDV0T0JMNHJDRDd5dmhCa2hBWXFSWE9JM2FqUTJ1SjFnQWstZzZuUmMzOHZNTW5sSFNodU5DUTNkUUZYWVpQbjM3Y0NGZWxoV0dqWThCZz09IiwKICAgICAgICAiTWVzc2FnZUlEIjogInNEX1BkcnlCdU9yMTRfNjVoOEwtZTFsemRRcERXeFVBbmd0dTF1d3FnRUk9IiwKICAgICAgICAiV2l0bmVzc1NpZ25hdHVyZXMiOiBbXQogICAgfQ==" + "challenge": { + "data": "eyJvYmplY3QiOiJmZWRlcmF0aW9uIiwiYWN0aW9uIjoiY2hhbGxlbmdlIiwidmFsdWUiOiJlYmEzZTI0ZWZjZDBiNTNmYTY5OTA4YmFkNWQxY2I2OTlkNzk4MGQ5MzEwOWRhMGIyYmZkNTAzN2MyYzg5ZWUwIiwidGltZXN0YW1wIjoxNzEzMzg1NTY4fQ==", + "sender": "zXgzQaa_NpUe-v0Zk_4q8k184ohQ5nTQhBDKgncHzq4=", + "signature": "BILYwYkT5tOBL4rCD7yvhBkhAYqRXOI3ajQ2uJ1gAk-g6nRc38vMMnlHShuNCQ3dQFXYZPn37cCFelhWGjY8Bg==", + "message_id": "sD_PdryBuOr14_65h8L-e1lzdQpDWxUAngtu1uwqgEI=", + "witness_signatures": [] + } } \ No newline at end of file diff --git a/protocol/query/method/message/data/dataFederationConnExpect.json b/protocol/query/method/message/data/dataFederationConnExpect.json deleted file mode 100644 index ec092791b4..0000000000 --- a/protocol/query/method/message/data/dataFederationConnExpect.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/dedis/popstellar/master/protocol/query/method/message/data/dataFederationConnExpect.json", - "description": "Sent by an organizer client to its server, signals that a connection from a remote LAO is expected", - "type": "object", - "properties": { - "object": { - "const": "conn" - }, - "action": { - "const": "expect" - }, - "lao_id": { - "type": "string", - "contentEncoding": "base64", - "$comment": "ID of the remote LAO" - }, - "server_address": { - "type": "string", - "pattern": "^(ws|wss):\\/\\/.*(:\\d{0,5})?\\/.*$", - "$comment": "public address of the remote organizer server" - }, - "public_key": { - "type": "string", - "contentEncoding": "base64", - "$comment": "public key of the remote organizer" - }, - "challenge": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The challenge value, which is a string of 32 random bytes encoded in base64 format" - }, - "valid_until": { - "type": "integer", - "description": "[Timestamp] of the expiration time", - "minimum": 0 - } - }, - "additionalProperties": false, - "required": ["value", "valid_until"] - } - }, - "additionalProperties": false, - "required": [ - "object", - "action", - "lao_id", - "server_address", - "public_key", - "challenge" - ] -} \ No newline at end of file diff --git a/protocol/query/method/message/data/dataFederationConnInit.json b/protocol/query/method/message/data/dataFederationConnInit.json deleted file mode 100644 index 16fe0c7e23..0000000000 --- a/protocol/query/method/message/data/dataFederationConnInit.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://raw.githubusercontent.com/dedis/popstellar/master/protocol/query/method/message/data/dataFederationConnInit.json", - "description": "Sent by an organizer client to its server, initiates a connection to a remote LAO", - "type": "object", - "properties": { - "object": { - "const": "conn" - }, - "action": { - "const": "init" - }, - "lao_id": { - "type": "string", - "contentEncoding": "base64", - "$comment": "ID of the remote LAO" - }, - "server_address": { - "type": "string", - "pattern": "^(ws|wss):\\/\\/.*(:\\d{0,5})?\\/.*$", - "$comment": "public address of the remote organizer server" - - }, - "public_key": { - "type": "string", - "contentEncoding": "base64", - "$comment": "public key of the remote organizer" - }, - "challenge": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The challenge value, which is a string of 32 random bytes encoded in base64 format" - }, - "valid_until": { - "type": "integer", - "description": "[Timestamp] of the expiration time", - "minimum": 0 - } - }, - "additionalProperties": false, - "required": ["value", "valid_until"] - } - }, - "additionalProperties": false, - "required": [ - "object", - "action", - "lao_id", - "server_address", - "public_key", - "challenge" - ] -} \ No newline at end of file diff --git a/protocol/query/method/message/data/dataFederationInit.json b/protocol/query/method/message/data/dataFederationInit.json index 5b29752b20..5f8dc8a6eb 100644 --- a/protocol/query/method/message/data/dataFederationInit.json +++ b/protocol/query/method/message/data/dataFederationInit.json @@ -26,8 +26,7 @@ "$comment": "public key of the remote organizer" }, "challenge": { - "type": "string", - "contentEncoding": "base64", + "$ref": "../message.json", "$comment": "message/message containing a FederationChallenge data" } }, From e1e2c57f4274b3cb9e3d9bb0a4a7a2292b7ccf27 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 24 Apr 2024 02:08:50 +0200 Subject: [PATCH 06/48] Add some tests for federation --- be1-go/channel/federation/federation.go | 25 +- be1-go/channel/federation/federation_test.go | 506 ++++++++++++++++++ .../federation_request_challenge.go | 14 +- be1-go/message/messagedata/mod.go | 2 +- 4 files changed, 529 insertions(+), 18 deletions(-) create mode 100644 be1-go/channel/federation/federation_test.go diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index ea0330ff02..c25c31ec07 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -133,7 +133,7 @@ func (c *Channel) Broadcast(broadcast method.Broadcast, socket socket.Socket) er func (c *Channel) NewFederationRegistry() registry.MessageRegistry { registry := registry.NewMessageRegistry() - registry.Register(messagedata.FederationRequestChallenge{}, c.processChallengeRequest) + registry.Register(messagedata.FederationChallengeRequest{}, c.processChallengeRequest) registry.Register(messagedata.FederationExpect{}, c.processFederationExpect) registry.Register(messagedata.FederationInit{}, c.processFederationInit) registry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) @@ -175,7 +175,7 @@ func (c *Channel) verifyMessage(msg message.Message) error { func (c *Channel) processFederationInit(msg message.Message, msgData interface{}, s socket.Socket) error { - _, ok := msgData.(messagedata.FederationInit) + _, ok := msgData.(*messagedata.FederationInit) if !ok { return xerrors.Errorf("message %v is not a federation#init message", msgData) @@ -242,7 +242,7 @@ func (c *Channel) processFederationInit(msg message.Message, func (c *Channel) processFederationExpect(msg message.Message, msgData interface{}, s socket.Socket) error { - _, ok := msgData.(messagedata.FederationExpect) + _, ok := msgData.(*messagedata.FederationExpect) if !ok { return xerrors.Errorf("message %v is not a federation#expect message", msgData) @@ -265,6 +265,11 @@ func (c *Channel) processFederationExpect(msg message.Message, return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) } + if federationExpect.Challenge != c.challenge { + return answer.NewAccessDeniedError("Invalid challenge %v", + federationExpect.Challenge) + } + c.state = ExpectConnect c.remoteOrganizerPk = federationExpect.PublicKey c.remoteChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) @@ -275,7 +280,7 @@ func (c *Channel) processFederationExpect(msg message.Message, func (c *Channel) processFederationChallenge(msg message.Message, msgData interface{}, s socket.Socket) error { - _, ok := msgData.(messagedata.FederationChallenge) + _, ok := msgData.(*messagedata.FederationChallenge) if !ok { return xerrors.Errorf( "message %v is not a federation#challenge message", msgData) @@ -320,28 +325,28 @@ func (c *Channel) processFederationChallenge(msg message.Message, func (c *Channel) processChallengeRequest(msg message.Message, msgData interface{}, s socket.Socket) error { - _, ok := msgData.(messagedata.FederationRequestChallenge) + _, ok := msgData.(*messagedata.FederationChallengeRequest) if !ok { return xerrors.Errorf( - "message %v is not a federation#request_challenge message", msgData) + "message %v is not a federation#challenge_request message", msgData) } // check if it is from the local organizer if c.localOrganizerPk != msg.Sender { return answer.NewAccessDeniedError( - "Only local organizer is allowed to send federation#request_challenge") + "Only local organizer is allowed to send federation#challenge_request") } if c.state != None && c.state != ExpectConnect { return answer.NewInternalServerError("The current state is %v", c.state) } - var federationRequestChallenge messagedata.FederationRequestChallenge + var federationChallengeRequest messagedata.FederationChallengeRequest - err := msg.UnmarshalData(&federationRequestChallenge) + err := msg.UnmarshalData(&federationChallengeRequest) if err != nil { return xerrors.Errorf( - "failed to unmarshal federationRequestChallenge data: %v", err) + "failed to unmarshal federationChallengeRequest data: %v", err) } randomBytes := make([]byte, 32) diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go new file mode 100644 index 0000000000..3493f03011 --- /dev/null +++ b/be1-go/channel/federation/federation_test.go @@ -0,0 +1,506 @@ +package federation + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/sign/schnorr" + "golang.org/x/sync/semaphore" + "golang.org/x/xerrors" + "io" + "popstellar/channel" + "popstellar/crypto" + jsonrpc "popstellar/message" + "popstellar/message/messagedata" + "popstellar/message/query" + "popstellar/message/query/method" + "popstellar/message/query/method/message" + "popstellar/network/socket" + "popstellar/validation" + "sync" + "testing" + "time" +) + +const ( + localServerAddress = "ws://localhost:19008/client" + remoteServerAddress = "ws://localhost:19009/client" + localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" + remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" + localLaoChannel = "/root/" + localLaoId + localFedChannel = localLaoChannel + "/federation" +) + +// TestChannel_FederationRequestChallenge tests that a FederationChallenge is +// received after a valid FederationChallengeRequest is published by the +// organizer +func Test_FederationRequestChallenge(t *testing.T) { + organizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + requestData := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: time.Now().Unix(), + } + + requestMsg := generateMessage(t, organizerKeypair, requestData) + publishMsg := generatePublish(t, localFedChannel, requestMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) + + require.NoError(t, socket.err) + require.NotNil(t, socket.msg) + + var challengePublish method.Publish + err = json.Unmarshal(socket.msg, &challengePublish) + require.NoError(t, err) + + require.Equal(t, localFedChannel, challengePublish.Params.Channel) + challengeMsg := challengePublish.Params.Message + + dataBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Data) + require.NoError(t, err) + signatureBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Signature) + require.NoError(t, err) + + err = schnorr.Verify(crypto.Suite, fakeHub.pubKeyServ, dataBytes, signatureBytes) + require.NoError(t, err) + + var challenge messagedata.FederationChallenge + err = challengeMsg.UnmarshalData(&challenge) + require.NoError(t, err) + + require.Equal(t, messagedata.FederationObject, challenge.Object) + require.Equal(t, messagedata.FederationActionChallenge, challenge.Action) + require.Greater(t, challenge.Timestamp, time.Now().Unix()) + bytes, err := hex.DecodeString(challenge.Value) + require.NoError(t, err) + require.Len(t, bytes, 32) +} + +func Test_FederationRequestChallenge_not_organizer(t *testing.T) { + organizerKeypair := generateKeyPair(t) + notOrganizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + requestData := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: time.Now().Unix(), + } + + requestMsg := generateMessage(t, notOrganizerKeypair, requestData) + publishMsg := generatePublish(t, localFedChannel, requestMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.Error(t, err) + + require.Nil(t, socket.msg) +} + +func Test_FederationExpect(t *testing.T) { + organizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + requestData := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: time.Now().Unix(), + } + + requestMsg := generateMessage(t, organizerKeypair, requestData) + publishMsg := generatePublish(t, localFedChannel, requestMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) + + require.NoError(t, socket.err) + require.NotNil(t, socket.msg) + + var challengePublish method.Publish + err = json.Unmarshal(socket.msg, &challengePublish) + require.NoError(t, err) + + require.Equal(t, localFedChannel, challengePublish.Params.Channel) + challengeMsg := challengePublish.Params.Message + + var challenge messagedata.FederationChallenge + err = challengeMsg.UnmarshalData(&challenge) + require.NoError(t, err) + + remoteOrganizerKeypair := generateKeyPair(t) + + federationExpect := messagedata.FederationExpect{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionExpect, + LaoId: remoteLaoId, + ServerAddress: remoteServerAddress, + PublicKey: remoteOrganizerKeypair.publicKey, + Challenge: messagedata.Challenge{ + Value: challenge.Value, + ValidUntil: challenge.Timestamp, + }, + } + + federationMsg := generateMessage(t, organizerKeypair, federationExpect) + federationPublish := generatePublish(t, localFedChannel, federationMsg) + + err = fedChannel.Publish(federationPublish, socket) + require.NoError(t, err) + + require.NoError(t, socket.err) + require.NotNil(t, socket.msg) +} + +func Test_FederationExpect_with_invalid_challenge(t *testing.T) { + organizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + requestData := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: time.Now().Unix(), + } + + requestMsg := generateMessage(t, organizerKeypair, requestData) + publishMsg := generatePublish(t, localFedChannel, requestMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) + + require.NoError(t, socket.err) + require.NotNil(t, socket.msg) + + remoteOrganizerKeypair := generateKeyPair(t) + + valueBytes := make([]byte, 32) + _, _ = rand.Read(valueBytes) + + federationExpect := messagedata.FederationExpect{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionExpect, + LaoId: remoteLaoId, + ServerAddress: remoteServerAddress, + PublicKey: remoteOrganizerKeypair.publicKey, + Challenge: messagedata.Challenge{ + Value: hex.EncodeToString(valueBytes), + ValidUntil: time.Now().Unix(), + }, + } + + expectMsg := generateMessage(t, organizerKeypair, federationExpect) + expectPublish := generatePublish(t, localFedChannel, expectMsg) + + err = fedChannel.Publish(expectPublish, socket) + require.Error(t, err) +} + +func Test_FederationChallenge_not_organizer(t *testing.T) { + organizerKeypair := generateKeyPair(t) + notOrganizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + requestData := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: time.Now().Unix(), + } + + requestMsg := generateMessage(t, organizerKeypair, requestData) + publishMsg := generatePublish(t, localFedChannel, requestMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) + + require.NoError(t, socket.err) + require.NotNil(t, socket.msg) + + var challengePublish method.Publish + err = json.Unmarshal(socket.msg, &challengePublish) + require.NoError(t, err) + + require.Equal(t, localFedChannel, challengePublish.Params.Channel) + challengeMsg := challengePublish.Params.Message + + var challenge messagedata.FederationChallenge + err = challengeMsg.UnmarshalData(&challenge) + require.NoError(t, err) + + remoteOrganizerKeypair := generateKeyPair(t) + + federationExpect := messagedata.FederationExpect{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionExpect, + LaoId: remoteLaoId, + ServerAddress: remoteServerAddress, + PublicKey: remoteOrganizerKeypair.publicKey, + Challenge: messagedata.Challenge{ + Value: challenge.Value, + ValidUntil: challenge.Timestamp, + }, + } + + federationMsg := generateMessage(t, notOrganizerKeypair, federationExpect) + federationPublish := generatePublish(t, localFedChannel, federationMsg) + + err = fedChannel.Publish(federationPublish, socket) + require.Error(t, err) +} + +func Test_FederationInit(t *testing.T) { + +} + +// ----------------------------------------------------------------------------- +// Utility functions + +type keypair struct { + public kyber.Point + publicKey string + private kyber.Scalar +} + +var nolog = zerolog.New(io.Discard) +var suite = crypto.Suite + +func generateKeyPair(t *testing.T) keypair { + secret := suite.Scalar().Pick(suite.RandomStream()) + point := suite.Point().Mul(secret, nil) + + pkBuf, err := point.MarshalBinary() + require.NoError(t, err) + + pkBase64 := base64.URLEncoding.EncodeToString(pkBuf) + + return keypair{point, pkBase64, secret} +} + +type fakeHub struct { + clientAddress string + + messageChan chan socket.IncomingMessage + + sync.RWMutex + channelByID map[string]channel.Channel + + closedSockets chan string + + pubKeyOwner kyber.Point + + pubKeyServ kyber.Point + secKeyServ kyber.Scalar + + schemaValidator *validation.SchemaValidator + + stop chan struct{} + + workers *semaphore.Weighted + + log zerolog.Logger + + laoFac channel.LaoFactory +} + +// NewFakeHub returns a fake Hub. +func NewFakeHub(clientAddress string, publicOrg kyber.Point, log zerolog.Logger, laoFac channel.LaoFactory) (*fakeHub, error) { + + schemaValidator, err := validation.NewSchemaValidator(log) + if err != nil { + return nil, xerrors.Errorf("failed to create the schema validator: %v", err) + } + + log = log.With().Str("role", "base hub").Logger() + + pubServ, secServ := generateKeys() + + hub := fakeHub{ + clientAddress: clientAddress, + messageChan: make(chan socket.IncomingMessage), + channelByID: make(map[string]channel.Channel), + closedSockets: make(chan string), + pubKeyOwner: publicOrg, + pubKeyServ: pubServ, + secKeyServ: secServ, + schemaValidator: schemaValidator, + stop: make(chan struct{}), + workers: semaphore.NewWeighted(10), + log: log, + laoFac: laoFac, + } + + return &hub, nil +} + +func generateKeys() (kyber.Point, kyber.Scalar) { + secret := suite.Scalar().Pick(suite.RandomStream()) + point := suite.Point().Mul(secret, nil) + + return point, secret +} + +func (h *fakeHub) NotifyNewChannel(channeID string, channel channel.Channel, socket socket.Socket) { + h.Lock() + h.channelByID[channeID] = channel + h.Unlock() +} + +// GetPubKeyOwner implements channel.HubFunctionalities +func (h *fakeHub) GetPubKeyOwner() kyber.Point { + return h.pubKeyOwner +} + +// GetPubKeyServ implements channel.HubFunctionalities +func (h *fakeHub) GetPubKeyServ() kyber.Point { + return h.pubKeyServ +} + +// GetClientServerAddress implements channel.HubFunctionalities +func (h *fakeHub) GetClientServerAddress() string { + return h.clientAddress +} + +// Sign implements channel.HubFunctionalities +func (h *fakeHub) Sign(data []byte) ([]byte, error) { + signatureBuf, err := schnorr.Sign(crypto.Suite, h.secKeyServ, data) + if err != nil { + return nil, xerrors.Errorf("failed to sign the data: %v", err) + } + + return signatureBuf, nil +} + +// NotifyWitnessMessage implements channel.HubFunctionalities +func (h *fakeHub) NotifyWitnessMessage(messageId string, publicKey string, signature string) {} + +// GetPeersInfo implements channel.HubFunctionalities +func (h *fakeHub) GetPeersInfo() []method.ServerInfo { + return nil +} + +func (h *fakeHub) GetSchemaValidator() validation.SchemaValidator { + return *h.schemaValidator +} + +func (h *fakeHub) GetServerNumber() int { + return 0 +} + +func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { + return nil +} + +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { + return nil, nil +} + +// fakeSocket is a fake implementation of a Socket +// +// - implements socket.Socket +type fakeSocket struct { + socket.Socket + + resultID int + res []message.Message + msg []byte + + err error + + // the sockSocket ID + id string +} + +// Send implements socket.Socket +func (f *fakeSocket) Send(msg []byte) { + f.msg = msg +} + +// SendResult implements socket.Socket +func (f *fakeSocket) SendResult(id int, res []message.Message, missingMsgs map[string][]message.Message) { + f.resultID = id + f.res = res +} + +// SendError implements socket.Socket +func (f *fakeSocket) SendError(id *int, err error) { + if id != nil { + f.resultID = *id + } else { + f.resultID = -1 + } + f.err = err +} + +func (f *fakeSocket) ID() string { + return f.id +} + +func generatePublish(t *testing.T, channel string, msg message.Message) method. + Publish { + return 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: channel, + Message: msg, + }, + } +} + +func generateMessage(t *testing.T, keys keypair, + data messagedata.MessageData) message.Message { + + dataBytes, err := json.Marshal(data) + require.NoError(t, err) + + dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) + + signatureBytes, err := schnorr.Sign(crypto.Suite, keys.private, dataBytes) + signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) + + return message.Message{ + Data: dataBase64, + Sender: keys.publicKey, + Signature: signatureBase64, + MessageID: messagedata.Hash(dataBase64, signatureBase64), + WitnessSignatures: []message.WitnessSignature{}, + } +} diff --git a/be1-go/message/messagedata/federation_request_challenge.go b/be1-go/message/messagedata/federation_request_challenge.go index f14eadfc64..8d6652a5a1 100644 --- a/be1-go/message/messagedata/federation_request_challenge.go +++ b/be1-go/message/messagedata/federation_request_challenge.go @@ -1,7 +1,7 @@ package messagedata -// FederationRequestChallenge defines a message data -type FederationRequestChallenge struct { +// FederationChallengeRequest defines a message data +type FederationChallengeRequest struct { Object string `json:"object"` Action string `json:"action"` @@ -10,16 +10,16 @@ type FederationRequestChallenge struct { } // GetObject implements MessageData -func (FederationRequestChallenge) GetObject() string { +func (FederationChallengeRequest) GetObject() string { return FederationObject } // GetAction implements MessageData -func (FederationRequestChallenge) GetAction() string { - return FederationActionRequestChallenge +func (FederationChallengeRequest) GetAction() string { + return FederationActionChallengeRequest } // NewEmpty implements MessageData -func (FederationRequestChallenge) NewEmpty() MessageData { - return &FederationRequestChallenge{} +func (FederationChallengeRequest) NewEmpty() MessageData { + return &FederationChallengeRequest{} } diff --git a/be1-go/message/messagedata/mod.go b/be1-go/message/messagedata/mod.go index 14c1977112..b836ecbdb1 100644 --- a/be1-go/message/messagedata/mod.go +++ b/be1-go/message/messagedata/mod.go @@ -50,7 +50,7 @@ const ( VoteActionWriteIn = "write_in" FederationObject = "federation" - FederationActionRequestChallenge = "request_challenge" + FederationActionChallengeRequest = "challenge_request" FederationActionChallenge = "challenge" FederationActionInit = "init" FederationActionExpect = "expect" From 6caa3bc769a623535a7b4d68a8af66cb9fbc247d Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 24 Apr 2024 12:26:52 +0200 Subject: [PATCH 07/48] Add test --- be1-go/hub/standard_hub/standard_hub_test.go | 73 ++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/be1-go/hub/standard_hub/standard_hub_test.go b/be1-go/hub/standard_hub/standard_hub_test.go index 9920f62640..fc92fb7640 100644 --- a/be1-go/hub/standard_hub/standard_hub_test.go +++ b/be1-go/hub/standard_hub/standard_hub_test.go @@ -2,10 +2,13 @@ package standard_hub import ( "bytes" + "crypto/rand" "encoding/base64" "encoding/json" "fmt" + "github.com/gorilla/websocket" "io" + "net/http" "os" "path/filepath" "popstellar/channel" @@ -1956,8 +1959,78 @@ func Test_Handle_GreetServer_Already_Received(t *testing.T) { require.NotEqual(t, serverInfo2, peersInfo[0]) } +// Test_ConnectToServerAsClient tests that a websocket connection to the given +// address is created, and that messages sent using this client are received +func Test_ConnectToServerAsClient(t *testing.T) { + listenAddress := "localhost:18004" + serverAddress := fmt.Sprintf("ws://%s/client", listenAddress) + keypair := generateKeyPair(t) + + hub, err := NewHub(keypair.public, "", "", nolog, nil) + require.NoError(t, err) + + mb := &messageBuffer{} + startWsServer(t, listenAddress, mb) + + time.Sleep(time.Millisecond) + + client, err := hub.ConnectToServerAsClient(serverAddress) + require.NoError(t, err) + require.IsType(t, &socket.ClientSocket{}, client) + + randomMsg0 := make([]byte, 128) + randomMsg1 := make([]byte, 128) + _, _ = rand.Read(randomMsg0) + _, _ = rand.Read(randomMsg1) + client.Send(randomMsg0) + client.Send(randomMsg1) + + time.Sleep(time.Millisecond) + + mb.Lock() + require.Len(t, mb.messages, 2) + require.Equal(t, randomMsg0, mb.messages[0]) + require.Equal(t, randomMsg1, mb.messages[1]) + mb.Unlock() +} + // ----------------------------------------------------------------------------- // Utility functions +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +type messageBuffer struct { + sync.Mutex + messages [][]byte +} + +func websocketHandler(t *testing.T, mb *messageBuffer) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + require.NoError(t, err) + defer conn.Close() + + for { + mt, msg, err := conn.ReadMessage() + require.NoError(t, err) + + require.Equal(t, websocket.TextMessage, mt) + mb.Lock() + mb.messages = append(mb.messages, msg) + mb.Unlock() + } + } +} + +func startWsServer(t *testing.T, listenAddress string, mb *messageBuffer) { + http.HandleFunc("/client", websocketHandler(t, mb)) + go func() { + err := http.ListenAndServe(listenAddress, nil) + require.NoError(t, err) + }() +} type keypair struct { public kyber.Point From 75c65baae5bcf19d4d6496be8f98499be3c33310 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 24 Apr 2024 14:22:55 +0200 Subject: [PATCH 08/48] Add test --- .../authentication/authentication_test.go | 2 +- be1-go/channel/channel.go | 2 +- be1-go/channel/chirp/chirp_test.go | 2 +- be1-go/channel/coin/coin_test.go | 2 +- be1-go/channel/consensus/consensus_test.go | 2 +- be1-go/channel/election/election_test.go | 2 +- be1-go/channel/federation/federation_test.go | 87 +++++++++++++++++-- .../generalChirping/generalChirping_test.go | 2 +- be1-go/channel/lao/lao_test.go | 2 +- be1-go/channel/reaction/reaction_test.go | 2 +- be1-go/hub/standard_hub/standard_hub.go | 3 +- 11 files changed, 89 insertions(+), 19 deletions(-) diff --git a/be1-go/channel/authentication/authentication_test.go b/be1-go/channel/authentication/authentication_test.go index bee43300d8..857fb8dc06 100644 --- a/be1-go/channel/authentication/authentication_test.go +++ b/be1-go/channel/authentication/authentication_test.go @@ -300,7 +300,7 @@ func (h *fakeHub) SendAndHandleMessage(_ method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/channel.go b/be1-go/channel/channel.go index 98448a0cc6..f4a2fad4fc 100644 --- a/be1-go/channel/channel.go +++ b/be1-go/channel/channel.go @@ -102,7 +102,7 @@ type HubFunctionalities interface { NotifyWitnessMessage(messageId string, publicKey string, signature string) GetClientServerAddress() string GetPeersInfo() []method.ServerInfo - ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) + ConnectToServerAsClient(serverAddress string) (socket.Socket, error) } // Broadcastable defines a channel that can broadcast diff --git a/be1-go/channel/chirp/chirp_test.go b/be1-go/channel/chirp/chirp_test.go index 1267a406e4..4bb096d462 100644 --- a/be1-go/channel/chirp/chirp_test.go +++ b/be1-go/channel/chirp/chirp_test.go @@ -644,7 +644,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/coin/coin_test.go b/be1-go/channel/coin/coin_test.go index ca6c0c7f6d..0240ef4cbd 100644 --- a/be1-go/channel/coin/coin_test.go +++ b/be1-go/channel/coin/coin_test.go @@ -829,7 +829,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/consensus/consensus_test.go b/be1-go/channel/consensus/consensus_test.go index 42711a918a..f5a467ab05 100644 --- a/be1-go/channel/consensus/consensus_test.go +++ b/be1-go/channel/consensus/consensus_test.go @@ -2042,7 +2042,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/election/election_test.go b/be1-go/channel/election/election_test.go index 31df432c10..7fcf710734 100644 --- a/be1-go/channel/election/election_test.go +++ b/be1-go/channel/election/election_test.go @@ -777,7 +777,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 3493f03011..0f97396d2b 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -12,6 +12,8 @@ import ( "golang.org/x/sync/semaphore" "golang.org/x/xerrors" "io" + "os" + "path/filepath" "popstellar/channel" "popstellar/crypto" jsonrpc "popstellar/message" @@ -27,12 +29,14 @@ import ( ) const ( - localServerAddress = "ws://localhost:19008/client" - remoteServerAddress = "ws://localhost:19009/client" - localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" - remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" - localLaoChannel = "/root/" + localLaoId - localFedChannel = localLaoChannel + "/federation" + relativeMsgDataExamplePath string = "../../../protocol/examples/messageData" + relativeQueryExamplePath string = "../../../protocol/examples/query" + localServerAddress = "ws://localhost:19008/client" + remoteServerAddress = "ws://localhost:19009/client" + localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" + remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" + localFedChannel = "/root/" + localLaoId + "/federation" + remoteFedChannel = "/root/" + remoteLaoId + "/federation" ) // TestChannel_FederationRequestChallenge tests that a FederationChallenge is @@ -279,6 +283,68 @@ func Test_FederationChallenge_not_organizer(t *testing.T) { } func Test_FederationInit(t *testing.T) { + organizerKeypair := generateKeyPair(t) + remoteOrganizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + challengeFile := filepath.Join(relativeMsgDataExamplePath, + "federation_challenge", + "federation_challenge.json") + challengeBytes, err := os.ReadFile(challengeFile) + require.NoError(t, err) + + challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) + + signatureBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) + require.NoError(t, err) + + signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) + + federationInitData := messagedata.FederationInit{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionInit, + LaoId: remoteLaoId, + ServerAddress: remoteServerAddress, + PublicKey: remoteOrganizerKeypair.publicKey, + ChallengeMsg: message.Message{ + Data: challengeBase64, + Sender: organizerKeypair.publicKey, + Signature: signatureBase64, + MessageID: messagedata.Hash(challengeBase64, signatureBase64), + WitnessSignatures: []message.WitnessSignature{}, + }, + } + + initMsg := generateMessage(t, organizerKeypair, federationInitData) + publishMsg := generatePublish(t, localFedChannel, initMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) + + require.NotNil(t, fakeHub.socketClient) + require.Equal(t, remoteServerAddress, fakeHub.socketClient.ID()) + + // A Publish message containing the challenge message should be sent + msgBytes := fakeHub.socketClient.msg + require.NotNil(t, msgBytes) + + var publishMsg2 method.Publish + err = json.Unmarshal(msgBytes, &publishMsg2) + require.NoError(t, err) + + require.Equal(t, remoteFedChannel, publishMsg2.Params.Channel) + require.Equal(t, challengeBase64, publishMsg2.Params.Message.Data) + require.Equal(t, signatureBase64, publishMsg2.Params.Message.Signature) + require.Equal(t, organizerKeypair.publicKey, publishMsg2.Params.Message.Sender) + + // should expect to receive back a FedererationResult + require.NoError(t, socket.err) + //require.NotNil(t, socket.msg) } @@ -330,6 +396,8 @@ type fakeHub struct { log zerolog.Logger laoFac channel.LaoFactory + + socketClient *fakeSocket } // NewFakeHub returns a fake Hub. @@ -420,8 +488,10 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { - return nil, nil +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { + h.socketClient = &fakeSocket{id: serverAddress} + + return h.socketClient, nil } // fakeSocket is a fake implementation of a Socket @@ -494,6 +564,7 @@ func generateMessage(t *testing.T, keys keypair, dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) signatureBytes, err := schnorr.Sign(crypto.Suite, keys.private, dataBytes) + require.NoError(t, err) signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) return message.Message{ diff --git a/be1-go/channel/generalChirping/generalChirping_test.go b/be1-go/channel/generalChirping/generalChirping_test.go index 280b1dd234..c7f79ca8d9 100644 --- a/be1-go/channel/generalChirping/generalChirping_test.go +++ b/be1-go/channel/generalChirping/generalChirping_test.go @@ -296,7 +296,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/lao/lao_test.go b/be1-go/channel/lao/lao_test.go index 088afc39ab..62c50c0927 100644 --- a/be1-go/channel/lao/lao_test.go +++ b/be1-go/channel/lao/lao_test.go @@ -836,7 +836,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/channel/reaction/reaction_test.go b/be1-go/channel/reaction/reaction_test.go index cc3cec3d0d..208a1af18c 100644 --- a/be1-go/channel/reaction/reaction_test.go +++ b/be1-go/channel/reaction/reaction_test.go @@ -688,7 +688,7 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (*socket.ClientSocket, error) { +func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { return nil, nil } diff --git a/be1-go/hub/standard_hub/standard_hub.go b/be1-go/hub/standard_hub/standard_hub.go index dd9fce48c1..bdf505eca3 100644 --- a/be1-go/hub/standard_hub/standard_hub.go +++ b/be1-go/hub/standard_hub/standard_hub.go @@ -600,8 +600,7 @@ func (h *Hub) GetPeersInfo() []method.ServerInfo { return h.peers.GetAllPeersInfo() } -func (h *Hub) ConnectToServerAsClient(serverAddress string) ( - *socket.ClientSocket, error) { +func (h *Hub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { ws, _, err := websocket.DefaultDialer.Dial(serverAddress, nil) if err != nil { return nil, err From 3784cd552512fe5fb7540859a6645bffce783e78 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 24 Apr 2024 14:49:43 +0200 Subject: [PATCH 09/48] remove duplicate literal --- be1-go/channel/federation/federation.go | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index c25c31ec07..e40433aec6 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -24,7 +24,8 @@ import ( ) const ( - msgID = "msg id" + msgID = "msg id" + invalidStateError = "Invalid state %v for message %v" ) type State int @@ -131,14 +132,14 @@ func (c *Channel) Broadcast(broadcast method.Broadcast, socket socket.Socket) er // NewFederationRegistry creates a new registry for the federation channel func (c *Channel) NewFederationRegistry() registry.MessageRegistry { - registry := registry.NewMessageRegistry() + fedRegistry := registry.NewMessageRegistry() - registry.Register(messagedata.FederationChallengeRequest{}, c.processChallengeRequest) - registry.Register(messagedata.FederationExpect{}, c.processFederationExpect) - registry.Register(messagedata.FederationInit{}, c.processFederationInit) - registry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) + fedRegistry.Register(messagedata.FederationChallengeRequest{}, c.processChallengeRequest) + fedRegistry.Register(messagedata.FederationExpect{}, c.processFederationExpect) + fedRegistry.Register(messagedata.FederationInit{}, c.processFederationInit) + fedRegistry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) - return registry + return fedRegistry } func (c *Channel) handleMessage(msg message.Message, @@ -173,7 +174,7 @@ func (c *Channel) verifyMessage(msg message.Message) error { } func (c *Channel) processFederationInit(msg message.Message, - msgData interface{}, s socket.Socket) error { + msgData interface{}, _ socket.Socket) error { _, ok := msgData.(*messagedata.FederationInit) if !ok { @@ -188,7 +189,7 @@ func (c *Channel) processFederationInit(msg message.Message, } if c.state != None { - return answer.NewInternalServerError("The current state is %v", c.state) + return answer.NewInternalServerError(invalidStateError, c.state, msg) } c.state = Initiating @@ -240,7 +241,7 @@ func (c *Channel) processFederationInit(msg message.Message, } func (c *Channel) processFederationExpect(msg message.Message, - msgData interface{}, s socket.Socket) error { + msgData interface{}, _ socket.Socket) error { _, ok := msgData.(*messagedata.FederationExpect) if !ok { @@ -255,7 +256,7 @@ func (c *Channel) processFederationExpect(msg message.Message, } if c.state != None { - return answer.NewInternalServerError("The current state is %v", c.state) + return answer.NewInternalServerError(invalidStateError, c.state, msg) } var federationExpect messagedata.FederationExpect @@ -293,7 +294,7 @@ func (c *Channel) processFederationChallenge(msg message.Message, } if c.state != ExpectConnect { - return answer.NewInternalServerError("The current state is %v", c.state) + return answer.NewInternalServerError(invalidStateError, c.state, msg) } var federationChallenge messagedata.FederationChallenge @@ -338,7 +339,7 @@ func (c *Channel) processChallengeRequest(msg message.Message, } if c.state != None && c.state != ExpectConnect { - return answer.NewInternalServerError("The current state is %v", c.state) + return answer.NewInternalServerError(invalidStateError, c.state, msg) } var federationChallengeRequest messagedata.FederationChallengeRequest From a631f5c14f183c129f9743aebb35f53defae88f3 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Thu, 25 Apr 2024 15:44:42 +0200 Subject: [PATCH 10/48] add result message schema --- .../federation_result/federation_result.json | 5 ++++ protocol/query/method/message/data/data.json | 3 ++ .../message/data/dataFederationResult.json | 28 +++++++++++++++++++ protocol/test/main.js | 2 ++ protocol/test/main.test.js | 4 +++ 5 files changed, 42 insertions(+) create mode 100644 protocol/examples/messageData/federation_result/federation_result.json create mode 100644 protocol/query/method/message/data/dataFederationResult.json diff --git a/protocol/examples/messageData/federation_result/federation_result.json b/protocol/examples/messageData/federation_result/federation_result.json new file mode 100644 index 0000000000..1bf5bdfbb1 --- /dev/null +++ b/protocol/examples/messageData/federation_result/federation_result.json @@ -0,0 +1,5 @@ +{ + "object": "federation", + "action": "result", + "status": "success" +} diff --git a/protocol/query/method/message/data/data.json b/protocol/query/method/message/data/data.json index 0dceaba4d6..382d5eb1d0 100644 --- a/protocol/query/method/message/data/data.json +++ b/protocol/query/method/message/data/data.json @@ -64,6 +64,9 @@ { "$ref": "dataFederationChallenge.json" }, + { + "$ref": "dataFederationResult.json" + }, { "$ref": "dataWitnessMessage.json" }, diff --git a/protocol/query/method/message/data/dataFederationResult.json b/protocol/query/method/message/data/dataFederationResult.json new file mode 100644 index 0000000000..a4aa2d1439 --- /dev/null +++ b/protocol/query/method/message/data/dataFederationResult.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/dedis/popstellar/master/protocol/query/method/message/data/dataFederationResult.json", + "description": "Sent by an server to a remote server, to inform them about the result of the authentication procedure", + "type": "object", + "properties": { + "object": { + "const": "federation" + }, + "action": { + "const": "result" + }, + "status": { + "type": "string", + "$comment": "status of the authentication attempt" + }, + "reason": { + "type": "string", + "$comment": "to be used in failures, describing the error that happened" + } + }, + "additionalProperties": false, + "required": [ + "object", + "action", + "status" + ] +} diff --git a/protocol/test/main.js b/protocol/test/main.js index 3075c2f29b..6a918a1b43 100644 --- a/protocol/test/main.js +++ b/protocol/test/main.js @@ -43,6 +43,7 @@ const message_data_federation_init_schema = require("../query/method/message/dat const message_data_federation_expect_schema = require("../query/method/message/data/dataFederationExpect.json") const message_data_federation_challenge_request_schema = require("../query/method/message/data/dataFederationChallengeRequest.json") const message_data_federation_challenge_schema = require("../query/method/message/data/dataFederationChallenge.json") +const message_data_federation_result_schema = require("../query/method/message/data/dataFederationResult.json") const message_data_chirp_add_schema = require("../query/method/message/data/dataAddChirp.json"); const message_data_chirp_notify_add_schema = require("../query/method/message/data/dataNotifyAddChirp.json"); @@ -112,6 +113,7 @@ ajv.addSchema([ message_data_federation_expect_schema, message_data_federation_challenge_request_schema, message_data_federation_challenge_schema, + message_data_federation_result_schema, message_data_chirp_notify_add_schema, message_data_chirp_add_schema, diff --git a/protocol/test/main.test.js b/protocol/test/main.test.js index 6ef0d13286..728da8d012 100644 --- a/protocol/test/main.test.js +++ b/protocol/test/main.test.js @@ -6,6 +6,7 @@ const ajv = require("./main"); const rootSchema = "https://raw.githubusercontent.com/dedis/popstellar/master/protocol/jsonRPC.json"; + const messageDataSchema = "https://raw.githubusercontent.com/dedis/popstellar/master/protocol/query/method/message/data/data.json"; @@ -263,6 +264,9 @@ test("message data: federation", () => { federation_challenge = require("../examples/messageData/federation_challenge/federation_challenge.json"); expect(federation_challenge).toBeValid(messageDataSchema); + federation_result = require("../examples/messageData/federation_result/federation_result.json"); + expect(federation_result).toBeValid(messageDataSchema); + }); test("message data: chirp", () => { From a02f78b2ccf24899a6808057dc6cb8e060bc5d50 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Mon, 29 Apr 2024 15:46:08 +0200 Subject: [PATCH 11/48] add federation result handling to be1 (WIP) --- be1-go/channel/federation/federation.go | 86 ++++++++++++++++++- be1-go/channel/federation/federation_test.go | 43 ++++++++++ .../message/messagedata/federation_result.go | 25 ++++++ be1-go/message/messagedata/mod.go | 1 + 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 be1-go/message/messagedata/federation_result.go diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index e40433aec6..163d816a83 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -138,6 +138,7 @@ func (c *Channel) NewFederationRegistry() registry.MessageRegistry { fedRegistry.Register(messagedata.FederationExpect{}, c.processFederationExpect) fedRegistry.Register(messagedata.FederationInit{}, c.processFederationInit) fedRegistry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) + fedRegistry.Register(messagedata.FederationResult{}, c.processFederationResult) return fedRegistry } @@ -237,6 +238,8 @@ func (c *Channel) processFederationInit(msg message.Message, c.remoteServer.Send(buf) + c.state = WaitResult + return nil } @@ -318,7 +321,55 @@ func (c *Channel) processFederationChallenge(msg message.Message, c.state = Connected c.remoteServer = s // Send Federation result to S1 - // c.remoteServer.Send(...) + + federationResultData := messagedata.FederationResult{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + } + + dataBytes, err := json.Marshal(federationResultData) + if err != nil { + return xerrors.Errorf("failed to marshal federation result message data: %v", err) + } + + dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) + signatureBytes, err := c.hub.Sign(dataBytes) + if err != nil { + return xerrors.Errorf("failed to sign federation result message: %v", err) + } + signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) + + federationResultMsg := message.Message{ + Data: dataBase64, + Sender: c.hub.GetPubKeyServ().String(), + Signature: signatureBase64, + MessageID: messagedata.Hash(dataBase64, signatureBase64), + WitnessSignatures: []message.WitnessSignature{}, + } + + rpcMessage := 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: c.remoteChannel, + Message: federationResultMsg, + }, + } + buf, err := json.Marshal(&rpcMessage) + if err != nil { + return xerrors.Errorf("failed to marshal publish query: %v", err) + } + + c.remoteServer.Send(buf) return nil } @@ -424,3 +475,36 @@ func (c *Channel) processChallengeRequest(msg message.Message, return nil } + +func (c *Channel) processFederationResult(msg message.Message, + msgData interface{}, s socket.Socket) error { + _, ok := msgData.(*messagedata.FederationResult) + if !ok { + return xerrors.Errorf("message %v is not a federation#result message", + msgData) + } + + /* + This state should probably be set after sending the FederationInit message? + */ + //if c.state != WaitResult { + // return answer.NewInternalServerError(invalidStateError, c.state, msg) + //} + + var federationResult messagedata.FederationResult + + err := msg.UnmarshalData(&federationResult) + if err != nil { + c.state = None + return xerrors.Errorf("failed to unmarshal FederationResult data: %v", err) + } + + if federationResult.Status != "success" { + if len(federationResult.Reason) > 0 { + return xerrors.Errorf("failed to establish federated connection: %v", federationResult.Reason) + } + return xerrors.Errorf("failed to establish federated connection") + } + + return nil +} diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 0f97396d2b..18c0e4f7c4 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -348,6 +348,49 @@ func Test_FederationInit(t *testing.T) { } +func Test_FederationResult(t *testing.T) { + organizerKeypair := generateKeyPair(t) + remoteOrganizerKeypair := generateKeyPair(t) + fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) + require.NoError(t, err) + + fedChannel := NewChannel(localFedChannel, fakeHub, nolog, + organizerKeypair.publicKey) + + remotePublicKeyBytes, err := remoteOrganizerKeypair.public.MarshalBinary() + require.NoError(t, err) + signedRemotePublicKey, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, remotePublicKeyBytes) + require.NoError(t, err) + + signedRemotePublicKeyBase64 := base64.URLEncoding.EncodeToString(signedRemotePublicKey) + + challengeFile := filepath.Join(relativeMsgDataExamplePath, + "federation_challenge", + "federation_challenge.json") + challengeBytes, err := os.ReadFile(challengeFile) + require.NoError(t, err) + signedChallengeBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) + require.NoError(t, err) + signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) + + t.Log(signedRemotePublicKeyBase64) + t.Log(signedChallengeBase64) + t.Log() + t.Log() + + federationResultData := messagedata.FederationResult{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + } + resultMsg := generateMessage(t, organizerKeypair, federationResultData) + publishMsg := generatePublish(t, localFedChannel, resultMsg) + socket := &fakeSocket{id: "sockSocket"} + + err = fedChannel.Publish(publishMsg, socket) + require.NoError(t, err) +} + // ----------------------------------------------------------------------------- // Utility functions diff --git a/be1-go/message/messagedata/federation_result.go b/be1-go/message/messagedata/federation_result.go new file mode 100644 index 0000000000..ac7d39a026 --- /dev/null +++ b/be1-go/message/messagedata/federation_result.go @@ -0,0 +1,25 @@ +package messagedata + +// FederationResult defines a message data +type FederationResult struct { + Object string `json:"object"` + Action string `json:"action"` + + Status string `json:"status"` + Reason string `json:"reason,omitempty"` +} + +// GetObject implements MessageData +func (FederationResult) GetObject() string { + return FederationObject +} + +// GetAction implements MessageData +func (FederationResult) GetAction() string { + return FederationActionResult +} + +// NewEmpty implements MessageData +func (FederationResult) NewEmpty() MessageData { + return &FederationResult{} +} diff --git a/be1-go/message/messagedata/mod.go b/be1-go/message/messagedata/mod.go index b836ecbdb1..7fbc82e939 100644 --- a/be1-go/message/messagedata/mod.go +++ b/be1-go/message/messagedata/mod.go @@ -54,6 +54,7 @@ const ( FederationActionChallenge = "challenge" FederationActionInit = "init" FederationActionExpect = "expect" + FederationActionResult = "result" ChirpObject = "chirp" ChirpActionAdd = "add" From 31e9c370a3563f279de269af20c4e6cc703331bf Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 1 May 2024 13:55:50 +0200 Subject: [PATCH 12/48] Use channel instead of time delay --- be1-go/hub/standard_hub/standard_hub_test.go | 54 ++++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/be1-go/hub/standard_hub/standard_hub_test.go b/be1-go/hub/standard_hub/standard_hub_test.go index fc92fb7640..de9573716a 100644 --- a/be1-go/hub/standard_hub/standard_hub_test.go +++ b/be1-go/hub/standard_hub/standard_hub_test.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/gorilla/websocket" "io" + "net" "net/http" "os" "path/filepath" @@ -1969,29 +1970,27 @@ func Test_ConnectToServerAsClient(t *testing.T) { hub, err := NewHub(keypair.public, "", "", nolog, nil) require.NoError(t, err) - mb := &messageBuffer{} - startWsServer(t, listenAddress, mb) + msgCh := make(chan []byte, 1) + startCh := make(chan struct{}, 1) + startWsServer(t, listenAddress, msgCh, startCh) - time.Sleep(time.Millisecond) + // wait that the ws server has started listening + <-startCh client, err := hub.ConnectToServerAsClient(serverAddress) require.NoError(t, err) require.IsType(t, &socket.ClientSocket{}, client) - randomMsg0 := make([]byte, 128) - randomMsg1 := make([]byte, 128) - _, _ = rand.Read(randomMsg0) - _, _ = rand.Read(randomMsg1) - client.Send(randomMsg0) - client.Send(randomMsg1) + randomMsg := make([]byte, 128) + _, _ = rand.Read(randomMsg) + client.Send(randomMsg) - time.Sleep(time.Millisecond) - - mb.Lock() - require.Len(t, mb.messages, 2) - require.Equal(t, randomMsg0, mb.messages[0]) - require.Equal(t, randomMsg1, mb.messages[1]) - mb.Unlock() + select { + case m := <-msgCh: + assert.Equal(t, randomMsg, m) + case <-time.After(time.Second): + t.Errorf("Timed out waiting for expected message") + } } // ----------------------------------------------------------------------------- @@ -2001,12 +2000,7 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, } -type messageBuffer struct { - sync.Mutex - messages [][]byte -} - -func websocketHandler(t *testing.T, mb *messageBuffer) func(w http.ResponseWriter, r *http.Request) { +func websocketHandler(t *testing.T, msgCh chan []byte) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) require.NoError(t, err) @@ -2017,17 +2011,21 @@ func websocketHandler(t *testing.T, mb *messageBuffer) func(w http.ResponseWrite require.NoError(t, err) require.Equal(t, websocket.TextMessage, mt) - mb.Lock() - mb.messages = append(mb.messages, msg) - mb.Unlock() + msgCh <- msg } } } -func startWsServer(t *testing.T, listenAddress string, mb *messageBuffer) { - http.HandleFunc("/client", websocketHandler(t, mb)) +func startWsServer(t *testing.T, listenAddress string, msgCh chan []byte, startCh chan struct{}) { + http.HandleFunc("/client", websocketHandler(t, msgCh)) go func() { - err := http.ListenAndServe(listenAddress, nil) + listener, err := net.Listen("tcp", listenAddress) + require.NoError(t, err) + + // signal that the ws server has started listening + startCh <- struct{}{} + + err = http.Serve(listener, nil) require.NoError(t, err) }() } From defed6b13ff60c2f0b385103f0923cde60b2957d Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Wed, 1 May 2024 16:23:09 +0200 Subject: [PATCH 13/48] adapt federation result schema & test --- be1-go/channel/federation/federation_test.go | 15 ++++++++++++--- be1-go/message/messagedata/federation_result.go | 7 ++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 18c0e4f7c4..888b0c550a 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -368,6 +368,7 @@ func Test_FederationResult(t *testing.T) { "federation_challenge", "federation_challenge.json") challengeBytes, err := os.ReadFile(challengeFile) + challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) require.NoError(t, err) signedChallengeBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) require.NoError(t, err) @@ -379,9 +380,17 @@ func Test_FederationResult(t *testing.T) { t.Log() federationResultData := messagedata.FederationResult{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionResult, - Status: "success", + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + PublicKey: remoteOrganizerKeypair.publicKey, + ChallengeMsg: message.Message{ + Data: challengeBase64, + Sender: organizerKeypair.publicKey, + Signature: signedChallengeBase64, + MessageID: messagedata.Hash(challengeBase64, signedChallengeBase64), + WitnessSignatures: []message.WitnessSignature{}, + }, } resultMsg := generateMessage(t, organizerKeypair, federationResultData) publishMsg := generatePublish(t, localFedChannel, resultMsg) diff --git a/be1-go/message/messagedata/federation_result.go b/be1-go/message/messagedata/federation_result.go index ac7d39a026..88a5987678 100644 --- a/be1-go/message/messagedata/federation_result.go +++ b/be1-go/message/messagedata/federation_result.go @@ -1,12 +1,17 @@ package messagedata +import "popstellar/message/query/method/message" + // FederationResult defines a message data type FederationResult struct { Object string `json:"object"` Action string `json:"action"` - Status string `json:"status"` + Reason string `json:"reason,omitempty"` + + PublicKey string `json:"public_key,omitempty"` + ChallengeMsg message.Message `json:"challenge"` } // GetObject implements MessageData From bcae615670106360d39877a472257a31c36cbfc7 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Wed, 1 May 2024 17:16:15 +0200 Subject: [PATCH 14/48] add initial result handling (+ rename wrongly named file) --- be1-go/channel/federation/federation.go | 32 +++++++++++++++++++ be1-go/channel/federation/federation_test.go | 7 +--- ...on_expect.json.go => federation_expect.go} | 0 3 files changed, 33 insertions(+), 6 deletions(-) rename be1-go/message/messagedata/{federation_expect.json.go => federation_expect.go} (100%) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index 163d816a83..2104ec41b3 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -7,9 +7,12 @@ import ( "encoding/json" "fmt" "github.com/rs/zerolog" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/sign/schnorr" "golang.org/x/xerrors" "popstellar/channel" "popstellar/channel/registry" + "popstellar/crypto" "popstellar/inbox" jsonrpc "popstellar/message" "popstellar/message/answer" @@ -506,5 +509,34 @@ func (c *Channel) processFederationResult(msg message.Message, return xerrors.Errorf("failed to establish federated connection") } + challengeDataBytes, err := base64.URLEncoding.DecodeString(federationResult.ChallengeMsg.Data) + if err != nil { + return xerrors.Errorf("failed to decode challenge data in FederationResult: %v", err) + + } + + challengeSignatureBytes, err := base64.URLEncoding.DecodeString(federationResult.ChallengeMsg.Signature) + if err != nil { + return xerrors.Errorf("failed to decode challenge signature in FederationResult: %v", err) + + } + + pkBytes, err := base64.URLEncoding.DecodeString(c.remoteOrganizerPk) + if err != nil { + return xerrors.Errorf("failed to decode remote organizers public key: %v", err) + + } + var remotePk kyber.Point + err = kyber.Point.UnmarshalBinary(remotePk, pkBytes) + if err != nil { + return xerrors.Errorf("failed to decode remote organizers public key: %v", err) + + } + err = schnorr.Verify(crypto.Suite, remotePk, challengeDataBytes, challengeSignatureBytes) + if err != nil { + return xerrors.Errorf("failed to verify signature on challenge in FederationResult message: %v", err) + + } + return nil } diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 888b0c550a..9db5587465 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -374,16 +374,11 @@ func Test_FederationResult(t *testing.T) { require.NoError(t, err) signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) - t.Log(signedRemotePublicKeyBase64) - t.Log(signedChallengeBase64) - t.Log() - t.Log() - federationResultData := messagedata.FederationResult{ Object: messagedata.FederationObject, Action: messagedata.FederationActionResult, Status: "success", - PublicKey: remoteOrganizerKeypair.publicKey, + PublicKey: signedRemotePublicKeyBase64, ChallengeMsg: message.Message{ Data: challengeBase64, Sender: organizerKeypair.publicKey, diff --git a/be1-go/message/messagedata/federation_expect.json.go b/be1-go/message/messagedata/federation_expect.go similarity index 100% rename from be1-go/message/messagedata/federation_expect.json.go rename to be1-go/message/messagedata/federation_expect.go From 2e386520e7659af4ecf600f66d6d788d69135db6 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Sat, 4 May 2024 23:19:09 +0200 Subject: [PATCH 15/48] Add a map for multiple organizations, add mutex locks --- be1-go/channel/federation/federation.go | 183 ++++++++++++++++-------- 1 file changed, 124 insertions(+), 59 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index e40433aec6..542188d565 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -20,6 +20,7 @@ import ( "popstellar/network/socket" "popstellar/validation" "strconv" + "sync" "time" ) @@ -38,6 +39,21 @@ const ( Connected ) +type remoteOrganization struct { + laoId string + fedChannel string + organizerPk string + + // store the pop tokens of the other lao + // popTokens map[string]struct{} + + challenge messagedata.Challenge + socket socket.Socket + + state State + sync.Mutex +} + // Channel is used to handle federation messages. type Channel struct { sockets channel.Sockets @@ -47,15 +63,15 @@ type Channel struct { log zerolog.Logger registry registry.MessageRegistry - localOrganizerPk string - remoteOrganizerPk string + localOrganizerPk string - remoteChannel string - remoteServer socket.Socket + // map remoteOrganizerPk -> remoteOrganization + remoteOrganizations map[string]*remoteOrganization - challenge messagedata.Challenge + // list of challenge requested but not used yet + challenges map[messagedata.Challenge]struct{} - state State + sync.Mutex } // NewChannel returns a new initialized federation channel @@ -65,13 +81,14 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, log = log.With().Str("channel", "federation").Logger() newChannel := &Channel{ - sockets: channel.NewSockets(), - inbox: box, - channelID: channelID, - hub: hub, - log: log, - localOrganizerPk: organizerPk, - state: None, + sockets: channel.NewSockets(), + inbox: box, + channelID: channelID, + hub: hub, + log: log, + localOrganizerPk: organizerPk, + remoteOrganizations: make(map[string]*remoteOrganization), + challenges: make(map[messagedata.Challenge]struct{}), } newChannel.registry = newChannel.NewFederationRegistry() @@ -188,30 +205,45 @@ func (c *Channel) processFederationInit(msg message.Message, "Only local organizer is allowed to send federation#init") } - if c.state != None { - return answer.NewInternalServerError(invalidStateError, c.state, msg) - } - c.state = Initiating - var federationInit messagedata.FederationInit err := msg.UnmarshalData(&federationInit) if err != nil { - c.state = None return xerrors.Errorf("failed to unmarshal FederationInit data: %v", err) } - c.remoteServer, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) + var federationChallenge messagedata.FederationChallenge + err = federationInit.ChallengeMsg.UnmarshalData(&federationChallenge) + if err != nil { + return xerrors.Errorf("failed to unmarshal FederationChallenge data: %v", err) + } + + remoteOrg := c.getRemoteOrganization(federationInit.PublicKey) + remoteOrg.Lock() + defer remoteOrg.Unlock() + + if remoteOrg.state != None { + return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) + } + + remoteOrg.state = Initiating + remoteOrg.organizerPk = federationInit.PublicKey + remoteOrg.laoId = federationInit.LaoId + remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + remoteOrg.challenge = messagedata.Challenge{ + Value: federationChallenge.Value, + ValidUntil: federationChallenge.Timestamp, + } + + remoteOrg.socket, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) if err != nil { - c.state = None + remoteOrg.state = None return answer.NewInternalServerError( "failed to connect to server %v: %v", federationInit.ServerAddress, err) } // send the challenge to the other server - c.remoteChannel = fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - challengePublish := method.Publish{ Base: query.Base{ JSONRPCBase: jsonrpc.JSONRPCBase{ @@ -224,18 +256,18 @@ func (c *Channel) processFederationInit(msg message.Message, Channel string `json:"channel"` Message message.Message `json:"message"` }{ - Channel: c.remoteChannel, + Channel: remoteOrg.fedChannel, Message: federationInit.ChallengeMsg, }, } buf, err := json.Marshal(challengePublish) if err != nil { - c.state = None + remoteOrg.state = None return xerrors.Errorf("failed to marshal challenge: %v", err) } - c.remoteServer.Send(buf) + remoteOrg.socket.Send(buf) return nil } @@ -255,10 +287,6 @@ func (c *Channel) processFederationExpect(msg message.Message, "Only local organizer is allowed to send federation#expect") } - if c.state != None { - return answer.NewInternalServerError(invalidStateError, c.state, msg) - } - var federationExpect messagedata.FederationExpect err := msg.UnmarshalData(&federationExpect) @@ -266,14 +294,30 @@ func (c *Channel) processFederationExpect(msg message.Message, return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) } - if federationExpect.Challenge != c.challenge { + remoteOrg := c.getRemoteOrganization(federationExpect.PublicKey) + remoteOrg.Lock() + defer remoteOrg.Unlock() + + if remoteOrg.state != None { + return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) + } + + c.Lock() + _, ok = c.challenges[federationExpect.Challenge] + // always remove the challenge, if present, to avoid challenge reuse + delete(c.challenges, federationExpect.Challenge) + c.Unlock() + + if !ok { return answer.NewAccessDeniedError("Invalid challenge %v", federationExpect.Challenge) } - c.state = ExpectConnect - c.remoteOrganizerPk = federationExpect.PublicKey - c.remoteChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + remoteOrg.state = ExpectConnect + remoteOrg.challenge = federationExpect.Challenge + remoteOrg.organizerPk = federationExpect.PublicKey + remoteOrg.laoId = federationExpect.LaoId + remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) return nil } @@ -287,16 +331,6 @@ func (c *Channel) processFederationChallenge(msg message.Message, "message %v is not a federation#challenge message", msgData) } - // check if it is from the remote organizer - if c.remoteOrganizerPk != msg.Sender { - return answer.NewAccessDeniedError( - "Only remote organizer is allowed to send federation#challenge") - } - - if c.state != ExpectConnect { - return answer.NewInternalServerError(invalidStateError, c.state, msg) - } - var federationChallenge messagedata.FederationChallenge err := msg.UnmarshalData(&federationChallenge) @@ -305,18 +339,36 @@ func (c *Channel) processFederationChallenge(msg message.Message, "failed to unmarshal federationChallenge data: %v", err) } - if c.challenge.Value != federationChallenge.Value { + // If not present, no FederationExpect was received for this organizer pk + remoteOrg, ok := c.remoteOrganizations[msg.Sender] + if !ok { + return answer.NewAccessDeniedError("Unexpected challenge") + } + remoteOrg.Lock() + defer remoteOrg.Unlock() + + // check if it is from the remote organizer + if remoteOrg.organizerPk != msg.Sender { + return answer.NewAccessDeniedError( + "Only remote organizer is allowed to send federation#challenge") + } + + if remoteOrg.state != ExpectConnect { + return answer.NewInternalServerError(invalidStateError, remoteOrg, msg) + } + + if remoteOrg.challenge.Value != federationChallenge.Value { return answer.NewAccessDeniedError("Invalid challenge %v", federationChallenge.Value) } - if c.challenge.ValidUntil < time.Now().Unix() { + if remoteOrg.challenge.ValidUntil < time.Now().Unix() { return answer.NewAccessDeniedError("This challenge has expired: %v", federationChallenge) } - c.state = Connected - c.remoteServer = s + remoteOrg.state = WaitResult + remoteOrg.socket = s // Send Federation result to S1 // c.remoteServer.Send(...) @@ -338,10 +390,6 @@ func (c *Channel) processChallengeRequest(msg message.Message, "Only local organizer is allowed to send federation#challenge_request") } - if c.state != None && c.state != ExpectConnect { - return answer.NewInternalServerError(invalidStateError, c.state, msg) - } - var federationChallengeRequest messagedata.FederationChallengeRequest err := msg.UnmarshalData(&federationChallengeRequest) @@ -357,18 +405,20 @@ func (c *Channel) processChallengeRequest(msg message.Message, } challengeValue := hex.EncodeToString(randomBytes) expirationTime := time.Now().Add(time.Minute * 5).Unix() - - c.challenge = messagedata.Challenge{ + challenge := messagedata.Challenge{ Value: challengeValue, ValidUntil: expirationTime, } - c.state = None + + c.Lock() + c.challenges[challenge] = struct{}{} + c.Unlock() federationChallenge := messagedata.FederationChallenge{ - Object: "federation", - Action: "challenge", - Value: c.challenge.Value, - Timestamp: c.challenge.ValidUntil, + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: challengeValue, + Timestamp: expirationTime, } challengeData, err := json.Marshal(federationChallenge) @@ -424,3 +474,18 @@ func (c *Channel) processChallengeRequest(msg message.Message, return nil } + +// getRemoteOrganization get the remoteOrganization for the given organizerPk +// or return a new empty one. +func (c *Channel) getRemoteOrganization(organizerPk string) *remoteOrganization { + c.Lock() + defer c.Unlock() + + org, ok := c.remoteOrganizations[organizerPk] + if !ok { + org = &remoteOrganization{state: None} + c.remoteOrganizations[organizerPk] = org + } + + return org +} From beff40f00803a09d3c10507d0252a28b5ede2833 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Sun, 5 May 2024 15:11:38 +0200 Subject: [PATCH 16/48] finalized result handling & changed tests --- be1-go/channel/federation/federation.go | 29 +++++++++++-- be1-go/channel/federation/federation_test.go | 43 +++++++++++++------- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index 2104ec41b3..983b7741cb 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "github.com/rs/zerolog" - "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/sign/schnorr" "golang.org/x/xerrors" "popstellar/channel" @@ -205,6 +204,9 @@ func (c *Channel) processFederationInit(msg message.Message, return xerrors.Errorf("failed to unmarshal FederationInit data: %v", err) } + // set remote organizer public key + c.remoteOrganizerPk = federationInit.PublicKey + c.remoteServer, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) if err != nil { c.state = None @@ -526,8 +528,10 @@ func (c *Channel) processFederationResult(msg message.Message, return xerrors.Errorf("failed to decode remote organizers public key: %v", err) } - var remotePk kyber.Point - err = kyber.Point.UnmarshalBinary(remotePk, pkBytes) + + remotePk := crypto.Suite.Point() + + err = remotePk.UnmarshalBinary(pkBytes) if err != nil { return xerrors.Errorf("failed to decode remote organizers public key: %v", err) @@ -538,5 +542,24 @@ func (c *Channel) processFederationResult(msg message.Message, } + pkSignatureBytes, err := base64.URLEncoding.DecodeString(federationResult.PublicKey) + if err != nil { + return xerrors.Errorf("failed to decode signature on local public key in FederationResult message: %v", err) + + } + localPkBinary, err := c.hub.GetPubKeyOwner().MarshalBinary() + if err != nil { + return xerrors.Errorf("failed to marshal local organizer public key: %v", err) + + } + + err = schnorr.Verify(crypto.Suite, remotePk, localPkBinary, pkSignatureBytes) + if err != nil { + return xerrors.Errorf("failed to verify remote signature on local organizer public key: %v", err) + + } + + c.state = Connected + return nil } diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 9db5587465..131c0d8c2e 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -5,17 +5,12 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" - "golang.org/x/sync/semaphore" - "golang.org/x/xerrors" "io" "os" "path/filepath" "popstellar/channel" "popstellar/crypto" + "popstellar/inbox" jsonrpc "popstellar/message" "popstellar/message/messagedata" "popstellar/message/query" @@ -26,6 +21,13 @@ import ( "sync" "testing" "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/sign/schnorr" + "golang.org/x/sync/semaphore" + "golang.org/x/xerrors" ) const ( @@ -354,15 +356,28 @@ func Test_FederationResult(t *testing.T) { fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) require.NoError(t, err) - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) + // dirty hack to manually create a channel, so we can then add the remote organizer pk without + // having to go through the init process + + box := inbox.NewInbox(localFedChannel) + newChannel := &Channel{ + sockets: channel.NewSockets(), + inbox: box, + channelID: localFedChannel, + hub: fakeHub, + log: nolog, + localOrganizerPk: organizerKeypair.publicKey, + remoteOrganizerPk: remoteOrganizerKeypair.publicKey, + state: None, + } + newChannel.registry = newChannel.NewFederationRegistry() - remotePublicKeyBytes, err := remoteOrganizerKeypair.public.MarshalBinary() + publicKeyBytes, err := organizerKeypair.public.MarshalBinary() require.NoError(t, err) - signedRemotePublicKey, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, remotePublicKeyBytes) + signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) require.NoError(t, err) - signedRemotePublicKeyBase64 := base64.URLEncoding.EncodeToString(signedRemotePublicKey) + signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) challengeFile := filepath.Join(relativeMsgDataExamplePath, "federation_challenge", @@ -370,7 +385,7 @@ func Test_FederationResult(t *testing.T) { challengeBytes, err := os.ReadFile(challengeFile) challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) require.NoError(t, err) - signedChallengeBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) + signedChallengeBytes, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, challengeBytes) require.NoError(t, err) signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) @@ -378,7 +393,7 @@ func Test_FederationResult(t *testing.T) { Object: messagedata.FederationObject, Action: messagedata.FederationActionResult, Status: "success", - PublicKey: signedRemotePublicKeyBase64, + PublicKey: signedPublicKeyBase64, ChallengeMsg: message.Message{ Data: challengeBase64, Sender: organizerKeypair.publicKey, @@ -391,7 +406,7 @@ func Test_FederationResult(t *testing.T) { publishMsg := generatePublish(t, localFedChannel, resultMsg) socket := &fakeSocket{id: "sockSocket"} - err = fedChannel.Publish(publishMsg, socket) + err = newChannel.Publish(publishMsg, socket) require.NoError(t, err) } From dceb6786ac22e8113464822930c3fe26b3cc52a0 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Tue, 7 May 2024 15:21:55 +0200 Subject: [PATCH 17/48] change remoteOrganisations map back to using remote organizers pk --- be1-go/channel/federation/federation.go | 71 ++++++++++--------- be1-go/channel/federation/federation_test.go | 23 +++--- .../messagedata/federation_challenge.go | 13 ++-- .../message/messagedata/federation_expect.go | 10 +-- .../federation_expect/federation_expect.json | 12 +++- .../message/data/dataFederationExpect.json | 11 ++- 6 files changed, 80 insertions(+), 60 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index f9bbda3d1e..89f6df7c61 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -42,15 +42,17 @@ const ( ) type remoteOrganization struct { - laoId string - fedChannel string - organizerPk string + laoId string + fedChannel string + organizerPk string + signedOrganizerPk string // store the pop tokens of the other lao // popTokens map[string]struct{} - challenge messagedata.Challenge - socket socket.Socket + challenge messagedata.FederationChallenge + challengeMsg message.Message + socket socket.Socket state State sync.Mutex @@ -67,11 +69,11 @@ type Channel struct { localOrganizerPk string - // map challenge -> remoteOrganization + // map remoteOrganizerPk -> remoteOrganization remoteOrganizations map[string]*remoteOrganization // list of challenge requested but not used yet - challenges map[messagedata.Challenge]struct{} + challenges map[messagedata.FederationChallenge]struct{} sync.Mutex } @@ -90,7 +92,7 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, log: log, localOrganizerPk: organizerPk, remoteOrganizations: make(map[string]*remoteOrganization), - challenges: make(map[messagedata.Challenge]struct{}), + challenges: make(map[messagedata.FederationChallenge]struct{}), } newChannel.registry = newChannel.NewFederationRegistry() @@ -221,7 +223,7 @@ func (c *Channel) processFederationInit(msg message.Message, return xerrors.Errorf("failed to unmarshal FederationChallenge data: %v", err) } - remoteOrg := c.getRemoteOrganization(federationChallenge.Value) + remoteOrg := c.getRemoteOrganization(federationInit.PublicKey) remoteOrg.Lock() defer remoteOrg.Unlock() @@ -233,10 +235,7 @@ func (c *Channel) processFederationInit(msg message.Message, remoteOrg.organizerPk = federationInit.PublicKey remoteOrg.laoId = federationInit.LaoId remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - remoteOrg.challenge = messagedata.Challenge{ - Value: federationChallenge.Value, - ValidUntil: federationChallenge.Timestamp, - } + remoteOrg.challenge = federationChallenge remoteOrg.socket, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) if err != nil { @@ -297,30 +296,36 @@ func (c *Channel) processFederationExpect(msg message.Message, return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) } - remoteOrg := c.getRemoteOrganization(federationExpect.Challenge.Value) + remoteOrg := c.getRemoteOrganization(federationExpect.PublicKey) remoteOrg.Lock() defer remoteOrg.Unlock() if remoteOrg.state != None { return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) } + var federationChallenge messagedata.FederationChallenge + err = msg.UnmarshalData(&federationChallenge) + if err != nil { + return xerrors.Errorf("failed to unmarshal federationChallenge data: %v", err) + } c.Lock() - _, ok = c.challenges[federationExpect.Challenge] + _, ok = c.challenges[federationChallenge] // always remove the challenge, if present, to avoid challenge reuse - delete(c.challenges, federationExpect.Challenge) + delete(c.challenges, federationChallenge) c.Unlock() if !ok { return answer.NewAccessDeniedError("Invalid challenge %v", - federationExpect.Challenge) + federationChallenge) } remoteOrg.state = ExpectConnect - remoteOrg.challenge = federationExpect.Challenge + remoteOrg.challenge = federationChallenge remoteOrg.organizerPk = federationExpect.PublicKey remoteOrg.laoId = federationExpect.LaoId remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + remoteOrg.challengeMsg = federationExpect.ChallengeMsg return nil } @@ -372,10 +377,13 @@ func (c *Channel) processFederationChallenge(msg message.Message, remoteOrg.state = WaitResult remoteOrg.socket = s + federationResultData := messagedata.FederationResult{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionResult, - Status: "success", + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + PublicKey: remoteOrg.signedOrganizerPk, + ChallengeMsg: remoteOrg.challengeMsg, } dataBytes, err := json.Marshal(federationResultData) @@ -453,22 +461,17 @@ func (c *Channel) processChallengeRequest(msg message.Message, } challengeValue := hex.EncodeToString(randomBytes) expirationTime := time.Now().Add(time.Minute * 5).Unix() - challenge := messagedata.Challenge{ + federationChallenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, Value: challengeValue, ValidUntil: expirationTime, } c.Lock() - c.challenges[challenge] = struct{}{} + c.challenges[federationChallenge] = struct{}{} c.Unlock() - federationChallenge := messagedata.FederationChallenge{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallenge, - Value: challengeValue, - Timestamp: expirationTime, - } - challengeData, err := json.Marshal(federationChallenge) if err != nil { return xerrors.Errorf( @@ -614,16 +617,16 @@ func (c *Channel) processFederationResult(msg message.Message, return nil } -// getRemoteOrganization get the remoteOrganization for a given challenge +// getRemoteOrganization get the remoteOrganization for the given organizerPk // or return a new empty one. -func (c *Channel) getRemoteOrganization(challenge string) *remoteOrganization { +func (c *Channel) getRemoteOrganization(organizerPk string) *remoteOrganization { c.Lock() defer c.Unlock() - org, ok := c.remoteOrganizations[challenge] + org, ok := c.remoteOrganizations[organizerPk] if !ok { org = &remoteOrganization{state: None} - c.remoteOrganizations[challenge] = org + c.remoteOrganizations[organizerPk] = org } return org diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 69d4c96263..4d6ab5f25a 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -89,7 +89,7 @@ func Test_FederationRequestChallenge(t *testing.T) { require.Equal(t, messagedata.FederationObject, challenge.Object) require.Equal(t, messagedata.FederationActionChallenge, challenge.Action) - require.Greater(t, challenge.Timestamp, time.Now().Unix()) + require.Greater(t, challenge.ValidUntil, time.Now().Unix()) bytes, err := hex.DecodeString(challenge.Value) require.NoError(t, err) require.Len(t, bytes, 32) @@ -163,10 +163,7 @@ func Test_FederationExpect(t *testing.T) { LaoId: remoteLaoId, ServerAddress: remoteServerAddress, PublicKey: remoteOrganizerKeypair.publicKey, - Challenge: messagedata.Challenge{ - Value: challenge.Value, - ValidUntil: challenge.Timestamp, - }, + ChallengeMsg: challengeMsg, } federationMsg := generateMessage(t, organizerKeypair, federationExpect) @@ -214,9 +211,12 @@ func Test_FederationExpect_with_invalid_challenge(t *testing.T) { LaoId: remoteLaoId, ServerAddress: remoteServerAddress, PublicKey: remoteOrganizerKeypair.publicKey, - Challenge: messagedata.Challenge{ - Value: hex.EncodeToString(valueBytes), - ValidUntil: time.Now().Unix(), + ChallengeMsg: message.Message{ + Data: "aaaaaaaaaaa", + Sender: organizerKeypair.publicKey, + Signature: "bbbbbbbbbbb", + MessageID: messagedata.Hash("aaaaaaaaaaa", "bbbbbbbbbbb"), + WitnessSignatures: []message.WitnessSignature{}, }, } @@ -271,10 +271,7 @@ func Test_FederationChallenge_not_organizer(t *testing.T) { LaoId: remoteLaoId, ServerAddress: remoteServerAddress, PublicKey: remoteOrganizerKeypair.publicKey, - Challenge: messagedata.Challenge{ - Value: challenge.Value, - ValidUntil: challenge.Timestamp, - }, + ChallengeMsg: challengeMsg, } federationMsg := generateMessage(t, notOrganizerKeypair, federationExpect) @@ -407,7 +404,7 @@ func Test_FederationResult(t *testing.T) { PublicKey: signedPublicKeyBase64, ChallengeMsg: message.Message{ Data: challengeBase64, - Sender: organizerKeypair.publicKey, + Sender: remoteOrganizerKeypair.publicKey, Signature: signedChallengeBase64, MessageID: messagedata.Hash(challengeBase64, signedChallengeBase64), WitnessSignatures: []message.WitnessSignature{}, diff --git a/be1-go/message/messagedata/federation_challenge.go b/be1-go/message/messagedata/federation_challenge.go index af9909651f..1bf6a36db8 100644 --- a/be1-go/message/messagedata/federation_challenge.go +++ b/be1-go/message/messagedata/federation_challenge.go @@ -6,16 +6,15 @@ type FederationChallenge struct { Action string `json:"action"` // Value is a 32 bytes array encoded in hexadecimal - Value string `json:"value"` - // Timestamp is a Unix timestamp - Timestamp int64 `json:"timestamp"` -} - -type Challenge struct { Value string `json:"value"` - ValidUntil int64 `json:"valid_until"` + ValidUntil int64 `json:"timestamp"` } +//type Challenge struct { +// Value string `json:"value"` +// ValidUntil int64 `json:"valid_until"` +//} + // GetObject implements MessageData func (FederationChallenge) GetObject() string { return FederationObject diff --git a/be1-go/message/messagedata/federation_expect.go b/be1-go/message/messagedata/federation_expect.go index f56bb4023d..7e909c62f3 100644 --- a/be1-go/message/messagedata/federation_expect.go +++ b/be1-go/message/messagedata/federation_expect.go @@ -1,14 +1,16 @@ package messagedata +import "popstellar/message/query/method/message" + // FederationExpect defines a message data type FederationExpect struct { Object string `json:"object"` Action string `json:"action"` - LaoId string `json:"lao_id"` - ServerAddress string `json:"server_address"` - PublicKey string `json:"public_key"` - Challenge Challenge `json:"challenge"` + LaoId string `json:"lao_id"` + ServerAddress string `json:"server_address"` + PublicKey string `json:"public_key"` + ChallengeMsg message.Message `json:"challenge"` } // GetObject implements MessageData diff --git a/protocol/examples/messageData/federation_expect/federation_expect.json b/protocol/examples/messageData/federation_expect/federation_expect.json index 4d09f9a06b..1713c4a24e 100644 --- a/protocol/examples/messageData/federation_expect/federation_expect.json +++ b/protocol/examples/messageData/federation_expect/federation_expect.json @@ -5,7 +5,17 @@ "public_key": "UvViTxoKsB3XVP_ctkmOKCJpMWb7fCzrcb1XDmhNe7Q=", "server_address": "wss://ethz.ch:9000/server", "challenge": { +<<<<<<< Updated upstream "value": "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4", "valid_until": 1712854874 } -} \ No newline at end of file +} +======= + "data": "eyJvYmplY3QiOiJmZWRlcmF0aW9uIiwiYWN0aW9uIjoiY2hhbGxlbmdlIiwidmFsdWUiOiI4MmVhZGRlMmE0YmE4MzI1MThiOTBiYjkzYzg0ODBlZTFhZTE2YTkxZDVlZmU5MjgxZTkxZTJlYzExZGEwM2U0IiwidmFsaWRfdW50aWwiOjE3MTI4NTQ4NzR9", + "sender": "zXgzQaa_NpUe-v0Zk_4q8k184ohQ5nTQhBDKgncHzq4=", + "signature": "BILYwYkT5tOBL4rCD7yvhBkhAYqRXOI3ajQ2uJ1gAk-g6nRc38vMMnlHShuNCQ3dQFXYZPn37cCFelhWGjY8Bg==", + "message_id": "sD_PdryBuOr14_65h8L-e1lzdQpDWxUAngtu1uwqgEI=", + "witness_signatures": [] + } +} +>>>>>>> Stashed changes diff --git a/protocol/query/method/message/data/dataFederationExpect.json b/protocol/query/method/message/data/dataFederationExpect.json index c1ce945a4d..3023be234a 100644 --- a/protocol/query/method/message/data/dataFederationExpect.json +++ b/protocol/query/method/message/data/dataFederationExpect.json @@ -26,6 +26,7 @@ "$comment": "public key of the remote organizer" }, "challenge": { +<<<<<<< Updated upstream "type": "object", "properties": { "value": { @@ -42,6 +43,10 @@ }, "additionalProperties": false, "required": ["value", "valid_until"] +======= + "$ref": "../message.json", + "$comment": "message/message containing a FederationChallenge data" +>>>>>>> Stashed changes } }, "additionalProperties": false, @@ -53,4 +58,8 @@ "public_key", "challenge" ] -} \ No newline at end of file +<<<<<<< Updated upstream +} +======= +} +>>>>>>> Stashed changes From dc3afd89ad1e91d20da0bda2b2f317a27247378c Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Wed, 8 May 2024 16:27:14 +0200 Subject: [PATCH 18/48] change challenge check & remove wrongly added merge remainders --- be1-go/channel/federation/federation.go | 3 +-- be1-go/channel/federation/federation_test.go | 1 + .../message/data/dataFederationExpect.json | 23 ------------------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index 89f6df7c61..bfb4cf6e5a 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -295,7 +295,6 @@ func (c *Channel) processFederationExpect(msg message.Message, if err != nil { return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) } - remoteOrg := c.getRemoteOrganization(federationExpect.PublicKey) remoteOrg.Lock() defer remoteOrg.Unlock() @@ -304,7 +303,7 @@ func (c *Channel) processFederationExpect(msg message.Message, return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) } var federationChallenge messagedata.FederationChallenge - err = msg.UnmarshalData(&federationChallenge) + err = federationExpect.ChallengeMsg.UnmarshalData(&federationChallenge) if err != nil { return xerrors.Errorf("failed to unmarshal federationChallenge data: %v", err) } diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 4d6ab5f25a..0807cd1c91 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -154,6 +154,7 @@ func Test_FederationExpect(t *testing.T) { var challenge messagedata.FederationChallenge err = challengeMsg.UnmarshalData(&challenge) require.NoError(t, err) + t.Log(challengeMsg) remoteOrganizerKeypair := generateKeyPair(t) diff --git a/protocol/query/method/message/data/dataFederationExpect.json b/protocol/query/method/message/data/dataFederationExpect.json index 3023be234a..9bd86f2bac 100644 --- a/protocol/query/method/message/data/dataFederationExpect.json +++ b/protocol/query/method/message/data/dataFederationExpect.json @@ -26,27 +26,8 @@ "$comment": "public key of the remote organizer" }, "challenge": { -<<<<<<< Updated upstream - "type": "object", - "properties": { - "value": { - "type": "string", - "contentEncoding": "hex", - "pattern": "^[0-9a-fA-F]{64}$", - "$comment": "A 32 bytes array encoded in hexadecimal" - }, - "valid_until": { - "type": "integer", - "description": "[Timestamp] of the expiration time", - "minimum": 0 - } - }, - "additionalProperties": false, - "required": ["value", "valid_until"] -======= "$ref": "../message.json", "$comment": "message/message containing a FederationChallenge data" ->>>>>>> Stashed changes } }, "additionalProperties": false, @@ -58,8 +39,4 @@ "public_key", "challenge" ] -<<<<<<< Updated upstream } -======= -} ->>>>>>> Stashed changes From 6a837ae1e0d490394f19bb3056377d636563eb88 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Wed, 8 May 2024 16:42:15 +0200 Subject: [PATCH 19/48] remove more merge remainders --- .../messageData/federation_expect/federation_expect.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/protocol/examples/messageData/federation_expect/federation_expect.json b/protocol/examples/messageData/federation_expect/federation_expect.json index 1713c4a24e..159c608a2d 100644 --- a/protocol/examples/messageData/federation_expect/federation_expect.json +++ b/protocol/examples/messageData/federation_expect/federation_expect.json @@ -5,12 +5,6 @@ "public_key": "UvViTxoKsB3XVP_ctkmOKCJpMWb7fCzrcb1XDmhNe7Q=", "server_address": "wss://ethz.ch:9000/server", "challenge": { -<<<<<<< Updated upstream - "value": "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4", - "valid_until": 1712854874 - } -} -======= "data": "eyJvYmplY3QiOiJmZWRlcmF0aW9uIiwiYWN0aW9uIjoiY2hhbGxlbmdlIiwidmFsdWUiOiI4MmVhZGRlMmE0YmE4MzI1MThiOTBiYjkzYzg0ODBlZTFhZTE2YTkxZDVlZmU5MjgxZTkxZTJlYzExZGEwM2U0IiwidmFsaWRfdW50aWwiOjE3MTI4NTQ4NzR9", "sender": "zXgzQaa_NpUe-v0Zk_4q8k184ohQ5nTQhBDKgncHzq4=", "signature": "BILYwYkT5tOBL4rCD7yvhBkhAYqRXOI3ajQ2uJ1gAk-g6nRc38vMMnlHShuNCQ3dQFXYZPn37cCFelhWGjY8Bg==", @@ -18,4 +12,3 @@ "witness_signatures": [] } } ->>>>>>> Stashed changes From 20d4fc419b81e77c0e26276e2d2ebbdccb7929c8 Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Sun, 12 May 2024 19:16:32 +0200 Subject: [PATCH 20/48] added suggestions --- be1-go/channel/federation/federation.go | 60 ++++++++++++-------- be1-go/channel/federation/federation_test.go | 10 ++-- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index bfb4cf6e5a..f11baa9709 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -1,14 +1,12 @@ package federation import ( + "bytes" "crypto/rand" "encoding/base64" "encoding/hex" "encoding/json" "fmt" - "github.com/rs/zerolog" - "go.dedis.ch/kyber/v3/sign/schnorr" - "golang.org/x/xerrors" "popstellar/channel" "popstellar/channel/registry" "popstellar/crypto" @@ -24,6 +22,10 @@ import ( "strconv" "sync" "time" + + "github.com/rs/zerolog" + "go.dedis.ch/kyber/v3/sign/schnorr" + "golang.org/x/xerrors" ) const ( @@ -42,10 +44,9 @@ const ( ) type remoteOrganization struct { - laoId string - fedChannel string - organizerPk string - signedOrganizerPk string + laoId string + fedChannel string + organizerPk string // store the pop tokens of the other lao // popTokens map[string]struct{} @@ -270,6 +271,7 @@ func (c *Channel) processFederationInit(msg message.Message, } remoteOrg.socket.Send(buf) + remoteOrg.state = WaitResult return nil } @@ -381,7 +383,7 @@ func (c *Channel) processFederationChallenge(msg message.Message, Object: messagedata.FederationObject, Action: messagedata.FederationActionResult, Status: "success", - PublicKey: remoteOrg.signedOrganizerPk, + PublicKey: remoteOrg.organizerPk, ChallengeMsg: remoteOrg.challengeMsg, } @@ -397,9 +399,15 @@ func (c *Channel) processFederationChallenge(msg message.Message, } signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) + serverPubKey, err := c.hub.GetPubKeyServ().MarshalBinary() + if err != nil { + return xerrors.Errorf("failed to marshal public key of server: %v", err) + + } + federationResultMsg := message.Message{ Data: dataBase64, - Sender: c.hub.GetPubKeyServ().String(), + Sender: base64.URLEncoding.EncodeToString(serverPubKey), Signature: signatureBase64, MessageID: messagedata.Hash(dataBase64, signatureBase64), WitnessSignatures: []message.WitnessSignature{}, @@ -427,6 +435,7 @@ func (c *Channel) processFederationChallenge(msg message.Message, } remoteOrg.socket.Send(buf) + return nil } @@ -533,13 +542,6 @@ func (c *Channel) processFederationResult(msg message.Message, msgData) } - /* - This state should probably be set after sending the FederationInit message? - */ - //if c.state != WaitResult { - // return answer.NewInternalServerError(invalidStateError, c.state, msg) - //} - var federationResult messagedata.FederationResult err := msg.UnmarshalData(&federationResult) @@ -572,9 +574,15 @@ func (c *Channel) processFederationResult(msg message.Message, } - remoteOrg := c.getRemoteOrganization(federationChallenge.Value) + remoteOrg := c.getRemoteOrganization(federationResult.ChallengeMsg.Sender) remoteOrg.Lock() defer remoteOrg.Unlock() + + if remoteOrg.state != WaitResult { + return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) + + } + pkBytes, err := base64.URLEncoding.DecodeString(remoteOrg.organizerPk) if err != nil { return xerrors.Errorf("failed to decode remote organizers public key: %v", err) @@ -594,23 +602,27 @@ func (c *Channel) processFederationResult(msg message.Message, } - pkSignatureBytes, err := base64.URLEncoding.DecodeString(federationResult.PublicKey) + resultPkBytes, err := base64.URLEncoding.DecodeString(federationResult.PublicKey) if err != nil { - return xerrors.Errorf("failed to decode signature on local public key in FederationResult message: %v", err) + return xerrors.Errorf("failed to decode local public key in FederationResult message: %v", err) } - localPkBinary, err := c.hub.GetPubKeyOwner().MarshalBinary() + localPkBytes, err := c.hub.GetPubKeyOwner().MarshalBinary() if err != nil { return xerrors.Errorf("failed to marshal local organizer public key: %v", err) } - - err = schnorr.Verify(crypto.Suite, remotePk, localPkBinary, pkSignatureBytes) - if err != nil { - return xerrors.Errorf("failed to verify remote signature on local organizer public key: %v", err) + if !(bytes.Equal(resultPkBytes, localPkBytes)) { + return xerrors.Errorf("invalid public key contained in FederationResult message") } + //err = schnorr.Verify(crypto.Suite, remotePk, localPkBinary, pkSignatureBytes) + //if err != nil { + // return xerrors.Errorf("failed to verify remote signature on local organizer public key: %v", err) + + //} + remoteOrg.state = Connected return nil diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 0807cd1c91..884cb02340 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -154,7 +154,6 @@ func Test_FederationExpect(t *testing.T) { var challenge messagedata.FederationChallenge err = challengeMsg.UnmarshalData(&challenge) require.NoError(t, err) - t.Log(challengeMsg) remoteOrganizerKeypair := generateKeyPair(t) @@ -364,9 +363,10 @@ func Test_FederationResult(t *testing.T) { state: None, } remoteOrg.organizerPk = remoteOrganizerKeypair.publicKey + remoteOrg.state = WaitResult var remoteOrgs = map[string]*remoteOrganization{ - "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4": remoteOrg, + remoteOrganizerKeypair.publicKey: remoteOrg, } newChannel := &Channel{ @@ -383,7 +383,7 @@ func Test_FederationResult(t *testing.T) { publicKeyBytes, err := organizerKeypair.public.MarshalBinary() require.NoError(t, err) - signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) + //signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) require.NoError(t, err) challengeFile := filepath.Join(relativeMsgDataExamplePath, @@ -393,7 +393,7 @@ func Test_FederationResult(t *testing.T) { challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) require.NoError(t, err) - signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) + //signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) signedChallengeBytes, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, challengeBytes) require.NoError(t, err) signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) @@ -402,7 +402,7 @@ func Test_FederationResult(t *testing.T) { Object: messagedata.FederationObject, Action: messagedata.FederationActionResult, Status: "success", - PublicKey: signedPublicKeyBase64, + PublicKey: base64.URLEncoding.EncodeToString(publicKeyBytes), ChallengeMsg: message.Message{ Data: challengeBase64, Sender: remoteOrganizerKeypair.publicKey, From 51b0b6df833e3647379628c70683f758c5ffc1a8 Mon Sep 17 00:00:00 2001 From: Florentin A Date: Mon, 13 May 2024 11:59:38 +0200 Subject: [PATCH 21/48] Update be1-go/channel/federation/federation.go Co-authored-by: Arnaud S5 --- be1-go/channel/federation/federation.go | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index f11baa9709..68c1588c4d 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -602,27 +602,10 @@ func (c *Channel) processFederationResult(msg message.Message, } - resultPkBytes, err := base64.URLEncoding.DecodeString(federationResult.PublicKey) - if err != nil { - return xerrors.Errorf("failed to decode local public key in FederationResult message: %v", err) - - } - localPkBytes, err := c.hub.GetPubKeyOwner().MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal local organizer public key: %v", err) - - } - if !(bytes.Equal(resultPkBytes, localPkBytes)) { + if c.localOrganizerPk != federationResult.PublicKey { return xerrors.Errorf("invalid public key contained in FederationResult message") - } - //err = schnorr.Verify(crypto.Suite, remotePk, localPkBinary, pkSignatureBytes) - //if err != nil { - // return xerrors.Errorf("failed to verify remote signature on local organizer public key: %v", err) - - //} - remoteOrg.state = Connected return nil From da1cdb99f586f7331c07d4fb7ef92df45a6b8b7e Mon Sep 17 00:00:00 2001 From: Florentin Aigner Date: Mon, 13 May 2024 12:06:30 +0200 Subject: [PATCH 22/48] fix failing test --- be1-go/channel/federation/federation.go | 1 - 1 file changed, 1 deletion(-) diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go index 68c1588c4d..be0de6be22 100644 --- a/be1-go/channel/federation/federation.go +++ b/be1-go/channel/federation/federation.go @@ -1,7 +1,6 @@ package federation import ( - "bytes" "crypto/rand" "encoding/base64" "encoding/hex" From c6b307ff6ee4b4822514ac890e6ca2a2698e091d Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 13 May 2024 21:07:49 +0200 Subject: [PATCH 23/48] fix json field name --- be1-go/message/messagedata/federation_challenge.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/be1-go/message/messagedata/federation_challenge.go b/be1-go/message/messagedata/federation_challenge.go index 1bf6a36db8..9620ba4c60 100644 --- a/be1-go/message/messagedata/federation_challenge.go +++ b/be1-go/message/messagedata/federation_challenge.go @@ -7,14 +7,9 @@ type FederationChallenge struct { // Value is a 32 bytes array encoded in hexadecimal Value string `json:"value"` - ValidUntil int64 `json:"timestamp"` + ValidUntil int64 `json:"valid_until"` } -//type Challenge struct { -// Value string `json:"value"` -// ValidUntil int64 `json:"valid_until"` -//} - // GetObject implements MessageData func (FederationChallenge) GetObject() string { return FederationObject From 0059dd2986cfe3d8bae74a1e86f6b76bf8b0a457 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 15:53:55 +0200 Subject: [PATCH 24/48] Start adapting federation code to new architecture --- .../internal/popserver/database/database.go | 4 + .../database/repository/repository.go | 23 + .../popserver/database/sqlite/sqlite.go | 84 ++++ .../popserver/database/sqlite/sqlite_const.go | 33 ++ be1-go/internal/popserver/handler/channel.go | 2 + .../internal/popserver/handler/federation.go | 453 ++++++++++++++++++ be1-go/internal/popserver/handler/root.go | 1 + 7 files changed, 600 insertions(+) create mode 100644 be1-go/internal/popserver/handler/federation.go diff --git a/be1-go/internal/popserver/database/database.go b/be1-go/internal/popserver/database/database.go index 6bc4eef2cb..b59609beca 100644 --- a/be1-go/internal/popserver/database/database.go +++ b/be1-go/internal/popserver/database/database.go @@ -61,3 +61,7 @@ func GetElectionRepositoryInstance() (repository.ElectionRepository, *answer.Err func GetReactionRepositoryInstance() (repository.ReactionRepository, *answer.Error) { return getInstance() } + +func GetFederationRepositoryInstance() (repository.FederationRepository, *answer.Error) { + return getInstance() +} diff --git a/be1-go/internal/popserver/database/repository/repository.go b/be1-go/internal/popserver/database/repository/repository.go index 539cff3023..3e29d1f91d 100644 --- a/be1-go/internal/popserver/database/repository/repository.go +++ b/be1-go/internal/popserver/database/repository/repository.go @@ -3,6 +3,7 @@ package repository import ( "go.dedis.ch/kyber/v3" "popstellar/internal/popserver/types" + "popstellar/message/messagedata" "popstellar/message/query/method/message" ) @@ -16,6 +17,7 @@ type Repository interface { ChirpRepository CoinRepository ReactionRepository + FederationRepository // StoreServerKeys stores the keys of the server StoreServerKeys(electionPubKey kyber.Point, electionSecretKey kyber.Scalar) error @@ -183,3 +185,24 @@ type ReactionRepository interface { // StoreMessageAndData stores a message with an object and an action inside the database. StoreMessageAndData(channelID string, msg message.Message) error } + +type FederationRepository interface { + // GetOrganizerPubKey returns the organizer public key of a LAO. + GetOrganizerPubKey(laoID string) (kyber.Point, error) + + // IsChallengeValid returns true if the challenge is valid and not used yet + IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error + + // RemoveChallenge removes the challenge from the database to avoid reuse + RemoveChallenge(challenge messagedata.FederationChallenge) error + + // GetFederationExpect return a FederationExpect where the organizer is + // the given public keys + GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) + + // GetServerKeys get the keys of the server + GetServerKeys() (kyber.Point, kyber.Scalar, error) + + // StoreMessageAndData stores a message with an object and an action inside the database. + StoreMessageAndData(channelID string, msg message.Message) error +} diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index abcc72eca4..36e5ce0e75 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1263,3 +1263,87 @@ func (s *SQLite) GetReactionSender(messageID string) (string, error) { } return sender, nil } + +//====================================================================================================================== +// FederationRepository interface implementation +//====================================================================================================================== + +func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error { + dbLock.RLock() + defer dbLock.RUnlock() + + var federationChallenge messagedata.FederationChallenge + + err := s.database.QueryRow(selectValidFederationChallenges, + senderPk, messagedata.FederationObject, + messagedata.FederationActionChallenge, + challenge.Value, challenge.ValidUntil).Scan(&federationChallenge) + if err != nil { + return err + } + + if federationChallenge != challenge { + return xerrors.New("the federation challenge doesn't match") + } + + return nil +} + +func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge) error { + dbLock.Lock() + defer dbLock.Unlock() + + result, err := s.database.Exec(deleteFederationChallenge, + messagedata.FederationObject, + messagedata.FederationActionChallenge, challenge.Value, + challenge.ValidUntil) + if err != nil { + return err + } + + nb, err := result.RowsAffected() + if err != nil { + return err + } + + if nb != 1 { + return xerrors.New("unexpected number of rows affected") + } + + return nil +} + +func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) { + dbLock.RLock() + defer dbLock.RUnlock() + + rows, err := s.database.Query(selectFederationExpects, senderPk, + messagedata.FederationObject, messagedata.FederationActionExpect, + remotePk) + if err != nil { + return messagedata.FederationExpect{}, err + } + + // iterate over all FederationExpect sent from the given sender pk, + // and search the one matching the given FederationChallenge + for rows.Next() { + var federationExpect messagedata.FederationExpect + + err = rows.Scan(&federationExpect) + if err != nil { + continue + } + + var federationChallenge messagedata.FederationChallenge + errAnswer := federationExpect.ChallengeMsg.UnmarshalMsgData(federationChallenge) + if errAnswer != nil { + return messagedata.FederationExpect{}, errAnswer + } + + if federationChallenge == challenge { + return federationExpect, nil + } + } + + return messagedata.FederationExpect{}, sql.ErrNoRows +} diff --git a/be1-go/internal/popserver/database/sqlite/sqlite_const.go b/be1-go/internal/popserver/database/sqlite/sqlite_const.go index 33d3092378..aab59ac00f 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite_const.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite_const.go @@ -16,6 +16,7 @@ const ( AuthType = "auth" PopChaType = "popcha" GeneralChirpType = "generalChirp" + FederationType = "federation" ) var channelTypeToID = map[string]string{ @@ -29,6 +30,7 @@ var channelTypeToID = map[string]string{ CoinType: "8", AuthType: "9", GeneralChirpType: "10", + FederationType: "11", } var channelTypes = []string{ @@ -42,6 +44,7 @@ var channelTypes = []string{ CoinType, AuthType, GeneralChirpType, + FederationType, } const ( @@ -284,6 +287,36 @@ const ( json_extract(messageData, '$.action') FROM message WHERE messageID = ?` + + selectValidFederationChallenges = ` + SELECT message + FROM message + WHERE json_extract(message, '$.sender') = ? + AND json_extract(messageData, '$.object') = ? + AND json_extract(messageData, '$.action') = ? + AND json_extract(messageData, '$.challenge.value') = ? + AND json_extract(messageData, '$.challenge.valid_until') = ? + ORDER BY message.storedTime DESC + ` + + deleteFederationChallenge = ` + DELETE + FROM message + WHERE json_extract(messageData, '$.object') = ? + AND json_extract(messageData, '$.action') = ? + AND json_extract(messageData, '$.challenge.value') = ? + AND json_extract(messageData, '$.challenge.valid_until') = ? + ` + + selectFederationExpects = ` + SELECT messageData + FROM message + WHERE json_extract(message, '$.sender') = ? + AND json_extract(messageData, '$.object') = ? + AND json_extract(messageData, '$.action') = ? + AND json_extract(messageData, '$.public_key') = ? + ORDER BY message.storedTime DESC + ` ) const ( diff --git a/be1-go/internal/popserver/handler/channel.go b/be1-go/internal/popserver/handler/channel.go index 43c08acfc3..275e37dd10 100644 --- a/be1-go/internal/popserver/handler/channel.go +++ b/be1-go/internal/popserver/handler/channel.go @@ -60,6 +60,8 @@ func handleChannel(channelPath string, msg message.Message) *answer.Error { errAnswer = handleChannelReaction(channelPath, msg) case sqlite.CoinType: errAnswer = handleChannelCoin(channelPath, msg) + case sqlite.FederationType: + errAnswer = handleChannelFederation(channelPath, msg) default: errAnswer = answer.NewInvalidResourceError("unknown channel type for %s", channelPath) } diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go new file mode 100644 index 0000000000..7f6758a162 --- /dev/null +++ b/be1-go/internal/popserver/handler/federation.go @@ -0,0 +1,453 @@ +package handler + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "go.dedis.ch/kyber/v3/sign/schnorr" + "popstellar/crypto" + "popstellar/internal/popserver/database" + "popstellar/internal/popserver/state" + jsonrpc "popstellar/message" + "popstellar/message/answer" + "popstellar/message/messagedata" + "popstellar/message/query" + "popstellar/message/query/method" + "popstellar/message/query/method/message" + "strings" + "time" +) + +func handleChannelFederation(channelPath string, msg message.Message) *answer.Error { + object, action, errAnswer := verifyDataAndGetObjectAction(msg) + if errAnswer != nil { + return errAnswer.Wrap("handleChannelFederation") + } + + if object != messagedata.FederationObject { + errAnswer = answer.NewInvalidMessageFieldError("invalid object %v", object) + return errAnswer.Wrap("handleChannelFederation") + } + + switch action { + case messagedata.FederationActionChallengeRequest: + errAnswer = handleRequestChallenge(msg, channelPath) + case messagedata.FederationActionInit: + errAnswer = handleInit(msg, channelPath) + case messagedata.FederationActionExpect: + errAnswer = handleExpect(msg, channelPath) + case messagedata.FederationActionChallenge: + errAnswer = handleChallenge(msg, channelPath) + + default: + errAnswer = answer.NewInvalidMessageFieldError("failed to handle %s#%s, invalid object#action", object, action) + } + + if errAnswer != nil { + return errAnswer.Wrap("handleChannelFederation") + } + + return nil +} + +// handleRequestChallenge expects the sender to be the organizer of the lao, +// a challenge message is then stored and broadcast on the same channel. +// The FederationChallengeRequest message is neither stored nor broadcast +func handleRequestChallenge(msg message.Message, channelPath string) *answer.Error { + var requestChallenge messagedata.FederationChallengeRequest + errAnswer := msg.UnmarshalMsgData(&requestChallenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + errAnswer = verifyLocalOrganizer(msg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + randomBytes := make([]byte, 32) + _, err := rand.Read(randomBytes) + if err != nil { + errAnswer = answer.NewInternalServerError( + "Failed to generate random bytes: %v", err) + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + challengeValue := hex.EncodeToString(randomBytes) + expirationTime := time.Now().Add(time.Minute * 5).Unix() + federationChallenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: challengeValue, + ValidUntil: expirationTime, + } + + // The challenge sent to the organizer is signed by the server but should + // not be confused with the challenge that will be signed by the organizer + challengeMsg, errAnswer := createMessage(federationChallenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + // store the generated challenge message, not the challenge request + err = db.StoreMessageAndData(channelPath, challengeMsg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + errAnswer = broadcastToAllClients(challengeMsg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationRequestChallenge") + } + + return nil +} + +// handleExpect checks that the message is from the local organizer and that +// it contains a valid challenge, then stores the msg +func handleExpect(msg message.Message, channelPath string) *answer.Error { + var federationExpect messagedata.FederationExpect + errAnswer := msg.UnmarshalMsgData(&federationExpect) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + errAnswer = verifyLocalOrganizer(msg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + // Both the FederationExpect and the embedded FederationChallenge need to + // be signed by the local organizer + errAnswer = verifyLocalOrganizer(federationExpect.ChallengeMsg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + var challenge messagedata.FederationChallenge + errAnswer = federationExpect.ChallengeMsg.UnmarshalMsgData(challenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + serverPk, errAnswer := getServerPk() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + err := db.IsChallengeValid(serverPk, challenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) + return errAnswer.Wrap("handleFederationExpect") + } + + err = db.StoreMessageAndData(channelPath, msg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationExpect") + } + + return nil +} + +func handleInit(msg message.Message, channelPath string) *answer.Error { + var federationInit messagedata.FederationInit + errAnswer := msg.UnmarshalMsgData(&federationInit) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + errAnswer = verifyLocalOrganizer(msg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + // Both the FederationInit and the embedded FederationChallenge need to + // be signed by the local organizer + errAnswer = verifyLocalOrganizer(federationInit.ChallengeMsg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + var challenge messagedata.FederationChallenge + errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + err := db.IsChallengeValid(channelPath, challenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) + return errAnswer.Wrap("handleFederationInit") + } + + err = db.StoreMessageAndData(channelPath, msg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationInit") + } + + // TODO fix + remote, err := state.ConnectTo(federationInit.ServerAddress) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to connect to %s: %v", federationInit.ServerAddress, err) + return errAnswer.Wrap("handleFederationInit") + } + + //Force the remote server to be subscribed to /root//federation + remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + _ = state.AddChannel(remoteChannel) + errAnswer = state.Subscribe(remote, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + subscribeMsg := method.Subscribe{ + Base: query.Base{ + JSONRPCBase: jsonrpc.JSONRPCBase{ + JSONRPC: "2.0", + }, + Method: "subscribe", + }, + Params: method.SubscribeParams{Channel: channelPath}, + } + + subscribeBytes, err := json.Marshal(subscribeMsg) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal subscribe: %v", err) + return errAnswer.Wrap("handleFederationInit") + } + + // Subscribe to /root//federation on the remote server + errAnswer = state.SendToAll(subscribeBytes, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + // send the challenge to a channel where the remote server is subscribed to + errAnswer = publishTo(federationInit.ChallengeMsg, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + return nil +} + +func handleChallenge(msg message.Message, channelPath string) *answer.Error { + var federationChallenge messagedata.FederationChallenge + errAnswer := msg.UnmarshalMsgData(&federationChallenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + + organizerPk, errAnswer := getOrganizerPk(channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + + federationExpect, err := db.GetFederationExpect(organizerPk, msg.Sender, + federationChallenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError( + "failed to get federation expect: %v", err) + return errAnswer.Wrap("handleFederationChallenge") + } + + err = db.RemoveChallenge(federationChallenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError("failed to use challenge: %v", err) + return errAnswer.Wrap("handleFederationChallenge") + } + + result := messagedata.FederationResult{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + Reason: "", + PublicKey: federationExpect.PublicKey, + ChallengeMsg: federationExpect.ChallengeMsg, + } + + resultMsg, errAnswer := createMessage(result) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + + // publish the FederationResult to the other server + remoteChannel := fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + errAnswer = publishTo(resultMsg, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + + // broadcast the FederationResult to the local organizer ? + //errAnswer = broadcastToAllClients(resultMsg, channelPath) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationChallenge") + //} + + return nil +} + +func getOrganizerPk(federationChannel string) (string, *answer.Error) { + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return "", errAnswer.Wrap("getOrganizerPk") + } + + laoChannel := strings.TrimSuffix(federationChannel, "/federation") + + organizerPk, err := db.GetOrganizerPubKey(laoChannel) + if err != nil { + errAnswer = answer.NewInternalServerError("failed to get key") + return "", errAnswer.Wrap("getOrganizerPk") + } + + organizerPkBytes, err := organizerPk.MarshalBinary() + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal organizer key: %v", err) + return "", errAnswer.Wrap("getOrganizerPk") + } + + return base64.URLEncoding.EncodeToString(organizerPkBytes), nil +} + +func getServerPk() (string, *answer.Error) { + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return "", errAnswer.Wrap("getServerPk") + } + + serverPk, _, err := db.GetServerKeys() + if err != nil { + errAnswer = answer.NewInternalServerError( + "Failed to get server keys: %v", err) + return "", errAnswer.Wrap("getServerPk") + } + + serverPkBytes, err := serverPk.MarshalBinary() + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal server pk: %v", err) + return "", errAnswer.Wrap("getServerPk") + } + + return base64.URLEncoding.EncodeToString(serverPkBytes), nil +} + +func verifyLocalOrganizer(msg message.Message, channelPath string) *answer.Error { + organizePk, errAnswer := getOrganizerPk(channelPath) + if errAnswer != nil { + return errAnswer.Wrap("verifyLocalOrganizer") + } + + if organizePk != msg.Sender { + errAnswer = answer.NewAccessDeniedError("sender is not the organizer of the channel") + return errAnswer.Wrap("verifyLocalSender") + } + + return nil +} + +func createMessage(data messagedata.MessageData) (message.Message, *answer.Error) { + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return message.Message{}, errAnswer.Wrap("createMessage") + } + + dataBytes, err := json.Marshal(data) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal %v: %v", data, err) + return message.Message{}, errAnswer.Wrap("createMessage") + } + dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) + + serverPk, serverSk, err := db.GetServerKeys() + if err != nil { + errAnswer = answer.NewInternalServerError( + "Failed to get server keys: %v", err) + return message.Message{}, errAnswer.Wrap("createMessage") + } + + senderBytes, err := serverPk.MarshalBinary() + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal key: %v", err) + return message.Message{}, errAnswer.Wrap("createMessage") + } + sender := base64.URLEncoding.EncodeToString(senderBytes) + + signatureBytes, err := schnorr.Sign(crypto.Suite, serverSk, dataBytes) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to sign message: %v", err) + return message.Message{}, errAnswer.Wrap("createMessage") + } + signature := base64.URLEncoding.EncodeToString(signatureBytes) + + msg := message.Message{ + Data: dataBase64, + Sender: sender, + Signature: signature, + MessageID: messagedata.Hash(dataBase64, signature), + WitnessSignatures: []message.WitnessSignature{}, + } + + return msg, nil +} + +func publishTo(msg message.Message, channel string) *answer.Error { + publishMsg := method.Publish{ + Base: query.Base{ + JSONRPCBase: jsonrpc.JSONRPCBase{ + JSONRPC: "2.0", + }, + Method: "publish", + }, + Params: method.PublishParams{ + Channel: channel, + Message: msg, + }, + } + + publishBytes, err := json.Marshal(&publishMsg) + if err != nil { + errAnswer := answer.NewInternalServerError( + "failed to marshal publish: %v", err) + return errAnswer.Wrap("publishTo") + } + + errAnswer := state.SendToAll(publishBytes, channel) + if errAnswer != nil { + return errAnswer.Wrap("publishTo") + } + + return nil +} diff --git a/be1-go/internal/popserver/handler/root.go b/be1-go/internal/popserver/handler/root.go index 9062738e21..90049a2b17 100644 --- a/be1-go/internal/popserver/handler/root.go +++ b/be1-go/internal/popserver/handler/root.go @@ -22,6 +22,7 @@ const ( Consensus = "/consensus" Coin = "/coin" Auth = "/authentication" + Federation = "/federation" ) func handleChannelRoot(msg message.Message) *answer.Error { From cb4f69a7b49545e8700ce52564bf9b275e5b5936 Mon Sep 17 00:00:00 2001 From: stuart Date: Mon, 20 May 2024 16:52:57 +0200 Subject: [PATCH 25/48] add hubParams state --- be1-go/channel/federation/federation_test.go | 1290 ++++++++--------- .../database/repository/mock_repository.go | 68 +- .../internal/popserver/handler/federation.go | 170 +-- be1-go/internal/popserver/hub.go | 38 +- be1-go/internal/popserver/state/state.go | 65 +- be1-go/internal/popserver/types/hub_params.go | 37 + 6 files changed, 928 insertions(+), 740 deletions(-) create mode 100644 be1-go/internal/popserver/types/hub_params.go diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go index 884cb02340..c275306e12 100644 --- a/be1-go/channel/federation/federation_test.go +++ b/be1-go/channel/federation/federation_test.go @@ -1,648 +1,648 @@ package federation -import ( - "crypto/rand" - "encoding/base64" - "encoding/hex" - "encoding/json" - "io" - "os" - "path/filepath" - "popstellar/channel" - "popstellar/crypto" - "popstellar/inbox" - jsonrpc "popstellar/message" - "popstellar/message/messagedata" - "popstellar/message/query" - "popstellar/message/query/method" - "popstellar/message/query/method/message" - "popstellar/network/socket" - "popstellar/validation" - "sync" - "testing" - "time" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" - "golang.org/x/sync/semaphore" - "golang.org/x/xerrors" -) - -const ( - relativeMsgDataExamplePath string = "../../../protocol/examples/messageData" - relativeQueryExamplePath string = "../../../protocol/examples/query" - localServerAddress = "ws://localhost:19008/client" - remoteServerAddress = "ws://localhost:19009/client" - localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" - remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" - localFedChannel = "/root/" + localLaoId + "/federation" - remoteFedChannel = "/root/" + remoteLaoId + "/federation" -) - -// TestChannel_FederationRequestChallenge tests that a FederationChallenge is -// received after a valid FederationChallengeRequest is published by the -// organizer -func Test_FederationRequestChallenge(t *testing.T) { - organizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - requestData := messagedata.FederationChallengeRequest{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallengeRequest, - Timestamp: time.Now().Unix(), - } - - requestMsg := generateMessage(t, organizerKeypair, requestData) - publishMsg := generatePublish(t, localFedChannel, requestMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.NoError(t, err) - - require.NoError(t, socket.err) - require.NotNil(t, socket.msg) - - var challengePublish method.Publish - err = json.Unmarshal(socket.msg, &challengePublish) - require.NoError(t, err) - - require.Equal(t, localFedChannel, challengePublish.Params.Channel) - challengeMsg := challengePublish.Params.Message - - dataBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Data) - require.NoError(t, err) - signatureBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Signature) - require.NoError(t, err) - - err = schnorr.Verify(crypto.Suite, fakeHub.pubKeyServ, dataBytes, signatureBytes) - require.NoError(t, err) - - var challenge messagedata.FederationChallenge - err = challengeMsg.UnmarshalData(&challenge) - require.NoError(t, err) - - require.Equal(t, messagedata.FederationObject, challenge.Object) - require.Equal(t, messagedata.FederationActionChallenge, challenge.Action) - require.Greater(t, challenge.ValidUntil, time.Now().Unix()) - bytes, err := hex.DecodeString(challenge.Value) - require.NoError(t, err) - require.Len(t, bytes, 32) -} - -func Test_FederationRequestChallenge_not_organizer(t *testing.T) { - organizerKeypair := generateKeyPair(t) - notOrganizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - requestData := messagedata.FederationChallengeRequest{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallengeRequest, - Timestamp: time.Now().Unix(), - } - - requestMsg := generateMessage(t, notOrganizerKeypair, requestData) - publishMsg := generatePublish(t, localFedChannel, requestMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.Error(t, err) - - require.Nil(t, socket.msg) -} - -func Test_FederationExpect(t *testing.T) { - organizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - requestData := messagedata.FederationChallengeRequest{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallengeRequest, - Timestamp: time.Now().Unix(), - } - - requestMsg := generateMessage(t, organizerKeypair, requestData) - publishMsg := generatePublish(t, localFedChannel, requestMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.NoError(t, err) - - require.NoError(t, socket.err) - require.NotNil(t, socket.msg) - - var challengePublish method.Publish - err = json.Unmarshal(socket.msg, &challengePublish) - require.NoError(t, err) - - require.Equal(t, localFedChannel, challengePublish.Params.Channel) - challengeMsg := challengePublish.Params.Message - - var challenge messagedata.FederationChallenge - err = challengeMsg.UnmarshalData(&challenge) - require.NoError(t, err) - - remoteOrganizerKeypair := generateKeyPair(t) - - federationExpect := messagedata.FederationExpect{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionExpect, - LaoId: remoteLaoId, - ServerAddress: remoteServerAddress, - PublicKey: remoteOrganizerKeypair.publicKey, - ChallengeMsg: challengeMsg, - } - - federationMsg := generateMessage(t, organizerKeypair, federationExpect) - federationPublish := generatePublish(t, localFedChannel, federationMsg) - - err = fedChannel.Publish(federationPublish, socket) - require.NoError(t, err) - - require.NoError(t, socket.err) - require.NotNil(t, socket.msg) -} - -func Test_FederationExpect_with_invalid_challenge(t *testing.T) { - organizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - requestData := messagedata.FederationChallengeRequest{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallengeRequest, - Timestamp: time.Now().Unix(), - } - - requestMsg := generateMessage(t, organizerKeypair, requestData) - publishMsg := generatePublish(t, localFedChannel, requestMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.NoError(t, err) - - require.NoError(t, socket.err) - require.NotNil(t, socket.msg) - - remoteOrganizerKeypair := generateKeyPair(t) - - valueBytes := make([]byte, 32) - _, _ = rand.Read(valueBytes) - - federationExpect := messagedata.FederationExpect{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionExpect, - LaoId: remoteLaoId, - ServerAddress: remoteServerAddress, - PublicKey: remoteOrganizerKeypair.publicKey, - ChallengeMsg: message.Message{ - Data: "aaaaaaaaaaa", - Sender: organizerKeypair.publicKey, - Signature: "bbbbbbbbbbb", - MessageID: messagedata.Hash("aaaaaaaaaaa", "bbbbbbbbbbb"), - WitnessSignatures: []message.WitnessSignature{}, - }, - } - - expectMsg := generateMessage(t, organizerKeypair, federationExpect) - expectPublish := generatePublish(t, localFedChannel, expectMsg) - - err = fedChannel.Publish(expectPublish, socket) - require.Error(t, err) -} - -func Test_FederationChallenge_not_organizer(t *testing.T) { - organizerKeypair := generateKeyPair(t) - notOrganizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - requestData := messagedata.FederationChallengeRequest{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallengeRequest, - Timestamp: time.Now().Unix(), - } - - requestMsg := generateMessage(t, organizerKeypair, requestData) - publishMsg := generatePublish(t, localFedChannel, requestMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.NoError(t, err) - - require.NoError(t, socket.err) - require.NotNil(t, socket.msg) - - var challengePublish method.Publish - err = json.Unmarshal(socket.msg, &challengePublish) - require.NoError(t, err) - - require.Equal(t, localFedChannel, challengePublish.Params.Channel) - challengeMsg := challengePublish.Params.Message - - var challenge messagedata.FederationChallenge - err = challengeMsg.UnmarshalData(&challenge) - require.NoError(t, err) - - remoteOrganizerKeypair := generateKeyPair(t) - - federationExpect := messagedata.FederationExpect{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionExpect, - LaoId: remoteLaoId, - ServerAddress: remoteServerAddress, - PublicKey: remoteOrganizerKeypair.publicKey, - ChallengeMsg: challengeMsg, - } - - federationMsg := generateMessage(t, notOrganizerKeypair, federationExpect) - federationPublish := generatePublish(t, localFedChannel, federationMsg) - - err = fedChannel.Publish(federationPublish, socket) - require.Error(t, err) -} - -func Test_FederationInit(t *testing.T) { - organizerKeypair := generateKeyPair(t) - remoteOrganizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - fedChannel := NewChannel(localFedChannel, fakeHub, nolog, - organizerKeypair.publicKey) - - challengeFile := filepath.Join(relativeMsgDataExamplePath, - "federation_challenge", - "federation_challenge.json") - challengeBytes, err := os.ReadFile(challengeFile) - require.NoError(t, err) - - challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) - - signatureBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) - require.NoError(t, err) - - signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) - - federationInitData := messagedata.FederationInit{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionInit, - LaoId: remoteLaoId, - ServerAddress: remoteServerAddress, - PublicKey: remoteOrganizerKeypair.publicKey, - ChallengeMsg: message.Message{ - Data: challengeBase64, - Sender: organizerKeypair.publicKey, - Signature: signatureBase64, - MessageID: messagedata.Hash(challengeBase64, signatureBase64), - WitnessSignatures: []message.WitnessSignature{}, - }, - } - - initMsg := generateMessage(t, organizerKeypair, federationInitData) - publishMsg := generatePublish(t, localFedChannel, initMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = fedChannel.Publish(publishMsg, socket) - require.NoError(t, err) - - require.NotNil(t, fakeHub.socketClient) - require.Equal(t, remoteServerAddress, fakeHub.socketClient.ID()) - - // A Publish message containing the challenge message should be sent - msgBytes := fakeHub.socketClient.msg - require.NotNil(t, msgBytes) - - var publishMsg2 method.Publish - err = json.Unmarshal(msgBytes, &publishMsg2) - require.NoError(t, err) - - require.Equal(t, remoteFedChannel, publishMsg2.Params.Channel) - require.Equal(t, challengeBase64, publishMsg2.Params.Message.Data) - require.Equal(t, signatureBase64, publishMsg2.Params.Message.Signature) - require.Equal(t, organizerKeypair.publicKey, publishMsg2.Params.Message.Sender) - - // should expect to receive back a FedererationResult - require.NoError(t, socket.err) - //require.NotNil(t, socket.msg) - -} - -func Test_FederationResult(t *testing.T) { - organizerKeypair := generateKeyPair(t) - remoteOrganizerKeypair := generateKeyPair(t) - fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) - require.NoError(t, err) - - // dirty hack to manually create a channel, so we can then add the remote organizer pk without - // having to go through the init process - - box := inbox.NewInbox(localFedChannel) - - remoteOrg := &remoteOrganization{ - organizerPk: remoteOrganizerKeypair.publicKey, - state: None, - } - remoteOrg.organizerPk = remoteOrganizerKeypair.publicKey - remoteOrg.state = WaitResult - - var remoteOrgs = map[string]*remoteOrganization{ - remoteOrganizerKeypair.publicKey: remoteOrg, - } - - newChannel := &Channel{ - sockets: channel.NewSockets(), - inbox: box, - channelID: localFedChannel, - hub: fakeHub, - log: nolog, - localOrganizerPk: organizerKeypair.publicKey, - remoteOrganizations: remoteOrgs, - } - - newChannel.registry = newChannel.NewFederationRegistry() - - publicKeyBytes, err := organizerKeypair.public.MarshalBinary() - require.NoError(t, err) - //signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) - require.NoError(t, err) - - challengeFile := filepath.Join(relativeMsgDataExamplePath, - "federation_challenge", - "federation_challenge.json") - challengeBytes, err := os.ReadFile(challengeFile) - challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) - require.NoError(t, err) - - //signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) - signedChallengeBytes, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, challengeBytes) - require.NoError(t, err) - signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) - - federationResultData := messagedata.FederationResult{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionResult, - Status: "success", - PublicKey: base64.URLEncoding.EncodeToString(publicKeyBytes), - ChallengeMsg: message.Message{ - Data: challengeBase64, - Sender: remoteOrganizerKeypair.publicKey, - Signature: signedChallengeBase64, - MessageID: messagedata.Hash(challengeBase64, signedChallengeBase64), - WitnessSignatures: []message.WitnessSignature{}, - }, - } - resultMsg := generateMessage(t, organizerKeypair, federationResultData) - publishMsg := generatePublish(t, localFedChannel, resultMsg) - socket := &fakeSocket{id: "sockSocket"} - - err = newChannel.Publish(publishMsg, socket) - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type keypair struct { - public kyber.Point - publicKey string - private kyber.Scalar -} - -var nolog = zerolog.New(io.Discard) -var suite = crypto.Suite - -func generateKeyPair(t *testing.T) keypair { - secret := suite.Scalar().Pick(suite.RandomStream()) - point := suite.Point().Mul(secret, nil) - - pkBuf, err := point.MarshalBinary() - require.NoError(t, err) - - pkBase64 := base64.URLEncoding.EncodeToString(pkBuf) - - return keypair{point, pkBase64, secret} -} - -type fakeHub struct { - clientAddress string - - messageChan chan socket.IncomingMessage - - sync.RWMutex - channelByID map[string]channel.Channel - - closedSockets chan string - - pubKeyOwner kyber.Point - - pubKeyServ kyber.Point - secKeyServ kyber.Scalar - - schemaValidator *validation.SchemaValidator - - stop chan struct{} - - workers *semaphore.Weighted - - log zerolog.Logger - - laoFac channel.LaoFactory - - socketClient *fakeSocket -} - -// NewFakeHub returns a fake Hub. -func NewFakeHub(clientAddress string, publicOrg kyber.Point, log zerolog.Logger, laoFac channel.LaoFactory) (*fakeHub, error) { - - schemaValidator, err := validation.NewSchemaValidator(log) - if err != nil { - return nil, xerrors.Errorf("failed to create the schema validator: %v", err) - } - - log = log.With().Str("role", "base hub").Logger() - - pubServ, secServ := generateKeys() - - hub := fakeHub{ - clientAddress: clientAddress, - messageChan: make(chan socket.IncomingMessage), - channelByID: make(map[string]channel.Channel), - closedSockets: make(chan string), - pubKeyOwner: publicOrg, - pubKeyServ: pubServ, - secKeyServ: secServ, - schemaValidator: schemaValidator, - stop: make(chan struct{}), - workers: semaphore.NewWeighted(10), - log: log, - laoFac: laoFac, - } - - return &hub, nil -} - -func generateKeys() (kyber.Point, kyber.Scalar) { - secret := suite.Scalar().Pick(suite.RandomStream()) - point := suite.Point().Mul(secret, nil) - - return point, secret -} - -func (h *fakeHub) NotifyNewChannel(channeID string, channel channel.Channel, socket socket.Socket) { - h.Lock() - h.channelByID[channeID] = channel - h.Unlock() -} - -// GetPubKeyOwner implements channel.HubFunctionalities -func (h *fakeHub) GetPubKeyOwner() kyber.Point { - return h.pubKeyOwner -} - -// GetPubKeyServ implements channel.HubFunctionalities -func (h *fakeHub) GetPubKeyServ() kyber.Point { - return h.pubKeyServ -} - -// GetClientServerAddress implements channel.HubFunctionalities -func (h *fakeHub) GetClientServerAddress() string { - return h.clientAddress -} - -// Sign implements channel.HubFunctionalities -func (h *fakeHub) Sign(data []byte) ([]byte, error) { - signatureBuf, err := schnorr.Sign(crypto.Suite, h.secKeyServ, data) - if err != nil { - return nil, xerrors.Errorf("failed to sign the data: %v", err) - } - - return signatureBuf, nil -} - -// NotifyWitnessMessage implements channel.HubFunctionalities -func (h *fakeHub) NotifyWitnessMessage(messageId string, publicKey string, signature string) {} - -// GetPeersInfo implements channel.HubFunctionalities -func (h *fakeHub) GetPeersInfo() []method.ServerInfo { - return nil -} - -func (h *fakeHub) GetSchemaValidator() validation.SchemaValidator { - return *h.schemaValidator -} - -func (h *fakeHub) GetServerNumber() int { - return 0 -} - -func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { - return nil -} - -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - h.socketClient = &fakeSocket{id: serverAddress} - - return h.socketClient, nil -} - -// fakeSocket is a fake implementation of a Socket +//import ( +// "crypto/rand" +// "encoding/base64" +// "encoding/hex" +// "encoding/json" +// "io" +// "os" +// "path/filepath" +// "popstellar/channel" +// "popstellar/crypto" +// "popstellar/inbox" +// jsonrpc "popstellar/message" +// "popstellar/message/messagedata" +// "popstellar/message/query" +// "popstellar/message/query/method" +// "popstellar/message/query/method/message" +// "popstellar/network/socket" +// "popstellar/validation" +// "sync" +// "testing" +// "time" // -// - implements socket.Socket -type fakeSocket struct { - socket.Socket - - resultID int - res []message.Message - msg []byte - - err error - - // the sockSocket ID - id string -} - -// Send implements socket.Socket -func (f *fakeSocket) Send(msg []byte) { - f.msg = msg -} - -// SendResult implements socket.Socket -func (f *fakeSocket) SendResult(id int, res []message.Message, missingMsgs map[string][]message.Message) { - f.resultID = id - f.res = res -} - -// SendError implements socket.Socket -func (f *fakeSocket) SendError(id *int, err error) { - if id != nil { - f.resultID = *id - } else { - f.resultID = -1 - } - f.err = err -} - -func (f *fakeSocket) ID() string { - return f.id -} - -func generatePublish(t *testing.T, channel string, msg message.Message) method. - Publish { - return 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: channel, - Message: msg, - }, - } -} - -func generateMessage(t *testing.T, keys keypair, - data messagedata.MessageData) message.Message { - - dataBytes, err := json.Marshal(data) - require.NoError(t, err) - - dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) - - signatureBytes, err := schnorr.Sign(crypto.Suite, keys.private, dataBytes) - require.NoError(t, err) - signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) - - return message.Message{ - Data: dataBase64, - Sender: keys.publicKey, - Signature: signatureBase64, - MessageID: messagedata.Hash(dataBase64, signatureBase64), - WitnessSignatures: []message.WitnessSignature{}, - } -} +// "github.com/rs/zerolog" +// "github.com/stretchr/testify/require" +// "go.dedis.ch/kyber/v3" +// "go.dedis.ch/kyber/v3/sign/schnorr" +// "golang.org/x/sync/semaphore" +// "golang.org/x/xerrors" +//) +// +//const ( +// relativeMsgDataExamplePath string = "../../../protocol/examples/messageData" +// relativeQueryExamplePath string = "../../../protocol/examples/query" +// localServerAddress = "ws://localhost:19008/client" +// remoteServerAddress = "ws://localhost:19009/client" +// localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" +// remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" +// localFedChannel = "/root/" + localLaoId + "/federation" +// remoteFedChannel = "/root/" + remoteLaoId + "/federation" +//) +// +//// TestChannel_FederationRequestChallenge tests that a FederationChallenge is +//// received after a valid FederationChallengeRequest is published by the +//// organizer +//func Test_FederationRequestChallenge(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// requestData := messagedata.FederationChallengeRequest{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionChallengeRequest, +// Timestamp: time.Now().Unix(), +// } +// +// requestMsg := generateMessage(t, organizerKeypair, requestData) +// publishMsg := generatePublish(t, localFedChannel, requestMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +// +// require.NoError(t, socket.err) +// require.NotNil(t, socket.msg) +// +// var challengePublish method.Publish +// err = json.Unmarshal(socket.msg, &challengePublish) +// require.NoError(t, err) +// +// require.Equal(t, localFedChannel, challengePublish.Params.Channel) +// challengeMsg := challengePublish.Params.Message +// +// dataBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Data) +// require.NoError(t, err) +// signatureBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Signature) +// require.NoError(t, err) +// +// err = schnorr.Verify(crypto.Suite, fakeHub.pubKeyServ, dataBytes, signatureBytes) +// require.NoError(t, err) +// +// var challenge messagedata.FederationChallenge +// err = challengeMsg.UnmarshalData(&challenge) +// require.NoError(t, err) +// +// require.Equal(t, messagedata.FederationObject, challenge.Object) +// require.Equal(t, messagedata.FederationActionChallenge, challenge.Action) +// require.Greater(t, challenge.ValidUntil, time.Now().Unix()) +// bytes, err := hex.DecodeString(challenge.Value) +// require.NoError(t, err) +// require.Len(t, bytes, 32) +//} +// +//func Test_FederationRequestChallenge_not_organizer(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// notOrganizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// requestData := messagedata.FederationChallengeRequest{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionChallengeRequest, +// Timestamp: time.Now().Unix(), +// } +// +// requestMsg := generateMessage(t, notOrganizerKeypair, requestData) +// publishMsg := generatePublish(t, localFedChannel, requestMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.Error(t, err) +// +// require.Nil(t, socket.msg) +//} +// +//func Test_FederationExpect(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// requestData := messagedata.FederationChallengeRequest{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionChallengeRequest, +// Timestamp: time.Now().Unix(), +// } +// +// requestMsg := generateMessage(t, organizerKeypair, requestData) +// publishMsg := generatePublish(t, localFedChannel, requestMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +// +// require.NoError(t, socket.err) +// require.NotNil(t, socket.msg) +// +// var challengePublish method.Publish +// err = json.Unmarshal(socket.msg, &challengePublish) +// require.NoError(t, err) +// +// require.Equal(t, localFedChannel, challengePublish.Params.Channel) +// challengeMsg := challengePublish.Params.Message +// +// var challenge messagedata.FederationChallenge +// err = challengeMsg.UnmarshalData(&challenge) +// require.NoError(t, err) +// +// remoteOrganizerKeypair := generateKeyPair(t) +// +// federationExpect := messagedata.FederationExpect{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionExpect, +// LaoId: remoteLaoId, +// ServerAddress: remoteServerAddress, +// PublicKey: remoteOrganizerKeypair.publicKey, +// ChallengeMsg: challengeMsg, +// } +// +// federationMsg := generateMessage(t, organizerKeypair, federationExpect) +// federationPublish := generatePublish(t, localFedChannel, federationMsg) +// +// err = fedChannel.Publish(federationPublish, socket) +// require.NoError(t, err) +// +// require.NoError(t, socket.err) +// require.NotNil(t, socket.msg) +//} +// +//func Test_FederationExpect_with_invalid_challenge(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// requestData := messagedata.FederationChallengeRequest{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionChallengeRequest, +// Timestamp: time.Now().Unix(), +// } +// +// requestMsg := generateMessage(t, organizerKeypair, requestData) +// publishMsg := generatePublish(t, localFedChannel, requestMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +// +// require.NoError(t, socket.err) +// require.NotNil(t, socket.msg) +// +// remoteOrganizerKeypair := generateKeyPair(t) +// +// valueBytes := make([]byte, 32) +// _, _ = rand.Read(valueBytes) +// +// federationExpect := messagedata.FederationExpect{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionExpect, +// LaoId: remoteLaoId, +// ServerAddress: remoteServerAddress, +// PublicKey: remoteOrganizerKeypair.publicKey, +// ChallengeMsg: message.Message{ +// Data: "aaaaaaaaaaa", +// Sender: organizerKeypair.publicKey, +// Signature: "bbbbbbbbbbb", +// MessageID: messagedata.Hash("aaaaaaaaaaa", "bbbbbbbbbbb"), +// WitnessSignatures: []message.WitnessSignature{}, +// }, +// } +// +// expectMsg := generateMessage(t, organizerKeypair, federationExpect) +// expectPublish := generatePublish(t, localFedChannel, expectMsg) +// +// err = fedChannel.Publish(expectPublish, socket) +// require.Error(t, err) +//} +// +//func Test_FederationChallenge_not_organizer(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// notOrganizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// requestData := messagedata.FederationChallengeRequest{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionChallengeRequest, +// Timestamp: time.Now().Unix(), +// } +// +// requestMsg := generateMessage(t, organizerKeypair, requestData) +// publishMsg := generatePublish(t, localFedChannel, requestMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +// +// require.NoError(t, socket.err) +// require.NotNil(t, socket.msg) +// +// var challengePublish method.Publish +// err = json.Unmarshal(socket.msg, &challengePublish) +// require.NoError(t, err) +// +// require.Equal(t, localFedChannel, challengePublish.Params.Channel) +// challengeMsg := challengePublish.Params.Message +// +// var challenge messagedata.FederationChallenge +// err = challengeMsg.UnmarshalData(&challenge) +// require.NoError(t, err) +// +// remoteOrganizerKeypair := generateKeyPair(t) +// +// federationExpect := messagedata.FederationExpect{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionExpect, +// LaoId: remoteLaoId, +// ServerAddress: remoteServerAddress, +// PublicKey: remoteOrganizerKeypair.publicKey, +// ChallengeMsg: challengeMsg, +// } +// +// federationMsg := generateMessage(t, notOrganizerKeypair, federationExpect) +// federationPublish := generatePublish(t, localFedChannel, federationMsg) +// +// err = fedChannel.Publish(federationPublish, socket) +// require.Error(t, err) +//} +// +//func Test_FederationInit(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// remoteOrganizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, +// organizerKeypair.publicKey) +// +// challengeFile := filepath.Join(relativeMsgDataExamplePath, +// "federation_challenge", +// "federation_challenge.json") +// challengeBytes, err := os.ReadFile(challengeFile) +// require.NoError(t, err) +// +// challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) +// +// signatureBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) +// require.NoError(t, err) +// +// signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) +// +// federationInitData := messagedata.FederationInit{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionInit, +// LaoId: remoteLaoId, +// ServerAddress: remoteServerAddress, +// PublicKey: remoteOrganizerKeypair.publicKey, +// ChallengeMsg: message.Message{ +// Data: challengeBase64, +// Sender: organizerKeypair.publicKey, +// Signature: signatureBase64, +// MessageID: messagedata.Hash(challengeBase64, signatureBase64), +// WitnessSignatures: []message.WitnessSignature{}, +// }, +// } +// +// initMsg := generateMessage(t, organizerKeypair, federationInitData) +// publishMsg := generatePublish(t, localFedChannel, initMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = fedChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +// +// require.NotNil(t, fakeHub.socketClient) +// require.Equal(t, remoteServerAddress, fakeHub.socketClient.ID()) +// +// // A Publish message containing the challenge message should be sent +// msgBytes := fakeHub.socketClient.msg +// require.NotNil(t, msgBytes) +// +// var publishMsg2 method.Publish +// err = json.Unmarshal(msgBytes, &publishMsg2) +// require.NoError(t, err) +// +// require.Equal(t, remoteFedChannel, publishMsg2.Params.Channel) +// require.Equal(t, challengeBase64, publishMsg2.Params.Message.Data) +// require.Equal(t, signatureBase64, publishMsg2.Params.Message.Signature) +// require.Equal(t, organizerKeypair.publicKey, publishMsg2.Params.Message.Sender) +// +// // should expect to receive back a FedererationResult +// require.NoError(t, socket.err) +// //require.NotNil(t, socket.msg) +// +//} +// +//func Test_FederationResult(t *testing.T) { +// organizerKeypair := generateKeyPair(t) +// remoteOrganizerKeypair := generateKeyPair(t) +// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) +// require.NoError(t, err) +// +// // dirty hack to manually create a channel, so we can then add the remote organizer pk without +// // having to go through the init process +// +// box := inbox.NewInbox(localFedChannel) +// +// remoteOrg := &remoteOrganization{ +// organizerPk: remoteOrganizerKeypair.publicKey, +// state: None, +// } +// remoteOrg.organizerPk = remoteOrganizerKeypair.publicKey +// remoteOrg.state = WaitResult +// +// var remoteOrgs = map[string]*remoteOrganization{ +// remoteOrganizerKeypair.publicKey: remoteOrg, +// } +// +// newChannel := &Channel{ +// sockets: channel.NewSockets(), +// inbox: box, +// channelID: localFedChannel, +// hub: fakeHub, +// log: nolog, +// localOrganizerPk: organizerKeypair.publicKey, +// remoteOrganizations: remoteOrgs, +// } +// +// newChannel.registry = newChannel.NewFederationRegistry() +// +// publicKeyBytes, err := organizerKeypair.public.MarshalBinary() +// require.NoError(t, err) +// //signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) +// require.NoError(t, err) +// +// challengeFile := filepath.Join(relativeMsgDataExamplePath, +// "federation_challenge", +// "federation_challenge.json") +// challengeBytes, err := os.ReadFile(challengeFile) +// challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) +// require.NoError(t, err) +// +// //signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) +// signedChallengeBytes, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, challengeBytes) +// require.NoError(t, err) +// signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) +// +// federationResultData := messagedata.FederationResult{ +// Object: messagedata.FederationObject, +// Action: messagedata.FederationActionResult, +// Status: "success", +// PublicKey: base64.URLEncoding.EncodeToString(publicKeyBytes), +// ChallengeMsg: message.Message{ +// Data: challengeBase64, +// Sender: remoteOrganizerKeypair.publicKey, +// Signature: signedChallengeBase64, +// MessageID: messagedata.Hash(challengeBase64, signedChallengeBase64), +// WitnessSignatures: []message.WitnessSignature{}, +// }, +// } +// resultMsg := generateMessage(t, organizerKeypair, federationResultData) +// publishMsg := generatePublish(t, localFedChannel, resultMsg) +// socket := &fakeSocket{id: "sockSocket"} +// +// err = newChannel.Publish(publishMsg, socket) +// require.NoError(t, err) +//} +// +//// ----------------------------------------------------------------------------- +//// Utility functions +// +//type keypair struct { +// public kyber.Point +// publicKey string +// private kyber.Scalar +//} +// +//var nolog = zerolog.New(io.Discard) +//var suite = crypto.Suite +// +//func generateKeyPair(t *testing.T) keypair { +// secret := suite.Scalar().Pick(suite.RandomStream()) +// point := suite.Point().Mul(secret, nil) +// +// pkBuf, err := point.MarshalBinary() +// require.NoError(t, err) +// +// pkBase64 := base64.URLEncoding.EncodeToString(pkBuf) +// +// return keypair{point, pkBase64, secret} +//} +// +//type fakeHub struct { +// clientAddress string +// +// messageChan chan socket.IncomingMessage +// +// sync.RWMutex +// channelByID map[string]channel.Channel +// +// closedSockets chan string +// +// pubKeyOwner kyber.Point +// +// pubKeyServ kyber.Point +// secKeyServ kyber.Scalar +// +// schemaValidator *validation.SchemaValidator +// +// stop chan struct{} +// +// workers *semaphore.Weighted +// +// log zerolog.Logger +// +// laoFac channel.LaoFactory +// +// socketClient *fakeSocket +//} +// +//// NewFakeHub returns a fake Hub. +//func NewFakeHub(clientAddress string, publicOrg kyber.Point, log zerolog.Logger, laoFac channel.LaoFactory) (*fakeHub, error) { +// +// schemaValidator, err := validation.NewSchemaValidator(log) +// if err != nil { +// return nil, xerrors.Errorf("failed to create the schema validator: %v", err) +// } +// +// log = log.With().Str("role", "base hub").Logger() +// +// pubServ, secServ := generateKeys() +// +// hub := fakeHub{ +// clientAddress: clientAddress, +// messageChan: make(chan socket.IncomingMessage), +// channelByID: make(map[string]channel.Channel), +// closedSockets: make(chan string), +// pubKeyOwner: publicOrg, +// pubKeyServ: pubServ, +// secKeyServ: secServ, +// schemaValidator: schemaValidator, +// stop: make(chan struct{}), +// workers: semaphore.NewWeighted(10), +// log: log, +// laoFac: laoFac, +// } +// +// return &hub, nil +//} +// +//func generateKeys() (kyber.Point, kyber.Scalar) { +// secret := suite.Scalar().Pick(suite.RandomStream()) +// point := suite.Point().Mul(secret, nil) +// +// return point, secret +//} +// +//func (h *fakeHub) NotifyNewChannel(channeID string, channel channel.Channel, socket socket.Socket) { +// h.Lock() +// h.channelByID[channeID] = channel +// h.Unlock() +//} +// +//// GetPubKeyOwner implements channel.HubFunctionalities +//func (h *fakeHub) GetPubKeyOwner() kyber.Point { +// return h.pubKeyOwner +//} +// +//// GetPubKeyServ implements channel.HubFunctionalities +//func (h *fakeHub) GetPubKeyServ() kyber.Point { +// return h.pubKeyServ +//} +// +//// GetClientServerAddress implements channel.HubFunctionalities +//func (h *fakeHub) GetClientServerAddress() string { +// return h.clientAddress +//} +// +//// Sign implements channel.HubFunctionalities +//func (h *fakeHub) Sign(data []byte) ([]byte, error) { +// signatureBuf, err := schnorr.Sign(crypto.Suite, h.secKeyServ, data) +// if err != nil { +// return nil, xerrors.Errorf("failed to sign the data: %v", err) +// } +// +// return signatureBuf, nil +//} +// +//// NotifyWitnessMessage implements channel.HubFunctionalities +//func (h *fakeHub) NotifyWitnessMessage(messageId string, publicKey string, signature string) {} +// +//// GetPeersInfo implements channel.HubFunctionalities +//func (h *fakeHub) GetPeersInfo() []method.ServerInfo { +// return nil +//} +// +//func (h *fakeHub) GetSchemaValidator() validation.SchemaValidator { +// return *h.schemaValidator +//} +// +//func (h *fakeHub) GetServerNumber() int { +// return 0 +//} +// +//func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { +// return nil +//} +// +//func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { +// h.socketClient = &fakeSocket{id: serverAddress} +// +// return h.socketClient, nil +//} +// +//// fakeSocket is a fake implementation of a Socket +//// +//// - implements socket.Socket +//type fakeSocket struct { +// socket.Socket +// +// resultID int +// res []message.Message +// msg []byte +// +// err error +// +// // the sockSocket ID +// id string +//} +// +//// Send implements socket.Socket +//func (f *fakeSocket) Send(msg []byte) { +// f.msg = msg +//} +// +//// SendResult implements socket.Socket +//func (f *fakeSocket) SendResult(id int, res []message.Message, missingMsgs map[string][]message.Message) { +// f.resultID = id +// f.res = res +//} +// +//// SendError implements socket.Socket +//func (f *fakeSocket) SendError(id *int, err error) { +// if id != nil { +// f.resultID = *id +// } else { +// f.resultID = -1 +// } +// f.err = err +//} +// +//func (f *fakeSocket) ID() string { +// return f.id +//} +// +//func generatePublish(t *testing.T, channel string, msg message.Message) method. +// Publish { +// return 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: channel, +// Message: msg, +// }, +// } +//} +// +//func generateMessage(t *testing.T, keys keypair, +// data messagedata.MessageData) message.Message { +// +// dataBytes, err := json.Marshal(data) +// require.NoError(t, err) +// +// dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) +// +// signatureBytes, err := schnorr.Sign(crypto.Suite, keys.private, dataBytes) +// require.NoError(t, err) +// signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) +// +// return message.Message{ +// Data: dataBase64, +// Sender: keys.publicKey, +// Signature: signatureBase64, +// MessageID: messagedata.Hash(dataBase64, signatureBase64), +// WitnessSignatures: []message.WitnessSignature{}, +// } +//} diff --git a/be1-go/internal/popserver/database/repository/mock_repository.go b/be1-go/internal/popserver/database/repository/mock_repository.go index a1a5e3f155..4c3c965eda 100644 --- a/be1-go/internal/popserver/database/repository/mock_repository.go +++ b/be1-go/internal/popserver/database/repository/mock_repository.go @@ -3,11 +3,13 @@ package repository import ( + messagedata "popstellar/message/messagedata" message "popstellar/message/query/method/message" - mock "github.com/stretchr/testify/mock" kyber "go.dedis.ch/kyber/v3" + mock "github.com/stretchr/testify/mock" + types "popstellar/internal/popserver/types" ) @@ -306,6 +308,34 @@ func (_m *MockRepository) GetElectionType(electionID string) (string, error) { return r0, r1 } +// GetFederationExpect provides a mock function with given fields: senderPk, remotePk, Challenge +func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) { + ret := _m.Called(senderPk, remotePk, Challenge) + + if len(ret) == 0 { + panic("no return value specified for GetFederationExpect") + } + + var r0 messagedata.FederationExpect + var r1 error + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) (messagedata.FederationExpect, error)); ok { + return rf(senderPk, remotePk, Challenge) + } + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) messagedata.FederationExpect); ok { + r0 = rf(senderPk, remotePk, Challenge) + } else { + r0 = ret.Get(0).(messagedata.FederationExpect) + } + + if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge) error); ok { + r1 = rf(senderPk, remotePk, Challenge) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLAOOrganizerPubKey provides a mock function with given fields: electionID func (_m *MockRepository) GetLAOOrganizerPubKey(electionID string) (kyber.Point, error) { ret := _m.Called(electionID) @@ -723,6 +753,24 @@ func (_m *MockRepository) IsAttendee(laoPath string, poptoken string) (bool, err return r0, r1 } +// IsChallengeValid provides a mock function with given fields: senderPk, challenge +func (_m *MockRepository) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error { + ret := _m.Called(senderPk, challenge) + + if len(ret) == 0 { + panic("no return value specified for IsChallengeValid") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, messagedata.FederationChallenge) error); ok { + r0 = rf(senderPk, challenge) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // IsElectionEnded provides a mock function with given fields: electionID func (_m *MockRepository) IsElectionEnded(electionID string) (bool, error) { ret := _m.Called(electionID) @@ -807,6 +855,24 @@ func (_m *MockRepository) IsElectionStartedOrEnded(electionID string) (bool, err return r0, r1 } +// RemoveChallenge provides a mock function with given fields: challenge +func (_m *MockRepository) RemoveChallenge(challenge messagedata.FederationChallenge) error { + ret := _m.Called(challenge) + + if len(ret) == 0 { + panic("no return value specified for RemoveChallenge") + } + + var r0 error + if rf, ok := ret.Get(0).(func(messagedata.FederationChallenge) error); ok { + r0 = rf(challenge) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // StoreChirpMessages provides a mock function with given fields: channel, generalChannel, msg, generalMsg func (_m *MockRepository) StoreChirpMessages(channel string, generalChannel string, msg message.Message, generalMsg message.Message) error { ret := _m.Called(channel, generalChannel, msg, generalMsg) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 7f6758a162..d68404ca97 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -164,91 +164,91 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { } func handleInit(msg message.Message, channelPath string) *answer.Error { - var federationInit messagedata.FederationInit - errAnswer := msg.UnmarshalMsgData(&federationInit) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - errAnswer = verifyLocalOrganizer(msg, channelPath) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - // Both the FederationInit and the embedded FederationChallenge need to - // be signed by the local organizer - errAnswer = verifyLocalOrganizer(federationInit.ChallengeMsg, channelPath) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - var challenge messagedata.FederationChallenge - errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - db, errAnswer := database.GetFederationRepositoryInstance() - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - err := db.IsChallengeValid(channelPath, challenge) - if err != nil { - errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) - return errAnswer.Wrap("handleFederationInit") - } - - err = db.StoreMessageAndData(channelPath, msg) - if err != nil { - errAnswer = answer.NewStoreDatabaseError(err.Error()) - return errAnswer.Wrap("handleFederationInit") - } - - // TODO fix - remote, err := state.ConnectTo(federationInit.ServerAddress) - if err != nil { - errAnswer = answer.NewInternalServerError( - "failed to connect to %s: %v", federationInit.ServerAddress, err) - return errAnswer.Wrap("handleFederationInit") - } - - //Force the remote server to be subscribed to /root//federation - remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - _ = state.AddChannel(remoteChannel) - errAnswer = state.Subscribe(remote, remoteChannel) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - subscribeMsg := method.Subscribe{ - Base: query.Base{ - JSONRPCBase: jsonrpc.JSONRPCBase{ - JSONRPC: "2.0", - }, - Method: "subscribe", - }, - Params: method.SubscribeParams{Channel: channelPath}, - } - - subscribeBytes, err := json.Marshal(subscribeMsg) - if err != nil { - errAnswer = answer.NewInternalServerError( - "failed to marshal subscribe: %v", err) - return errAnswer.Wrap("handleFederationInit") - } - - // Subscribe to /root//federation on the remote server - errAnswer = state.SendToAll(subscribeBytes, remoteChannel) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } - - // send the challenge to a channel where the remote server is subscribed to - errAnswer = publishTo(federationInit.ChallengeMsg, remoteChannel) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationInit") - } + //var federationInit messagedata.FederationInit + //errAnswer := msg.UnmarshalMsgData(&federationInit) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //errAnswer = verifyLocalOrganizer(msg, channelPath) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //// Both the FederationInit and the embedded FederationChallenge need to + //// be signed by the local organizer + //errAnswer = verifyLocalOrganizer(federationInit.ChallengeMsg, channelPath) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //var challenge messagedata.FederationChallenge + //errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //db, errAnswer := database.GetFederationRepositoryInstance() + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //err := db.IsChallengeValid(channelPath, challenge) + //if err != nil { + // errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) + // return errAnswer.Wrap("handleFederationInit") + //} + // + //err = db.StoreMessageAndData(channelPath, msg) + //if err != nil { + // errAnswer = answer.NewStoreDatabaseError(err.Error()) + // return errAnswer.Wrap("handleFederationInit") + //} + // + //// TODO fix + //remote, err := state.ConnectTo(federationInit.ServerAddress) + //if err != nil { + // errAnswer = answer.NewInternalServerError( + // "failed to connect to %s: %v", federationInit.ServerAddress, err) + // return errAnswer.Wrap("handleFederationInit") + //} + // + ////Force the remote server to be subscribed to /root//federation + //remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + //_ = state.AddChannel(remoteChannel) + //errAnswer = state.Subscribe(remote, remoteChannel) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //subscribeMsg := method.Subscribe{ + // Base: query.Base{ + // JSONRPCBase: jsonrpc.JSONRPCBase{ + // JSONRPC: "2.0", + // }, + // Method: "subscribe", + // }, + // Params: method.SubscribeParams{Channel: channelPath}, + //} + // + //subscribeBytes, err := json.Marshal(subscribeMsg) + //if err != nil { + // errAnswer = answer.NewInternalServerError( + // "failed to marshal subscribe: %v", err) + // return errAnswer.Wrap("handleFederationInit") + //} + // + //// Subscribe to /root//federation on the remote server + //errAnswer = state.SendToAll(subscribeBytes, remoteChannel) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} + // + //// send the challenge to a channel where the remote server is subscribed to + //errAnswer = publishTo(federationInit.ChallengeMsg, remoteChannel) + //if errAnswer != nil { + // return errAnswer.Wrap("handleFederationInit") + //} return nil } diff --git a/be1-go/internal/popserver/hub.go b/be1-go/internal/popserver/hub.go index 3e22ef69df..0cac7aeb52 100644 --- a/be1-go/internal/popserver/hub.go +++ b/be1-go/internal/popserver/hub.go @@ -3,6 +3,7 @@ package popserver import ( "encoding/json" "golang.org/x/xerrors" + "popstellar" "popstellar/internal/popserver/config" "popstellar/internal/popserver/database" "popstellar/internal/popserver/handler" @@ -13,12 +14,14 @@ import ( "popstellar/message/query" "popstellar/message/query/method" "popstellar/network/socket" + "sync" "time" ) const heartbeatDelay = 30 * time.Second type Hub struct { + wg *sync.WaitGroup messageChan chan socket.IncomingMessage stop chan struct{} closedSockets chan string @@ -26,10 +29,35 @@ type Hub struct { } func NewHub() *Hub { + wg, errAnswer := state.GetWaitGroup() + if errAnswer != nil { + popstellar.Logger.Err(errAnswer) + return nil + } + + messageChan, errAnswer := state.GetMessageChan() + if errAnswer != nil { + popstellar.Logger.Err(errAnswer) + return nil + } + + stop, errAnswer := state.GetStopChan() + if errAnswer != nil { + popstellar.Logger.Err(errAnswer) + return nil + } + + closedSockets, errAnswer := state.GetClosedSockets() + if errAnswer != nil { + popstellar.Logger.Err(errAnswer) + return nil + } + return &Hub{ - messageChan: make(chan socket.IncomingMessage), - stop: make(chan struct{}), - closedSockets: make(chan string), + wg: wg, + messageChan: messageChan, + stop: stop, + closedSockets: closedSockets, serverSockets: types.NewSockets(), } } @@ -39,7 +67,9 @@ func (h *Hub) NotifyNewServer(socket socket.Socket) { } func (h *Hub) Start() { + h.wg.Add(2) go func() { + defer h.wg.Done() ticker := time.NewTicker(heartbeatDelay) defer ticker.Stop() @@ -54,6 +84,7 @@ func (h *Hub) Start() { } }() go func() { + defer h.wg.Done() utils.LogInfo("start the Hub") for { utils.LogInfo("waiting for a new message") @@ -79,6 +110,7 @@ func (h *Hub) Start() { func (h *Hub) Stop() { close(h.stop) + h.wg.Wait() } func (h *Hub) Receiver() chan<- socket.IncomingMessage { diff --git a/be1-go/internal/popserver/state/state.go b/be1-go/internal/popserver/state/state.go index 519a1bddab..3c2bfd3415 100644 --- a/be1-go/internal/popserver/state/state.go +++ b/be1-go/internal/popserver/state/state.go @@ -13,9 +13,17 @@ var once sync.Once var instance *state type state struct { - subs Subscriber - peers Peerer - queries Querier + subs Subscriber + peers Peerer + queries Querier + hubParams HubParameter +} + +type HubParameter interface { + GetWaitGroup() *sync.WaitGroup + GetMessageChan() chan socket.IncomingMessage + GetStopChan() chan struct{} + GetClosedSockets() chan string } type Subscriber interface { @@ -44,9 +52,10 @@ type Querier interface { func InitState(log *zerolog.Logger) { once.Do(func() { instance = &state{ - subs: types.NewSubscribers(), - peers: types.NewPeers(), - queries: types.NewQueries(log), + subs: types.NewSubscribers(), + peers: types.NewPeers(), + queries: types.NewQueries(log), + hubParams: types.NewHubParams(), } }) } @@ -219,3 +228,47 @@ func AddQuery(ID int, query method.GetMessagesById) *answer.Error { return nil } + +func getHubParams() (HubParameter, *answer.Error) { + if instance == nil || instance.hubParams == nil { + return nil, answer.NewInternalServerError("hubparams was not instantiated") + } + + return instance.hubParams, nil +} + +func GetWaitGroup() (*sync.WaitGroup, *answer.Error) { + hubParams, errAnswer := getHubParams() + if errAnswer != nil { + return nil, errAnswer + } + + return hubParams.GetWaitGroup(), nil +} + +func GetMessageChan() (chan socket.IncomingMessage, *answer.Error) { + hubParams, errAnswer := getHubParams() + if errAnswer != nil { + return nil, errAnswer + } + + return hubParams.GetMessageChan(), nil +} + +func GetStopChan() (chan struct{}, *answer.Error) { + hubParams, errAnswer := getHubParams() + if errAnswer != nil { + return nil, errAnswer + } + + return hubParams.GetStopChan(), nil +} + +func GetClosedSockets() (chan string, *answer.Error) { + hubParams, errAnswer := getHubParams() + if errAnswer != nil { + return nil, errAnswer + } + + return hubParams.GetClosedSockets(), nil +} diff --git a/be1-go/internal/popserver/types/hub_params.go b/be1-go/internal/popserver/types/hub_params.go new file mode 100644 index 0000000000..44debb1c7a --- /dev/null +++ b/be1-go/internal/popserver/types/hub_params.go @@ -0,0 +1,37 @@ +package types + +import ( + "popstellar/network/socket" + "sync" +) + +type HubParams struct { + wg sync.WaitGroup + messageChan chan socket.IncomingMessage + closedSockets chan string + stop chan struct{} +} + +func NewHubParams() *HubParams { + return &HubParams{ + messageChan: make(chan socket.IncomingMessage), + stop: make(chan struct{}), + closedSockets: make(chan string), + } +} + +func (h *HubParams) GetWaitGroup() *sync.WaitGroup { + return &h.wg +} + +func (h *HubParams) GetMessageChan() chan socket.IncomingMessage { + return h.messageChan +} + +func (h *HubParams) GetStopChan() chan struct{} { + return h.stop +} + +func (h *HubParams) GetClosedSockets() chan string { + return h.closedSockets +} From 806f51e8d4348f47fb2bceecadc0ac710da2084a Mon Sep 17 00:00:00 2001 From: stuart Date: Mon, 20 May 2024 17:03:43 +0200 Subject: [PATCH 26/48] comment cli test --- be1-go/cli/pop_test.go | 220 ++++++++++++++++++++--------------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/be1-go/cli/pop_test.go b/be1-go/cli/pop_test.go index ea429c6c4d..a53eadb5f5 100644 --- a/be1-go/cli/pop_test.go +++ b/be1-go/cli/pop_test.go @@ -1,112 +1,112 @@ package main -import ( - "context" - "os" - "sync" - "testing" - "time" -) - -const waitUp = time.Second * 2 - -func TestConnectMultipleServers(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - wait := sync.WaitGroup{} - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", "--server-port", "9011", "--client-port", "8010", "--auth-port", "9103"} - t.Logf("running server 1: %v", args) - - run(ctx, args) - t.Log("server 1 done") - }() - - time.Sleep(waitUp) - t.Log("server 1 up") - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", - "--client-port", "8020", "--server-port", "9003", "--other-servers", "localhost:9011", "--auth-port", "9101"} - t.Logf("running server 2: %v", args) - - run(ctx, args) - t.Log("server 2 done") - }() - - time.Sleep(waitUp) - t.Log("server 2 up") - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", "--server-port", "9004", "--client-port", "8021", "--other-servers", "localhost:9011", "localhost:9003", "--auth-port", "9102"} - t.Logf("running server 3: %v", args) - - run(ctx, args) - t.Log("server 3 done") - }() - - time.Sleep(waitUp) - t.Log("server 3 up") - - cancel() - wait.Wait() -} - -func TestConnectMultipleServersWithoutPK(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - wait := sync.WaitGroup{} - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "serve", "--server-port", "9011", "--client-port", "8010", "--auth-port", "9101"} - t.Logf("running server 1: %v", args) - - run(ctx, args) - t.Log("server 1 done") - }() - - time.Sleep(waitUp) - t.Log("server 1 up") - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "serve", "--server-port", "9003", "--client-port", "8020", "--other-servers", "localhost:9011", "--auth-port", "9102"} - t.Logf("running server 2: %v", args) - - run(ctx, args) - t.Log("server 2 done") - }() - - time.Sleep(waitUp) - t.Log("server 2 up") - - wait.Add(1) - go func() { - defer wait.Done() - - args := []string{os.Args[0], "server", "serve", "--server-port", "9004", "--client-port", "8021", "--other-servers", "localhost:9011", "localhost:9003", "--auth-port", "9103"} - t.Logf("running server 3: %v", args) - - run(ctx, args) - t.Log("server 3 done") - }() - - time.Sleep(waitUp) - t.Log("server 3 up") - - cancel() - wait.Wait() -} +//import ( +// "context" +// "os" +// "sync" +// "testing" +// "time" +//) +// +//const waitUp = time.Second * 2 +// +//func TestConnectMultipleServers(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// wait := sync.WaitGroup{} +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", "--server-port", "9011", "--client-port", "8010", "--auth-port", "9103"} +// t.Logf("running server 1: %v", args) +// +// run(ctx, args) +// t.Log("server 1 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 1 up") +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", +// "--client-port", "8020", "--server-port", "9003", "--other-servers", "localhost:9011", "--auth-port", "9101"} +// t.Logf("running server 2: %v", args) +// +// run(ctx, args) +// t.Log("server 2 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 2 up") +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "--pk", "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", "serve", "--server-port", "9004", "--client-port", "8021", "--other-servers", "localhost:9011", "localhost:9003", "--auth-port", "9102"} +// t.Logf("running server 3: %v", args) +// +// run(ctx, args) +// t.Log("server 3 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 3 up") +// +// cancel() +// wait.Wait() +//} +// +//func TestConnectMultipleServersWithoutPK(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// wait := sync.WaitGroup{} +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "serve", "--server-port", "9011", "--client-port", "8010", "--auth-port", "9101"} +// t.Logf("running server 1: %v", args) +// +// run(ctx, args) +// t.Log("server 1 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 1 up") +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "serve", "--server-port", "9003", "--client-port", "8020", "--other-servers", "localhost:9011", "--auth-port", "9102"} +// t.Logf("running server 2: %v", args) +// +// run(ctx, args) +// t.Log("server 2 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 2 up") +// +// wait.Add(1) +// go func() { +// defer wait.Done() +// +// args := []string{os.Args[0], "server", "serve", "--server-port", "9004", "--client-port", "8021", "--other-servers", "localhost:9011", "localhost:9003", "--auth-port", "9103"} +// t.Logf("running server 3: %v", args) +// +// run(ctx, args) +// t.Log("server 3 done") +// }() +// +// time.Sleep(waitUp) +// t.Log("server 3 up") +// +// cancel() +// wait.Wait() +//} From 4506788f35497e8cc4e750e5f1fae7371c711cb1 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 18:05:11 +0200 Subject: [PATCH 27/48] fix code --- .../internal/popserver/handler/federation.go | 211 +++++++++++------- 1 file changed, 126 insertions(+), 85 deletions(-) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index d68404ca97..a7cb7f1336 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -6,7 +6,9 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/gorilla/websocket" "go.dedis.ch/kyber/v3/sign/schnorr" + "popstellar" "popstellar/crypto" "popstellar/internal/popserver/database" "popstellar/internal/popserver/state" @@ -16,6 +18,7 @@ import ( "popstellar/message/query" "popstellar/message/query/method" "popstellar/message/query/method/message" + "popstellar/network/socket" "strings" "time" ) @@ -164,91 +167,90 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { } func handleInit(msg message.Message, channelPath string) *answer.Error { - //var federationInit messagedata.FederationInit - //errAnswer := msg.UnmarshalMsgData(&federationInit) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //errAnswer = verifyLocalOrganizer(msg, channelPath) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //// Both the FederationInit and the embedded FederationChallenge need to - //// be signed by the local organizer - //errAnswer = verifyLocalOrganizer(federationInit.ChallengeMsg, channelPath) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //var challenge messagedata.FederationChallenge - //errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //db, errAnswer := database.GetFederationRepositoryInstance() - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //err := db.IsChallengeValid(channelPath, challenge) - //if err != nil { - // errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) - // return errAnswer.Wrap("handleFederationInit") - //} - // - //err = db.StoreMessageAndData(channelPath, msg) - //if err != nil { - // errAnswer = answer.NewStoreDatabaseError(err.Error()) - // return errAnswer.Wrap("handleFederationInit") - //} - // - //// TODO fix - //remote, err := state.ConnectTo(federationInit.ServerAddress) - //if err != nil { - // errAnswer = answer.NewInternalServerError( - // "failed to connect to %s: %v", federationInit.ServerAddress, err) - // return errAnswer.Wrap("handleFederationInit") - //} - // - ////Force the remote server to be subscribed to /root//federation - //remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - //_ = state.AddChannel(remoteChannel) - //errAnswer = state.Subscribe(remote, remoteChannel) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //subscribeMsg := method.Subscribe{ - // Base: query.Base{ - // JSONRPCBase: jsonrpc.JSONRPCBase{ - // JSONRPC: "2.0", - // }, - // Method: "subscribe", - // }, - // Params: method.SubscribeParams{Channel: channelPath}, - //} - // - //subscribeBytes, err := json.Marshal(subscribeMsg) - //if err != nil { - // errAnswer = answer.NewInternalServerError( - // "failed to marshal subscribe: %v", err) - // return errAnswer.Wrap("handleFederationInit") - //} - // - //// Subscribe to /root//federation on the remote server - //errAnswer = state.SendToAll(subscribeBytes, remoteChannel) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} - // - //// send the challenge to a channel where the remote server is subscribed to - //errAnswer = publishTo(federationInit.ChallengeMsg, remoteChannel) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationInit") - //} + var federationInit messagedata.FederationInit + errAnswer := msg.UnmarshalMsgData(&federationInit) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + errAnswer = verifyLocalOrganizer(msg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + // Both the FederationInit and the embedded FederationChallenge need to + // be signed by the local organizer + errAnswer = verifyLocalOrganizer(federationInit.ChallengeMsg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + var challenge messagedata.FederationChallenge + errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + err := db.IsChallengeValid(channelPath, challenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) + return errAnswer.Wrap("handleFederationInit") + } + + err = db.StoreMessageAndData(channelPath, msg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationInit") + } + + remote, err := connectTo(federationInit.ServerAddress) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to connect to %s: %v", federationInit.ServerAddress, err) + return errAnswer.Wrap("handleFederationInit") + } + + //Force the remote server to be subscribed to /root//federation + remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + _ = state.AddChannel(remoteChannel) + errAnswer = state.Subscribe(remote, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + subscribeMsg := method.Subscribe{ + Base: query.Base{ + JSONRPCBase: jsonrpc.JSONRPCBase{ + JSONRPC: "2.0", + }, + Method: "subscribe", + }, + Params: method.SubscribeParams{Channel: channelPath}, + } + + subscribeBytes, err := json.Marshal(subscribeMsg) + if err != nil { + errAnswer = answer.NewInternalServerError( + "failed to marshal subscribe: %v", err) + return errAnswer.Wrap("handleFederationInit") + } + + // Subscribe to /root//federation on the remote server + errAnswer = state.SendToAll(subscribeBytes, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } + + // send the challenge to a channel where the remote server is subscribed to + errAnswer = publishTo(federationInit.ChallengeMsg, remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationInit") + } return nil } @@ -375,6 +377,45 @@ func verifyLocalOrganizer(msg message.Message, channelPath string) *answer.Error return nil } +func connectTo(serverAddress string) (socket.Socket, *answer.Error) { + ws, _, err := websocket.DefaultDialer.Dial(serverAddress, nil) + if err != nil { + errAnswer := answer.NewInternalServerError( + "failed to connect to server %s: %v", serverAddress, err) + return nil, errAnswer.Wrap("connectTo") + } + + messageChan, errAnswer := state.GetMessageChan() + if errAnswer != nil { + return nil, errAnswer.Wrap("connectTo") + } + + closedSockets, errAnswer := state.GetClosedSockets() + if errAnswer != nil { + return nil, errAnswer.Wrap("connectTo") + } + + wg, errAnswer := state.GetWaitGroup() + if errAnswer != nil { + return nil, errAnswer.Wrap("connectTo") + } + + stopChan, errAnswer := state.GetStopChan() + if errAnswer != nil { + return nil, errAnswer.Wrap("connectTo") + } + + client := socket.NewClientSocket(messageChan, closedSockets, ws, wg, + stopChan, popstellar.Logger) + + wg.Add(2) + + go client.WritePump() + go client.ReadPump() + + return client, nil +} + func createMessage(data messagedata.MessageData) (message.Message, *answer.Error) { db, errAnswer := database.GetFederationRepositoryInstance() if errAnswer != nil { From ca0d0be26e6e1a1e9c3ac3d621a268076d884d82 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 21:28:32 +0200 Subject: [PATCH 28/48] Add messages generators for federation tests --- .../popserver/generatortest/federation.go | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 be1-go/internal/popserver/generatortest/federation.go diff --git a/be1-go/internal/popserver/generatortest/federation.go b/be1-go/internal/popserver/generatortest/federation.go new file mode 100644 index 0000000000..f200340211 --- /dev/null +++ b/be1-go/internal/popserver/generatortest/federation.go @@ -0,0 +1,127 @@ +package generatortest + +import ( + "encoding/json" + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3" + "popstellar/message/messagedata" + "popstellar/message/query/method/message" + "testing" +) + +func NewFederationChallengeRequest(t *testing.T, sender string, + timestamp int64, senderSk kyber.Scalar) message.Message { + + challengeRequest := messagedata.FederationChallengeRequest{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallengeRequest, + Timestamp: timestamp, + } + + challengeRequestBuf, err := json.Marshal(challengeRequest) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, challengeRequestBuf) + + return msg +} + +func NewFederationChallenge(t *testing.T, sender string, value string, + validUntil int64, senderSk kyber.Scalar) message.Message { + + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeBuf, err := json.Marshal(challenge) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, challengeBuf) + + return msg +} + +func NewFederationExpect(t *testing.T, sender, laoId, serverAddress, + publicKey string, challengeMsg message.Message, + senderSk kyber.Scalar) message.Message { + + expect := messagedata.FederationExpect{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionExpect, + LaoId: laoId, + ServerAddress: serverAddress, + PublicKey: publicKey, + ChallengeMsg: challengeMsg, + } + + expectBuf, err := json.Marshal(expect) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, expectBuf) + + return msg +} + +func NewFederationInit(t *testing.T, sender, laoId, serverAddress, + publicKey string, challengeMsg message.Message, + senderSk kyber.Scalar) message.Message { + + expect := messagedata.FederationInit{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionInit, + LaoId: laoId, + ServerAddress: serverAddress, + PublicKey: publicKey, + ChallengeMsg: challengeMsg, + } + + expectBuf, err := json.Marshal(expect) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, expectBuf) + + return msg +} + +func NewSuccessFederationResult(t *testing.T, sender, publicKey string, + challengeMsg message.Message, senderSk kyber.Scalar) message.Message { + + result := messagedata.FederationResult{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "success", + Reason: "", + PublicKey: publicKey, + ChallengeMsg: challengeMsg, + } + + resultBuf, err := json.Marshal(result) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, resultBuf) + + return msg +} + +func NewFailedFederationResult(t *testing.T, sender, reason string, + challengeMsg message.Message, senderSk kyber.Scalar) message.Message { + + result := messagedata.FederationResult{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionResult, + Status: "failure", + Reason: reason, + PublicKey: "", + ChallengeMsg: challengeMsg, + } + + resultBuf, err := json.Marshal(result) + require.NoError(t, err) + + msg := newMessage(t, sender, senderSk, resultBuf) + + return msg +} From 5b8ca760394fd6cbad586662e217f18d23c49c02 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 21:58:04 +0200 Subject: [PATCH 29/48] Add some federation unit tests --- .../popserver/handler/federation_test.go | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 be1-go/internal/popserver/handler/federation_test.go diff --git a/be1-go/internal/popserver/handler/federation_test.go b/be1-go/internal/popserver/handler/federation_test.go new file mode 100644 index 0000000000..4b412deeba --- /dev/null +++ b/be1-go/internal/popserver/handler/federation_test.go @@ -0,0 +1,273 @@ +package handler + +import ( + "database/sql" + "encoding/base64" + "fmt" + "github.com/stretchr/testify/require" + "popstellar/internal/popserver/config" + "popstellar/internal/popserver/database" + "popstellar/internal/popserver/database/repository" + "popstellar/internal/popserver/generatortest" + "popstellar/internal/popserver/state" + "popstellar/internal/popserver/types" + "popstellar/message/messagedata" + "testing" + "time" +) + +func Test_handleChannelFederation(t *testing.T) { + var args []input + + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + + organizerPk, organizerSk := generateKeys() + organizer2Pk, organizer2Sk := generateKeys() + notOrganizerPk, notOrganizerSk := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + state.SetState(subs, peers, queries) + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + organizer2Buf, err := organizer2Pk.MarshalBinary() + require.NoError(t, err) + organizer2 := base64.URLEncoding.EncodeToString(organizer2Buf) + + notOrganizerBuf, err := notOrganizerPk.MarshalBinary() + require.NoError(t, err) + notOrganizer := base64.URLEncoding.EncodeToString(notOrganizerBuf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + + // Test 1 Error when FederationChallengeRequest sender is not the same as + // the lao organizer + args = append(args, input{ + name: "Test 1", + channel: channelPath, + msg: generatortest.NewFederationChallengeRequest(t, + notOrganizer, validUntil, notOrganizerSk), + isError: true, + contains: "sender is not the organizer of the channel", + }) + + // Test 2 Error when FederationChallengeRequest timestamp is negative + args = append(args, input{ + name: "Test 2", + channel: channelPath, + msg: generatortest.NewFederationChallengeRequest(t, + organizer, -1, organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 3 Error when FederationExpect sender is not the same as the lao + // organizer + args = append(args, input{ + name: "Test 3", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, notOrganizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + notOrganizerSk), + isError: true, + contains: "sender is not the organizer of the channel", + }) + + // Test 4 Error when FederationExpect serverAddress is not valid format + args = append(args, input{ + name: "Test 4", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, organizer, laoID, + "ws:localhost:12345/client", organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 5 Error when FederationExpect publicKey is not valid format + args = append(args, input{ + name: "Test 5", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, organizer, laoID, + serverAddressA, "organizer2", + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 6 Error when FederationExpect laoId is not valid format + args = append(args, input{ + name: "Test 6", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, organizer, "laoID", + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 7 Error when FederationExpect challenge message is not a challenge + args = append(args, input{ + name: "Test 7", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, organizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallengeRequest(t, organizer, + validUntil, organizerSk), + organizerSk), + isError: true, + contains: "failed to unmarshal jsonData", + }) + + // Test 8 Error when FederationExpect challenge is not from organizer + args = append(args, input{ + name: "Test 8", + channel: channelPath, + msg: generatortest.NewFederationExpect(t, organizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, notOrganizer, + value, validUntil, notOrganizerSk), + organizerSk), + isError: true, + contains: "sender is not the organizer of the channel", + }) + + // Test 9 Error when FederationInit sender is not the same as the lao + // organizer + args = append(args, input{ + name: "Test 9", + channel: channelPath, + msg: generatortest.NewFederationInit(t, notOrganizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + notOrganizerSk), + isError: true, + contains: "sender is not the organizer of the channel", + }) + + // Test 10 Error when FederationInit serverAddress is not valid format + args = append(args, input{ + name: "Test 10", + channel: channelPath, + msg: generatortest.NewFederationInit(t, organizer, laoID, + "ws:localhost:12345/client", organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 11 Error when FederationInit publicKey is not valid format + args = append(args, input{ + name: "Test 11", + channel: channelPath, + msg: generatortest.NewFederationInit(t, organizer, laoID, + serverAddressA, "organizer2", + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 12 Error when FederationInit laoId is not valid format + args = append(args, input{ + name: "Test 12", + channel: channelPath, + msg: generatortest.NewFederationInit(t, organizer, "laoID", + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, organizer, + value, validUntil, organizerSk), + organizerSk), + isError: true, + contains: "VerifyJSON", + }) + + // Test 13 Error when FederationInit challenge message is not a challenge + args = append(args, input{ + name: "Test 13", + channel: channelPath, + msg: generatortest.NewFederationInit(t, organizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallengeRequest(t, organizer, + validUntil, organizerSk), + organizerSk), + isError: true, + contains: "failed to unmarshal jsonData", + }) + + // Test 14 Error when FederationInit challenge is not from organizer + args = append(args, input{ + name: "Test 14", + channel: channelPath, + msg: generatortest.NewFederationInit(t, organizer, laoID, + serverAddressA, organizer2, + generatortest.NewFederationChallenge(t, notOrganizer, + value, validUntil, notOrganizerSk), + organizerSk), + isError: true, + contains: "sender is not the organizer of the channel", + }) + + federationChallenge1 := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + mockRepository.On("GetFederationExpect", organizer, + notOrganizer, federationChallenge1).Return(messagedata. + FederationExpect{}, sql.ErrNoRows) + + // Test 15 Error when FederationChallenge is received without any + // matching FederationExpect + args = append(args, input{ + name: "Test 15", + channel: channelPath, + msg: generatortest.NewFederationChallenge(t, notOrganizer, value, + validUntil, notOrganizerSk), + isError: true, + contains: "failed to get federation expect", + }) + + require.NotEqual(t, notOrganizerPk, organizer2Sk) + + for _, arg := range args { + t.Run(arg.name, func(t *testing.T) { + errAnswer := handleChannelFederation(arg.channel, arg.msg) + if arg.isError { + require.Contains(t, errAnswer.Error(), arg.contains) + } else { + require.Nil(t, errAnswer) + } + }) + } +} From 5b2b28fd22dac7b1bdd943c78f4c2d82d6ddf070 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 23:11:30 +0200 Subject: [PATCH 30/48] Add federation result handler --- .../internal/popserver/handler/federation.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index a7cb7f1336..956f2377a2 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -43,6 +43,8 @@ func handleChannelFederation(channelPath string, msg message.Message) *answer.Er errAnswer = handleExpect(msg, channelPath) case messagedata.FederationActionChallenge: errAnswer = handleChallenge(msg, channelPath) + case messagedata.FederationActionResult: + errAnswer = handleResult(msg, channelPath) default: errAnswer = answer.NewInvalidMessageFieldError("failed to handle %s#%s, invalid object#action", object, action) @@ -316,6 +318,68 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { return nil } +func handleResult(msg message.Message, channelPath string) *answer.Error { + var result messagedata.FederationResult + errAnswer := msg.UnmarshalMsgData(&result) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + + // verify that the embedded challenge is correctly signed, + // we compare the sender field of the challenge later + errAnswer = verifyMessage(result.ChallengeMsg) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + + if result.Status != "success" { + errAnswer = answer.NewInternalServerError( + "failed to establish federated connection: %s", result.Reason) + return errAnswer.Wrap("handleFederationResult") + } + + db, errAnswer := database.GetFederationRepositoryInstance() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + + organizerPk, errAnswer := getOrganizerPk(channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + + // the result is from the other server if publicKey == organizer + if result.PublicKey != organizerPk { + errAnswer = answer.NewInvalidMessageFieldError( + "invalid public key contained in FederationResult message") + return errAnswer.Wrap("handleFederationResult") + } + + var federationChallenge messagedata.FederationChallenge + errAnswer = result.ChallengeMsg.UnmarshalMsgData(&federationChallenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + + // try to get a matching FederationInit, if found then we know that + // the local organizer was waiting this result + _, err := db.GetFederationInit(organizerPk, + result.ChallengeMsg.Sender, federationChallenge) + if err != nil { + errAnswer = answer.NewQueryDatabaseError( + "failed to get federation init: %v", err) + return errAnswer.Wrap("handleFederationResult") + } + + err = db.StoreMessageAndData(channelPath, msg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationResult") + } + + return nil +} + func getOrganizerPk(federationChannel string) (string, *answer.Error) { db, errAnswer := database.GetFederationRepositoryInstance() if errAnswer != nil { From 20b1db59fd9463205653762e67090d50d9500156 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 20 May 2024 23:12:42 +0200 Subject: [PATCH 31/48] Add the database function GetFederationInit --- .../database/repository/mock_repository.go | 29 +++++++++++++++ .../database/repository/repository.go | 4 +++ .../popserver/database/sqlite/sqlite.go | 35 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/be1-go/internal/popserver/database/repository/mock_repository.go b/be1-go/internal/popserver/database/repository/mock_repository.go index 4c3c965eda..316f1bc17c 100644 --- a/be1-go/internal/popserver/database/repository/mock_repository.go +++ b/be1-go/internal/popserver/database/repository/mock_repository.go @@ -336,6 +336,35 @@ func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, return r0, r1 } +// GetFederationInit provides a mock function with given fields: senderPk, remotePk, Challenge +func (_m *MockRepository) GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationInit, + error) { + ret := _m.Called(senderPk, remotePk, Challenge) + + if len(ret) == 0 { + panic("no return value specified for GetFederationInit") + } + + var r0 messagedata.FederationInit + var r1 error + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) (messagedata.FederationInit, error)); ok { + return rf(senderPk, remotePk, Challenge) + } + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) messagedata.FederationInit); ok { + r0 = rf(senderPk, remotePk, Challenge) + } else { + r0 = ret.Get(0).(messagedata.FederationInit) + } + + if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge) error); ok { + r1 = rf(senderPk, remotePk, Challenge) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLAOOrganizerPubKey provides a mock function with given fields: electionID func (_m *MockRepository) GetLAOOrganizerPubKey(electionID string) (kyber.Point, error) { ret := _m.Called(electionID) diff --git a/be1-go/internal/popserver/database/repository/repository.go b/be1-go/internal/popserver/database/repository/repository.go index 3e29d1f91d..502830d9a1 100644 --- a/be1-go/internal/popserver/database/repository/repository.go +++ b/be1-go/internal/popserver/database/repository/repository.go @@ -200,6 +200,10 @@ type FederationRepository interface { // the given public keys GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) + // GetFederationInit return a FederationExpect where the organizer is + // the given public keys + GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationInit, error) + // GetServerKeys get the keys of the server GetServerKeys() (kyber.Point, kyber.Scalar, error) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 36e5ce0e75..35709740e7 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1347,3 +1347,38 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge return messagedata.FederationExpect{}, sql.ErrNoRows } + +func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge messagedata.FederationChallenge) (messagedata.FederationInit, error) { + dbLock.RLock() + defer dbLock.RUnlock() + + rows, err := s.database.Query(selectFederationExpects, senderPk, + messagedata.FederationObject, messagedata.FederationActionInit, + remotePk) + if err != nil { + return messagedata.FederationInit{}, err + } + + // iterate over all FederationInit sent from the given sender pk, + // and search the one matching the given FederationChallenge + for rows.Next() { + var federationInit messagedata.FederationInit + + err = rows.Scan(&federationInit) + if err != nil { + continue + } + + var federationChallenge messagedata.FederationChallenge + errAnswer := federationInit.ChallengeMsg.UnmarshalMsgData(federationChallenge) + if errAnswer != nil { + return messagedata.FederationInit{}, errAnswer + } + + if federationChallenge == challenge { + return federationInit, nil + } + } + + return messagedata.FederationInit{}, sql.ErrNoRows +} From bc6813f4443bebe659073aa042ebcc4809a559c4 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 02:12:51 +0200 Subject: [PATCH 32/48] fix missing pointer and missing channel --- .../popserver/database/sqlite/sqlite.go | 4 +-- .../internal/popserver/handler/federation.go | 14 +++++++-- .../popserver/handler/federation_test.go | 4 +-- be1-go/internal/popserver/handler/root.go | 1 + .../internal/popserver/handler/root_test.go | 1 + .../messagedata/federation_challenge.go | 31 +++++++++++++++++++ 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 35709740e7..05cf4832ce 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1335,7 +1335,7 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge } var federationChallenge messagedata.FederationChallenge - errAnswer := federationExpect.ChallengeMsg.UnmarshalMsgData(federationChallenge) + errAnswer := federationExpect.ChallengeMsg.UnmarshalMsgData(&federationChallenge) if errAnswer != nil { return messagedata.FederationExpect{}, errAnswer } @@ -1370,7 +1370,7 @@ func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge m } var federationChallenge messagedata.FederationChallenge - errAnswer := federationInit.ChallengeMsg.UnmarshalMsgData(federationChallenge) + errAnswer := federationInit.ChallengeMsg.UnmarshalMsgData(&federationChallenge) if errAnswer != nil { return messagedata.FederationInit{}, errAnswer } diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 956f2377a2..9043b75073 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -138,7 +138,12 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { } var challenge messagedata.FederationChallenge - errAnswer = federationExpect.ChallengeMsg.UnmarshalMsgData(challenge) + errAnswer = federationExpect.ChallengeMsg.UnmarshalMsgData(&challenge) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + + errAnswer = challenge.Verify() if errAnswer != nil { return errAnswer.Wrap("handleFederationExpect") } @@ -188,11 +193,16 @@ func handleInit(msg message.Message, channelPath string) *answer.Error { } var challenge messagedata.FederationChallenge - errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(challenge) + errAnswer = federationInit.ChallengeMsg.UnmarshalMsgData(&challenge) if errAnswer != nil { return errAnswer.Wrap("handleFederationInit") } + errAnswer = challenge.Verify() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + db, errAnswer := database.GetFederationRepositoryInstance() if errAnswer != nil { return errAnswer.Wrap("handleFederationInit") diff --git a/be1-go/internal/popserver/handler/federation_test.go b/be1-go/internal/popserver/handler/federation_test.go index 4b412deeba..f861018c13 100644 --- a/be1-go/internal/popserver/handler/federation_test.go +++ b/be1-go/internal/popserver/handler/federation_test.go @@ -141,7 +141,7 @@ func Test_handleChannelFederation(t *testing.T) { validUntil, organizerSk), organizerSk), isError: true, - contains: "failed to unmarshal jsonData", + contains: "invalid message", }) // Test 8 Error when FederationExpect challenge is not from organizer @@ -220,7 +220,7 @@ func Test_handleChannelFederation(t *testing.T) { validUntil, organizerSk), organizerSk), isError: true, - contains: "failed to unmarshal jsonData", + contains: "invalid message", }) // Test 14 Error when FederationInit challenge is not from organizer diff --git a/be1-go/internal/popserver/handler/root.go b/be1-go/internal/popserver/handler/root.go index 90049a2b17..e4f3b4b889 100644 --- a/be1-go/internal/popserver/handler/root.go +++ b/be1-go/internal/popserver/handler/root.go @@ -143,6 +143,7 @@ func createLaoAndChannels(msg, laoGreetMsg message.Message, organizerPubBuf []by laoPath + Consensus: sqlite.ConsensusType, laoPath + Coin: sqlite.CoinType, laoPath + Auth: sqlite.AuthType, + laoPath + Federation: sqlite.FederationType, } db, errAnswer := database.GetRootRepositoryInstance() diff --git a/be1-go/internal/popserver/handler/root_test.go b/be1-go/internal/popserver/handler/root_test.go index 178f073859..f518561e75 100644 --- a/be1-go/internal/popserver/handler/root_test.go +++ b/be1-go/internal/popserver/handler/root_test.go @@ -136,6 +136,7 @@ func newLaoCreateMsg(t *testing.T, organizer, sender, laoName string, mockReposi laoPath + Consensus: sqlite.ConsensusType, laoPath + Coin: sqlite.CoinType, laoPath + Auth: sqlite.AuthType, + laoPath + Federation: sqlite.FederationType, } mockRepository.On("StoreLaoWithLaoGreet", channels, diff --git a/be1-go/message/messagedata/federation_challenge.go b/be1-go/message/messagedata/federation_challenge.go index 9620ba4c60..4d0c60d8be 100644 --- a/be1-go/message/messagedata/federation_challenge.go +++ b/be1-go/message/messagedata/federation_challenge.go @@ -1,5 +1,10 @@ package messagedata +import ( + "encoding/hex" + "popstellar/message/answer" +) + // FederationChallenge defines a message data type FederationChallenge struct { Object string `json:"object"` @@ -24,3 +29,29 @@ func (FederationChallenge) GetAction() string { func (FederationChallenge) NewEmpty() MessageData { return &FederationChallenge{} } + +func (message FederationChallenge) Verify() *answer.Error { + if message.Object != message.GetObject() { + return answer.NewInvalidMessageFieldError( + "object is %s instead of %s", + message.Object, message.GetAction()) + } + + if message.Action != message.GetAction() { + return answer.NewInvalidMessageFieldError( + "action is %s instead of %s", + message.Action, message.GetAction()) + } + + if message.ValidUntil < 0 { + return answer.NewInvalidMessageFieldError("valid_until is negative") + } + + valueBytes, err := hex.DecodeString(message.Value) + if err != nil || len(valueBytes) != 32 { + return answer.NewInvalidMessageFieldError( + "value is not a 32 bytes array encoded in hexadecimal") + } + + return nil +} From cfe25573a7b2a00db243b602f2c3fb620d5c03b4 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 10:33:40 +0200 Subject: [PATCH 33/48] Fix sqlite queries --- .../internal/popserver/database/sqlite/sqlite_const.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite_const.go b/be1-go/internal/popserver/database/sqlite/sqlite_const.go index aab59ac00f..9a533732bd 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite_const.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite_const.go @@ -289,13 +289,13 @@ const ( WHERE messageID = ?` selectValidFederationChallenges = ` - SELECT message + SELECT messageData FROM message WHERE json_extract(message, '$.sender') = ? AND json_extract(messageData, '$.object') = ? AND json_extract(messageData, '$.action') = ? - AND json_extract(messageData, '$.challenge.value') = ? - AND json_extract(messageData, '$.challenge.valid_until') = ? + AND json_extract(messageData, '$.value') = ? + AND json_extract(messageData, '$.valid_until') = ? ORDER BY message.storedTime DESC ` @@ -304,8 +304,8 @@ const ( FROM message WHERE json_extract(messageData, '$.object') = ? AND json_extract(messageData, '$.action') = ? - AND json_extract(messageData, '$.challenge.value') = ? - AND json_extract(messageData, '$.challenge.valid_until') = ? + AND json_extract(messageData, '$.value') = ? + AND json_extract(messageData, '$.valid_until') = ? ` selectFederationExpects = ` From 82c4e1491ee255942f938811f21dccdb58051054 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 12:15:18 +0200 Subject: [PATCH 34/48] Fix sqlite queries --- .../database/repository/mock_repository.go | 47 ++++++++-------- .../database/repository/repository.go | 6 +-- .../popserver/database/sqlite/sqlite.go | 53 ++++++++++++------- .../popserver/database/sqlite/sqlite_const.go | 18 +++++-- .../internal/popserver/handler/federation.go | 18 +++---- 5 files changed, 80 insertions(+), 62 deletions(-) diff --git a/be1-go/internal/popserver/database/repository/mock_repository.go b/be1-go/internal/popserver/database/repository/mock_repository.go index 316f1bc17c..baa1bbc592 100644 --- a/be1-go/internal/popserver/database/repository/mock_repository.go +++ b/be1-go/internal/popserver/database/repository/mock_repository.go @@ -308,9 +308,9 @@ func (_m *MockRepository) GetElectionType(electionID string) (string, error) { return r0, r1 } -// GetFederationExpect provides a mock function with given fields: senderPk, remotePk, Challenge -func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) { - ret := _m.Called(senderPk, remotePk, Challenge) +// GetFederationExpect provides a mock function with given fields: senderPk, remotePk, Challenge, channelPath +func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationExpect, error) { + ret := _m.Called(senderPk, remotePk, Challenge, channelPath) if len(ret) == 0 { panic("no return value specified for GetFederationExpect") @@ -318,17 +318,17 @@ func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, var r0 messagedata.FederationExpect var r1 error - if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) (messagedata.FederationExpect, error)); ok { - return rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge, string) (messagedata.FederationExpect, error)); ok { + return rf(senderPk, remotePk, Challenge, channelPath) } - if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) messagedata.FederationExpect); ok { - r0 = rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge, string) messagedata.FederationExpect); ok { + r0 = rf(senderPk, remotePk, Challenge, channelPath) } else { r0 = ret.Get(0).(messagedata.FederationExpect) } - if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge) error); ok { - r1 = rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge, string) error); ok { + r1 = rf(senderPk, remotePk, Challenge, channelPath) } else { r1 = ret.Error(1) } @@ -336,10 +336,9 @@ func (_m *MockRepository) GetFederationExpect(senderPk string, remotePk string, return r0, r1 } -// GetFederationInit provides a mock function with given fields: senderPk, remotePk, Challenge -func (_m *MockRepository) GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationInit, - error) { - ret := _m.Called(senderPk, remotePk, Challenge) +// GetFederationInit provides a mock function with given fields: senderPk, remotePk, Challenge, channelPath +func (_m *MockRepository) GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationInit, error) { + ret := _m.Called(senderPk, remotePk, Challenge, channelPath) if len(ret) == 0 { panic("no return value specified for GetFederationInit") @@ -347,17 +346,17 @@ func (_m *MockRepository) GetFederationInit(senderPk string, remotePk string, Ch var r0 messagedata.FederationInit var r1 error - if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) (messagedata.FederationInit, error)); ok { - return rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge, string) (messagedata.FederationInit, error)); ok { + return rf(senderPk, remotePk, Challenge, channelPath) } - if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge) messagedata.FederationInit); ok { - r0 = rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(0).(func(string, string, messagedata.FederationChallenge, string) messagedata.FederationInit); ok { + r0 = rf(senderPk, remotePk, Challenge, channelPath) } else { r0 = ret.Get(0).(messagedata.FederationInit) } - if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge) error); ok { - r1 = rf(senderPk, remotePk, Challenge) + if rf, ok := ret.Get(1).(func(string, string, messagedata.FederationChallenge, string) error); ok { + r1 = rf(senderPk, remotePk, Challenge, channelPath) } else { r1 = ret.Error(1) } @@ -782,17 +781,17 @@ func (_m *MockRepository) IsAttendee(laoPath string, poptoken string) (bool, err return r0, r1 } -// IsChallengeValid provides a mock function with given fields: senderPk, challenge -func (_m *MockRepository) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error { - ret := _m.Called(senderPk, challenge) +// IsChallengeValid provides a mock function with given fields: senderPk, challenge, channelPath +func (_m *MockRepository) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge, channelPath string) error { + ret := _m.Called(senderPk, challenge, channelPath) if len(ret) == 0 { panic("no return value specified for IsChallengeValid") } var r0 error - if rf, ok := ret.Get(0).(func(string, messagedata.FederationChallenge) error); ok { - r0 = rf(senderPk, challenge) + if rf, ok := ret.Get(0).(func(string, messagedata.FederationChallenge, string) error); ok { + r0 = rf(senderPk, challenge, channelPath) } else { r0 = ret.Error(0) } diff --git a/be1-go/internal/popserver/database/repository/repository.go b/be1-go/internal/popserver/database/repository/repository.go index 502830d9a1..666316d43c 100644 --- a/be1-go/internal/popserver/database/repository/repository.go +++ b/be1-go/internal/popserver/database/repository/repository.go @@ -191,18 +191,18 @@ type FederationRepository interface { GetOrganizerPubKey(laoID string) (kyber.Point, error) // IsChallengeValid returns true if the challenge is valid and not used yet - IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error + IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge, channelPath string) error // RemoveChallenge removes the challenge from the database to avoid reuse RemoveChallenge(challenge messagedata.FederationChallenge) error // GetFederationExpect return a FederationExpect where the organizer is // the given public keys - GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) + GetFederationExpect(senderPk string, remotePk string, Challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationExpect, error) // GetFederationInit return a FederationExpect where the organizer is // the given public keys - GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge) (messagedata.FederationInit, error) + GetFederationInit(senderPk string, remotePk string, Challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationInit, error) // GetServerKeys get the keys of the server GetServerKeys() (kyber.Point, kyber.Scalar, error) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 05cf4832ce..b7e5caaf76 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1268,16 +1268,21 @@ func (s *SQLite) GetReactionSender(messageID string) (string, error) { // FederationRepository interface implementation //====================================================================================================================== -func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge) error { +func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge, channelPath string) error { dbLock.RLock() defer dbLock.RUnlock() - var federationChallenge messagedata.FederationChallenge - - err := s.database.QueryRow(selectValidFederationChallenges, + var federationChallengeBytes []byte + err := s.database.QueryRow(selectValidFederationChallenges, channelPath, senderPk, messagedata.FederationObject, - messagedata.FederationActionChallenge, - challenge.Value, challenge.ValidUntil).Scan(&federationChallenge) + messagedata.FederationActionChallenge, challenge.Value, + challenge.ValidUntil).Scan(&federationChallengeBytes) + if err != nil { + return err + } + + var federationChallenge messagedata.FederationChallenge + err = json.Unmarshal(federationChallengeBytes, &federationChallenge) if err != nil { return err } @@ -1289,7 +1294,7 @@ func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.Federat return nil } -func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge) error { +func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge, channelPath string) error { dbLock.Lock() defer dbLock.Unlock() @@ -1313,13 +1318,13 @@ func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge) erro return nil } -func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge messagedata.FederationChallenge) (messagedata.FederationExpect, error) { +func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationExpect, error) { dbLock.RLock() defer dbLock.RUnlock() - rows, err := s.database.Query(selectFederationExpects, senderPk, - messagedata.FederationObject, messagedata.FederationActionExpect, - remotePk) + rows, err := s.database.Query(selectFederationExpects, channelPath, + senderPk, messagedata.FederationObject, + messagedata.FederationActionExpect, remotePk) if err != nil { return messagedata.FederationExpect{}, err } @@ -1327,9 +1332,14 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge // iterate over all FederationExpect sent from the given sender pk, // and search the one matching the given FederationChallenge for rows.Next() { - var federationExpect messagedata.FederationExpect + var federationExpectBytes []byte + err = rows.Scan(&federationExpectBytes) + if err != nil { + continue + } - err = rows.Scan(&federationExpect) + var federationExpect messagedata.FederationExpect + err = json.Unmarshal(federationExpectBytes, &federationExpect) if err != nil { continue } @@ -1348,13 +1358,13 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge return messagedata.FederationExpect{}, sql.ErrNoRows } -func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge messagedata.FederationChallenge) (messagedata.FederationInit, error) { +func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationInit, error) { dbLock.RLock() defer dbLock.RUnlock() - rows, err := s.database.Query(selectFederationExpects, senderPk, - messagedata.FederationObject, messagedata.FederationActionInit, - remotePk) + rows, err := s.database.Query(selectFederationExpects, channelPath, + senderPk, messagedata.FederationObject, + messagedata.FederationActionInit, remotePk) if err != nil { return messagedata.FederationInit{}, err } @@ -1362,9 +1372,14 @@ func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge m // iterate over all FederationInit sent from the given sender pk, // and search the one matching the given FederationChallenge for rows.Next() { - var federationInit messagedata.FederationInit + var federationInitBytes []byte + err = rows.Scan(&federationInitBytes) + if err != nil { + continue + } - err = rows.Scan(&federationInit) + var federationInit messagedata.FederationInit + err = json.Unmarshal(federationInitBytes, &federationInit) if err != nil { continue } diff --git a/be1-go/internal/popserver/database/sqlite/sqlite_const.go b/be1-go/internal/popserver/database/sqlite/sqlite_const.go index 9a533732bd..9c7d021791 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite_const.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite_const.go @@ -290,8 +290,13 @@ const ( selectValidFederationChallenges = ` SELECT messageData - FROM message - WHERE json_extract(message, '$.sender') = ? + FROM ( + SELECT * + FROM message + JOIN channelMessage ON message.messageID = channelMessage.messageID + ) + WHERE channelPath = ? + AND json_extract(message, '$.sender') = ? AND json_extract(messageData, '$.object') = ? AND json_extract(messageData, '$.action') = ? AND json_extract(messageData, '$.value') = ? @@ -310,8 +315,13 @@ const ( selectFederationExpects = ` SELECT messageData - FROM message - WHERE json_extract(message, '$.sender') = ? + FROM ( + SELECT * + FROM message + JOIN channelMessage ON message.messageID = channelMessage.messageID + ) + WHERE channelPath = ? + AND json_extract(message, '$.sender') = ? AND json_extract(messageData, '$.object') = ? AND json_extract(messageData, '$.action') = ? AND json_extract(messageData, '$.public_key') = ? diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 9043b75073..baf64c67e5 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -158,7 +158,7 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationExpect") } - err := db.IsChallengeValid(serverPk, challenge) + err := db.IsChallengeValid(serverPk, challenge, channelPath) if err != nil { errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) return errAnswer.Wrap("handleFederationExpect") @@ -208,20 +208,14 @@ func handleInit(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationInit") } - err := db.IsChallengeValid(channelPath, challenge) - if err != nil { - errAnswer = answer.NewQueryDatabaseError("No valid challenge: %v", err) - return errAnswer.Wrap("handleFederationInit") - } - - err = db.StoreMessageAndData(channelPath, msg) + err := db.StoreMessageAndData(channelPath, msg) if err != nil { errAnswer = answer.NewStoreDatabaseError(err.Error()) return errAnswer.Wrap("handleFederationInit") } - remote, err := connectTo(federationInit.ServerAddress) - if err != nil { + remote, errAnswer := connectTo(federationInit.ServerAddress) + if errAnswer != nil { errAnswer = answer.NewInternalServerError( "failed to connect to %s: %v", federationInit.ServerAddress, err) return errAnswer.Wrap("handleFederationInit") @@ -285,7 +279,7 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { } federationExpect, err := db.GetFederationExpect(organizerPk, msg.Sender, - federationChallenge) + federationChallenge, channelPath) if err != nil { errAnswer = answer.NewQueryDatabaseError( "failed to get federation expect: %v", err) @@ -374,7 +368,7 @@ func handleResult(msg message.Message, channelPath string) *answer.Error { // try to get a matching FederationInit, if found then we know that // the local organizer was waiting this result _, err := db.GetFederationInit(organizerPk, - result.ChallengeMsg.Sender, federationChallenge) + result.ChallengeMsg.Sender, federationChallenge, channelPath) if err != nil { errAnswer = answer.NewQueryDatabaseError( "failed to get federation init: %v", err) From 47e6474a9b05145a3283f6d35a100398c6852ff9 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 12:17:23 +0200 Subject: [PATCH 35/48] Fix sqlite queries --- be1-go/internal/popserver/database/sqlite/sqlite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index b7e5caaf76..9aa758da9b 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1294,7 +1294,7 @@ func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.Federat return nil } -func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge, channelPath string) error { +func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge) error { dbLock.Lock() defer dbLock.Unlock() From 0cd60d2145ba7c54b49df1e0e02ea5db940ce9c4 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 14:05:32 +0200 Subject: [PATCH 36/48] Add some tests --- .../popserver/handler/federation_test.go | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/be1-go/internal/popserver/handler/federation_test.go b/be1-go/internal/popserver/handler/federation_test.go index f861018c13..ed05127ef8 100644 --- a/be1-go/internal/popserver/handler/federation_test.go +++ b/be1-go/internal/popserver/handler/federation_test.go @@ -3,7 +3,9 @@ package handler import ( "database/sql" "encoding/base64" + "encoding/json" "fmt" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "popstellar/internal/popserver/config" "popstellar/internal/popserver/database" @@ -12,6 +14,8 @@ import ( "popstellar/internal/popserver/state" "popstellar/internal/popserver/types" "popstellar/message/messagedata" + "popstellar/message/query/method" + "popstellar/network/socket" "testing" "time" ) @@ -244,7 +248,7 @@ func Test_handleChannelFederation(t *testing.T) { } mockRepository.On("GetFederationExpect", organizer, - notOrganizer, federationChallenge1).Return(messagedata. + notOrganizer, federationChallenge1, channelPath).Return(messagedata. FederationExpect{}, sql.ErrNoRows) // Test 15 Error when FederationChallenge is received without any @@ -271,3 +275,118 @@ func Test_handleChannelFederation(t *testing.T) { }) } } + +func Test_handleRequestChallenge(t *testing.T) { + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + + organizerPk, organizerSk := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + state.SetState(subs, peers, queries) + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + + errAnswer := subs.AddChannel(channelPath) + require.Nil(t, err) + + fakeSocket := socket.FakeSocket{Id: "1"} + errAnswer = subs.Subscribe(channelPath, &fakeSocket) + require.Nil(t, errAnswer) + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + mockRepository.On("GetServerKeys").Return(serverPk, serverSk, nil) + mockRepository.On("StoreMessageAndData", channelPath, + mock.AnythingOfType("message.Message")).Return(nil) + + errAnswer = handleRequestChallenge(generatortest. + NewFederationChallengeRequest(t, organizer, time.Now().Unix(), + organizerSk), channelPath) + require.Nil(t, errAnswer) + + require.NotNil(t, fakeSocket.Msg) + var broadcastMsg method.Broadcast + err = json.Unmarshal(fakeSocket.Msg, &broadcastMsg) + require.NoError(t, err) + + require.Equal(t, "broadcast", broadcastMsg.Method) + require.Equal(t, channelPath, broadcastMsg.Params.Channel) + + var challenge messagedata.FederationChallenge + errAnswer = broadcastMsg.Params.Message.UnmarshalMsgData(&challenge) + require.Nil(t, errAnswer) + + errAnswer = challenge.Verify() + require.Nil(t, errAnswer) +} + +func Test_handleFederationExpect(t *testing.T) { + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + + organizerPk, organizerSk := generateKeys() + organizer2Pk, _ := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + state.SetState(subs, peers, queries) + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + organizer2Buf, err := organizer2Pk.MarshalBinary() + require.NoError(t, err) + organizer2 := base64.URLEncoding.EncodeToString(organizer2Buf) + + serverBuf, err := serverPk.MarshalBinary() + require.NoError(t, err) + server := base64.URLEncoding.EncodeToString(serverBuf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + federationChallenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + mockRepository.On("GetServerKeys").Return(serverPk, serverSk, nil) + mockRepository.On("StoreMessageAndData", channelPath, + mock.AnythingOfType("message.Message")).Return(nil) + + mockRepository.On("IsChallengeValid", server, federationChallenge, + channelPath).Return(nil) + + federationExpect := generatortest.NewFederationExpect(t, organizer, laoID, + serverAddressA, organizer2, generatortest.NewFederationChallenge(t, + organizer, value, validUntil, organizerSk), organizerSk) + + errAnswer := handleExpect(federationExpect, channelPath) + require.Nil(t, errAnswer) +} From 022247687e44619e1baa57ab7e1218bb8ec7a8ca Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 14:34:14 +0200 Subject: [PATCH 37/48] broadcast the federation result --- be1-go/internal/popserver/handler/federation.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index baf64c67e5..8497933270 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -164,6 +164,12 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationExpect") } + remoteChannel := fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + errAnswer = state.AddChannel(remoteChannel) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationExpect") + } + err = db.StoreMessageAndData(channelPath, msg) if err != nil { errAnswer = answer.NewStoreDatabaseError(err.Error()) @@ -314,10 +320,10 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { } // broadcast the FederationResult to the local organizer ? - //errAnswer = broadcastToAllClients(resultMsg, channelPath) - //if errAnswer != nil { - // return errAnswer.Wrap("handleFederationChallenge") - //} + errAnswer = broadcastToAllClients(resultMsg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } return nil } From f0cb4f88e2fe0375f0f3da1eb3694ae9fc8be3fe Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 14:34:21 +0200 Subject: [PATCH 38/48] replace rlock by lock --- be1-go/internal/popserver/database/sqlite/sqlite.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 9aa758da9b..57d6958073 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -1269,8 +1269,8 @@ func (s *SQLite) GetReactionSender(messageID string) (string, error) { //====================================================================================================================== func (s *SQLite) IsChallengeValid(senderPk string, challenge messagedata.FederationChallenge, channelPath string) error { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var federationChallengeBytes []byte err := s.database.QueryRow(selectValidFederationChallenges, channelPath, @@ -1319,8 +1319,8 @@ func (s *SQLite) RemoveChallenge(challenge messagedata.FederationChallenge) erro } func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationExpect, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() rows, err := s.database.Query(selectFederationExpects, channelPath, senderPk, messagedata.FederationObject, @@ -1359,8 +1359,8 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge } func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge messagedata.FederationChallenge, channelPath string) (messagedata.FederationInit, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() rows, err := s.database.Query(selectFederationExpects, channelPath, senderPk, messagedata.FederationObject, From 63f06cef1ba35a66962c853cfce5f35c87aa6f85 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 14:35:54 +0200 Subject: [PATCH 39/48] fix sqlite queries --- be1-go/internal/popserver/database/sqlite/sqlite_const.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite_const.go b/be1-go/internal/popserver/database/sqlite/sqlite_const.go index 9c7d021791..9c242472b3 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite_const.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite_const.go @@ -301,7 +301,7 @@ const ( AND json_extract(messageData, '$.action') = ? AND json_extract(messageData, '$.value') = ? AND json_extract(messageData, '$.valid_until') = ? - ORDER BY message.storedTime DESC + ORDER BY storedTime DESC ` deleteFederationChallenge = ` @@ -325,7 +325,7 @@ const ( AND json_extract(messageData, '$.object') = ? AND json_extract(messageData, '$.action') = ? AND json_extract(messageData, '$.public_key') = ? - ORDER BY message.storedTime DESC + ORDER BY storedTime DESC ` ) From f9f6a3cdb4b55389cbfe1df1c4d73ca7083e9447 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 16:22:57 +0200 Subject: [PATCH 40/48] fix a test --- be1-go/internal/popserver/handler/federation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be1-go/internal/popserver/handler/federation_test.go b/be1-go/internal/popserver/handler/federation_test.go index ed05127ef8..3848d341cd 100644 --- a/be1-go/internal/popserver/handler/federation_test.go +++ b/be1-go/internal/popserver/handler/federation_test.go @@ -300,7 +300,7 @@ func Test_handleRequestChallenge(t *testing.T) { channelPath := fmt.Sprintf("/root/%s/federation", laoID) errAnswer := subs.AddChannel(channelPath) - require.Nil(t, err) + require.Nil(t, errAnswer) fakeSocket := socket.FakeSocket{Id: "1"} errAnswer = subs.Subscribe(channelPath, &fakeSocket) From 7f4e7a85d630907154395dfabfe7774f2f3b86ba Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Tue, 21 May 2024 21:57:14 +0200 Subject: [PATCH 41/48] Verify if a challenge is expired --- be1-go/internal/popserver/handler/federation.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 8497933270..7e77bd0513 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -298,6 +298,12 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationChallenge") } + if federationChallenge.ValidUntil < time.Now().Unix() { + errAnswer = answer.NewAccessDeniedError( + "This challenge has expired: %v", federationChallenge) + return errAnswer.Wrap("handleFederationChallenge") + } + result := messagedata.FederationResult{ Object: messagedata.FederationObject, Action: messagedata.FederationActionResult, From 77bc244aaf91ecb19c978e36e93fd2f2bed3bb42 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 22 May 2024 12:49:56 +0200 Subject: [PATCH 42/48] Fix an error --- be1-go/internal/popserver/handler/federation.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 7e77bd0513..3482e9f2c0 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -165,10 +165,7 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { } remoteChannel := fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) - errAnswer = state.AddChannel(remoteChannel) - if errAnswer != nil { - return errAnswer.Wrap("handleFederationExpect") - } + _ = state.AddChannel(remoteChannel) err = db.StoreMessageAndData(channelPath, msg) if err != nil { @@ -325,7 +322,7 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationChallenge") } - // broadcast the FederationResult to the local organizer ? + // broadcast the FederationResult to the local organizer errAnswer = broadcastToAllClients(resultMsg, channelPath) if errAnswer != nil { return errAnswer.Wrap("handleFederationChallenge") @@ -393,6 +390,11 @@ func handleResult(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationResult") } + errAnswer = broadcastToAllClients(msg, channelPath) + if errAnswer != nil { + return errAnswer.Wrap("handleFederationChallenge") + } + return nil } From 0c6dcff07093a370029520c0eec03abe80193df8 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 22 May 2024 19:10:42 +0200 Subject: [PATCH 43/48] replace rlock by lock, add missing rows.Close, move missing tx.Rollback --- .../popserver/database/sqlite/sqlite.go | 124 +++++++++--------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 57d6958073..90b8c92555 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -44,8 +44,8 @@ func (s *SQLite) StoreServerKeys(electionPubKey kyber.Point, electionSecretKey k } func (s *SQLite) GetServerKeys() (kyber.Point, kyber.Scalar, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var serverPubBuf []byte var serverSecBuf []byte @@ -131,8 +131,8 @@ func addPendingSignatures(tx *sql.Tx, msg *message.Message) error { // GetMessagesByID returns a set of messages by their IDs. func (s *SQLite) GetMessagesByID(IDs []string) (map[string]message.Message, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() if len(IDs) == 0 { return make(map[string]message.Message), nil @@ -174,8 +174,8 @@ func (s *SQLite) GetMessagesByID(IDs []string) (map[string]message.Message, erro // GetMessageByID returns a message by its ID. func (s *SQLite) GetMessageByID(ID string) (message.Message, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var messageByte []byte err := s.database.QueryRow(selectMessage, ID).Scan(&messageByte) @@ -236,8 +236,8 @@ func (s *SQLite) StoreChannel(channelPath, channelType, laoPath string) error { } func (s *SQLite) GetAllChannels() ([]string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() rows, err := s.database.Query(selectAllChannels) if err != nil { @@ -266,8 +266,8 @@ func (s *SQLite) GetAllChannels() ([]string, error) { // GetChannelType returns the type of the channelPath. func (s *SQLite) GetChannelType(channelPath string) (string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var channelType string err := s.database.QueryRow(selectChannelType, channelPath).Scan(&channelType) @@ -276,8 +276,8 @@ func (s *SQLite) GetChannelType(channelPath string) (string, error) { // GetAllMessagesFromChannel returns all the messages received + sent on a channel sorted by stored time. func (s *SQLite) GetAllMessagesFromChannel(channelPath string) ([]message.Message, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() rows, err := s.database.Query(selectAllMessagesFromChannel, channelPath) if err != nil { @@ -305,8 +305,8 @@ func (s *SQLite) GetAllMessagesFromChannel(channelPath string) ([]message.Messag } func (s *SQLite) GetResultForGetMessagesByID(params map[string][]string) (map[string][]message.Message, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var interfaces []interface{} // isBaseChannel must be true @@ -351,8 +351,8 @@ func (s *SQLite) GetResultForGetMessagesByID(params map[string][]string) (map[st } func (s *SQLite) GetParamsHeartbeat() (map[string][]string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() rows, err := s.database.Query(selectBaseChannelMessages, true) if err != nil { @@ -380,8 +380,8 @@ func (s *SQLite) GetParamsHeartbeat() (map[string][]string, error) { } func (s *SQLite) GetParamsForGetMessageByID(params map[string][]string) (map[string][]string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var interfaces []interface{} // isBaseChannel must be true @@ -434,8 +434,8 @@ func (s *SQLite) GetParamsForGetMessageByID(params map[string][]string) (map[str //====================================================================================================================== func (s *SQLite) HasChannel(channelPath string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var c string err := s.database.QueryRow(selectChannelPath, channelPath).Scan(&c) @@ -449,8 +449,8 @@ func (s *SQLite) HasChannel(channelPath string) (bool, error) { } func (s *SQLite) HasMessage(messageID string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var msgID string err := s.database.QueryRow(selectMessageID, messageID).Scan(&msgID) @@ -480,6 +480,7 @@ func (s *SQLite) StoreLaoWithLaoGreet( if err != nil { return err } + defer tx.Rollback() msgByte, err := json.Marshal(msg) if err != nil { @@ -540,7 +541,6 @@ func (s *SQLite) StoreLaoWithLaoGreet( return err } - defer tx.Rollback() return nil } @@ -549,8 +549,8 @@ func (s *SQLite) StoreLaoWithLaoGreet( //====================================================================================================================== func (s *SQLite) GetOrganizerPubKey(laoPath string) (kyber.Point, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var organizerPubBuf []byte err := s.database.QueryRow(selectPublicKey, laoPath).Scan(&organizerPubBuf) @@ -566,8 +566,8 @@ func (s *SQLite) GetOrganizerPubKey(laoPath string) (kyber.Point, error) { } func (s *SQLite) GetRollCallState(channelPath string) (string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var state string err := s.database.QueryRow(selectLastRollCallMessage, messagedata.RollCallObject, channelPath).Scan(&state) @@ -578,8 +578,8 @@ func (s *SQLite) GetRollCallState(channelPath string) (string, error) { } func (s *SQLite) CheckPrevOpenOrReopenID(channel, nextID string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var lastMsg []byte var lastAction string @@ -614,8 +614,8 @@ func (s *SQLite) CheckPrevOpenOrReopenID(channel, nextID string) (bool, error) { } func (s *SQLite) CheckPrevCreateOrCloseID(channel, nextID string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var lastMsg []byte var lastAction string @@ -650,8 +650,8 @@ func (s *SQLite) CheckPrevCreateOrCloseID(channel, nextID string) (bool, error) } func (s *SQLite) GetLaoWitnesses(laoPath string) (map[string]struct{}, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var witnesses []string err := s.database.QueryRow(selectLaoWitnesses, laoPath, messagedata.LAOObject, messagedata.LAOActionCreate).Scan(&witnesses) @@ -832,8 +832,8 @@ func (s *SQLite) StoreElectionWithElectionKey( //====================================================================================================================== func (s *SQLite) GetLAOOrganizerPubKey(electionPath string) (kyber.Point, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() tx, err := s.database.Begin() if err != nil { @@ -861,8 +861,8 @@ func (s *SQLite) GetLAOOrganizerPubKey(electionPath string) (kyber.Point, error) } func (s *SQLite) GetElectionSecretKey(electionPath string) (kyber.Scalar, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var electionSecretBuf []byte err := s.database.QueryRow(selectSecretKey, electionPath).Scan(&electionSecretBuf) @@ -879,8 +879,8 @@ func (s *SQLite) GetElectionSecretKey(electionPath string) (kyber.Scalar, error) } func (s *SQLite) getElectionState(electionPath string) (string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var state string err := s.database.QueryRow(selectLastElectionMessage, electionPath, messagedata.ElectionObject, messagedata.VoteActionCastVote).Scan(&state) @@ -891,8 +891,8 @@ func (s *SQLite) getElectionState(electionPath string) (string, error) { } func (s *SQLite) IsElectionStartedOrEnded(electionPath string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() state, err := s.getElectionState(electionPath) if err != nil { @@ -903,8 +903,8 @@ func (s *SQLite) IsElectionStartedOrEnded(electionPath string) (bool, error) { } func (s *SQLite) IsElectionStarted(electionPath string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() state, err := s.getElectionState(electionPath) if err != nil { @@ -914,8 +914,8 @@ func (s *SQLite) IsElectionStarted(electionPath string) (bool, error) { } func (s *SQLite) IsElectionEnded(electionPath string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() state, err := s.getElectionState(electionPath) if err != nil { @@ -925,8 +925,8 @@ func (s *SQLite) IsElectionEnded(electionPath string) (bool, error) { } func (s *SQLite) GetElectionCreationTime(electionPath string) (int64, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var creationTime int64 err := s.database.QueryRow(selectElectionCreationTime, electionPath, messagedata.ElectionObject, messagedata.ElectionActionSetup).Scan(&creationTime) @@ -937,8 +937,8 @@ func (s *SQLite) GetElectionCreationTime(electionPath string) (int64, error) { } func (s *SQLite) GetElectionType(electionPath string) (string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var electionType string err := s.database.QueryRow(selectElectionType, electionPath, messagedata.ElectionObject, messagedata.ElectionActionSetup).Scan(&electionType) @@ -949,8 +949,8 @@ func (s *SQLite) GetElectionType(electionPath string) (string, error) { } func (s *SQLite) GetElectionAttendees(electionPath string) (map[string]struct{}, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var rollCallCloseBytes []byte err := s.database.QueryRow(selectElectionAttendees, @@ -978,8 +978,8 @@ func (s *SQLite) GetElectionAttendees(electionPath string) (map[string]struct{}, } func (s *SQLite) getElectionSetup(electionPath string, tx *sql.Tx) (messagedata.ElectionSetup, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var electionSetupBytes []byte err := tx.QueryRow(selectElectionSetup, electionPath, messagedata.ElectionObject, messagedata.ElectionActionSetup).Scan(&electionSetupBytes) @@ -997,8 +997,8 @@ func (s *SQLite) getElectionSetup(electionPath string, tx *sql.Tx) (messagedata. } func (s *SQLite) GetElectionQuestions(electionPath string) (map[string]types.Question, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() tx, err := s.database.Begin() if err != nil { @@ -1026,8 +1026,8 @@ func (s *SQLite) GetElectionQuestions(electionPath string) (map[string]types.Que } func (s *SQLite) GetElectionQuestionsWithValidVotes(electionPath string) (map[string]types.Question, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() tx, err := s.database.Begin() if err != nil { @@ -1219,8 +1219,8 @@ func (s *SQLite) StoreChirpMessages(channel, generalChannel string, msg, general //====================================================================================================================== func (s *SQLite) IsAttendee(laoPath, poptoken string) (bool, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var rollCallCloseBytes []byte err := s.database.QueryRow(selectLastRollCallClose, laoPath, messagedata.RollCallObject, messagedata.RollCallActionClose).Scan(&rollCallCloseBytes) @@ -1244,8 +1244,8 @@ func (s *SQLite) IsAttendee(laoPath, poptoken string) (bool, error) { } func (s *SQLite) GetReactionSender(messageID string) (string, error) { - dbLock.RLock() - defer dbLock.RUnlock() + dbLock.Lock() + defer dbLock.Unlock() var sender string var object string @@ -1328,6 +1328,7 @@ func (s *SQLite) GetFederationExpect(senderPk string, remotePk string, challenge if err != nil { return messagedata.FederationExpect{}, err } + defer rows.Close() // iterate over all FederationExpect sent from the given sender pk, // and search the one matching the given FederationChallenge @@ -1368,6 +1369,7 @@ func (s *SQLite) GetFederationInit(senderPk string, remotePk string, challenge m if err != nil { return messagedata.FederationInit{}, err } + defer rows.Close() // iterate over all FederationInit sent from the given sender pk, // and search the one matching the given FederationChallenge From 4c5a2381b1de2210d3ca54738b7f8f997dbc736f Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Wed, 22 May 2024 19:37:54 +0200 Subject: [PATCH 44/48] fix election deadlock --- be1-go/internal/popserver/database/sqlite/sqlite.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite.go b/be1-go/internal/popserver/database/sqlite/sqlite.go index 90b8c92555..5e55b91e03 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite.go @@ -879,9 +879,6 @@ func (s *SQLite) GetElectionSecretKey(electionPath string) (kyber.Scalar, error) } func (s *SQLite) getElectionState(electionPath string) (string, error) { - dbLock.Lock() - defer dbLock.Unlock() - var state string err := s.database.QueryRow(selectLastElectionMessage, electionPath, messagedata.ElectionObject, messagedata.VoteActionCastVote).Scan(&state) if err != nil && !errors.Is(err, sql.ErrNoRows) { @@ -978,9 +975,6 @@ func (s *SQLite) GetElectionAttendees(electionPath string) (map[string]struct{}, } func (s *SQLite) getElectionSetup(electionPath string, tx *sql.Tx) (messagedata.ElectionSetup, error) { - dbLock.Lock() - defer dbLock.Unlock() - var electionSetupBytes []byte err := tx.QueryRow(selectElectionSetup, electionPath, messagedata.ElectionObject, messagedata.ElectionActionSetup).Scan(&electionSetupBytes) if err != nil { From 99cc78ea7f7218dbd07e7a48d826088e8e77031b Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Thu, 23 May 2024 11:30:17 +0200 Subject: [PATCH 45/48] remove old code --- .../authentication/authentication_test.go | 4 - be1-go/channel/channel.go | 1 - be1-go/channel/chirp/chirp_test.go | 4 - be1-go/channel/coin/coin_test.go | 4 - be1-go/channel/consensus/consensus_test.go | 4 - be1-go/channel/election/election_test.go | 4 - be1-go/channel/federation/federation.go | 626 ----------------- be1-go/channel/federation/federation_test.go | 648 ------------------ .../generalChirping/generalChirping_test.go | 4 - be1-go/channel/lao/lao.go | 12 +- be1-go/channel/lao/lao_test.go | 4 - be1-go/channel/reaction/reaction_test.go | 4 - be1-go/hub/standard_hub/standard_hub.go | 22 - be1-go/hub/standard_hub/standard_hub_test.go | 71 -- 14 files changed, 1 insertion(+), 1411 deletions(-) delete mode 100644 be1-go/channel/federation/federation.go delete mode 100644 be1-go/channel/federation/federation_test.go diff --git a/be1-go/channel/authentication/authentication_test.go b/be1-go/channel/authentication/authentication_test.go index ef66f1e852..7f8f711996 100644 --- a/be1-go/channel/authentication/authentication_test.go +++ b/be1-go/channel/authentication/authentication_test.go @@ -303,10 +303,6 @@ func (h *fakeHub) SendAndHandleMessage(_ method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, diff --git a/be1-go/channel/channel.go b/be1-go/channel/channel.go index 08fcbdb5bb..73ca9423a8 100644 --- a/be1-go/channel/channel.go +++ b/be1-go/channel/channel.go @@ -102,7 +102,6 @@ type HubFunctionalities interface { NotifyWitnessMessage(messageId string, publicKey string, signature string) GetClientServerAddress() string GetPeersInfo() []method.GreetServerParams - ConnectToServerAsClient(serverAddress string) (socket.Socket, error) } // Broadcastable defines a channel that can broadcast diff --git a/be1-go/channel/chirp/chirp_test.go b/be1-go/channel/chirp/chirp_test.go index 91e103a3c7..667fc17c7a 100644 --- a/be1-go/channel/chirp/chirp_test.go +++ b/be1-go/channel/chirp/chirp_test.go @@ -644,10 +644,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/coin/coin_test.go b/be1-go/channel/coin/coin_test.go index 359aa6788d..8f7ff8d5ae 100644 --- a/be1-go/channel/coin/coin_test.go +++ b/be1-go/channel/coin/coin_test.go @@ -829,10 +829,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/consensus/consensus_test.go b/be1-go/channel/consensus/consensus_test.go index 9dd5f56def..75cc1e8a43 100644 --- a/be1-go/channel/consensus/consensus_test.go +++ b/be1-go/channel/consensus/consensus_test.go @@ -2042,10 +2042,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - func (h *fakeHub) NotifyNewChannel(channelID string, channel channel.Channel, socket socket.Socket) {} // fakeSocket is a fake implementation of a socket diff --git a/be1-go/channel/election/election_test.go b/be1-go/channel/election/election_test.go index fcfed5717d..32263115b3 100644 --- a/be1-go/channel/election/election_test.go +++ b/be1-go/channel/election/election_test.go @@ -777,10 +777,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/federation/federation.go b/be1-go/channel/federation/federation.go deleted file mode 100644 index be0de6be22..0000000000 --- a/be1-go/channel/federation/federation.go +++ /dev/null @@ -1,626 +0,0 @@ -package federation - -import ( - "crypto/rand" - "encoding/base64" - "encoding/hex" - "encoding/json" - "fmt" - "popstellar/channel" - "popstellar/channel/registry" - "popstellar/crypto" - "popstellar/inbox" - jsonrpc "popstellar/message" - "popstellar/message/answer" - "popstellar/message/messagedata" - "popstellar/message/query" - "popstellar/message/query/method" - "popstellar/message/query/method/message" - "popstellar/network/socket" - "popstellar/validation" - "strconv" - "sync" - "time" - - "github.com/rs/zerolog" - "go.dedis.ch/kyber/v3/sign/schnorr" - "golang.org/x/xerrors" -) - -const ( - msgID = "msg id" - invalidStateError = "Invalid state %v for message %v" -) - -type State int - -const ( - None State = iota + 1 - ExpectConnect - Initiating - WaitResult - Connected -) - -type remoteOrganization struct { - laoId string - fedChannel string - organizerPk string - - // store the pop tokens of the other lao - // popTokens map[string]struct{} - - challenge messagedata.FederationChallenge - challengeMsg message.Message - socket socket.Socket - - state State - sync.Mutex -} - -// Channel is used to handle federation messages. -type Channel struct { - sockets channel.Sockets - inbox *inbox.Inbox - channelID string - hub channel.HubFunctionalities - log zerolog.Logger - registry registry.MessageRegistry - - localOrganizerPk string - - // map remoteOrganizerPk -> remoteOrganization - remoteOrganizations map[string]*remoteOrganization - - // list of challenge requested but not used yet - challenges map[messagedata.FederationChallenge]struct{} - - sync.Mutex -} - -// NewChannel returns a new initialized federation channel -func NewChannel(channelID string, hub channel.HubFunctionalities, - log zerolog.Logger, organizerPk string) channel.Channel { - box := inbox.NewInbox(channelID) - log = log.With().Str("channel", "federation").Logger() - - newChannel := &Channel{ - sockets: channel.NewSockets(), - inbox: box, - channelID: channelID, - hub: hub, - log: log, - localOrganizerPk: organizerPk, - remoteOrganizations: make(map[string]*remoteOrganization), - challenges: make(map[messagedata.FederationChallenge]struct{}), - } - - newChannel.registry = newChannel.NewFederationRegistry() - - return newChannel -} - -// Subscribe is used to handle a subscribe message from the client. -func (c *Channel) Subscribe(socket socket.Socket, msg method.Subscribe) error { - c.log.Info().Str(msgID, strconv.Itoa(msg.ID)).Msg("received a subscribe") - c.sockets.Upsert(socket) - - return nil -} - -// Unsubscribe is used to handle an unsubscribe message. -func (c *Channel) Unsubscribe(socketID string, msg method.Unsubscribe) error { - c.log.Info().Str(msgID, strconv.Itoa(msg.ID)).Msg("received an unsubscribe") - - ok := c.sockets.Delete(socketID) - if !ok { - return answer.NewError(-2, "client is not subscribed to this channel") - } - - return nil -} - -// Publish is used to handle publish messages in the federation channel. -func (c *Channel) Publish(publish method.Publish, socket socket.Socket) error { - c.log.Info(). - Str(msgID, strconv.Itoa(publish.ID)). - Msg("received a publish") - - err := c.verifyMessage(publish.Params.Message) - if err != nil { - return xerrors.Errorf("failed to verify publish message: %v", err) - } - - err = c.handleMessage(publish.Params.Message, socket) - if err != nil { - return xerrors.Errorf("failed to handle a publish message: %v", err) - } - - return nil -} - -// Catchup is used to handle a catchup message. -func (c *Channel) Catchup(catchup method.Catchup) []message.Message { - c.log.Info().Str(msgID, strconv.Itoa(catchup.ID)).Msg("received a catchup") - - return c.inbox.GetSortedMessages() -} - -// Broadcast is used to handle a broadcast message. -func (c *Channel) Broadcast(broadcast method.Broadcast, socket socket.Socket) error { - return answer.NewInvalidActionError("broadcast is not supported") -} - -// NewFederationRegistry creates a new registry for the federation channel -func (c *Channel) NewFederationRegistry() registry.MessageRegistry { - fedRegistry := registry.NewMessageRegistry() - - fedRegistry.Register(messagedata.FederationChallengeRequest{}, c.processChallengeRequest) - fedRegistry.Register(messagedata.FederationExpect{}, c.processFederationExpect) - fedRegistry.Register(messagedata.FederationInit{}, c.processFederationInit) - fedRegistry.Register(messagedata.FederationChallenge{}, c.processFederationChallenge) - fedRegistry.Register(messagedata.FederationResult{}, c.processFederationResult) - - return fedRegistry -} - -func (c *Channel) handleMessage(msg message.Message, - socket socket.Socket) error { - err := c.registry.Process(msg, socket) - if err != nil { - return xerrors.Errorf("failed to process message: %v", err) - } - - return nil -} - -// verifyMessage checks if a message in a Publish or Broadcast method is valid -func (c *Channel) verifyMessage(msg message.Message) error { - jsonData, err := base64.URLEncoding.DecodeString(msg.Data) - if err != nil { - return xerrors.Errorf("failed to decode message data: %v", err) - } - - // Verify the data - err = c.hub.GetSchemaValidator().VerifyJSON(jsonData, validation.Data) - if err != nil { - return xerrors.Errorf("failed to verify json schema: %w", err) - } - - // Check if the message already exists - if _, ok := c.inbox.GetMessage(msg.MessageID); ok { - return answer.NewError(-3, "message already exists") - } - - return nil -} - -func (c *Channel) processFederationInit(msg message.Message, - msgData interface{}, _ socket.Socket) error { - - _, ok := msgData.(*messagedata.FederationInit) - if !ok { - return xerrors.Errorf("message %v is not a federation#init message", - msgData) - } - - // check if it is from the local organizer - if c.localOrganizerPk != msg.Sender { - return answer.NewAccessDeniedError( - "Only local organizer is allowed to send federation#init") - } - - var federationInit messagedata.FederationInit - - err := msg.UnmarshalData(&federationInit) - if err != nil { - return xerrors.Errorf("failed to unmarshal FederationInit data: %v", err) - } - - var federationChallenge messagedata.FederationChallenge - err = federationInit.ChallengeMsg.UnmarshalData(&federationChallenge) - if err != nil { - return xerrors.Errorf("failed to unmarshal FederationChallenge data: %v", err) - } - - remoteOrg := c.getRemoteOrganization(federationInit.PublicKey) - remoteOrg.Lock() - defer remoteOrg.Unlock() - - if remoteOrg.state != None { - return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) - } - - remoteOrg.state = Initiating - remoteOrg.organizerPk = federationInit.PublicKey - remoteOrg.laoId = federationInit.LaoId - remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationInit.LaoId) - remoteOrg.challenge = federationChallenge - - remoteOrg.socket, err = c.hub.ConnectToServerAsClient(federationInit.ServerAddress) - if err != nil { - remoteOrg.state = None - return answer.NewInternalServerError( - "failed to connect to server %v: %v", - federationInit.ServerAddress, err) - } - - // send the challenge to the other server - challengePublish := 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: remoteOrg.fedChannel, - Message: federationInit.ChallengeMsg, - }, - } - - buf, err := json.Marshal(challengePublish) - if err != nil { - remoteOrg.state = None - return xerrors.Errorf("failed to marshal challenge: %v", err) - } - - remoteOrg.socket.Send(buf) - remoteOrg.state = WaitResult - - return nil -} - -func (c *Channel) processFederationExpect(msg message.Message, - msgData interface{}, _ socket.Socket) error { - - _, ok := msgData.(*messagedata.FederationExpect) - if !ok { - return xerrors.Errorf("message %v is not a federation#expect message", - msgData) - } - - // check if it is from the local organizer - if c.localOrganizerPk != msg.Sender { - return answer.NewAccessDeniedError( - "Only local organizer is allowed to send federation#expect") - } - - var federationExpect messagedata.FederationExpect - - err := msg.UnmarshalData(&federationExpect) - if err != nil { - return xerrors.Errorf("failed to unmarshal federationExpect data: %v", err) - } - remoteOrg := c.getRemoteOrganization(federationExpect.PublicKey) - remoteOrg.Lock() - defer remoteOrg.Unlock() - - if remoteOrg.state != None { - return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) - } - var federationChallenge messagedata.FederationChallenge - err = federationExpect.ChallengeMsg.UnmarshalData(&federationChallenge) - if err != nil { - return xerrors.Errorf("failed to unmarshal federationChallenge data: %v", err) - } - - c.Lock() - _, ok = c.challenges[federationChallenge] - // always remove the challenge, if present, to avoid challenge reuse - delete(c.challenges, federationChallenge) - c.Unlock() - - if !ok { - return answer.NewAccessDeniedError("Invalid challenge %v", - federationChallenge) - } - - remoteOrg.state = ExpectConnect - remoteOrg.challenge = federationChallenge - remoteOrg.organizerPk = federationExpect.PublicKey - remoteOrg.laoId = federationExpect.LaoId - remoteOrg.fedChannel = fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) - remoteOrg.challengeMsg = federationExpect.ChallengeMsg - - return nil -} - -func (c *Channel) processFederationChallenge(msg message.Message, - msgData interface{}, s socket.Socket) error { - - _, ok := msgData.(*messagedata.FederationChallenge) - if !ok { - return xerrors.Errorf( - "message %v is not a federation#challenge message", msgData) - } - - var federationChallenge messagedata.FederationChallenge - - err := msg.UnmarshalData(&federationChallenge) - if err != nil { - return xerrors.Errorf( - "failed to unmarshal federationChallenge data: %v", err) - } - - // If not present, no FederationExpect was received for this organizer pk - remoteOrg, ok := c.remoteOrganizations[msg.Sender] - if !ok { - return answer.NewAccessDeniedError("Unexpected challenge") - } - remoteOrg.Lock() - defer remoteOrg.Unlock() - - // check if it is from the remote organizer - if remoteOrg.organizerPk != msg.Sender { - return answer.NewAccessDeniedError( - "Only remote organizer is allowed to send federation#challenge") - } - - if remoteOrg.state != ExpectConnect { - return answer.NewInternalServerError(invalidStateError, remoteOrg, msg) - } - - if remoteOrg.challenge.Value != federationChallenge.Value { - return answer.NewAccessDeniedError("Invalid challenge %v", - federationChallenge.Value) - } - - if remoteOrg.challenge.ValidUntil < time.Now().Unix() { - return answer.NewAccessDeniedError("This challenge has expired: %v", - federationChallenge) - } - - remoteOrg.state = WaitResult - remoteOrg.socket = s - - federationResultData := messagedata.FederationResult{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionResult, - Status: "success", - PublicKey: remoteOrg.organizerPk, - ChallengeMsg: remoteOrg.challengeMsg, - } - - dataBytes, err := json.Marshal(federationResultData) - if err != nil { - return xerrors.Errorf("failed to marshal federation result message data: %v", err) - } - - dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) - signatureBytes, err := c.hub.Sign(dataBytes) - if err != nil { - return xerrors.Errorf("failed to sign federation result message: %v", err) - } - signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) - - serverPubKey, err := c.hub.GetPubKeyServ().MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal public key of server: %v", err) - - } - - federationResultMsg := message.Message{ - Data: dataBase64, - Sender: base64.URLEncoding.EncodeToString(serverPubKey), - Signature: signatureBase64, - MessageID: messagedata.Hash(dataBase64, signatureBase64), - WitnessSignatures: []message.WitnessSignature{}, - } - - rpcMessage := 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: remoteOrg.fedChannel, - Message: federationResultMsg, - }, - } - buf, err := json.Marshal(&rpcMessage) - if err != nil { - return xerrors.Errorf("failed to marshal publish query: %v", err) - } - - remoteOrg.socket.Send(buf) - - return nil -} - -func (c *Channel) processChallengeRequest(msg message.Message, - msgData interface{}, s socket.Socket) error { - - _, ok := msgData.(*messagedata.FederationChallengeRequest) - if !ok { - return xerrors.Errorf( - "message %v is not a federation#challenge_request message", msgData) - } - - // check if it is from the local organizer - if c.localOrganizerPk != msg.Sender { - return answer.NewAccessDeniedError( - "Only local organizer is allowed to send federation#challenge_request") - } - - var federationChallengeRequest messagedata.FederationChallengeRequest - - err := msg.UnmarshalData(&federationChallengeRequest) - if err != nil { - return xerrors.Errorf( - "failed to unmarshal federationChallengeRequest data: %v", err) - } - - randomBytes := make([]byte, 32) - _, err = rand.Read(randomBytes) - if err != nil { - return answer.NewInternalServerError("Failed to generate random bytes: %v", err) - } - challengeValue := hex.EncodeToString(randomBytes) - expirationTime := time.Now().Add(time.Minute * 5).Unix() - federationChallenge := messagedata.FederationChallenge{ - Object: messagedata.FederationObject, - Action: messagedata.FederationActionChallenge, - Value: challengeValue, - ValidUntil: expirationTime, - } - - c.Lock() - c.challenges[federationChallenge] = struct{}{} - c.Unlock() - - challengeData, err := json.Marshal(federationChallenge) - if err != nil { - return xerrors.Errorf( - "failed to marshal federationChallenge data: %v", err) - } - data := base64.URLEncoding.EncodeToString(challengeData) - - senderBytes, err := c.hub.GetPubKeyServ().MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal server public key: %v", err) - } - sender := base64.URLEncoding.EncodeToString(senderBytes) - - signatureBytes, err := c.hub.Sign(challengeData) - if err != nil { - return xerrors.Errorf("failed to sign message: %v", err) - } - signature := base64.URLEncoding.EncodeToString(signatureBytes) - - challengeMsg := message.Message{ - Data: data, - Sender: sender, - Signature: signature, - MessageID: messagedata.Hash(data, signature), - WitnessSignatures: []message.WitnessSignature{}, - } - - rpcMessage := method.Broadcast{ - Base: query.Base{ - JSONRPCBase: jsonrpc.JSONRPCBase{ - JSONRPC: "2.0", - }, - Method: "broadcast", - }, - Params: struct { - Channel string `json:"channel"` - Message message.Message `json:"message"` - }{ - c.channelID, - challengeMsg, - }, - } - - buf, err := json.Marshal(&rpcMessage) - if err != nil { - return xerrors.Errorf("failed to marshal broadcast query: %v", err) - } - - // send back the challenge directly to the organizer only - s.Send(buf) - - return nil -} - -func (c *Channel) processFederationResult(msg message.Message, - msgData interface{}, s socket.Socket) error { - _, ok := msgData.(*messagedata.FederationResult) - if !ok { - return xerrors.Errorf("message %v is not a federation#result message", - msgData) - } - - var federationResult messagedata.FederationResult - - err := msg.UnmarshalData(&federationResult) - if err != nil { - return xerrors.Errorf("failed to unmarshal FederationResult data: %v", err) - } - - if federationResult.Status != "success" { - if len(federationResult.Reason) > 0 { - return xerrors.Errorf("failed to establish federated connection: %v", federationResult.Reason) - } - return xerrors.Errorf("failed to establish federated connection") - } - - var federationChallenge messagedata.FederationChallenge - err = federationResult.ChallengeMsg.UnmarshalData(&federationChallenge) - if err != nil { - return xerrors.Errorf("failed to unmarshal challenge from FederationResult data: %v", err) - } - - challengeDataBytes, err := base64.URLEncoding.DecodeString(federationResult.ChallengeMsg.Data) - if err != nil { - return xerrors.Errorf("failed to decode challenge data in FederationResult: %v", err) - - } - - challengeSignatureBytes, err := base64.URLEncoding.DecodeString(federationResult.ChallengeMsg.Signature) - if err != nil { - return xerrors.Errorf("failed to decode challenge signature in FederationResult: %v", err) - - } - - remoteOrg := c.getRemoteOrganization(federationResult.ChallengeMsg.Sender) - remoteOrg.Lock() - defer remoteOrg.Unlock() - - if remoteOrg.state != WaitResult { - return answer.NewInternalServerError(invalidStateError, remoteOrg.state, msg) - - } - - pkBytes, err := base64.URLEncoding.DecodeString(remoteOrg.organizerPk) - if err != nil { - return xerrors.Errorf("failed to decode remote organizers public key: %v", err) - - } - - remotePk := crypto.Suite.Point() - - err = remotePk.UnmarshalBinary(pkBytes) - if err != nil { - return xerrors.Errorf("failed to decode remote organizers public key: %v", err) - - } - err = schnorr.Verify(crypto.Suite, remotePk, challengeDataBytes, challengeSignatureBytes) - if err != nil { - return xerrors.Errorf("failed to verify signature on challenge in FederationResult message: %v", err) - - } - - if c.localOrganizerPk != federationResult.PublicKey { - return xerrors.Errorf("invalid public key contained in FederationResult message") - } - - remoteOrg.state = Connected - - return nil -} - -// getRemoteOrganization get the remoteOrganization for the given organizerPk -// or return a new empty one. -func (c *Channel) getRemoteOrganization(organizerPk string) *remoteOrganization { - c.Lock() - defer c.Unlock() - - org, ok := c.remoteOrganizations[organizerPk] - if !ok { - org = &remoteOrganization{state: None} - c.remoteOrganizations[organizerPk] = org - } - - return org -} diff --git a/be1-go/channel/federation/federation_test.go b/be1-go/channel/federation/federation_test.go deleted file mode 100644 index c275306e12..0000000000 --- a/be1-go/channel/federation/federation_test.go +++ /dev/null @@ -1,648 +0,0 @@ -package federation - -//import ( -// "crypto/rand" -// "encoding/base64" -// "encoding/hex" -// "encoding/json" -// "io" -// "os" -// "path/filepath" -// "popstellar/channel" -// "popstellar/crypto" -// "popstellar/inbox" -// jsonrpc "popstellar/message" -// "popstellar/message/messagedata" -// "popstellar/message/query" -// "popstellar/message/query/method" -// "popstellar/message/query/method/message" -// "popstellar/network/socket" -// "popstellar/validation" -// "sync" -// "testing" -// "time" -// -// "github.com/rs/zerolog" -// "github.com/stretchr/testify/require" -// "go.dedis.ch/kyber/v3" -// "go.dedis.ch/kyber/v3/sign/schnorr" -// "golang.org/x/sync/semaphore" -// "golang.org/x/xerrors" -//) -// -//const ( -// relativeMsgDataExamplePath string = "../../../protocol/examples/messageData" -// relativeQueryExamplePath string = "../../../protocol/examples/query" -// localServerAddress = "ws://localhost:19008/client" -// remoteServerAddress = "ws://localhost:19009/client" -// localLaoId = "JYYWSfI2Au1lS7gGAZCUueY9PRMtu3ltKOFLjsdQs7s=" -// remoteLaoId = "ztPxKxfhToSloYcruyjfurcFD3sfDJ2B3o9l6v7Erho=" -// localFedChannel = "/root/" + localLaoId + "/federation" -// remoteFedChannel = "/root/" + remoteLaoId + "/federation" -//) -// -//// TestChannel_FederationRequestChallenge tests that a FederationChallenge is -//// received after a valid FederationChallengeRequest is published by the -//// organizer -//func Test_FederationRequestChallenge(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// requestData := messagedata.FederationChallengeRequest{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionChallengeRequest, -// Timestamp: time.Now().Unix(), -// } -// -// requestMsg := generateMessage(t, organizerKeypair, requestData) -// publishMsg := generatePublish(t, localFedChannel, requestMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -// -// require.NoError(t, socket.err) -// require.NotNil(t, socket.msg) -// -// var challengePublish method.Publish -// err = json.Unmarshal(socket.msg, &challengePublish) -// require.NoError(t, err) -// -// require.Equal(t, localFedChannel, challengePublish.Params.Channel) -// challengeMsg := challengePublish.Params.Message -// -// dataBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Data) -// require.NoError(t, err) -// signatureBytes, err := base64.URLEncoding.DecodeString(challengeMsg.Signature) -// require.NoError(t, err) -// -// err = schnorr.Verify(crypto.Suite, fakeHub.pubKeyServ, dataBytes, signatureBytes) -// require.NoError(t, err) -// -// var challenge messagedata.FederationChallenge -// err = challengeMsg.UnmarshalData(&challenge) -// require.NoError(t, err) -// -// require.Equal(t, messagedata.FederationObject, challenge.Object) -// require.Equal(t, messagedata.FederationActionChallenge, challenge.Action) -// require.Greater(t, challenge.ValidUntil, time.Now().Unix()) -// bytes, err := hex.DecodeString(challenge.Value) -// require.NoError(t, err) -// require.Len(t, bytes, 32) -//} -// -//func Test_FederationRequestChallenge_not_organizer(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// notOrganizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// requestData := messagedata.FederationChallengeRequest{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionChallengeRequest, -// Timestamp: time.Now().Unix(), -// } -// -// requestMsg := generateMessage(t, notOrganizerKeypair, requestData) -// publishMsg := generatePublish(t, localFedChannel, requestMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.Error(t, err) -// -// require.Nil(t, socket.msg) -//} -// -//func Test_FederationExpect(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// requestData := messagedata.FederationChallengeRequest{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionChallengeRequest, -// Timestamp: time.Now().Unix(), -// } -// -// requestMsg := generateMessage(t, organizerKeypair, requestData) -// publishMsg := generatePublish(t, localFedChannel, requestMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -// -// require.NoError(t, socket.err) -// require.NotNil(t, socket.msg) -// -// var challengePublish method.Publish -// err = json.Unmarshal(socket.msg, &challengePublish) -// require.NoError(t, err) -// -// require.Equal(t, localFedChannel, challengePublish.Params.Channel) -// challengeMsg := challengePublish.Params.Message -// -// var challenge messagedata.FederationChallenge -// err = challengeMsg.UnmarshalData(&challenge) -// require.NoError(t, err) -// -// remoteOrganizerKeypair := generateKeyPair(t) -// -// federationExpect := messagedata.FederationExpect{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionExpect, -// LaoId: remoteLaoId, -// ServerAddress: remoteServerAddress, -// PublicKey: remoteOrganizerKeypair.publicKey, -// ChallengeMsg: challengeMsg, -// } -// -// federationMsg := generateMessage(t, organizerKeypair, federationExpect) -// federationPublish := generatePublish(t, localFedChannel, federationMsg) -// -// err = fedChannel.Publish(federationPublish, socket) -// require.NoError(t, err) -// -// require.NoError(t, socket.err) -// require.NotNil(t, socket.msg) -//} -// -//func Test_FederationExpect_with_invalid_challenge(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// requestData := messagedata.FederationChallengeRequest{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionChallengeRequest, -// Timestamp: time.Now().Unix(), -// } -// -// requestMsg := generateMessage(t, organizerKeypair, requestData) -// publishMsg := generatePublish(t, localFedChannel, requestMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -// -// require.NoError(t, socket.err) -// require.NotNil(t, socket.msg) -// -// remoteOrganizerKeypair := generateKeyPair(t) -// -// valueBytes := make([]byte, 32) -// _, _ = rand.Read(valueBytes) -// -// federationExpect := messagedata.FederationExpect{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionExpect, -// LaoId: remoteLaoId, -// ServerAddress: remoteServerAddress, -// PublicKey: remoteOrganizerKeypair.publicKey, -// ChallengeMsg: message.Message{ -// Data: "aaaaaaaaaaa", -// Sender: organizerKeypair.publicKey, -// Signature: "bbbbbbbbbbb", -// MessageID: messagedata.Hash("aaaaaaaaaaa", "bbbbbbbbbbb"), -// WitnessSignatures: []message.WitnessSignature{}, -// }, -// } -// -// expectMsg := generateMessage(t, organizerKeypair, federationExpect) -// expectPublish := generatePublish(t, localFedChannel, expectMsg) -// -// err = fedChannel.Publish(expectPublish, socket) -// require.Error(t, err) -//} -// -//func Test_FederationChallenge_not_organizer(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// notOrganizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// requestData := messagedata.FederationChallengeRequest{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionChallengeRequest, -// Timestamp: time.Now().Unix(), -// } -// -// requestMsg := generateMessage(t, organizerKeypair, requestData) -// publishMsg := generatePublish(t, localFedChannel, requestMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -// -// require.NoError(t, socket.err) -// require.NotNil(t, socket.msg) -// -// var challengePublish method.Publish -// err = json.Unmarshal(socket.msg, &challengePublish) -// require.NoError(t, err) -// -// require.Equal(t, localFedChannel, challengePublish.Params.Channel) -// challengeMsg := challengePublish.Params.Message -// -// var challenge messagedata.FederationChallenge -// err = challengeMsg.UnmarshalData(&challenge) -// require.NoError(t, err) -// -// remoteOrganizerKeypair := generateKeyPair(t) -// -// federationExpect := messagedata.FederationExpect{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionExpect, -// LaoId: remoteLaoId, -// ServerAddress: remoteServerAddress, -// PublicKey: remoteOrganizerKeypair.publicKey, -// ChallengeMsg: challengeMsg, -// } -// -// federationMsg := generateMessage(t, notOrganizerKeypair, federationExpect) -// federationPublish := generatePublish(t, localFedChannel, federationMsg) -// -// err = fedChannel.Publish(federationPublish, socket) -// require.Error(t, err) -//} -// -//func Test_FederationInit(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// remoteOrganizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// fedChannel := NewChannel(localFedChannel, fakeHub, nolog, -// organizerKeypair.publicKey) -// -// challengeFile := filepath.Join(relativeMsgDataExamplePath, -// "federation_challenge", -// "federation_challenge.json") -// challengeBytes, err := os.ReadFile(challengeFile) -// require.NoError(t, err) -// -// challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) -// -// signatureBytes, err := schnorr.Sign(crypto.Suite, organizerKeypair.private, challengeBytes) -// require.NoError(t, err) -// -// signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) -// -// federationInitData := messagedata.FederationInit{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionInit, -// LaoId: remoteLaoId, -// ServerAddress: remoteServerAddress, -// PublicKey: remoteOrganizerKeypair.publicKey, -// ChallengeMsg: message.Message{ -// Data: challengeBase64, -// Sender: organizerKeypair.publicKey, -// Signature: signatureBase64, -// MessageID: messagedata.Hash(challengeBase64, signatureBase64), -// WitnessSignatures: []message.WitnessSignature{}, -// }, -// } -// -// initMsg := generateMessage(t, organizerKeypair, federationInitData) -// publishMsg := generatePublish(t, localFedChannel, initMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = fedChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -// -// require.NotNil(t, fakeHub.socketClient) -// require.Equal(t, remoteServerAddress, fakeHub.socketClient.ID()) -// -// // A Publish message containing the challenge message should be sent -// msgBytes := fakeHub.socketClient.msg -// require.NotNil(t, msgBytes) -// -// var publishMsg2 method.Publish -// err = json.Unmarshal(msgBytes, &publishMsg2) -// require.NoError(t, err) -// -// require.Equal(t, remoteFedChannel, publishMsg2.Params.Channel) -// require.Equal(t, challengeBase64, publishMsg2.Params.Message.Data) -// require.Equal(t, signatureBase64, publishMsg2.Params.Message.Signature) -// require.Equal(t, organizerKeypair.publicKey, publishMsg2.Params.Message.Sender) -// -// // should expect to receive back a FedererationResult -// require.NoError(t, socket.err) -// //require.NotNil(t, socket.msg) -// -//} -// -//func Test_FederationResult(t *testing.T) { -// organizerKeypair := generateKeyPair(t) -// remoteOrganizerKeypair := generateKeyPair(t) -// fakeHub, err := NewFakeHub("", organizerKeypair.public, nolog, nil) -// require.NoError(t, err) -// -// // dirty hack to manually create a channel, so we can then add the remote organizer pk without -// // having to go through the init process -// -// box := inbox.NewInbox(localFedChannel) -// -// remoteOrg := &remoteOrganization{ -// organizerPk: remoteOrganizerKeypair.publicKey, -// state: None, -// } -// remoteOrg.organizerPk = remoteOrganizerKeypair.publicKey -// remoteOrg.state = WaitResult -// -// var remoteOrgs = map[string]*remoteOrganization{ -// remoteOrganizerKeypair.publicKey: remoteOrg, -// } -// -// newChannel := &Channel{ -// sockets: channel.NewSockets(), -// inbox: box, -// channelID: localFedChannel, -// hub: fakeHub, -// log: nolog, -// localOrganizerPk: organizerKeypair.publicKey, -// remoteOrganizations: remoteOrgs, -// } -// -// newChannel.registry = newChannel.NewFederationRegistry() -// -// publicKeyBytes, err := organizerKeypair.public.MarshalBinary() -// require.NoError(t, err) -// //signedPublicKey, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, publicKeyBytes) -// require.NoError(t, err) -// -// challengeFile := filepath.Join(relativeMsgDataExamplePath, -// "federation_challenge", -// "federation_challenge.json") -// challengeBytes, err := os.ReadFile(challengeFile) -// challengeBase64 := base64.URLEncoding.EncodeToString(challengeBytes) -// require.NoError(t, err) -// -// //signedPublicKeyBase64 := base64.URLEncoding.EncodeToString(signedPublicKey) -// signedChallengeBytes, err := schnorr.Sign(crypto.Suite, remoteOrganizerKeypair.private, challengeBytes) -// require.NoError(t, err) -// signedChallengeBase64 := base64.URLEncoding.EncodeToString(signedChallengeBytes) -// -// federationResultData := messagedata.FederationResult{ -// Object: messagedata.FederationObject, -// Action: messagedata.FederationActionResult, -// Status: "success", -// PublicKey: base64.URLEncoding.EncodeToString(publicKeyBytes), -// ChallengeMsg: message.Message{ -// Data: challengeBase64, -// Sender: remoteOrganizerKeypair.publicKey, -// Signature: signedChallengeBase64, -// MessageID: messagedata.Hash(challengeBase64, signedChallengeBase64), -// WitnessSignatures: []message.WitnessSignature{}, -// }, -// } -// resultMsg := generateMessage(t, organizerKeypair, federationResultData) -// publishMsg := generatePublish(t, localFedChannel, resultMsg) -// socket := &fakeSocket{id: "sockSocket"} -// -// err = newChannel.Publish(publishMsg, socket) -// require.NoError(t, err) -//} -// -//// ----------------------------------------------------------------------------- -//// Utility functions -// -//type keypair struct { -// public kyber.Point -// publicKey string -// private kyber.Scalar -//} -// -//var nolog = zerolog.New(io.Discard) -//var suite = crypto.Suite -// -//func generateKeyPair(t *testing.T) keypair { -// secret := suite.Scalar().Pick(suite.RandomStream()) -// point := suite.Point().Mul(secret, nil) -// -// pkBuf, err := point.MarshalBinary() -// require.NoError(t, err) -// -// pkBase64 := base64.URLEncoding.EncodeToString(pkBuf) -// -// return keypair{point, pkBase64, secret} -//} -// -//type fakeHub struct { -// clientAddress string -// -// messageChan chan socket.IncomingMessage -// -// sync.RWMutex -// channelByID map[string]channel.Channel -// -// closedSockets chan string -// -// pubKeyOwner kyber.Point -// -// pubKeyServ kyber.Point -// secKeyServ kyber.Scalar -// -// schemaValidator *validation.SchemaValidator -// -// stop chan struct{} -// -// workers *semaphore.Weighted -// -// log zerolog.Logger -// -// laoFac channel.LaoFactory -// -// socketClient *fakeSocket -//} -// -//// NewFakeHub returns a fake Hub. -//func NewFakeHub(clientAddress string, publicOrg kyber.Point, log zerolog.Logger, laoFac channel.LaoFactory) (*fakeHub, error) { -// -// schemaValidator, err := validation.NewSchemaValidator(log) -// if err != nil { -// return nil, xerrors.Errorf("failed to create the schema validator: %v", err) -// } -// -// log = log.With().Str("role", "base hub").Logger() -// -// pubServ, secServ := generateKeys() -// -// hub := fakeHub{ -// clientAddress: clientAddress, -// messageChan: make(chan socket.IncomingMessage), -// channelByID: make(map[string]channel.Channel), -// closedSockets: make(chan string), -// pubKeyOwner: publicOrg, -// pubKeyServ: pubServ, -// secKeyServ: secServ, -// schemaValidator: schemaValidator, -// stop: make(chan struct{}), -// workers: semaphore.NewWeighted(10), -// log: log, -// laoFac: laoFac, -// } -// -// return &hub, nil -//} -// -//func generateKeys() (kyber.Point, kyber.Scalar) { -// secret := suite.Scalar().Pick(suite.RandomStream()) -// point := suite.Point().Mul(secret, nil) -// -// return point, secret -//} -// -//func (h *fakeHub) NotifyNewChannel(channeID string, channel channel.Channel, socket socket.Socket) { -// h.Lock() -// h.channelByID[channeID] = channel -// h.Unlock() -//} -// -//// GetPubKeyOwner implements channel.HubFunctionalities -//func (h *fakeHub) GetPubKeyOwner() kyber.Point { -// return h.pubKeyOwner -//} -// -//// GetPubKeyServ implements channel.HubFunctionalities -//func (h *fakeHub) GetPubKeyServ() kyber.Point { -// return h.pubKeyServ -//} -// -//// GetClientServerAddress implements channel.HubFunctionalities -//func (h *fakeHub) GetClientServerAddress() string { -// return h.clientAddress -//} -// -//// Sign implements channel.HubFunctionalities -//func (h *fakeHub) Sign(data []byte) ([]byte, error) { -// signatureBuf, err := schnorr.Sign(crypto.Suite, h.secKeyServ, data) -// if err != nil { -// return nil, xerrors.Errorf("failed to sign the data: %v", err) -// } -// -// return signatureBuf, nil -//} -// -//// NotifyWitnessMessage implements channel.HubFunctionalities -//func (h *fakeHub) NotifyWitnessMessage(messageId string, publicKey string, signature string) {} -// -//// GetPeersInfo implements channel.HubFunctionalities -//func (h *fakeHub) GetPeersInfo() []method.ServerInfo { -// return nil -//} -// -//func (h *fakeHub) GetSchemaValidator() validation.SchemaValidator { -// return *h.schemaValidator -//} -// -//func (h *fakeHub) GetServerNumber() int { -// return 0 -//} -// -//func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { -// return nil -//} -// -//func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { -// h.socketClient = &fakeSocket{id: serverAddress} -// -// return h.socketClient, nil -//} -// -//// fakeSocket is a fake implementation of a Socket -//// -//// - implements socket.Socket -//type fakeSocket struct { -// socket.Socket -// -// resultID int -// res []message.Message -// msg []byte -// -// err error -// -// // the sockSocket ID -// id string -//} -// -//// Send implements socket.Socket -//func (f *fakeSocket) Send(msg []byte) { -// f.msg = msg -//} -// -//// SendResult implements socket.Socket -//func (f *fakeSocket) SendResult(id int, res []message.Message, missingMsgs map[string][]message.Message) { -// f.resultID = id -// f.res = res -//} -// -//// SendError implements socket.Socket -//func (f *fakeSocket) SendError(id *int, err error) { -// if id != nil { -// f.resultID = *id -// } else { -// f.resultID = -1 -// } -// f.err = err -//} -// -//func (f *fakeSocket) ID() string { -// return f.id -//} -// -//func generatePublish(t *testing.T, channel string, msg message.Message) method. -// Publish { -// return 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: channel, -// Message: msg, -// }, -// } -//} -// -//func generateMessage(t *testing.T, keys keypair, -// data messagedata.MessageData) message.Message { -// -// dataBytes, err := json.Marshal(data) -// require.NoError(t, err) -// -// dataBase64 := base64.URLEncoding.EncodeToString(dataBytes) -// -// signatureBytes, err := schnorr.Sign(crypto.Suite, keys.private, dataBytes) -// require.NoError(t, err) -// signatureBase64 := base64.URLEncoding.EncodeToString(signatureBytes) -// -// return message.Message{ -// Data: dataBase64, -// Sender: keys.publicKey, -// Signature: signatureBase64, -// MessageID: messagedata.Hash(dataBase64, signatureBase64), -// WitnessSignatures: []message.WitnessSignature{}, -// } -//} diff --git a/be1-go/channel/generalChirping/generalChirping_test.go b/be1-go/channel/generalChirping/generalChirping_test.go index 929c3ee77a..dfaacd7daf 100644 --- a/be1-go/channel/generalChirping/generalChirping_test.go +++ b/be1-go/channel/generalChirping/generalChirping_test.go @@ -296,10 +296,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/channel/lao/lao.go b/be1-go/channel/lao/lao.go index f61cb6df43..35a13c475f 100644 --- a/be1-go/channel/lao/lao.go +++ b/be1-go/channel/lao/lao.go @@ -11,7 +11,6 @@ import ( "popstellar/channel/coin" "popstellar/channel/consensus" "popstellar/channel/election" - "popstellar/channel/federation" "popstellar/channel/generalChirping" "popstellar/channel/reaction" "popstellar/channel/registry" @@ -123,15 +122,6 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, msg message.Me consensusCh := consensus.NewChannel(consensusPath, hub, log, organizerPubKey) hub.NotifyNewChannel(consensusPath, consensusCh, socket) - federationPath := fmt.Sprintf("%s/federation", channelID) - organizerPkBytes, err := organizerPubKey.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("failed to encode organizer key: %v", err) - } - organizerPk := base64.URLEncoding.EncodeToString(organizerPkBytes) - federationCh := federation.NewChannel(federationPath, hub, log, organizerPk) - hub.NotifyNewChannel(federationPath, federationCh, socket) - newChannel := &Channel{ channelID: channelID, sockets: channel.NewSockets(), @@ -148,7 +138,7 @@ func NewChannel(channelID string, hub channel.HubFunctionalities, msg message.Me newChannel.registry = newChannel.NewLAORegistry() - err = newChannel.createAndSendLAOGreet() + err := newChannel.createAndSendLAOGreet() if err != nil { return nil, xerrors.Errorf("failed to send the greeting message: %v", err) } diff --git a/be1-go/channel/lao/lao_test.go b/be1-go/channel/lao/lao_test.go index b101958a77..6d5d37efff 100644 --- a/be1-go/channel/lao/lao_test.go +++ b/be1-go/channel/lao/lao_test.go @@ -836,10 +836,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a Socket // // - implements socket.Socket diff --git a/be1-go/channel/reaction/reaction_test.go b/be1-go/channel/reaction/reaction_test.go index 3ba3bc1f42..ba953c0e8f 100644 --- a/be1-go/channel/reaction/reaction_test.go +++ b/be1-go/channel/reaction/reaction_test.go @@ -688,10 +688,6 @@ func (h *fakeHub) SendAndHandleMessage(msg method.Broadcast) error { return nil } -func (h *fakeHub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - return nil, nil -} - // fakeSocket is a fake implementation of a socket // // - implements socket.Socket diff --git a/be1-go/hub/standard_hub/standard_hub.go b/be1-go/hub/standard_hub/standard_hub.go index e5548d4751..70ac150110 100644 --- a/be1-go/hub/standard_hub/standard_hub.go +++ b/be1-go/hub/standard_hub/standard_hub.go @@ -4,8 +4,6 @@ import ( "context" "encoding/base64" "encoding/json" - "github.com/gorilla/websocket" - "popstellar" "popstellar/channel" "popstellar/crypto" state "popstellar/hub/standard_hub/hub_state" @@ -600,26 +598,6 @@ func (h *Hub) GetPeersInfo() []method.GreetServerParams { return h.peers.GetAllPeersInfo() } -func (h *Hub) ConnectToServerAsClient(serverAddress string) (socket.Socket, error) { - ws, _, err := websocket.DefaultDialer.Dial(serverAddress, nil) - if err != nil { - return nil, err - } - - wg := &sync.WaitGroup{} - done := make(chan struct{}) - log := popstellar.Logger - - client := socket.NewClientSocket(h.Receiver(), h.OnSocketClose(), ws, wg, - done, log) - wg.Add(2) - - go client.WritePump() - go client.ReadPump() - - return client, nil -} - func generateKeys() (kyber.Point, kyber.Scalar) { secret := suite.Scalar().Pick(suite.RandomStream()) point := suite.Point().Mul(secret, nil) diff --git a/be1-go/hub/standard_hub/standard_hub_test.go b/be1-go/hub/standard_hub/standard_hub_test.go index 838a4c6069..d491d8a44b 100644 --- a/be1-go/hub/standard_hub/standard_hub_test.go +++ b/be1-go/hub/standard_hub/standard_hub_test.go @@ -2,14 +2,10 @@ package standard_hub import ( "bytes" - "crypto/rand" "encoding/base64" "encoding/json" "fmt" - "github.com/gorilla/websocket" "io" - "net" - "net/http" "os" "path/filepath" "popstellar/channel" @@ -1960,75 +1956,8 @@ func Test_Handle_GreetServer_Already_Received(t *testing.T) { require.NotEqual(t, serverInfo2, peersInfo[0]) } -// Test_ConnectToServerAsClient tests that a websocket connection to the given -// address is created, and that messages sent using this client are received -func Test_ConnectToServerAsClient(t *testing.T) { - listenAddress := "localhost:18004" - serverAddress := fmt.Sprintf("ws://%s/client", listenAddress) - keypair := generateKeyPair(t) - - hub, err := NewHub(keypair.public, "", "", nolog, nil) - require.NoError(t, err) - - msgCh := make(chan []byte, 1) - startCh := make(chan struct{}, 1) - startWsServer(t, listenAddress, msgCh, startCh) - - // wait that the ws server has started listening - <-startCh - - client, err := hub.ConnectToServerAsClient(serverAddress) - require.NoError(t, err) - require.IsType(t, &socket.ClientSocket{}, client) - - randomMsg := make([]byte, 128) - _, _ = rand.Read(randomMsg) - client.Send(randomMsg) - - select { - case m := <-msgCh: - assert.Equal(t, randomMsg, m) - case <-time.After(time.Second): - t.Errorf("Timed out waiting for expected message") - } -} - // ----------------------------------------------------------------------------- // Utility functions -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - -func websocketHandler(t *testing.T, msgCh chan []byte) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - require.NoError(t, err) - defer conn.Close() - - for { - mt, msg, err := conn.ReadMessage() - require.NoError(t, err) - - require.Equal(t, websocket.TextMessage, mt) - msgCh <- msg - } - } -} - -func startWsServer(t *testing.T, listenAddress string, msgCh chan []byte, startCh chan struct{}) { - http.HandleFunc("/client", websocketHandler(t, msgCh)) - go func() { - listener, err := net.Listen("tcp", listenAddress) - require.NoError(t, err) - - // signal that the ws server has started listening - startCh <- struct{}{} - - err = http.Serve(listener, nil) - require.NoError(t, err) - }() -} type keypair struct { public kyber.Point From 0d4a128ccc92129c78b10c9e76db382f5863aca5 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 27 May 2024 00:52:31 +0200 Subject: [PATCH 46/48] fix some code --- .../internal/popserver/handler/chirp_test.go | 3 +- .../internal/popserver/handler/coin_test.go | 3 +- .../popserver/handler/election_test.go | 3 +- .../internal/popserver/handler/federation.go | 28 +++++++++++++++---- be1-go/internal/popserver/handler/lao_test.go | 3 +- .../internal/popserver/handler/query_test.go | 18 ++++++++---- .../popserver/handler/reaction_test.go | 3 +- .../internal/popserver/handler/root_test.go | 3 +- be1-go/internal/popserver/state/state.go | 9 +++--- 9 files changed, 51 insertions(+), 22 deletions(-) diff --git a/be1-go/internal/popserver/handler/chirp_test.go b/be1-go/internal/popserver/handler/chirp_test.go index 494c62796f..97fb059931 100644 --- a/be1-go/internal/popserver/handler/chirp_test.go +++ b/be1-go/internal/popserver/handler/chirp_test.go @@ -21,8 +21,9 @@ func Test_handleChannelChirp(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) organizerBuf, err := base64.URLEncoding.DecodeString(ownerPubBuf64) require.NoError(t, err) diff --git a/be1-go/internal/popserver/handler/coin_test.go b/be1-go/internal/popserver/handler/coin_test.go index e332a89ccf..99dfc86ff1 100644 --- a/be1-go/internal/popserver/handler/coin_test.go +++ b/be1-go/internal/popserver/handler/coin_test.go @@ -31,8 +31,9 @@ func Test_handleChannelCoin(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) mockRepository := repository.NewMockRepository(t) database.SetDatabase(mockRepository) diff --git a/be1-go/internal/popserver/handler/election_test.go b/be1-go/internal/popserver/handler/election_test.go index 89bf46e852..4f56d46d85 100644 --- a/be1-go/internal/popserver/handler/election_test.go +++ b/be1-go/internal/popserver/handler/election_test.go @@ -27,6 +27,7 @@ func Test_handleChannelElection(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() ownerPubBuf, err := base64.URLEncoding.DecodeString(ownerPubBuf64) require.NoError(t, err) @@ -40,7 +41,7 @@ func Test_handleChannelElection(t *testing.T) { config.SetConfig(ownerPublicKey, serverPublicKey, serverSecretKey, "clientAddress", "serverAddress") - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) laoID := base64.URLEncoding.EncodeToString([]byte("laoID")) electionID := base64.URLEncoding.EncodeToString([]byte("electionID")) diff --git a/be1-go/internal/popserver/handler/federation.go b/be1-go/internal/popserver/handler/federation.go index 3482e9f2c0..5c00a07c89 100644 --- a/be1-go/internal/popserver/handler/federation.go +++ b/be1-go/internal/popserver/handler/federation.go @@ -23,6 +23,10 @@ import ( "time" ) +const ( + channelPattern = "/root/%s/federation" +) + func handleChannelFederation(channelPath string, msg message.Message) *answer.Error { object, action, errAnswer := verifyDataAndGetObjectAction(msg) if errAnswer != nil { @@ -164,7 +168,7 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationExpect") } - remoteChannel := fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + remoteChannel := fmt.Sprintf(channelPattern, federationExpect.LaoId) _ = state.AddChannel(remoteChannel) err = db.StoreMessageAndData(channelPath, msg) @@ -176,6 +180,9 @@ func handleExpect(msg message.Message, channelPath string) *answer.Error { return nil } +// handleInit checks that the message is from the local organizer and that +// it contains a valid challenge, then stores the msg, +// connect to the server and send the embedded challenge func handleInit(msg message.Message, channelPath string) *answer.Error { var federationInit messagedata.FederationInit errAnswer := msg.UnmarshalMsgData(&federationInit) @@ -203,7 +210,7 @@ func handleInit(msg message.Message, channelPath string) *answer.Error { errAnswer = challenge.Verify() if errAnswer != nil { - return errAnswer.Wrap("handleFederationExpect") + return errAnswer.Wrap("handleFederationInit") } db, errAnswer := database.GetFederationRepositoryInstance() @@ -219,13 +226,11 @@ func handleInit(msg message.Message, channelPath string) *answer.Error { remote, errAnswer := connectTo(federationInit.ServerAddress) if errAnswer != nil { - errAnswer = answer.NewInternalServerError( - "failed to connect to %s: %v", federationInit.ServerAddress, err) return errAnswer.Wrap("handleFederationInit") } //Force the remote server to be subscribed to /root//federation - remoteChannel := fmt.Sprintf("/root/%s/federation", federationInit.LaoId) + remoteChannel := fmt.Sprintf(channelPattern, federationInit.LaoId) _ = state.AddChannel(remoteChannel) errAnswer = state.Subscribe(remote, remoteChannel) if errAnswer != nil { @@ -315,8 +320,14 @@ func handleChallenge(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationChallenge") } + err = db.StoreMessageAndData(channelPath, resultMsg) + if err != nil { + errAnswer = answer.NewStoreDatabaseError(err.Error()) + return errAnswer.Wrap("handleFederationChallenge") + } + // publish the FederationResult to the other server - remoteChannel := fmt.Sprintf("/root/%s/federation", federationExpect.LaoId) + remoteChannel := fmt.Sprintf(channelPattern, federationExpect.LaoId) errAnswer = publishTo(resultMsg, remoteChannel) if errAnswer != nil { return errAnswer.Wrap("handleFederationChallenge") @@ -374,6 +385,11 @@ func handleResult(msg message.Message, channelPath string) *answer.Error { return errAnswer.Wrap("handleFederationResult") } + errAnswer = federationChallenge.Verify() + if errAnswer != nil { + return errAnswer.Wrap("handleFederationResult") + } + // try to get a matching FederationInit, if found then we know that // the local organizer was waiting this result _, err := db.GetFederationInit(organizerPk, diff --git a/be1-go/internal/popserver/handler/lao_test.go b/be1-go/internal/popserver/handler/lao_test.go index 9631e89fb1..7e3d23255f 100644 --- a/be1-go/internal/popserver/handler/lao_test.go +++ b/be1-go/internal/popserver/handler/lao_test.go @@ -24,8 +24,9 @@ func Test_handleChannelLao(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) ownerPubBuf, err := base64.URLEncoding.DecodeString(ownerPubBuf64) require.NoError(t, err) diff --git a/be1-go/internal/popserver/handler/query_test.go b/be1-go/internal/popserver/handler/query_test.go index 3e88abf10b..9d12702240 100644 --- a/be1-go/internal/popserver/handler/query_test.go +++ b/be1-go/internal/popserver/handler/query_test.go @@ -52,8 +52,9 @@ func Test_handleGreetServer(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) serverSecretKey := crypto.Suite.Scalar().Pick(crypto.Suite.RandomStream()) serverPublicKey := crypto.Suite.Point().Mul(serverSecretKey, nil) @@ -138,8 +139,9 @@ func Test_handleSubscribe(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) type input struct { name string @@ -226,8 +228,9 @@ func Test_handleUnsubscribe(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) type input struct { name string @@ -337,8 +340,9 @@ func Test_handleCatchUp(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) mockRepository := repository.NewMockRepository(t) database.SetDatabase(mockRepository) @@ -418,8 +422,9 @@ func Test_handleHeartbeat(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) mockRepository := repository.NewMockRepository(t) database.SetDatabase(mockRepository) @@ -549,8 +554,9 @@ func Test_handleGetMessagesByID(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) mockRepository := repository.NewMockRepository(t) database.SetDatabase(mockRepository) diff --git a/be1-go/internal/popserver/handler/reaction_test.go b/be1-go/internal/popserver/handler/reaction_test.go index 342b442aae..f9b54f7b66 100644 --- a/be1-go/internal/popserver/handler/reaction_test.go +++ b/be1-go/internal/popserver/handler/reaction_test.go @@ -20,8 +20,9 @@ func Test_handleChannelReaction(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) organizerBuf, err := base64.URLEncoding.DecodeString(ownerPubBuf64) require.NoError(t, err) diff --git a/be1-go/internal/popserver/handler/root_test.go b/be1-go/internal/popserver/handler/root_test.go index f518561e75..91710c74fb 100644 --- a/be1-go/internal/popserver/handler/root_test.go +++ b/be1-go/internal/popserver/handler/root_test.go @@ -38,8 +38,9 @@ func Test_handleChannelRoot(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() - state.SetState(subs, peers, queries) + state.SetState(subs, peers, queries, hubParams) organizerBuf, err := base64.URLEncoding.DecodeString(ownerPubBuf64) require.NoError(t, err) diff --git a/be1-go/internal/popserver/state/state.go b/be1-go/internal/popserver/state/state.go index 3c2bfd3415..8083e53945 100644 --- a/be1-go/internal/popserver/state/state.go +++ b/be1-go/internal/popserver/state/state.go @@ -62,11 +62,12 @@ func InitState(log *zerolog.Logger) { // ONLY FOR TEST PURPOSE // SetState is only here to be used to reset the state before each test -func SetState(subs Subscriber, peers Peerer, queries Querier) { +func SetState(subs Subscriber, peers Peerer, queries Querier, hubParams HubParameter) { instance = &state{ - subs: subs, - peers: peers, - queries: queries, + subs: subs, + peers: peers, + queries: queries, + hubParams: hubParams, } } From 400f5ccd68779868a7ae9bc35df7aa8361ccd900 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 27 May 2024 00:55:35 +0200 Subject: [PATCH 47/48] Add some tests --- .../popserver/handler/federation_test.go | 360 +++++++++++++++++- 1 file changed, 353 insertions(+), 7 deletions(-) diff --git a/be1-go/internal/popserver/handler/federation_test.go b/be1-go/internal/popserver/handler/federation_test.go index 3848d341cd..8ed0ca1264 100644 --- a/be1-go/internal/popserver/handler/federation_test.go +++ b/be1-go/internal/popserver/handler/federation_test.go @@ -5,8 +5,11 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/gorilla/websocket" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "net" + "net/http" "popstellar/internal/popserver/config" "popstellar/internal/popserver/database" "popstellar/internal/popserver/database/repository" @@ -14,6 +17,7 @@ import ( "popstellar/internal/popserver/state" "popstellar/internal/popserver/types" "popstellar/message/messagedata" + "popstellar/message/query" "popstellar/message/query/method" "popstellar/network/socket" "testing" @@ -29,6 +33,9 @@ func Test_handleChannelFederation(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() + + state.SetState(subs, peers, queries, hubParams) organizerPk, organizerSk := generateKeys() organizer2Pk, organizer2Sk := generateKeys() @@ -37,8 +44,6 @@ func Test_handleChannelFederation(t *testing.T) { config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") - state.SetState(subs, peers, queries) - organizerBuf, err := organizerPk.MarshalBinary() require.NoError(t, err) organizer := base64.URLEncoding.EncodeToString(organizerBuf) @@ -262,7 +267,43 @@ func Test_handleChannelFederation(t *testing.T) { contains: "failed to get federation expect", }) - require.NotEqual(t, notOrganizerPk, organizer2Sk) + // Test 16 Error when FederationResult challenge message is not a challenge + args = append(args, input{ + name: "Test 16", + channel: channelPath, + msg: generatortest.NewSuccessFederationResult(t, organizer2, + organizer, generatortest.NewFederationChallengeRequest(t, + organizer2, validUntil, organizer2Sk), organizer2Sk), + isError: true, + contains: "invalid message field", + }) + + // Test 17 Error when FederationResult PublicKey is not the organizerPk + args = append(args, input{ + name: "Test 17", + channel: channelPath, + msg: generatortest.NewSuccessFederationResult(t, organizer2, + notOrganizer, generatortest.NewFederationChallenge(t, organizer2, + value, validUntil, organizer2Sk), organizer2Sk), + isError: true, + contains: "invalid message field", + }) + + mockRepository.On("GetFederationInit", organizer, + organizer2, federationChallenge1, channelPath).Return(messagedata. + FederationInit{}, sql.ErrNoRows) + + // Test 18 Error when FederationResult is received without any + // matching FederationInit + args = append(args, input{ + name: "Test 18", + channel: channelPath, + msg: generatortest.NewSuccessFederationResult(t, organizer2, + organizer, generatortest.NewFederationChallenge(t, organizer2, + value, validUntil, organizer2Sk), organizer2Sk), + isError: true, + contains: "failed to get federation init", + }) for _, arg := range args { t.Run(arg.name, func(t *testing.T) { @@ -283,14 +324,15 @@ func Test_handleRequestChallenge(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() + + state.SetState(subs, peers, queries, hubParams) organizerPk, organizerSk := generateKeys() serverPk, serverSk := generateKeys() config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") - state.SetState(subs, peers, queries) - organizerBuf, err := organizerPk.MarshalBinary() require.NoError(t, err) organizer := base64.URLEncoding.EncodeToString(organizerBuf) @@ -339,6 +381,9 @@ func Test_handleFederationExpect(t *testing.T) { subs := types.NewSubscribers() queries := types.NewQueries(&noLog) peers := types.NewPeers() + hubParams := types.NewHubParams() + + state.SetState(subs, peers, queries, hubParams) organizerPk, organizerSk := generateKeys() organizer2Pk, _ := generateKeys() @@ -346,8 +391,6 @@ func Test_handleFederationExpect(t *testing.T) { config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") - state.SetState(subs, peers, queries) - organizerBuf, err := organizerPk.MarshalBinary() require.NoError(t, err) organizer := base64.URLEncoding.EncodeToString(organizerBuf) @@ -390,3 +433,306 @@ func Test_handleFederationExpect(t *testing.T) { errAnswer := handleExpect(federationExpect, channelPath) require.Nil(t, errAnswer) } + +func Test_handleFederationInit(t *testing.T) { + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + hubParams := types.NewHubParams() + + state.SetState(subs, peers, queries, hubParams) + + organizerPk, organizerSk := generateKeys() + organizer2Pk, _ := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + organizer2Buf, err := organizer2Pk.MarshalBinary() + require.NoError(t, err) + organizer2 := base64.URLEncoding.EncodeToString(organizer2Buf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoID2 := "OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMQ==" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + channelPath2 := fmt.Sprintf("/root/%s/federation", laoID2) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + challengeMsg := generatortest.NewFederationChallenge(t, organizer, value, + validUntil, organizerSk) + + initMsg := generatortest.NewFederationInit(t, organizer, laoID2, + serverAddressA, organizer2, challengeMsg, organizerSk) + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + mockRepository.On("StoreMessageAndData", channelPath, initMsg).Return(nil) + + serverBStarted := make(chan struct{}) + msgChan := make(chan []byte, 10) + mux := http.NewServeMux() + mux.HandleFunc("/client", websocketHandler(t, msgChan)) + serverB := &http.Server{Addr: "localhost:9801", Handler: mux} + + go websocketServer(t, serverB, serverBStarted) + <-serverBStarted + defer serverB.Close() + + errAnswer := handleInit(initMsg, channelPath) + require.Nil(t, errAnswer) + + var msgBytes []byte + select { + case msgBytes = <-msgChan: + case <-time.After(time.Second): + require.Fail(t, "Timed out waiting for expected message") + } + + var subMsg method.Subscribe + err = json.Unmarshal(msgBytes, &subMsg) + require.NoError(t, err) + require.Equal(t, query.MethodSubscribe, subMsg.Method) + require.Equal(t, method.SubscribeParams{Channel: channelPath}, subMsg.Params) + + select { + case msgBytes = <-msgChan: + case <-time.After(time.Second): + require.Fail(t, "Timed out waiting for expected message") + } + var publishMsg method.Publish + err = json.Unmarshal(msgBytes, &publishMsg) + require.NoError(t, err) + require.Equal(t, query.MethodPublish, publishMsg.Method) + require.Equal(t, channelPath2, publishMsg.Params.Channel) + require.Equal(t, challengeMsg, publishMsg.Params.Message) +} + +func Test_handleFederationChallenge(t *testing.T) { + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + hubParams := types.NewHubParams() + + state.SetState(subs, peers, queries, hubParams) + + organizerPk, organizerSk := generateKeys() + organizer2Pk, organizer2Sk := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + organizer2Buf, err := organizer2Pk.MarshalBinary() + require.NoError(t, err) + organizer2 := base64.URLEncoding.EncodeToString(organizer2Buf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoID2 := "OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMQ==" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + channelPath2 := fmt.Sprintf("/root/%s/federation", laoID2) + + errAnswer := subs.AddChannel(channelPath) + require.Nil(t, errAnswer) + errAnswer = subs.AddChannel(channelPath2) + require.Nil(t, errAnswer) + + fakeSocket1 := socket.FakeSocket{Id: "1"} + errAnswer = subs.Subscribe(channelPath, &fakeSocket1) + require.Nil(t, errAnswer) + + fakeSocket2 := socket.FakeSocket{Id: "2"} + errAnswer = subs.Subscribe(channelPath2, &fakeSocket2) + require.Nil(t, errAnswer) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeMsg := generatortest.NewFederationChallenge(t, organizer, value, + validUntil, organizerSk) + + challengeMsg2 := generatortest.NewFederationChallenge(t, organizer2, + value, validUntil, organizer2Sk) + + federationExpect := messagedata.FederationExpect{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionExpect, + LaoId: laoID2, + ServerAddress: serverAddressA, + PublicKey: organizer2, + ChallengeMsg: challengeMsg, + } + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + mockRepository.On("StoreMessageAndData", channelPath, + mock.AnythingOfType("message.Message")).Return(nil) + mockRepository.On("GetFederationExpect", organizer, organizer2, + challenge, channelPath).Return(federationExpect, nil) + mockRepository.On("RemoveChallenge", challenge).Return(nil) + mockRepository.On("GetServerKeys").Return(serverPk, serverSk, nil) + + errAnswer = handleChallenge(challengeMsg2, channelPath) + require.Nil(t, errAnswer) + + // The same federation result message should be received by both sockets + // on fakeSocket1, representing the organizer, it should be in a broadcast + require.NotNil(t, fakeSocket1.Msg) + var broadcastMsg method.Broadcast + err = json.Unmarshal(fakeSocket1.Msg, &broadcastMsg) + require.NoError(t, err) + require.Equal(t, query.MethodBroadcast, broadcastMsg.Method) + + // on fakeSocket2, representing the other server, it should in a publish + require.NotNil(t, fakeSocket2.Msg) + var publishMsg method.Publish + err = json.Unmarshal(fakeSocket2.Msg, &publishMsg) + require.NoError(t, err) + require.Equal(t, query.MethodPublish, publishMsg.Method) + require.Equal(t, broadcastMsg.Params.Message, publishMsg.Params.Message) + + var resultMsg messagedata.FederationResult + errAnswer = broadcastMsg.Params.Message.UnmarshalMsgData(&resultMsg) + require.Nil(t, errAnswer) + + // it should contain the challenge from organizer, not organizer2 + require.Equal(t, challengeMsg, resultMsg.ChallengeMsg) + require.Equal(t, "success", resultMsg.Status) + require.Empty(t, resultMsg.Reason) + require.Equal(t, organizer2, resultMsg.PublicKey) +} + +func Test_handleFederationResult(t *testing.T) { + mockRepository := repository.NewMockRepository(t) + database.SetDatabase(mockRepository) + + subs := types.NewSubscribers() + queries := types.NewQueries(&noLog) + peers := types.NewPeers() + hubParams := types.NewHubParams() + + organizerPk, organizerSk := generateKeys() + organizer2Pk, organizer2Sk := generateKeys() + serverPk, serverSk := generateKeys() + + config.SetConfig(organizerPk, serverPk, serverSk, "client", "server") + + state.SetState(subs, peers, queries, hubParams) + + organizerBuf, err := organizerPk.MarshalBinary() + require.NoError(t, err) + organizer := base64.URLEncoding.EncodeToString(organizerBuf) + + organizer2Buf, err := organizer2Pk.MarshalBinary() + require.NoError(t, err) + organizer2 := base64.URLEncoding.EncodeToString(organizer2Buf) + + laoID := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + laoPath := fmt.Sprintf("/root/%s", laoID) + channelPath := fmt.Sprintf("/root/%s/federation", laoID) + + errAnswer := subs.AddChannel(channelPath) + require.Nil(t, errAnswer) + + fakeSocket := socket.FakeSocket{Id: "1"} + errAnswer = subs.Subscribe(channelPath, &fakeSocket) + require.Nil(t, errAnswer) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeMsg := generatortest.NewFederationChallenge(t, organizer, value, + validUntil, organizerSk) + + challengeMsg2 := generatortest.NewFederationChallenge(t, organizer2, value, + validUntil, organizer2Sk) + + federationInit := messagedata.FederationInit{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionInit, + LaoId: laoID, + ServerAddress: serverAddressA, + PublicKey: organizer, + ChallengeMsg: challengeMsg, + } + + federationResultMsg := generatortest.NewSuccessFederationResult(t, + organizer2, organizer, challengeMsg2, organizer2Sk) + + mockRepository.On("GetOrganizerPubKey", laoPath).Return(organizerPk, nil) + mockRepository.On("StoreMessageAndData", channelPath, + federationResultMsg).Return(nil) + mockRepository.On("GetFederationInit", organizer, organizer2, challenge, + channelPath).Return(federationInit, nil) + + errAnswer = handleResult(federationResultMsg, channelPath) + require.Nil(t, errAnswer) + + require.NotNil(t, fakeSocket.Msg) + var broadcastMsg method.Broadcast + err = json.Unmarshal(fakeSocket.Msg, &broadcastMsg) + require.NoError(t, err) + + require.Equal(t, query.MethodBroadcast, broadcastMsg.Method) + require.Equal(t, federationResultMsg, broadcastMsg.Params.Message) +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +func websocketHandler(t *testing.T, msgCh chan []byte) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + require.NoError(t, err) + defer conn.Close() + + for { + mt, msg, err := conn.ReadMessage() + require.NoError(t, err) + + require.Equal(t, websocket.TextMessage, mt) + msgCh <- msg + } + } +} + +func websocketServer(t *testing.T, server *http.Server, started chan struct{}) { + l, err := net.Listen("tcp", server.Addr) + require.NoError(t, err) + + started <- struct{}{} + + err = server.Serve(l) + require.ErrorIs(t, http.ErrServerClosed, err) +} From 3a69958bba85bb8b04d4d6bf5076d430ef35b512 Mon Sep 17 00:00:00 2001 From: arnauds5 Date: Mon, 27 May 2024 14:50:05 +0200 Subject: [PATCH 48/48] Add some tests --- .../popserver/database/sqlite/sqlite_test.go | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/be1-go/internal/popserver/database/sqlite/sqlite_test.go b/be1-go/internal/popserver/database/sqlite/sqlite_test.go index c570d274ef..e23661c4ed 100644 --- a/be1-go/internal/popserver/database/sqlite/sqlite_test.go +++ b/be1-go/internal/popserver/database/sqlite/sqlite_test.go @@ -1,8 +1,10 @@ package sqlite import ( + "database/sql" "encoding/base64" "encoding/json" + "fmt" "github.com/stretchr/testify/require" "os" "path/filepath" @@ -13,6 +15,7 @@ import ( "popstellar/message/query/method/message" "sort" "testing" + "time" ) //====================================================================================================================== @@ -826,3 +829,136 @@ func Test_SQLite_GetReactionSender(t *testing.T) { require.NoError(t, err) require.Equal(t, "sender1", sender) } + +func Test_SQLite_IsChallengeValid(t *testing.T) { + lite, dir, err := newFakeSQLite(t) + require.NoError(t, err) + defer lite.Close() + defer os.RemoveAll(dir) + + sender := "qlDMnnFv_W4zovvD0pp4FFjCfJ78z_O7LqxBXGdO0lA=" + notSender := "7_9A6K6dbfN04GUwEaLCDNLcdnaTjmBVw1qWH_C9M3s=" + fedPath := "/root/lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=/federation" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeMsg := generatortest.NewFederationChallenge(t, sender, value, + validUntil, nil) + + err = lite.StoreMessageAndData(fedPath, challengeMsg) + require.NoError(t, err) + + err = lite.IsChallengeValid(sender, challenge, fedPath) + require.NoError(t, err) + + err = lite.IsChallengeValid(notSender, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) + + challenge.Value = "12345678" + err = lite.IsChallengeValid(sender, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) + + challenge.Value = value + challenge.ValidUntil = validUntil + 1 + err = lite.IsChallengeValid(sender, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) +} + +func Test_SQLite_GetFederationExpect(t *testing.T) { + lite, dir, err := newFakeSQLite(t) + require.NoError(t, err) + defer lite.Close() + defer os.RemoveAll(dir) + + organizer := "qlDMnnFv_W4zovvD0pp4FFjCfJ78z_O7LqxBXGdO0lA=" + organizer2 := "NzfRC3bUGHLy-I_iUbDXq9CAXcnnDdue9P2hqJHF6bk=" + + laoId := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + fedPath := fmt.Sprintf("/root/%s/federation", laoId) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeMsg := generatortest.NewFederationChallenge(t, organizer, value, + validUntil, nil) + + expectMsg := generatortest.NewFederationExpect(t, organizer, laoId, + serverAddressA, organizer2, challengeMsg, nil) + + _, err = lite.GetFederationExpect(organizer, organizer2, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) + + err = lite.StoreMessageAndData(fedPath, expectMsg) + require.NoError(t, err) + + expect, err := lite.GetFederationExpect(organizer, organizer2, challenge, fedPath) + require.NoError(t, err) + require.Equal(t, challengeMsg, expect.ChallengeMsg) + require.Equal(t, organizer2, expect.PublicKey) + require.Equal(t, serverAddressA, expect.ServerAddress) + require.Equal(t, laoId, expect.LaoId) + + _, err = lite.GetFederationExpect(organizer2, organizer, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) +} + +func Test_SQLite_GetFederationInit(t *testing.T) { + lite, dir, err := newFakeSQLite(t) + require.NoError(t, err) + defer lite.Close() + defer os.RemoveAll(dir) + + organizer := "qlDMnnFv_W4zovvD0pp4FFjCfJ78z_O7LqxBXGdO0lA=" + organizer2 := "NzfRC3bUGHLy-I_iUbDXq9CAXcnnDdue9P2hqJHF6bk=" + + laoId := "lsWUv1bKBQ0t1DqWZTFwb0nhLsP_EtfGoXHny4hsrwA=" + fedPath := fmt.Sprintf("/root/%s/federation", laoId) + + serverAddressA := "ws://localhost:9801/client" + value := "82eadde2a4ba832518b90bb93c8480ee1ae16a91d5efe9281e91e2ec11da03e4" + validUntil := time.Now().Add(5 * time.Minute).Unix() + + challenge := messagedata.FederationChallenge{ + Object: messagedata.FederationObject, + Action: messagedata.FederationActionChallenge, + Value: value, + ValidUntil: validUntil, + } + + challengeMsg := generatortest.NewFederationChallenge(t, organizer, value, + validUntil, nil) + + expectMsg := generatortest.NewFederationInit(t, organizer, laoId, + serverAddressA, organizer2, challengeMsg, nil) + + _, err = lite.GetFederationInit(organizer, organizer2, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) + + err = lite.StoreMessageAndData(fedPath, expectMsg) + require.NoError(t, err) + + expect, err := lite.GetFederationInit(organizer, organizer2, challenge, fedPath) + require.NoError(t, err) + require.Equal(t, challengeMsg, expect.ChallengeMsg) + require.Equal(t, organizer2, expect.PublicKey) + require.Equal(t, serverAddressA, expect.ServerAddress) + require.Equal(t, laoId, expect.LaoId) + + _, err = lite.GetFederationInit(organizer2, organizer, challenge, fedPath) + require.ErrorIs(t, err, sql.ErrNoRows) +}