Skip to content
Snippets Groups Projects
Select Git revision
  • 706fe9e555c94efe9bd52fedb350fa97d8554983
  • release default protected
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • hotfix/TestHostPool_UpdateNdf_AddFilter
  • XX-4719/announcementChannels
  • xx-4717/logLevel
  • jonah/noob-channel
  • master protected
  • XX-4707/tagDiskJson
  • xx-4698/notification-retry
  • hotfix/notifylockup
  • syncNodes
  • hotfix/localCB
  • XX-4677/NewChanManagerMobile
  • XX-4689/DmSync
  • duplicatePrefix
  • XX-4601/HavenInvites
  • finalizedUICallbacks
  • XX-4673/AdminKeySync
  • debugNotifID
  • anne/test
  • v4.7.5
  • v4.7.4
  • v4.7.3
  • v4.7.2
  • v4.7.1
  • v4.6.3
  • v4.6.1
  • v4.5.0
  • v4.4.4
  • v4.3.11
  • v4.3.8
  • v4.3.7
  • v4.3.6
  • v4.3.5
  • v4.2.0
  • v4.3.0
  • v4.3.4
  • v4.3.3
  • v4.3.2
  • v4.3.1
41 results

sendCmixUtils.go

  • user avatar
    Benjamin Wenger authored
    706fe9e5
    History
    sendCmixUtils.go 9.16 KiB
    ///////////////////////////////////////////////////////////////////////////////
    // Copyright © 2020 xx network SEZC                                          //
    //                                                                           //
    // Use of this source code is governed by a license that can be found in the //
    // LICENSE file                                                              //
    ///////////////////////////////////////////////////////////////////////////////
    
    package message
    
    import (
    	"github.com/pkg/errors"
    	jww "github.com/spf13/jwalterweatherman"
    	"gitlab.com/elixxir/client/interfaces/message"
    	"gitlab.com/elixxir/client/interfaces/params"
    	preimage2 "gitlab.com/elixxir/client/interfaces/preimage"
    	"gitlab.com/elixxir/client/storage"
    	"gitlab.com/elixxir/client/storage/cmix"
    	pb "gitlab.com/elixxir/comms/mixmessages"
    	"gitlab.com/elixxir/comms/network"
    	"gitlab.com/elixxir/crypto/fastRNG"
    	"gitlab.com/elixxir/crypto/fingerprint"
    	"gitlab.com/elixxir/primitives/format"
    	"gitlab.com/elixxir/primitives/states"
    	"gitlab.com/xx_network/comms/connect"
    	"gitlab.com/xx_network/primitives/id"
    	"gitlab.com/xx_network/primitives/id/ephemeral"
    	"strconv"
    	"strings"
    	"time"
    )
    
    // Interface for SendCMIX comms; allows mocking this in testing.
    type sendCmixCommsInterface interface {
    	// SendPutMessage places a cMix message on the gateway to be
    	// sent through cMix.
    	SendPutMessage(host *connect.Host, message *pb.GatewaySlot,
    		timeout time.Duration) (*pb.GatewaySlotResponse, error)
    	// SendPutManyMessages places a list of cMix messages on the gateway
    	// to be sent through cMix.
    	SendPutManyMessages(host *connect.Host, messages *pb.GatewaySlots,
    		timeout time.Duration) (*pb.GatewaySlotResponse, error)
    }
    
    // how much in the future a round needs to be to send to it
    const sendTimeBuffer = 1000 * time.Millisecond
    const unrecoverableError = "failed with an unrecoverable error"
    
    // handlePutMessageError handles errors received from a PutMessage or a
    // PutManyMessage network call. A printable error will be returned giving more
    // context. If the error is not among recoverable errors, then the recoverable
    // boolean will be returned false. If the error is among recoverable errors,
    // then the boolean will return true.
    // recoverable means we should try resending to the round
    func handlePutMessageError(firstGateway *id.ID, instance *network.Instance,
    	session *storage.Session, nodeRegistration chan network.NodeGateway,
    	recipientString string, bestRound *pb.RoundInfo,
    	err error) (returnErr error) {
    
    	// If the comm errors or the message fails to send, then continue retrying;
    	// otherwise, return if it sends properly
    	if strings.Contains(err.Error(), "try a different round.") {
    		return errors.WithMessagef(err, "Failed to send to [%s] due to "+
    			"round error with round %d, bailing...",
    			recipientString, bestRound.ID)
    	} else if strings.Contains(err.Error(), "Could not authenticate client. "+
    		"Is the client registered with this node?") {
    		// If send failed due to the gateway not recognizing the authorization,
    		// then renegotiate with the node to refresh it
    		nodeID := firstGateway.DeepCopy()
    		nodeID.SetType(id.Node)
    
    		// Delete the keys
    		session.Cmix().Remove(nodeID)
    
    		// Trigger
    		go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID})
    
    		return errors.WithMessagef(err, "Failed to send to [%s] via %s "+
    			"due to failed authentication, retrying...",
    			recipientString, firstGateway)
    	}
    
    	return errors.WithMessage(err, "Failed to put cmix message")
    
    }
    
    // processRound is a helper function that determines the gateway to send to for
    // a round and retrieves the round keys.
    func processRound(instance *network.Instance, session *storage.Session,
    	nodeRegistration chan network.NodeGateway, bestRound *pb.RoundInfo,
    	recipientString, messageDigest string) (*id.ID, *cmix.RoundKeys, error) {
    
    	// Build the topology
    	idList, err := id.NewIDListFromBytes(bestRound.Topology)
    	if err != nil {
    		return nil, nil, errors.WithMessagef(err, "Failed to use topology for "+
    			"round %d when sending to [%s] (msgDigest(s): %s)",
    			bestRound.ID, recipientString, messageDigest)
    	}
    	topology := connect.NewCircuit(idList)
    
    	// Get the keys for the round, reject if any nodes do not have keying
    	// relationships
    	roundKeys, missingKeys := session.Cmix().GetRoundKeys(topology)
    	if len(missingKeys) > 0 {
    		go handleMissingNodeKeys(instance, nodeRegistration, missingKeys)
    
    		return nil, nil, errors.Errorf("Failed to send on round %d to [%s] "+
    			"(msgDigest(s): %s) due to missing relationships with nodes: %s",
    			bestRound.ID, recipientString, messageDigest, missingKeys)
    	}
    
    	// Get the gateway to transmit to
    	firstGateway := topology.GetNodeAtIndex(0).DeepCopy()
    	firstGateway.SetType(id.Gateway)
    
    	return firstGateway, roundKeys, nil
    }
    
    // buildSlotMessage is a helper function which forms a slotted message to send
    // to a gateway. It encrypts passed in message and generates an ephemeral ID for
    // the recipient.
    func buildSlotMessage(msg format.Message, recipient *id.ID, target *id.ID,
    	stream *fastRNG.Stream, senderId *id.ID, bestRound *pb.RoundInfo,
    	roundKeys *cmix.RoundKeys, param params.CMIX) (*pb.GatewaySlot,
    	format.Message, ephemeral.Id,
    	error) {
    
    	// Set the ephemeral ID
    	ephID, _, _, err := ephemeral.GetId(recipient,
    		uint(bestRound.AddressSpaceSize),
    		int64(bestRound.Timestamps[states.QUEUED]))
    	if err != nil {
    		jww.FATAL.Panicf("Failed to generate ephemeral ID when sending to %s "+
    			"(msgDigest: %s):  %+v", err, recipient, msg.Digest())
    	}
    
    	ephIdFilled, err := ephID.Fill(uint(bestRound.AddressSpaceSize), stream)
    	if err != nil {
    		jww.FATAL.Panicf("Failed to obfuscate the ephemeralID when sending "+
    			"to %s (msgDigest: %s): %+v", recipient, msg.Digest(), err)
    	}
    
    	msg.SetEphemeralRID(ephIdFilled[:])
    
    	// use the alternate identity preimage if it is set
    	var preimage []byte
    	if param.IdentityPreimage != nil {
    		preimage = param.IdentityPreimage
    		jww.INFO.Printf("Sending to %s with override preimage %v", recipient, preimage)
    	} else {
    		preimage = preimage2.MakeDefault(recipient)
    		jww.INFO.Printf("Sending to %s with default preimage %v", recipient, preimage)
    	}
    
    	// Set the identity fingerprint
    
    	ifp := fingerprint.IdentityFP(append([]byte{0}, msg.GetContents()...), preimage)
    
    	msg.SetIdentityFP(ifp)
    
    	// Encrypt the message
    	salt := make([]byte, 32)
    	_, err = stream.Read(salt)
    	if err != nil {
    		jww.ERROR.Printf("Failed to generate salt when sending to %s "+
    			"(msgDigest: %s): %+v", recipient, msg.Digest(), err)
    		return nil, format.Message{}, ephemeral.Id{}, errors.WithMessage(err,
    			"Failed to generate salt, this should never happen")
    	}
    
    	encMsg, kmacs := roundKeys.Encrypt(msg, salt, id.Round(bestRound.ID))
    
    	// Build the message payload
    	msgPacket := &pb.Slot{
    		SenderID: senderId.Bytes(),
    		PayloadA: encMsg.GetPayloadA(),
    		PayloadB: encMsg.GetPayloadB(),
    		Salt:     salt,
    		KMACs:    kmacs,
    	}
    
    	// Create the wrapper to the gateway
    	slot := &pb.GatewaySlot{
    		Message: msgPacket,
    		RoundID: bestRound.ID,
    		Target:  target.Bytes(),
    	}
    
    	// Add the mac proving ownership
    	slot.MAC = roundKeys.MakeClientGatewayKey(salt,
    		network.GenerateSlotDigest(slot))
    
    	return slot, encMsg, ephID, nil
    }
    
    // handleMissingNodeKeys signals to the node registration thread to register a
    // node if keys are missing. Identity is triggered automatically when the node
    // is first seen, so this should on trigger on rare events.
    func handleMissingNodeKeys(instance *network.Instance,
    	newNodeChan chan network.NodeGateway, nodes []*id.ID) {
    	for _, n := range nodes {
    		ng, err := instance.GetNodeAndGateway(n)
    		if err != nil {
    			jww.ERROR.Printf("Node contained in round cannot be found: %s", err)
    			continue
    		}
    
    		select {
    		case newNodeChan <- ng:
    		default:
    			jww.ERROR.Printf("Failed to send node registration for %s", n)
    		}
    
    	}
    }
    
    // messageListToStrings serializes a list of message.TargetedCmixMessage into a
    // string of comma seperated recipient IDs and a string of comma seperated
    // message digests. Duplicate recipient IDs are printed once. Intended for use
    // in printing to log.
    func messageListToStrings(msgList []message.TargetedCmixMessage) (string, string) {
    	idStrings := make([]string, 0, len(msgList))
    	idMap := make(map[id.ID]bool, len(msgList))
    	msgDigests := make([]string, len(msgList))
    	for i, msg := range msgList {
    		if !idMap[*msg.Recipient] {
    			idStrings = append(idStrings, msg.Recipient.String())
    			idMap[*msg.Recipient] = true
    		}
    		msgDigests[i] = msg.Message.Digest()
    	}
    
    	return strings.Join(idStrings, ", "), strings.Join(msgDigests, ", ")
    }
    
    // messagesToDigestString serializes a list of cMix messages into a string of
    // comma seperated message digests. Intended for use in printing to log.
    func messagesToDigestString(msgs []format.Message) string {
    	msgDigests := make([]string, len(msgs))
    	for i, msg := range msgs {
    		msgDigests[i] = msg.Digest()
    	}
    
    	return strings.Join(msgDigests, ", ")
    }
    
    // ephemeralIdListToString serializes a list of ephemeral IDs into a string of
    // comma seperated integer representations. Intended for use in printing to log.
    func ephemeralIdListToString(idList []ephemeral.Id) string {
    	idStrings := make([]string, len(idList))
    	for i, ephID := range idList {
    		idStrings[i] = strconv.FormatInt(ephID.Int64(), 10)
    	}
    
    	return strings.Join(idStrings, ",")
    }