diff --git a/broadcast/sizedBroadcast.go b/broadcast/sizedBroadcast.go index e909ab30014ca926ad03a9040d8e5bbc9211ca29..770083a2d78058eac2831599d0002d225a7a30ec 100644 --- a/broadcast/sizedBroadcast.go +++ b/broadcast/sizedBroadcast.go @@ -49,7 +49,10 @@ func NewSizedBroadcast(maxPayloadSize int, payload []byte) ([]byte, error) { b := make([]byte, sizeSize) binary.LittleEndian.PutUint16(b, uint16(len(payload))) - return append(b, payload...), nil + ret := make([]byte, maxPayloadSize) + p := append(b, payload...) + copy(ret, p) + return ret, nil } // DecodeSizedBroadcast the data into its original payload of the correct size. diff --git a/broadcast/symmetricClient.go b/broadcast/symmetricClient.go index 28d718868a0659f9f2816f7fd2b16a4feb9cb116..3f0c7104d5dcb905a54775fd0fe3b026c263d9cd 100644 --- a/broadcast/symmetricClient.go +++ b/broadcast/symmetricClient.go @@ -15,7 +15,9 @@ import ( "gitlab.com/elixxir/client/cmix/message" crypto "gitlab.com/elixxir/crypto/broadcast" "gitlab.com/elixxir/crypto/fastRNG" + "gitlab.com/elixxir/crypto/hash" "gitlab.com/elixxir/primitives/format" + "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" "time" @@ -62,6 +64,15 @@ type Client interface { // listening for new messages on the callback immediately. func NewSymmetricClient(channel crypto.Symmetric, listenerCb ListenerFunc, net Client, rng *fastRNG.StreamGenerator) Symmetric { + sc := &symmetricClient{ + channel: channel, + net: net, + rng: rng, + } + if !sc.verifyID() { + jww.FATAL.Panicf("Failed ID verification for symmetric channel") + } + // Add channel's identity net.AddIdentity(channel.ReceptionID, identity.Forever, true) @@ -83,11 +94,7 @@ func NewSymmetricClient(channel crypto.Symmetric, listenerCb ListenerFunc, jww.INFO.Printf("New symmetric broadcast client created for channel %q (%s)", channel.Name, channel.ReceptionID) - return &symmetricClient{ - channel: channel, - net: net, - rng: rng, - } + return sc } // MaxPayloadSize returns the maximum size for a broadcasted payload. @@ -141,3 +148,20 @@ func (s *symmetricClient) Stop() { // Delete all registered services s.net.DeleteClientService(s.channel.ReceptionID) } + +func (s *symmetricClient) verifyID() bool { + h, err := hash.NewCMixHash() + if err != nil { + jww.FATAL.Panicf("[verifyID] Failed to create cmix hash") + return false + } + h.Write([]byte(s.channel.Name)) + h.Write([]byte(s.channel.Description)) + h.Write(s.channel.Salt) + h.Write(rsa.CreatePublicKeyPem(s.channel.RsaPubKey)) + ridBytes := h.Sum(nil) + gen := &id.ID{} + copy(gen[:], ridBytes) + gen.SetType(id.User) + return s.channel.ReceptionID.Cmp(gen) +} diff --git a/cmd/symmetric.go b/cmd/symmetric.go new file mode 100644 index 0000000000000000000000000000000000000000..e61b87195e22132b04a57bd93c5517a795a1a85d --- /dev/null +++ b/cmd/symmetric.go @@ -0,0 +1,220 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/elixxir/crypto/hash" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/utils" + "os" + "time" +) + +// singleCmd is the single-use subcommand that allows for sending and responding +// to single-use messages. +var symmetricCmd = &cobra.Command{ + Use: "symmetric", + Short: "Send symmetric broadcast messages", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + client := initClient() + + // Write user contact to file + user := client.GetUser() + jww.INFO.Printf("User: %s", user.ReceptionID) + jww.INFO.Printf("User Transmission: %s", user.TransmissionID) + writeContact(user.GetContact()) + + err := client.StartNetworkFollower(5 * time.Second) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + + // Wait until connected or crash on timeout + connected := make(chan bool, 10) + client.GetNetworkInterface().AddHealthCallback( + func(isconnected bool) { + connected <- isconnected + }) + waitUntilConnected(connected) + + // Create new symmetric or load from path if exists + path, err := utils.ExpandPath(viper.GetString("path")) + var symmetric *crypto.Symmetric + if utils.Exists(path) { + // Load symmetric from path + symmBytes, err := utils.ReadFile(path) + if err != nil { + + } + symmetric, err = crypto.UnmarshalSymmetric(symmBytes) + if err != nil { + + } + } else { + // New symmetric + name := viper.GetString("name") + desc := viper.GetString("description") + if name == "" { + jww.FATAL.Panicf("Name cannot be empty") + } else if desc == "" { + jww.FATAL.Panicf("description cannot be empty") + } + + var pubKey *rsa.PublicKey + var salt, pubKeyBytes []byte + if viper.GetBool("new") { + privKey, err := rsa.GenerateKey(client.GetRng().GetStream(), rsa.DefaultRSABitLen) + if err != nil { + + } + pubKey = privKey.GetPublic() + pubKeyBytes = rsa.CreatePublicKeyPem(pubKey) + } else { + pubKeyBytes := []byte(viper.GetString("rsaPub")) + pubKey, err = rsa.LoadPublicKeyFromPem(pubKeyBytes) + if err != nil { + + } + salt = []byte(viper.GetString("salt")) + } + + h, err := hash.NewCMixHash() + if err != nil { + + } + h.Write([]byte(name)) + h.Write([]byte(desc)) + h.Write(salt) + h.Write(pubKeyBytes) + ridBytes := h.Sum(nil) + + rid := &id.ID{} + copy(rid[:], ridBytes) + rid.SetType(id.User) + + symmetric = &crypto.Symmetric{ + ReceptionID: rid, + Name: name, + Description: desc, + Salt: salt, + RsaPubKey: pubKey, + } + + symmBytes, err := symmetric.Marshal() + if err != nil { + + } + // Write to file if there + if path != "" { + err = utils.WriteFile(path, symmBytes, os.ModePerm, os.ModeDir) + if err != nil { + + } + } else { + fmt.Printf("Symmetric marshalled: %+v", symmBytes) + } + } + + // Create receiver callback + receiveChan := make(chan []byte, 100) + cb := func(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round) + receiveChan <- payload + } + + // Connect to symmetric broadcast channel + scl := broadcast.NewSymmetricClient(*symmetric, cb, client.GetNetworkInterface(), client.GetRng()) + message := viper.GetString("broadcast") + fmt.Println(message) + // Send a broadcast over the channel + if message != "" { + broadcastMessage, err := broadcast.NewSizedBroadcast(scl.MaxPayloadSize(), []byte(message)) + if err != nil { + + } + rid, eid, err := scl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send symmentric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid) + } + + // Receive messages over the channel + expectedCnt := viper.GetUint("receiveCount") + waitSecs := viper.GetUint("waitTimeout") + waitTimeout := time.Duration(waitSecs) * time.Second + receivedCount := uint(0) + done := false + for !done && expectedCnt != 0 { + timeout := time.NewTimer(waitTimeout) + select { + case receivedPayload := <-receiveChan: + receivedCount++ + receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + continue + } + fmt.Printf("Symmetric broadcast message received: %s\n", string(receivedBroadcast)) + if receivedCount == expectedCnt { + done = true + } + case <-timeout.C: + fmt.Println("Timed out") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) + done = true + } + } + + jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt) + err = client.StopNetworkFollower() + if err != nil { + jww.WARN.Printf( + "Failed to cleanly close threads: %+v\n", + err) + } + }, +} + +func init() { + // Single-use subcommand options + symmetricCmd.Flags().StringP("name", "", "", + "Symmetric channel name") + _ = viper.BindPFlag("name", symmetricCmd.Flags().Lookup("name")) + + symmetricCmd.Flags().StringP("rsaPub", "", "", + "Symmetric channel rsa pub key") + _ = viper.BindPFlag("rsaPub", symmetricCmd.Flags().Lookup("rsaPub")) + + symmetricCmd.Flags().StringP("salt", "", "", + "Symmetric channel salt") + _ = viper.BindPFlag("salt", symmetricCmd.Flags().Lookup("salt")) + + symmetricCmd.Flags().StringP("description", "", "", + "Symmetric channel description") + _ = viper.BindPFlag("description", symmetricCmd.Flags().Lookup("description")) + + symmetricCmd.Flags().StringP("path", "", "", + "Symmetric channel output path") + _ = viper.BindPFlag("path", symmetricCmd.Flags().Lookup("path")) + + symmetricCmd.Flags().BoolP("new", "", false, + "Create new symmetric channel") + _ = viper.BindPFlag("new", symmetricCmd.Flags().Lookup("new")) + + symmetricCmd.Flags().StringP("broadcast", "", "", + "Message to send via symmetric broadcast") + _ = viper.BindPFlag("broadcast", symmetricCmd.Flags().Lookup("broadcast")) + + rootCmd.AddCommand(symmetricCmd) +}