diff --git a/broadcast/broadcastClient.go b/broadcast/broadcastClient.go index 6e1c7391cc6f97ed429cfd4164508444ac3eb0b3..b9daa8c115385fb899db1acb196d4ce0a424256d 100644 --- a/broadcast/broadcastClient.go +++ b/broadcast/broadcastClient.go @@ -24,6 +24,8 @@ type broadcastClient struct { rng *fastRNG.StreamGenerator } +type NewBroadcastChannelFunc func(channel crypto.Channel, net Client, rng *fastRNG.StreamGenerator) (Channel, error) + // NewBroadcastChannel creates a channel interface based on crypto.Channel, accepts net client connection & callback for received messages func NewBroadcastChannel(channel crypto.Channel, net Client, rng *fastRNG.StreamGenerator) (Channel, error) { bc := &broadcastClient{ diff --git a/broadcast/interface.go b/broadcast/interface.go index bd464945549ba67b4800dcef5c1f4f9d339d061d..2a3aa4be7f41bbfb2d311e94b590a66ef479a68b 100644 --- a/broadcast/interface.go +++ b/broadcast/interface.go @@ -13,7 +13,6 @@ import ( "gitlab.com/elixxir/client/cmix/message" "gitlab.com/elixxir/client/cmix/rounds" crypto "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/crypto/multicastRSA" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" @@ -41,6 +40,14 @@ type Channel interface { Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) + // BroadcastWithAssembler broadcasts a payload over a symmetric channel. With + // a payload assembled after the round is selected, allowing the round + // info to be included in the payload. + // Network must be healthy to send + // Requires a payload of size bc.MaxSymmetricPayloadSize() + BroadcastWithAssembler(assembler Assembler, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) + // BroadcastAsymmetric broadcasts an asymmetric payload to the channel. The payload size must be // equal to MaxPayloadSize & private key for channel must be passed in BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( @@ -54,13 +61,15 @@ type Channel interface { Stop() } +// Assembler is a function which allows a bre +type Assembler func(rid id.Round) (payload []byte, err error) + // Client contains the methods from cmix.Client that are required by // symmetricClient. type Client interface { GetMaxMessageLength() int - Send(recipient *id.ID, fingerprint format.Fingerprint, - service message.Service, payload, mac []byte, - cMixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) + SendWithAssembler(recipient *id.ID, assembler cmix.MessageAssembler, + cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) IsHealthy() bool AddIdentity(id *id.ID, validUntil time.Time, persistent bool) AddService(clientID *id.ID, newService message.Service, diff --git a/broadcast/symmetric.go b/broadcast/symmetric.go index 601c2bebec39f7c00c1e389a9f2d0d8ccc5122f3..295aaf4543f3d10221c14c4992a942d402287d09 100644 --- a/broadcast/symmetric.go +++ b/broadcast/symmetric.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "gitlab.com/elixxir/client/cmix" "gitlab.com/elixxir/client/cmix/message" + "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" ) @@ -38,32 +39,56 @@ func (bc *broadcastClient) maxSymmetricPayload() int { // Network must be healthy to send // Requires a payload of size bc.MaxSymmetricPayloadSize() func (bc *broadcastClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) ( + id.Round, ephemeral.Id, error) { + assemble := func(rid id.Round) ([]byte, error) { + return payload, nil + } + return bc.BroadcastWithAssembler(assemble, cMixParams) +} + +// BroadcastWithAssembler broadcasts a payload over a symmetric channel. With +// a payload assembled after the round is selected, allowing the round +// info to be included in the payload. +// Network must be healthy to send +// Requires a payload of size bc.MaxSymmetricPayloadSize() +func (bc *broadcastClient) BroadcastWithAssembler(assembler Assembler, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) { if !bc.net.IsHealthy() { return 0, ephemeral.Id{}, errors.New(errNetworkHealth) } - if len(payload) != bc.maxSymmetricPayload() { - return 0, ephemeral.Id{}, - errors.Errorf(errPayloadSize, len(payload), bc.maxSymmetricPayload()) - } + assemble := func(rid id.Round) (fp format.Fingerprint, + service message.Service, encryptedPayload, mac []byte, err error) { - // Encrypt payload - rng := bc.rng.GetStream() - encryptedPayload, mac, fp := bc.channel.EncryptSymmetric(payload, rng) - rng.Close() + //assemble the passed payload + payload, err := assembler(rid) + if err != nil { + return format.Fingerprint{}, message.Service{}, nil, nil, err + } - // Create service using symmetric broadcast service tag & channel reception ID - // Allows anybody with this info to listen for messages on this channel - service := message.Service{ - Identifier: bc.channel.ReceptionID.Bytes(), - Tag: symmetricBroadcastServiceTag, - } + if len(payload) != bc.maxSymmetricPayload() { + return format.Fingerprint{}, message.Service{}, nil, nil, + errors.Errorf(errPayloadSize, len(payload), bc.maxSymmetricPayload()) + } + + // Encrypt payload + rng := bc.rng.GetStream() + defer rng.Close() + encryptedPayload, mac, fp = bc.channel.EncryptSymmetric(payload, rng) + + // Create service using symmetric broadcast service tag & channel reception ID + // Allows anybody with this info to listen for messages on this channel + service = message.Service{ + Identifier: bc.channel.ReceptionID.Bytes(), + Tag: symmetricBroadcastServiceTag, + } - if cMixParams.DebugTag == cmix.DefaultDebugTag { - cMixParams.DebugTag = symmCMixSendTag + if cMixParams.DebugTag == cmix.DefaultDebugTag { + cMixParams.DebugTag = symmCMixSendTag + } + return } - return bc.net.Send( - bc.channel.ReceptionID, fp, service, encryptedPayload, mac, cMixParams) + return bc.net.SendWithAssembler(bc.channel.ReceptionID, assemble, + cMixParams) } diff --git a/channels/eventModel.go b/channels/eventModel.go new file mode 100644 index 0000000000000000000000000000000000000000..2712071e0b96072f58cfba1b72e3443e8a9b6839 --- /dev/null +++ b/channels/eventModel.go @@ -0,0 +1,95 @@ +package channels + +import ( + "errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/primitives/states" + "sync" + "time" + + "gitlab.com/elixxir/client/cmix/rounds" + cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" + cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/xx_network/primitives/id" +) + +var ( + MessageTypeAlreadyRegistered = errors.New("the given message type has " + + "already been registered") +) + +type EventModel interface { + JoinChannel(channel cryptoBroadcast.Channel) + LeaveChannel(ChannelID *id.ID) + + ReceiveTextMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID, + messageType MessageType, SenderUsername string, Content []byte, + timestamp time.Time, lease time.Duration, round rounds.Round) + ReceiveAdminTextMessage(ChannelID *id.ID, MessageID cryptoChannel.MessageID, + messageType MessageType, SenderUsername string, Content []byte, + timestamp time.Time, lease time.Duration, round rounds.Round) +} + +type MessageTypeReceiveMessage func(ChannelID *id.ID, + MessageID cryptoChannel.MessageID, messageType MessageType, + SenderUsername string, Content []byte, timestamp time.Time, + lease time.Duration, round rounds.Round) + +type events struct { + model EventModel + registered map[MessageType]MessageTypeReceiveMessage + mux sync.RWMutex +} + +func initEvents(model EventModel) *events { + e := &events{ + model: model, + registered: make(map[MessageType]MessageTypeReceiveMessage), + mux: sync.RWMutex{}, + } + + //set up default message types + e.registered[Text] = e.model.ReceiveTextMessage + e.registered[AdminText] = e.model.ReceiveAdminTextMessage + return e +} + +func (e *events) RegisterReceiveHandler(messageType MessageType, + listener MessageTypeReceiveMessage) error { + e.mux.Lock() + defer e.mux.Unlock() + + //check if the type is already registered + if _, exists := e.registered[messageType]; exists { + return MessageTypeAlreadyRegistered + } + + //register the message type + e.registered[messageType] = listener + jww.INFO.Printf("Registered Listener for Message Type %s", messageType) + return nil +} + +func (e *events) hear(chID *id.ID, umi *UserMessageInternal, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + um := umi.GetUserMessage() + cm := umi.GetChannelMessage() + messageType := MessageType(cm.PayloadType) + + //check if the type is already registered + e.mux.RLock() + listener, exists := e.registered[messageType] + e.mux.RUnlock() + if !exists { + jww.WARN.Printf("Received message from %s on channel %s in "+ + "round %d which could not be handled due to unregistered message "+ + "type %s; Contents: %v", um.Username, chID, round.ID, messageType, + cm.Payload) + } + + //Call the listener. This is already in an instanced event, no new thread needed. + listener(chID, umi.GetMessageID(), messageType, um.Username, + cm.Payload, round.Timestamps[states.QUEUED], time.Duration(cm.Lease), round) + return +} diff --git a/channels/genericUserListener.go b/channels/genericUserListener.go new file mode 100644 index 0000000000000000000000000000000000000000..d6fa707ffcd7d2f67c3af28206fc70a9a3b2b3cb --- /dev/null +++ b/channels/genericUserListener.go @@ -0,0 +1,86 @@ +package channels + +import ( + "crypto/ed25519" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/primitives/states" + "gitlab.com/xx_network/primitives/id" + "time" +) + +type genericUserListener struct { + name NameService + events *events + chID *id.ID +} + +func (gul *genericUserListener) Listen(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + + //Remove the padding + payloadUnpadded, err := broadcast.DecodeSizedBroadcast(payload) + if err != nil { + jww.WARN.Printf("Failed to strip the padding on User Message "+ + "on channel %s", gul.chID) + return + } + + //Decode the message as a user message + umi, err := UnmarshalUserMessageInternal(payloadUnpadded) + if err != nil { + jww.WARN.Printf("Failed to unmarshal User Message on "+ + "channel %s", gul.chID) + return + } + + um := umi.GetUserMessage() + cm := umi.GetChannelMessage() + msgID := umi.GetMessageID() + + /*CRYPTOGRAPHICALLY RELEVANT CHECKS*/ + + // check the round to ensure the message is not a replay + if id.Round(cm.RoundID) != round.ID { + jww.WARN.Printf("The round message %s send on %d referenced "+ + "(%d) was not the same as the round the message was found on (%d)", + msgID, gul.chID, cm.RoundID, round.ID, gul.chID) + return + } + + // check that the username lease is valid + usernameLeaseEnd := time.Unix(0, um.UsernameLease) + if usernameLeaseEnd.After(round.Timestamps[states.QUEUED]) { + jww.WARN.Printf("Message %s on channel %s purportedly from %s "+ + "has an expired lease, ended %s, round %d was sent at %s", msgID, + gul.chID, um.Username, usernameLeaseEnd, round.ID, + round.Timestamps[states.QUEUED]) + return + } + + // check that the signature from the nameserver is valid + if !gul.name.ValidateChannelMessage(um.Username, + time.Unix(0, um.UsernameLease), um.ECCPublicKey, um.ValidationSignature) { + jww.WARN.Printf("Message %s on channel %s purportedly from %s "+ + "failed the check of its Name Server with signature %v", msgID, + gul.chID, um.Username, um.ValidationSignature) + return + } + + // check that the user properly signed the message + if !ed25519.Verify(um.ECCPublicKey, um.Message, um.Signature) { + jww.WARN.Printf("Message %s on channel %s purportedly from %s "+ + "failed its user signature with signature %v", msgID, + gul.chID, um.Username, um.Signature) + return + } + + //TODO: Processing of the message relative to admin commands will be here + + //Submit the message to the event model for listening + gul.events.hear(gul.chID, umi, receptionID, round) + + return +} diff --git a/channels/interface.go b/channels/interface.go new file mode 100644 index 0000000000000000000000000000000000000000..66ad46c23b07e947b598c0158ea804dacb026b78 --- /dev/null +++ b/channels/interface.go @@ -0,0 +1,4 @@ +package channels + +type Manager interface { +} diff --git a/channels/joinedChannel.go b/channels/joinedChannel.go new file mode 100644 index 0000000000000000000000000000000000000000..9a6fd4dad0d9c77e8ec2724e6c79af84a5fcd93f --- /dev/null +++ b/channels/joinedChannel.go @@ -0,0 +1,215 @@ +package channels + +import ( + "encoding/json" + "errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/storage/versioned" + cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" +) + +const ( + joinedChannelsVersion = 0 + joinedChannelsKey = "JoinedChannelsKey" + joinedChannelVersion = 0 + joinedChannelKey = "JoinedChannelKey-" +) + +var ChannelAlreadyExistsErr = errors.New("the channel cannot be added " + + "becasue it already exists") + +var ChannelDoesNotExistsErr = errors.New("the channel cannot be found") + +// store Stores the list of joined channels to disk while taking the read lock +func (m *manager) store() error { + m.mux.RLock() + defer m.mux.RUnlock() + return m.storeUnsafe() +} + +// storeUnsafe Stores the list of joined channels to disk without taking the +// read lock. Must be used by another function which has already taken the read +// lock +func (m *manager) storeUnsafe() error { + + channelsList := m.getChannelsUnsafe() + + data, err := json.Marshal(&channelsList) + if err != nil { + return err + } + obj := &versioned.Object{ + Version: joinedChannelsVersion, + Timestamp: netTime.Now(), + Data: data, + } + + return m.kv.Set(joinedChannelsKey, + joinedChannelsVersion, obj) +} + +// loadChannels loads all currently joined channels from disk and registers +// them for message reception +func (m *manager) loadChannels() map[*id.ID]*joinedChannel { + + obj, err := m.kv.Get(joinedChannelsKey, + joinedChannelsVersion) + if err != nil { + jww.FATAL.Panicf("Failed to load channels %+v", err) + } + + chList := make([]*id.ID, 0, len(m.channels)) + if err = json.Unmarshal(obj.Data, &chList); err != nil { + jww.FATAL.Panicf("Failed to load channels %+v", err) + } + + chMap := make(map[*id.ID]*joinedChannel) + + for i := range chList { + jc, err := loadJoinedChannel(chList[i], m.kv, m.client, m.rng, m.name, + &m.events, m.broadcastMaker) + if err != nil { + jww.FATAL.Panicf("Failed to load channel %s: %+v", + chList[i], err) + } + chMap[chList[i]] = jc + } + return chMap +} + +//addChannel Adds a channel +func (m *manager) addChannel(channel cryptoBroadcast.Channel) error { + m.mux.Lock() + defer m.mux.Unlock() + if _, exists := m.channels[channel.ReceptionID]; exists { + return ChannelAlreadyExistsErr + } + + b, err := m.broadcastMaker(channel, m.client, m.rng) + if err != nil { + return err + } + + //Connect to listeners + err = b.RegisterListener((&genericUserListener{ + name: m.name, + events: &m.events, + chID: channel.ReceptionID, + }).Listen, broadcast.Symmetric) + if err != nil { + return err + } + + jc := &joinedChannel{ + broadcast: b, + } + + if err = jc.Store(m.kv); err != nil { + return err + } + + if err = m.storeUnsafe(); err != nil { + return err + } + return nil +} + +//getChannel returns the given channel, if it exists +func (m *manager) getChannel(channelId *id.ID) (*joinedChannel, error) { + m.mux.RLock() + defer m.mux.RUnlock() + + jc, exists := m.channels[channelId] + if !exists { + return nil, ChannelDoesNotExistsErr + } + + return jc, nil +} + +//getChannels returns the ids of all channels that have been joined +//use getChannelsUnsafe if you already have taken the mux +func (m *manager) getChannels() []*id.ID { + m.mux.Lock() + defer m.mux.Unlock() + return m.getChannelsUnsafe() +} + +//getChannelsUnsafe returns the ids of all channels that have been joined +//is unsafe because it does not take the mux, only use when under a lock. +func (m *manager) getChannelsUnsafe() []*id.ID { + list := make([]*id.ID, 0, len(m.channels)) + for chID := range m.channels { + list = append(list, chID) + } + return list +} + +// joinedChannel which holds channel info. Will expand to include admin data, +// so will be treated as a struct for now +type joinedChannel struct { + broadcast broadcast.Channel +} + +// joinedChannelDisk is the representation for storage +type joinedChannelDisk struct { + broadcast cryptoBroadcast.Channel +} + +//Store writes the given channel to a unique storage location within the EKV +func (jc *joinedChannel) Store(kv *versioned.KV) error { + jcd := joinedChannelDisk{broadcast: jc.broadcast.Get()} + data, err := json.Marshal(&jcd) + if err != nil { + return err + } + obj := &versioned.Object{ + Version: joinedChannelVersion, + Timestamp: netTime.Now(), + Data: data, + } + + return kv.Set(makeJoinedChannelKey(jc.broadcast.Get().ReceptionID), + joinedChannelVersion, obj) +} + +//loadJoinedChannel loads a given channel from ekv storage +func loadJoinedChannel(chId *id.ID, kv *versioned.KV, net broadcast.Client, + rngGen *fastRNG.StreamGenerator, name NameService, e *events, + broadcastMaker broadcast.NewBroadcastChannelFunc) (*joinedChannel, error) { + obj, err := kv.Get(makeJoinedChannelKey(chId), joinedChannelVersion) + if err != nil { + return nil, err + } + + jcd := &joinedChannelDisk{} + + err = json.Unmarshal(obj.Data, jcd) + if err != nil { + return nil, err + } + b, err := broadcastMaker(jcd.broadcast, net, rngGen) + if err != nil { + return nil, err + } + + err = b.RegisterListener((&genericUserListener{ + name: name, + events: e, + chID: jcd.broadcast.ReceptionID, + }).Listen, broadcast.Symmetric) + if err != nil { + return nil, err + } + + jc := &joinedChannel{broadcast: b} + return jc, nil +} + +func makeJoinedChannelKey(chId *id.ID) string { + return joinedChannelKey + chId.HexEncode() +} diff --git a/channels/manager.go b/channels/manager.go new file mode 100644 index 0000000000000000000000000000000000000000..2e928c90a59ef234347fd902e2f11265cdfecc0a --- /dev/null +++ b/channels/manager.go @@ -0,0 +1,103 @@ +package channels + +import ( + "github.com/golang/protobuf/proto" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/storage/versioned" + cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "sync" + "time" +) + +type manager struct { + //List of all channels + channels map[*id.ID]*joinedChannel + mux sync.RWMutex + + //External references + kv *versioned.KV + client broadcast.Client + rng *fastRNG.StreamGenerator + name NameService + + //Events model + events + broadcastMaker broadcast.NewBroadcastChannelFunc +} + +func (m *manager) Send(channelID *id.ID, msg []byte, validUntil time.Duration, + messageType MessageType, params cmix.CMIXParams) (cryptoChannel.MessageID, id.Round, ephemeral.Id, + error) { + + //find the channel + ch, err := m.getChannel(channelID) + if err != nil { + return cryptoChannel.MessageID{}, 0, ephemeral.Id{}, err + } + + var msgId cryptoChannel.MessageID + //Note: we are not checking check if message is too long before trying to + //find a round + + //Build the function pointer that will build the message + assemble := func(rid id.Round) ([]byte, error) { + + //Build the message + chMsg := &ChannelMessage{ + Lease: validUntil.Nanoseconds(), + RoundID: uint64(rid), + PayloadType: uint32(messageType), + Payload: msg, + } + + //Serialize the message + chMsgSerial, err := proto.Marshal(chMsg) + if err != nil { + return nil, err + } + + //Sign the message + messageSig, err := m.name.SignChannelMessage(chMsgSerial) + if err != nil { + return nil, err + } + + //Build the user message + validationSig, unameLease := m.name.GetChannelValidationSignature() + + usrMsg := &UserMessage{ + Message: chMsgSerial, + ValidationSignature: validationSig, + Signature: messageSig, + Username: m.name.GetUsername(), + ECCPublicKey: m.name.GetChannelPubkey(), + UsernameLease: unameLease.Unix(), + } + + //Serialize the user message + usrMsgSerial, err := proto.Marshal(usrMsg) + if err != nil { + return nil, err + } + + //Fill in any extra bits in the payload to ensure it is the right size + usrMsgSerialSized, err := broadcast.NewSizedBroadcast( + ch.broadcast.MaxAsymmetricPayloadSize(), usrMsgSerial) + if err != nil { + return nil, err + } + + msgId = cryptoChannel.MakeMessageID(usrMsgSerialSized) + + return usrMsgSerialSized, nil + } + + //TODO: send the send message over to reception manually so it is added to + //the database early + rid, ephid, err := ch.broadcast.BroadcastWithAssembler(assemble, params) + return msgId, rid, ephid, err +} diff --git a/channels/messageTypes.go b/channels/messageTypes.go new file mode 100644 index 0000000000000000000000000000000000000000..aad369c270a342381ff7dc0f6473e7ea58e1952b --- /dev/null +++ b/channels/messageTypes.go @@ -0,0 +1,19 @@ +package channels + +import "fmt" + +type MessageType uint32 + +const ( + Text = MessageType(1) + AdminText = MessageType(2) +) + +func (mt MessageType) String() string { + switch mt { + case Text: + return "Text" + default: + return fmt.Sprintf("Unknown messageType %d", mt) + } +} diff --git a/channels/messages.go b/channels/messages.go index ee3491c7b551b7750e3ab80308cd52e96bb76281..cc071a74c92ba452deb4f353d6eb783c2e9f6cee 100644 --- a/channels/messages.go +++ b/channels/messages.go @@ -9,51 +9,65 @@ package channels import ( "github.com/golang/protobuf/proto" - "sync" + "gitlab.com/elixxir/crypto/channel" ) // UserMessageInternal is the internal structure of a UserMessage protobuf. type UserMessageInternal struct { - mux sync.RWMutex userMessage *UserMessage channelMessage *ChannelMessage + messageID channel.MessageID } -func NewUserMessageInternal(ursMsg *UserMessage) *UserMessageInternal { +func NewUserMessageInternal(ursMsg *UserMessage) (*UserMessageInternal, error) { + chanMessage := &ChannelMessage{} + err := proto.Unmarshal(ursMsg.Message, chanMessage) + if err != nil { + return nil, err + } + + channelMessage := chanMessage return &UserMessageInternal{ - mux: sync.RWMutex{}, userMessage: ursMsg, - channelMessage: nil, + channelMessage: channelMessage, + messageID: channel.MakeMessageID(channelMessage.Payload), + }, nil +} + +func UnmarshalUserMessageInternal(usrMsg []byte) (*UserMessageInternal, error) { + + um := &UserMessage{} + if err := proto.Unmarshal(usrMsg, um); err != nil { + return nil, err + } + + chanMessage := &ChannelMessage{} + err := proto.Unmarshal(um.Message, chanMessage) + if err != nil { + return nil, err } + + channelMessage := chanMessage + + return &UserMessageInternal{ + userMessage: um, + channelMessage: channelMessage, + }, nil } // GetUserMessage retrieves the UserMessage within // UserMessageInternal. func (umi *UserMessageInternal) GetUserMessage() *UserMessage { - umi.mux.RLock() - umi.mux.RUnlock() return umi.userMessage } // GetChannelMessage retrieves the ChannelMessage within -// UserMessageInternal. This is a lazy getter which will -// deserialize the ChannelMessage within the UserMessage.Message field. -// This deserialized ChannelMessage will then be placed into -// UserMessageInternal's channelMessage field and return. On subsequent calls it will return -// the message stored in UserMessageInternal. -func (umi *UserMessageInternal) GetChannelMessage() (*ChannelMessage, error) { - umi.mux.Lock() - defer umi.mux.Unlock() - - if umi.channelMessage == nil { - chanMessage := &ChannelMessage{} - err := proto.Unmarshal(umi.userMessage.Message, chanMessage) - if err != nil { - return nil, err - } - - umi.channelMessage = chanMessage - } +// UserMessageInternal. +func (umi *UserMessageInternal) GetChannelMessage() *ChannelMessage { + return umi.channelMessage +} - return umi.channelMessage, nil +// GetMessageID retrieves the messageID for the message. +func (umi *UserMessageInternal) GetMessageID() channel.MessageID { + return umi.messageID } diff --git a/channels/messages_test.go b/channels/messages_test.go index f3bf0eb029c312b1a6853d541351ad6c45af1ce3..f99d26aedc075c7304ec0717c74e2249ba60098b 100644 --- a/channels/messages_test.go +++ b/channels/messages_test.go @@ -30,7 +30,7 @@ func TestUserMessageInternal_GetChannelMessage(t *testing.T) { Username: "hunter", } - internal := NewUserMessageInternal(usrMsg) + internal, _ := NewUserMessageInternal(usrMsg) received, err := internal.GetChannelMessage() if err != nil { t.Fatalf("GetChannelMessage error: %v", err) diff --git a/channels/nameService.go b/channels/nameService.go new file mode 100644 index 0000000000000000000000000000000000000000..9b772486c4d36d8664a9ec93d9c8e5bda7a31e87 --- /dev/null +++ b/channels/nameService.go @@ -0,0 +1,29 @@ +package channels + +import ( + "crypto/ed25519" + "time" +) + +// NameService is an interface which encapsulates +// the user identity channel tracking service. +type NameService interface { + + // GetUsername returns the username. + GetUsername() string + + // GetChannelValidationSignature returns the validation + // signature and the time it was signed. + GetChannelValidationSignature() (signature []byte, lease time.Time) + + // GetChannelPubkey returns the user's public key. + GetChannelPubkey() ed25519.PublicKey + + // SignChannelMessage returns the signature of the + // given message. + SignChannelMessage(message []byte) (signature []byte, err error) + + // ValidateChannelMessage + ValidateChannelMessage(username string, lease time.Time, + pubKey ed25519.PublicKey, authorIDSignature []byte) bool +} diff --git a/cmix/interface.go b/cmix/interface.go index 717dbb0bbe8b26a288aaa79427bc6d2ed07a30a6..3f071bd2f65d0dc568a8d13500460a37bfdda361 100644 --- a/cmix/interface.go +++ b/cmix/interface.go @@ -80,6 +80,24 @@ type Client interface { SendMany(messages []TargetedCmixMessage, p CMIXParams) ( id.Round, []ephemeral.Id, error) + // SendWithAssembler sends a variable cmix payload to the provided recipient. + // The payload sent is based on the Complier function passed in, which accepts + // a round ID and returns the necessary payload data. + // Returns the round ID of the round the payload was sent or an error if it + // fails. + // This does not have end-to-end encryption on it and is used exclusively as + // a send for higher order cryptographic protocols. Do not use unless + // implementing a protocol on top. + // recipient - cMix ID of the recipient. + // assembler - MessageAssembler function, accepting round ID and returning + // fingerprint + // format.Fingerprint, service message.Service, payload, mac []byte + // Will return an error if the network is unhealthy or if it fails to send + // (along with the reason). Blocks until successful sends or errors. + // WARNING: Do not roll your own crypto. + SendWithAssembler(recipient *id.ID, assembler MessageAssembler, + cmixParams CMIXParams) (id.Round, ephemeral.Id, error) + /* === Message Reception ================================================ */ /* Identities are all network identities which the client is currently trying to pick up message on. An identity must be added to receive @@ -290,12 +308,15 @@ type Client interface { type ClientErrorReport func(source, message, trace string) -// MessageAssembler func accepts a round ID, returning fingerprint, service, payload & mac. -// This allows users to pass in a paylaod which will contain the round ID over which the message is sent. -type MessageAssembler func(rid id.Round) (fingerprint format.Fingerprint, service message.Service, payload, mac []byte) +// MessageAssembler func accepts a round ID, returning fingerprint, service, +// payload & mac. This allows users to pass in a paylaod which will contain the +// round ID over which the message is sent. +type MessageAssembler func(rid id.Round) (fingerprint format.Fingerprint, + service message.Service, payload, mac []byte, err error) -// messageAssembler is an internal wrapper around MessageAssembler which returns a format.message -// This is necessary to preserve the interaction between sendCmixHelper and critical messages +// messageAssembler is an internal wrapper around MessageAssembler which +// returns a format.message This is necessary to preserve the interaction +// between sendCmixHelper and critical messages type messageAssembler func(rid id.Round) (format.Message, error) type clientCommsInterface interface { diff --git a/cmix/sendCmix.go b/cmix/sendCmix.go index 648e1ef1c8691211f40bf11fc76e86bc17e5dfe7..70e83106bae0ab85073a12c0ca349b0566aff55b 100644 --- a/cmix/sendCmix.go +++ b/cmix/sendCmix.go @@ -58,8 +58,9 @@ func (c *client) Send(recipient *id.ID, fingerprint format.Fingerprint, service message.Service, payload, mac []byte, cmixParams CMIXParams) ( id.Round, ephemeral.Id, error) { // create an internal assembler function to pass to sendWithAssembler - assembler := func(rid id.Round) (format.Fingerprint, message.Service, []byte, []byte) { - return fingerprint, service, payload, mac + assembler := func(rid id.Round) (format.Fingerprint, message.Service, + []byte, []byte, error) { + return fingerprint, service, payload, mac, nil } return c.sendWithAssembler(recipient, assembler, cmixParams) } @@ -98,7 +99,11 @@ func (c *client) sendWithAssembler(recipient *id.ID, assembler MessageAssembler, // Create an internal messageAssembler which returns a format.Message assemblerFunc := func(rid id.Round) (format.Message, error) { - fingerprint, service, payload, mac := assembler(rid) + fingerprint, service, payload, mac, err := assembler(rid) + + if err != nil { + return format.Message{}, err + } if len(payload) != c.maxMsgLen { return format.Message{}, errors.Errorf( diff --git a/go.mod b/go.mod index 64b09fb6e6d885caf0d30ba08bb37f066e5993af..c783049e4a394de789f9bd4d72ff01cd7fc25eb6 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module gitlab.com/elixxir/client go 1.17 -replace gitlab.com/elixxir/crypto => /home/human/code/crypto - require ( github.com/cloudflare/circl v1.2.0 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 @@ -15,7 +13,7 @@ require ( github.com/spf13/viper v1.12.0 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f gitlab.com/elixxir/comms v0.0.4-0.20220805121030-b95005ac4528 - gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46 + gitlab.com/elixxir/crypto v0.0.7-0.20220822200404-0be5ac9167ba gitlab.com/elixxir/ekv v0.1.7 gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd diff --git a/go.sum b/go.sum index cccd675d6b722e0aaac060aeb9f6d0c0bd2630a2..15026f41da3db123c76448fdd513095d108f0814 100644 --- a/go.sum +++ b/go.sum @@ -431,6 +431,8 @@ gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp0 gitlab.com/elixxir/crypto v0.0.7-0.20220317172048-3de167bd9406/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46 h1:C8nAiMnL8IOGjQ5qErbpzAjRMVFMoB1GunYk8pGOEz8= gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46/go.mod h1:Oy+VWQ2Sa0Ybata3oTV+Yc46hkaDwAsuIMW0wJ01z2M= +gitlab.com/elixxir/crypto v0.0.7-0.20220822200404-0be5ac9167ba h1:aaz9Xxm1EooDzr644KdSPg+iVkyztndQ6+DfLIBIuv0= +gitlab.com/elixxir/crypto v0.0.7-0.20220822200404-0be5ac9167ba/go.mod h1:Oy+VWQ2Sa0Ybata3oTV+Yc46hkaDwAsuIMW0wJ01z2M= gitlab.com/elixxir/ekv v0.1.7 h1:OW2z+N4QCqqMFzouAwFTWWMKz0Y/PDhyYReN7gQ5NiQ= gitlab.com/elixxir/ekv v0.1.7/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=