diff --git a/cmix/client.go b/cmix/client.go index 996cacdb46016a7675cde906cbcd9c66dc68c766..d164b4d2a60620176fe7fee12c5a26e95e351c99 100644 --- a/cmix/client.go +++ b/cmix/client.go @@ -189,9 +189,14 @@ func (c *client) initialize(ndf *ndf.NetworkDefinition) error { // Set up critical message tracking (sendCmix only) critSender := func(msg format.Message, recipient *id.ID, params CMIXParams, ) (id.Round, ephemeral.Id, error) { - return sendCmixHelper(c.Sender, msg, recipient, params, c.instance, + compiler := func(round id.Round) (format.Message, error) { + return msg, nil + } + rid, eid, _, sendErr := sendCmixHelper(c.Sender, compiler, recipient, params, c.instance, c.session.GetCmixGroup(), c.Registrar, c.rng, c.events, c.session.GetTransmissionID(), c.comms) + return rid, eid, sendErr + } c.crit = newCritical(c.session.GetKV(), c.Monitor, diff --git a/cmix/interface.go b/cmix/interface.go index a5db8b3cfe2089e94d03861769785ab99bd4390a..717dbb0bbe8b26a288aaa79427bc6d2ed07a30a6 100644 --- a/cmix/interface.go +++ b/cmix/interface.go @@ -290,6 +290,14 @@ 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 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 { followNetworkComms SendCmixCommsInterface diff --git a/cmix/sendCmix.go b/cmix/sendCmix.go index 73dfcfbd2c02b6f1cafcf49cd4662826edf7e3e8..648e1ef1c8691211f40bf11fc76e86bc17e5dfe7 100644 --- a/cmix/sendCmix.go +++ b/cmix/sendCmix.go @@ -56,34 +56,74 @@ import ( // WARNING: Do not roll your own crypto. 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 + } + return c.sendWithAssembler(recipient, assembler, cmixParams) +} + +// 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. +func (c *client) SendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) ( + id.Round, ephemeral.Id, error) { + // Critical messaging and assembler-based message payloads are not compatible + if cmixParams.Critical { + return 0, ephemeral.Id{}, errors.New("Cannot send critical messages with a message assembler") + } + return c.sendWithAssembler(recipient, assembler, cmixParams) +} + +// sendWithAssembler wraps the passed in MessageAssembler in a messageAssembler for sendCmixHelper, +// and sets up critical message handling where applicable. +func (c *client) sendWithAssembler(recipient *id.ID, assembler MessageAssembler, cmixParams CMIXParams) ( id.Round, ephemeral.Id, error) { if !c.Monitor.IsHealthy() { return 0, ephemeral.Id{}, errors.New( "Cannot send cmix message when the network is not healthy") } - if len(payload) != c.maxMsgLen { - return 0, ephemeral.Id{}, errors.Errorf( - "bad message length (%d, need %d)", - len(payload), c.maxMsgLen) - } + // Create an internal messageAssembler which returns a format.Message + assemblerFunc := func(rid id.Round) (format.Message, error) { + fingerprint, service, payload, mac := assembler(rid) - // Build message. Will panic if inputs are not correct. - msg := format.NewMessage(c.session.GetCmixGroup().GetP().ByteLen()) - msg.SetContents(payload) - msg.SetKeyFP(fingerprint) - msg.SetSIH(service.Hash(msg.GetContents())) - msg.SetMac(mac) + if len(payload) != c.maxMsgLen { + return format.Message{}, errors.Errorf( + "bad message length (%d, need %d)", + len(payload), c.maxMsgLen) + } - jww.TRACE.Printf("sendCmix Contents: %v, KeyFP: %v, MAC: %v, SIH: %v", - msg.GetContents(), msg.GetKeyFP(), msg.GetMac(), - msg.GetSIH()) + // Build message. Will panic if inputs are not correct. + msg := format.NewMessage(c.session.GetCmixGroup().GetP().ByteLen()) + msg.SetContents(payload) + msg.SetKeyFP(fingerprint) + msg.SetSIH(service.Hash(msg.GetContents())) + msg.SetMac(mac) - if cmixParams.Critical { - c.crit.AddProcessing(msg, recipient, cmixParams) + jww.TRACE.Printf("sendCmix Contents: %v, KeyFP: %v, MAC: %v, SIH: %v", + msg.GetContents(), msg.GetKeyFP(), msg.GetMac(), + msg.GetSIH()) + + if cmixParams.Critical { + c.crit.AddProcessing(msg, recipient, cmixParams) + } + return msg, nil } - rid, ephID, rtnErr := sendCmixHelper(c.Sender, msg, recipient, cmixParams, + rid, ephID, msg, rtnErr := sendCmixHelper(c.Sender, assemblerFunc, recipient, cmixParams, c.instance, c.session.GetCmixGroup(), c.Registrar, c.rng, c.events, c.session.GetTransmissionID(), c.comms) @@ -104,10 +144,10 @@ func (c *client) Send(recipient *id.ID, fingerprint format.Fingerprint, // If the message is successfully sent, the ID of the round sent it is returned, // which can be registered with the network instance to get a callback on its // status. -func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, +func sendCmixHelper(sender gateway.Sender, assembler messageAssembler, recipient *id.ID, cmixParams CMIXParams, instance *network.Instance, grp *cyclic.Group, nodes nodes.Registrar, rng *fastRNG.StreamGenerator, events event.Reporter, - senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, error) { + senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, format.Message, error) { timeStart := netTime.Now() maxTimeout := sender.GetHostParams().SendTimeout @@ -120,15 +160,11 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, } jww.INFO.Printf("[Send-%s] Looking for round to send cMix message to "+ - "%s (msgDigest: %s)", cmixParams.DebugTag, recipient, msg.Digest()) + "%s", cmixParams.DebugTag, recipient) stream := rng.GetStream() defer stream.Close() - // Flip leading bits randomly to thwart a tagging attack. - // See cmix.SetGroupBits for more info. - cmix.SetGroupBits(msg, grp, stream) - for numRoundTries := uint(0); numRoundTries < cmixParams.RoundTries; numRoundTries++ { elapsed := netTime.Since(timeStart) jww.TRACE.Printf("[Send-%s] try %d, elapsed: %s", @@ -136,15 +172,15 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, if elapsed > cmixParams.Timeout { jww.INFO.Printf("[Send-%s] No rounds to send to %s "+ - "(msgDigest: %s) were found before timeout %s", - cmixParams.DebugTag, recipient, msg.Digest(), cmixParams.Timeout) - return 0, ephemeral.Id{}, errors.New("Sending cmix message timed out") + "were found before timeout %s", + cmixParams.DebugTag, recipient, cmixParams.Timeout) + return 0, ephemeral.Id{}, format.Message{}, errors.New("Sending cmix message timed out") } if numRoundTries > 0 { jww.INFO.Printf("[Send-%s] Attempt %d to find round to send "+ - "message to %s (msgDigest: %s)", cmixParams.DebugTag, - numRoundTries+1, recipient, msg.Digest()) + "message to %s", cmixParams.DebugTag, + numRoundTries+1, recipient) } // Find the best round to send to, excluding attempted rounds @@ -152,8 +188,8 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, bestRound, err := instance.GetWaitingRounds().GetUpcomingRealtime( remainingTime, attempted, sendTimeBuffer) if err != nil { - jww.WARN.Printf("[Send-%s] Failed to GetUpcomingRealtime "+ - "(msgDigest: %s): %+v", cmixParams.DebugTag, msg.Digest(), err) + jww.WARN.Printf("[Send-%s] Failed to GetUpcomingRealtime: "+ + "%+v", cmixParams.DebugTag, err) } if bestRound == nil { @@ -185,6 +221,16 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, continue } + msg, err := assembler(id.Round(bestRound.ID)) + if err != nil { + jww.ERROR.Printf("Failed to compile message: %+v", err) + return 0, ephemeral.Id{}, format.Message{}, err + } + + // Flip leading bits randomly to thwart a tagging attack. + // See cmix.SetGroupBits for more info. + cmix.SetGroupBits(msg, grp, stream) + // Retrieve host and key information from round firstGateway, roundKeys, err := processRound( nodes, bestRound, recipient.String(), msg.Digest()) @@ -201,7 +247,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, wrappedMsg, encMsg, ephID, err := buildSlotMessage(msg, recipient, firstGateway, stream, senderId, bestRound, roundKeys) if err != nil { - return 0, ephemeral.Id{}, err + return 0, ephemeral.Id{}, format.Message{}, err } jww.INFO.Printf("[Send-%s] Sending to EphID %d (%s), on round %d "+ @@ -251,7 +297,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, // Exit if the thread has been stopped if stoppable.CheckErr(err) { - return 0, ephemeral.Id{}, err + return 0, ephemeral.Id{}, format.Message{}, err } // If the comm errors or the message fails to send, continue retrying @@ -260,7 +306,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, jww.ERROR.Printf("[Send-%s] SendCmix failed to send to "+ "EphID %d (%s) on round %d: %+v", cmixParams.DebugTag, ephID.Int64(), recipient, bestRound.ID, err) - return 0, ephemeral.Id{}, err + return 0, ephemeral.Id{}, format.Message{}, err } jww.ERROR.Printf("[Send-%s] SendCmix failed to send to "+ @@ -280,7 +326,7 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, jww.INFO.Print(m) events.Report(1, "MessageSend", "Metric", m) - return id.Round(bestRound.ID), ephID, nil + return id.Round(bestRound.ID), ephID, msg, nil } else { jww.FATAL.Panicf("[Send-%s] Gateway %s returned no error, "+ "but failed to accept message when sending to EphID %d (%s) "+ @@ -289,6 +335,6 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID, } } - return 0, ephemeral.Id{}, + return 0, ephemeral.Id{}, format.Message{}, errors.New("failed to send the message, unknown error") } diff --git a/go.mod b/go.mod index c07f1e0a18e016ce446e62ec1e45d7f0c2bec363..5bb8e838962b73a263217f083138058fcdb87375 100644 --- a/go.mod +++ b/go.mod @@ -13,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.20220606201132-c370d5039cea + gitlab.com/elixxir/crypto v0.0.7-0.20220808171640-473891de4c46 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 35b35f27bea260a64edf8071d298894195082a41..cccd675d6b722e0aaac060aeb9f6d0c0bd2630a2 100644 --- a/go.sum +++ b/go.sum @@ -429,8 +429,8 @@ gitlab.com/elixxir/comms v0.0.4-0.20220805121030-b95005ac4528/go.mod h1:Zi1O21+p gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= gitlab.com/elixxir/crypto v0.0.7-0.20220317172048-3de167bd9406/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= -gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea h1:+FjwbKl6X9TDT7qd7gG5N5PSbziPWP3NgjK5ci1b7/8= -gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea/go.mod h1:Oy+VWQ2Sa0Ybata3oTV+Yc46hkaDwAsuIMW0wJ01z2M= +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/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=