diff --git a/auth/request.go b/auth/request.go index 3a80c3c989c4ac5c755d558de46d72c94e60ec80..8fd4aaac2ca098b37654ebcbcb09e2b9435ce04f 100644 --- a/auth/request.go +++ b/auth/request.go @@ -31,6 +31,14 @@ import ( const terminator = ";" +const ( + // ErrChannelExists is a message returned in state.Request when an + // authenticated channel exists between the partner and me. + // Note that modifications to this should go over usages of this message + // in other packages (if applicable). + ErrChannelExists = "Authenticated channel already established with partner" +) + // Request sends a contact request from the user identity in the imported e2e // structure to the passed contact, as well as the passed facts (will error if // they are too long). @@ -46,8 +54,7 @@ func (s *state) Request(partner contact.Contact, myfacts fact.FactList) (id.Roun // check that an authenticated channel does not already exist if _, err := s.e2e.GetPartner(partner.ID); err == nil || !strings.Contains(err.Error(), ratchet.NoPartnerErrorStr) { - return 0, errors.Errorf("Authenticated channel already " + - "established with partner") + return 0, errors.Errorf(ErrChannelExists) } return s.request(partner, myfacts, false) diff --git a/cmd/backup.go b/cmd/backup.go index c8a5c78608785a10f8b18d2813a5a9a4197c7933..f3c7f308d30b7f3d9f286970a8f72b8c5a9c0c2a 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -24,7 +24,7 @@ import ( // loadOrInitBackup will build a new xxdk.E2e from existing storage // or from a new storage that it will create if none already exists func loadOrInitBackup(backupPath string, backupPass string, password []byte, storeDir string, - cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, cbs xxdk.AuthCallbacks) *xxdk.E2e { jww.INFO.Printf("Using Backup sender") // create a new client if none exist @@ -92,7 +92,7 @@ func loadOrInitBackup(backupPath string, backupPass string, password []byte, sto } } - messenger, err := xxdk.Login(net, authCbs, identity, e2eParams) + messenger, err := xxdk.Login(net, cbs, identity, e2eParams) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/connect.go b/cmd/connect.go new file mode 100644 index 0000000000000000000000000000000000000000..f4ecd1222be8eb9872c4c01be69145ec2c907e5e --- /dev/null +++ b/cmd/connect.go @@ -0,0 +1,581 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/connect" + "gitlab.com/elixxir/client/e2e" + "gitlab.com/elixxir/client/e2e/receive" + "gitlab.com/elixxir/client/xxdk" + "gitlab.com/xx_network/primitives/id" + "time" +) + +// connectionCmd handles the operation of connection operations within the CLI. +var connectionCmd = &cobra.Command{ + Use: "connection", + Short: "Runs clients and servers in the connections paradigm.", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + logLevel := viper.GetUint("logleval") + logPath := viper.GetString("log") + initLog(logLevel, logPath) + jww.INFO.Printf(Version()) + + statePass := parsePassword(viper.GetString("password")) + statePath := viper.GetString("session") + regCode := viper.GetString("regcode") + cmixParams, e2eParams := initParams() + forceLegacy := viper.GetBool("force-legacy") + if viper.GetBool("startServer") { + if viper.GetBool("authenticated") { + secureConnServer(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams) + } else { + insecureConnServer(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams) + } + } else { + if viper.GetBool("authenticated") { + secureConnClient(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams) + } else { + insecureConnClient(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams) + } + + } + + }, +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Connection Server Logic +//////////////////////////////////////////////////////////////////////////////////////////// + +// Secure (authenticated) connection server path +func secureConnServer(forceLegacy bool, statePass []byte, statePath, regCode string, + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) { + connChan := make(chan connect.Connection, 1) + + // Load client state and identity------------------------------------------ + net := loadOrInitNet(statePass, statePath, regCode, cmixParams) + identity := loadOrInitReceptionIdentity(forceLegacy, net) + + // Save contact file------------------------------------------------------- + writeContact(identity.GetContact()) + + // Handle incoming connections--------------------------------------------- + authCb := connect.AuthenticatedCallback( + func(connection connect.AuthenticatedConnection) { + partnerId := connection.GetPartner().PartnerId() + jww.INFO.Printf("[CONN] Received authenticated connection from %s", partnerId) + fmt.Println("Established authenticated connection with client") + + _, err := connection.RegisterListener(catalog.XxMessage, listener{"AuthServer"}) + if err != nil { + jww.FATAL.Panicf("Failed to register listener for client message!") + } + + connChan <- connection + }) + + // Start connection server------------------------------------------------- + connectionParam := connect.DefaultConnectionListParams() + connectServer, err := connect.StartAuthenticatedServer(identity, + authCb, net, e2eParams, connectionParam) + if err != nil { + jww.FATAL.Panicf("Failed to start authenticated "+ + "connection server: %v", err) + } + + fmt.Println("Established connection server, begin listening...") + jww.INFO.Printf("[CONN] Established connection server, begin listening...") + + // Start network threads--------------------------------------------------- + networkFollowerTimeout := 5 * time.Second + err = connectServer.Messenger.StartNetworkFollower(networkFollowerTimeout) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Set up a wait for the network to be connected + waitUntilConnected := func(connected chan bool) { + waitTimeout := 30 * time.Second + timeoutTimer := time.NewTimer(waitTimeout) + isConnected := false + // Wait until we connect or panic if we cannot before the timeout + for !isConnected { + select { + case isConnected = <-connected: + jww.INFO.Printf("Network Status: %v", isConnected) + break + case <-timeoutTimer.C: + jww.FATAL.Panicf("Timeout on starting network follower") + } + } + } + + // Create a tracker channel to be notified of network changes + connected := make(chan bool, 10) + // Provide a callback that will be signalled when network health + // status changes + connectServer.Messenger.GetCmix().AddHealthCallback( + func(isConnected bool) { + connected <- isConnected + }) + // Wait until connected or crash on timeout + waitUntilConnected(connected) + + // Wait for connection establishment---------------------------------------- + + // Wait for connection to be established + connectionTimeout := time.NewTimer(240 * time.Second) + select { + case conn := <-connChan: + // Perform functionality shared by client & server + miscConnectionFunctions(connectServer.Messenger, conn) + + case <-connectionTimeout.C: + connectionTimeout.Stop() + jww.FATAL.Panicf("[CONN] Failed to establish connection within " + + "default time period, closing process") + } + + // Keep server running to receive messages------------------------------------ + serverTimeout := viper.GetDuration("serverTimeout") + if serverTimeout != 0 { + timer := time.NewTimer(serverTimeout) + select { + case <-timer.C: + fmt.Println("Shutting down connection server") + timer.Stop() + return + } + } + + // If timeout is not specified, leave as long-running thread + select {} + +} + +// Insecure (unauthenticated) connection server path +func insecureConnServer(forceLegacy bool, statePass []byte, statePath, regCode string, + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) { + + connChan := make(chan connect.Connection, 1) + + // Load client state and identity------------------------------------------ + net := loadOrInitNet(statePass, statePath, regCode, cmixParams) + identity := loadOrInitReceptionIdentity(forceLegacy, net) + + // Save contact file------------------------------------------------------- + writeContact(identity.GetContact()) + + // Handle incoming connections--------------------------------------------- + cb := connect.Callback(func(connection connect.Connection) { + partnerId := connection.GetPartner().PartnerId() + jww.INFO.Printf("[CONN] Received connection request from %s", partnerId) + fmt.Println("Established connection with client") + + _, err := connection.RegisterListener(catalog.XxMessage, listener{"ConnectionServer"}) + if err != nil { + jww.FATAL.Panicf("Failed to register listener for client message!") + } + + connChan <- connection + }) + + // Start connection server------------------------------------------------- + connectionParam := connect.DefaultConnectionListParams() + connectServer, err := connect.StartServer(identity, + cb, net, e2eParams, connectionParam) + if err != nil { + jww.FATAL.Panicf("[CONN] Failed to start connection server: %v", err) + } + + fmt.Println("Established connection server, begin listening...") + jww.INFO.Printf("[CONN] Established connection server, begin listening...") + + // Start network threads--------------------------------------------------- + networkFollowerTimeout := 5 * time.Second + err = connectServer.Messenger.StartNetworkFollower(networkFollowerTimeout) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Set up a wait for the network to be connected + waitUntilConnected := func(connected chan bool) { + waitTimeout := 30 * time.Second + timeoutTimer := time.NewTimer(waitTimeout) + isConnected := false + // Wait until we connect or panic if we cannot before the timeout + for !isConnected { + select { + case isConnected = <-connected: + jww.INFO.Printf("Network Status: %v", isConnected) + break + case <-timeoutTimer.C: + jww.FATAL.Panicf("Timeout on starting network follower") + } + } + } + + // Create a tracker channel to be notified of network changes + connected := make(chan bool, 10) + // Provide a callback that will be signalled when network health + // status changes + connectServer.Messenger.GetCmix().AddHealthCallback( + func(isConnected bool) { + connected <- isConnected + }) + // Wait until connected or crash on timeout + waitUntilConnected(connected) + + // Wait for connection establishment---------------------------------------- + + // Wait for connection to be established + connectionTimeout := time.NewTimer(240 * time.Second) + select { + case conn := <-connChan: + // Perform functionality shared by client & server + miscConnectionFunctions(connectServer.Messenger, conn) + + case <-connectionTimeout.C: + connectionTimeout.Stop() + jww.FATAL.Panicf("[CONN] Failed to establish connection within " + + "default time period, closing process") + } + + // Keep server running to receive messages------------------------------------ + if viper.GetDuration("serverTimeout") != 0 { + timer := time.NewTimer(viper.GetDuration("serverTimeout")) + select { + case <-timer.C: + fmt.Println("Shutting down connection server") + timer.Stop() + return + } + } + + // If timeout is not specified, leave as long-running thread + select {} + +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Connection Client Logic +//////////////////////////////////////////////////////////////////////////////////////////// + +// Secure (authenticated) connection client path +func secureConnClient(forceLegacy bool, statePass []byte, statePath, regCode string, + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) { + // Load client ------------------------------------------------------------------ + messenger := loadOrInitMessenger(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams, xxdk.DefaultAuthCallbacks{}) + + // Start network threads--------------------------------------------------------- + + // Set networkFollowerTimeout to a value of your choice (seconds) + networkFollowerTimeout := 5 * time.Second + err := messenger.StartNetworkFollower(networkFollowerTimeout) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Set up a wait for the network to be connected + waitUntilConnected := func(connected chan bool) { + waitTimeout := 30 * time.Second + timeoutTimer := time.NewTimer(waitTimeout) + isConnected := false + // Wait until we connect or panic if we cannot before the timeout + for !isConnected { + select { + case isConnected = <-connected: + jww.INFO.Printf("Network Status: %v", isConnected) + break + case <-timeoutTimer.C: + jww.FATAL.Panicf("Timeout on starting network follower") + } + } + } + + // Create a tracker channel to be notified of network changes + connected := make(chan bool, 10) + // Provide a callback that will be signalled when network + // health status changes + messenger.GetCmix().AddHealthCallback( + func(isConnected bool) { + connected <- isConnected + }) + // Wait until connected or crash on timeout + waitUntilConnected(connected) + + // Connect with the server------------------------------------------------- + contactPath := viper.GetString("connect") + serverContact := getContactFromFile(contactPath) + fmt.Println("Sending connection request") + + // Establish connection with partner + conn, err := connect.ConnectWithAuthentication(serverContact, messenger, + e2eParams) + if err != nil { + jww.FATAL.Panicf("[CONN] Failed to build connection with %s: %v", + serverContact.ID, err) + } + + jww.INFO.Printf("[CONN] Established authenticated connection with %s", + conn.GetPartner().PartnerId()) + fmt.Println("Established authenticated connection with server.") + + miscConnectionFunctions(messenger, conn) + +} + +// Insecure (unauthenticated) connection client path +func insecureConnClient(forceLegacy bool, statePass []byte, statePath, regCode string, + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) { + + // Load client ------------------------------------------------------------------ + messenger := loadOrInitMessenger(forceLegacy, statePass, statePath, regCode, + cmixParams, e2eParams, xxdk.DefaultAuthCallbacks{}) + + // Start network threads--------------------------------------------------------- + + // Set networkFollowerTimeout to a value of your choice (seconds) + networkFollowerTimeout := 5 * time.Second + err := messenger.StartNetworkFollower(networkFollowerTimeout) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } + + // Set up a wait for the network to be connected + waitUntilConnected := func(connected chan bool) { + waitTimeout := 30 * time.Second + timeoutTimer := time.NewTimer(waitTimeout) + isConnected := false + // Wait until we connect or panic if we cannot before the timeout + for !isConnected { + select { + case isConnected = <-connected: + jww.INFO.Printf("Network Status: %v", isConnected) + break + case <-timeoutTimer.C: + jww.FATAL.Panicf("Timeout on starting network follower") + } + } + } + + // Create a tracker channel to be notified of network changes + connected := make(chan bool, 10) + // Provide a callback that will be signalled when network + // health status changes + messenger.GetCmix().AddHealthCallback( + func(isConnected bool) { + connected <- isConnected + }) + // Wait until connected or crash on timeout + waitUntilConnected(connected) + + // Connect with the server------------------------------------------------- + contactPath := viper.GetString("connect") + serverContact := getContactFromFile(contactPath) + fmt.Println("Sending connection request") + jww.INFO.Printf("[CONN] Sending connection request to %s", + serverContact.ID) + + // Establish connection with partner + handler, err := connect.Connect(serverContact, messenger, + e2eParams) + if err != nil { + jww.FATAL.Panicf("[CONN] Failed to build connection with %s: %v", + serverContact.ID, err) + + } + + fmt.Println("Established connection with server") + jww.INFO.Printf("[CONN] Established connection with %s", handler.GetPartner().PartnerId()) + + miscConnectionFunctions(messenger, handler) +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Misc Logic (shared between client & server) +//////////////////////////////////////////////////////////////////////////////////////////// + +// miscConnectionFunctions contains miscellaneous functionality for the subcommand connect. +// This functionality should be shared between client & server. +func miscConnectionFunctions(client *xxdk.E2e, conn connect.Connection) { + // Send a message to connection partner-------------------------------------------- + msgBody := viper.GetString("message") + paramsE2E := e2e.GetDefaultParams() + if msgBody != "" { + // Send message + jww.INFO.Printf("[CONN] Sending message to %s", + conn.GetPartner().PartnerId()) + payload := []byte(msgBody) + for { + roundIDs, _, _, err := conn.SendE2E(catalog.XxMessage, payload, + paramsE2E) + if err != nil { + jww.FATAL.Panicf("[CONN] Failed to send E2E message: %v", err) + } + + // Verify message sends were successful when verifySendFlag is present + if viper.GetBool("verify-sends") { + retryChan := make(chan struct{}) + done := make(chan struct{}, 1) + + // Construct the callback function which + // verifies successful message send or retries + f := func(allRoundsSucceeded, timedOut bool, + rounds map[id.Round]cmix.RoundResult) { + printRoundResults( + rounds, roundIDs, payload, conn.GetPartner().PartnerId()) + if !allRoundsSucceeded { + retryChan <- struct{}{} + } else { + done <- struct{}{} + } + } + + // Monitor rounds for results + err := client.GetCmix().GetRoundResults( + paramsE2E.CMIXParams.Timeout, f, roundIDs...) + if err != nil { + jww.DEBUG.Printf("Could not verify messages were sent " + + "successfully, resending messages...") + continue + } + + select { + case <-retryChan: + // On a retry, go to the top of the loop + jww.DEBUG.Printf("Messages were not sent successfully," + + " resending messages...") + continue + case <-done: + // Close channels on verification success + close(done) + close(retryChan) + break + } + + } + jww.INFO.Printf("[CONN] Sent message %q to %s", msgBody, + conn.GetPartner().PartnerId()) + fmt.Printf("Sent message %q to connection partner.\n", msgBody) + break + } + } + + // Disconnect from connection partner-------------------------------------------- + if viper.GetBool("disconnect") { + // Close the connection + if err := conn.Close(); err != nil { + jww.FATAL.Panicf("Failed to disconnect with %s: %v", + conn.GetPartner().PartnerId(), err) + } + jww.INFO.Printf("[CONN] Disconnected from %s", + conn.GetPartner().PartnerId()) + fmt.Println("Disconnected from partner") + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Recreated Callback & Listener for connection testing +/////////////////////////////////////////////////////////////////////////////// + +//var connAuthCbs *authConnHandler + +// listener implements the receive.Listener interface +type listener struct { + name string +} + +// Hear will be called whenever a message matching +// the RegisterListener call is received +// User-defined message handling logic goes here +func (l listener) Hear(item receive.Message) { + fmt.Printf("%s heard message \"%s\"\n", l.name, string(item.Payload)) +} + +// Name is used for debugging purposes +func (l listener) Name() string { + return l.name +} + +/////////////////////////////////////////////////////////////////////////////// +// Command Line Flags / +/////////////////////////////////////////////////////////////////////////////// + +// init initializes commands and flags for Cobra. +func init() { + connectionCmd.Flags().String("connect", "", + "This flag is a client side operation. "+ + "This flag expects a path to a contact file (similar "+ + "to destfile). It will parse this into an contact object,"+ + " referred to as a server contact. The client will "+ + "establish a connection with the server contact. "+ + "If a connection already exists between "+ + "the client and the server, this will be used instead of "+ + "resending a connection request to the server.") + err := viper.BindPFlag("connect", connectionCmd.Flags().Lookup("connect")) + if err != nil { + jww.ERROR.Printf("viper.BindPFlag failed for %q: %+v", "connect", err) + } + + connectionCmd.Flags().Bool("startServer", false, + "This flag is a server-side operation and takes no arguments. "+ + "This initiates a connection server. "+ + "Calling this flag will have this process call "+ + "connection.StartServer().") + err = viper.BindPFlag("startServer", connectionCmd.Flags().Lookup("startServer")) + if err != nil { + jww.ERROR.Printf("viper.BindPFlag failed for %q: %+v", "startServer", err) + } + + connectionCmd.Flags().Duration("serverTimeout", time.Duration(0), + "This flag is a connection parameter. "+ + "This takes as an argument a time.Duration. "+ + "This duration specifies how long a server will run before "+ + "closing. Without this flag present, a server will be "+ + "long-running.") + err = viper.BindPFlag("serverTimeout", connectionCmd.Flags().Lookup("serverTimeout")) + if err != nil { + jww.ERROR.Printf("viper.BindPFlag failed for %q: %+v", "serverTimeout", err) + } + + connectionCmd.Flags().Bool("disconnect", false, + "This flag is available to both server and client. "+ + "This uses a contact object from a file specified by --destfile."+ + "This will close the connection with the given contact "+ + "if it exists.") + err = viper.BindPFlag("disconnect", connectionCmd.Flags().Lookup("disconnect")) + if err != nil { + jww.ERROR.Printf("viper.BindPFlag failed for %q: %+v", "disconnect", err) + } + + connectionCmd.Flags().Bool("authenticated", false, + "This flag is available to both server and client. "+ + "This flag operates as a switch for the authenticated code-path. "+ + "With this flag present, any additional connection related flags"+ + " will call the applicable authenticated counterpart") + err = viper.BindPFlag("authenticated", connectionCmd.Flags().Lookup("authenticated")) + if err != nil { + jww.ERROR.Printf("viper.BindPFlag failed for %q: %+v", "authenticated", err) + } + + rootCmd.AddCommand(connectionCmd) +} diff --git a/cmd/init.go b/cmd/init.go index 993ee81dba5aa347f37be1159073e9e00ff6e2bb..86356025bda36b5ca713c5a4b9664ba92471a24f 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -74,9 +74,20 @@ func init() { // loadOrInitMessenger will build a new xxdk.E2e from existing storage // or from a new storage that it will create if none already exists func loadOrInitMessenger(forceLegacy bool, password []byte, storeDir, regCode string, - cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, cbs xxdk.AuthCallbacks) *xxdk.E2e { jww.INFO.Printf("Using normal sender") + net := loadOrInitNet(password, storeDir, regCode, cmixParams) + identity := loadOrInitReceptionIdentity(forceLegacy, net) + messenger, err := xxdk.Login(net, cbs, identity, e2eParams) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + return messenger +} + +func loadOrInitNet(password []byte, storeDir, regCode string, + cmixParams xxdk.CMIXParams) *xxdk.Cmix { // create a new client if none exist if _, err := os.Stat(storeDir); errors.Is(err, fs.ErrNotExist) { // Initialize from scratch @@ -97,6 +108,10 @@ func loadOrInitMessenger(forceLegacy bool, password []byte, storeDir, regCode st jww.FATAL.Panicf("%+v", err) } + return net +} + +func loadOrInitReceptionIdentity(forceLegacy bool, net *xxdk.Cmix) xxdk.ReceptionIdentity { // Load or initialize xxdk.ReceptionIdentity storage identity, err := xxdk.LoadReceptionIdentity(identityStorageKey, net) if err != nil { @@ -116,17 +131,14 @@ func loadOrInitMessenger(forceLegacy bool, password []byte, storeDir, regCode st } } - messenger, err := xxdk.Login(net, authCbs, identity, e2eParams) - if err != nil { - jww.FATAL.Panicf("%+v", err) - } - return messenger + return identity + } // loadOrInitVanity will build a new xxdk.E2e from existing storage // or from a new storage that it will create if none already exists func loadOrInitVanity(password []byte, storeDir, regCode, userIdPrefix string, - cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, cbs xxdk.AuthCallbacks) *xxdk.E2e { jww.INFO.Printf("Using Vanity sender") // create a new client if none exist @@ -163,7 +175,7 @@ func loadOrInitVanity(password []byte, storeDir, regCode, userIdPrefix string, } } - messenger, err := xxdk.Login(net, authCbs, identity, e2eParams) + messenger, err := xxdk.Login(net, cbs, identity, e2eParams) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/precan.go b/cmd/precan.go index 84076dff5472239cd1310cab37949d16f7d29b93..90e3c31b2352bbac53973faba5a91fdc0a29c024 100644 --- a/cmd/precan.go +++ b/cmd/precan.go @@ -24,7 +24,7 @@ import ( // loadOrInitPrecan will build a new xxdk.E2e from existing storage // or from a new storage that it will create if none already exists func loadOrInitPrecan(precanId uint, password []byte, storeDir string, - cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, cbs xxdk.AuthCallbacks) *xxdk.E2e { jww.INFO.Printf("Using Precanned sender") // create a new client if none exist @@ -60,7 +60,7 @@ func loadOrInitPrecan(precanId uint, password []byte, storeDir string, } } - messenger, err := xxdk.Login(net, authCbs, identity, e2eParams) + messenger, err := xxdk.Login(net, cbs, identity, e2eParams) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/proto.go b/cmd/proto.go index b8585ca784a1751be5ec1a0be99143d81a2e82ba..9d6cacbe9ce3e6c31a04df410f28d690808dce99 100644 --- a/cmd/proto.go +++ b/cmd/proto.go @@ -22,7 +22,7 @@ import ( // loadOrInitProto will build a new xxdk.E2e from existing storage // or from a new storage that it will create if none already exists func loadOrInitProto(protoUserPath string, password []byte, storeDir string, - cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { + cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, cbs xxdk.AuthCallbacks) *xxdk.E2e { jww.INFO.Printf("Using Proto sender") // create a new client if none exist @@ -70,7 +70,7 @@ func loadOrInitProto(protoUserPath string, password []byte, storeDir string, } } - messenger, err := xxdk.Login(net, authCbs, identity, e2eParams) + messenger, err := xxdk.Login(net, cbs, identity, e2eParams) if err != nil { jww.FATAL.Panicf("%+v", err) } diff --git a/cmd/root.go b/cmd/root.go index 4b360c8d188b767768548bf2d2904a46a1f0d5db..ba5e285ac55aed54ce0dc1480dacc807fdb7a7ec 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -595,15 +595,15 @@ func initE2e(cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e { // Initialize the client of the proper type var messenger *xxdk.E2e if precanId != 0 { - messenger = loadOrInitPrecan(precanId, storePassword, storeDir, cmixParams, e2eParams) + messenger = loadOrInitPrecan(precanId, storePassword, storeDir, cmixParams, e2eParams, authCbs) } else if protoUserPath != "" { - messenger = loadOrInitProto(protoUserPath, storePassword, storeDir, cmixParams, e2eParams) + messenger = loadOrInitProto(protoUserPath, storePassword, storeDir, cmixParams, e2eParams, authCbs) } else if userIdPrefix != "" { - messenger = loadOrInitVanity(storePassword, storeDir, regCode, userIdPrefix, cmixParams, e2eParams) + messenger = loadOrInitVanity(storePassword, storeDir, regCode, userIdPrefix, cmixParams, e2eParams, authCbs) } else if backupPath != "" { - messenger = loadOrInitBackup(backupPath, backupPass, storePassword, storeDir, cmixParams, e2eParams) + messenger = loadOrInitBackup(backupPath, backupPass, storePassword, storeDir, cmixParams, e2eParams, authCbs) } else { - messenger = loadOrInitMessenger(forceLegacy, storePassword, storeDir, regCode, cmixParams, e2eParams) + messenger = loadOrInitMessenger(forceLegacy, storePassword, storeDir, regCode, cmixParams, e2eParams, authCbs) } // Handle protoUser output diff --git a/connect/authenticated.go b/connect/authenticated.go index 11f0d2de9d92cb2b42e34dab9618b61d48d77328..7e16fd0f822c7aae8b758c978ac868f23ce8dfd1 100644 --- a/connect/authenticated.go +++ b/connect/authenticated.go @@ -71,8 +71,8 @@ func ConnectWithAuthentication(recipient contact.Contact, messenger *xxdk.E2e, if err != nil { return nil, err } - return connectWithAuthentication(conn, timeStart, recipient, identity.Salt, privKey, - messenger.GetRng(), messenger.GetCmix(), p) + return connectWithAuthentication(conn, timeStart, recipient, + identity.Salt, privKey, messenger.GetRng(), messenger.GetCmix(), p) } // connectWithAuthentication builds and sends an IdentityAuthentication to @@ -175,7 +175,7 @@ func connectWithAuthentication(conn Connection, timeStart time.Time, // authenticate themselves. An established AuthenticatedConnection will // be passed via the callback. func StartAuthenticatedServer(identity xxdk.ReceptionIdentity, - cb AuthenticatedCallback, net *xxdk.Cmix, p xxdk.E2EParams, + authCb AuthenticatedCallback, net *xxdk.Cmix, p xxdk.E2EParams, clParams ConnectionListParams) ( *ConnectionServer, error) { @@ -187,7 +187,7 @@ func StartAuthenticatedServer(identity xxdk.ReceptionIdentity, // be passed along via the AuthenticatedCallback _, err := connection.RegisterListener( catalog.ConnectionAuthenticationRequest, - buildAuthConfirmationHandler(cb, connection)) + buildAuthConfirmationHandler(authCb, connection)) if err != nil { jww.ERROR.Printf( "Failed to register listener on connection with %s: %+v", diff --git a/connect/client.go b/connect/client.go index a916bdca352da2e7aee21d0b2549702acfeebfdf..809c5da3017ba707dd9037ce624ae1045f3f252f 100644 --- a/connect/client.go +++ b/connect/client.go @@ -21,21 +21,11 @@ func buildClientAuthRequest(newPartner partner.Manager, rng *fastRNG.StreamGenerator, rsaPrivKey *rsa.PrivateKey, salt []byte) ([]byte, error) { - // The connection fingerprint (hashed) will be used as a nonce + // Create signature connectionFp := newPartner.ConnectionFingerprint().Bytes() - opts := rsa.NewDefaultOptions() - h := opts.Hash.New() - h.Write(connectionFp) - nonce := h.Sum(nil) - - // Sign the connection fingerprint stream := rng.GetStream() defer stream.Close() - signature, err := rsa.Sign(stream, rsaPrivKey, - opts.Hash, nonce, opts) - if err != nil { - return nil, errors.Errorf("failed to sign nonce: %+v", err) - } + signature, err := sign(stream, rsaPrivKey, connectionFp) // Construct message pemEncodedRsaPubKey := rsa.CreatePublicKeyPem(rsaPrivKey.GetPublic()) @@ -44,11 +34,12 @@ func buildClientAuthRequest(newPartner partner.Manager, RsaPubKey: pemEncodedRsaPubKey, Salt: salt, } + + // Marshal message payload, err := proto.Marshal(iar) if err != nil { return nil, errors.Errorf("failed to marshal identity request "+ "message: %+v", err) } - return payload, nil } diff --git a/connect/connect.go b/connect/connect.go index ea755c0b181cbace7c64f2bb8878dcfd612b022e..6e48d0532ed2f3f04c2adb7d0813e29432517614 100644 --- a/connect/connect.go +++ b/connect/connect.go @@ -8,6 +8,7 @@ package connect import ( "io" + "strings" "sync/atomic" "time" @@ -95,6 +96,16 @@ func Connect(recipient contact.Contact, messenger *xxdk.E2e, // Perform the auth request _, err := messenger.GetAuth().Request(recipient, nil) if err != nil { + // Return connection if a partnership already exists + if strings.Contains(err.Error(), auth.ErrChannelExists) { + newPartner, err := messenger.GetE2E().GetPartner(recipient.ID) + if err != nil { + return nil, err + } + conn := BuildConnection(newPartner, + messenger.GetE2E(), messenger.GetAuth(), p) + return conn, nil + } return nil, err } diff --git a/connect/crypto.go b/connect/crypto.go new file mode 100644 index 0000000000000000000000000000000000000000..391aa1b2778dd936754182f78f57e5fa80854f39 --- /dev/null +++ b/connect/crypto.go @@ -0,0 +1,56 @@ +package connect + +import ( + "github.com/pkg/errors" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/xx" + "gitlab.com/xx_network/primitives/id" + "io" +) + +// Sign creates a signature authenticating an identity for a connection. +func sign(rng io.Reader, rsaPrivKey *rsa.PrivateKey, + connectionFp []byte) ([]byte, error) { + // The connection fingerprint (hashed) will be used as a nonce + opts := rsa.NewDefaultOptions() + h := opts.Hash.New() + h.Write(connectionFp) + nonce := h.Sum(nil) + + // Sign the connection fingerprint + return rsa.Sign(rng, rsaPrivKey, + opts.Hash, nonce, opts) + +} + +// Verify takes a signature for an authentication attempt +// and verifies the information. +func verify(partnerId *id.ID, partnerPubKey *rsa.PublicKey, + signature, connectionFp, salt []byte) error { + + // Verify the partner's known ID against the information passed + // along the wire + partnerWireId, err := xx.NewID(partnerPubKey, salt, id.User) + if err != nil { + return err + } + + if !partnerId.Cmp(partnerWireId) { + return errors.New("Failed confirm partner's ID over the wire") + } + + // Hash the connection fingerprint + opts := rsa.NewDefaultOptions() + h := opts.Hash.New() + h.Write(connectionFp) + nonce := h.Sum(nil) + + // Verify the signature + err = rsa.Verify(partnerPubKey, opts.Hash, nonce, signature, opts) + if err != nil { + return err + } + + return nil + +} diff --git a/connect/crypto_test.go b/connect/crypto_test.go new file mode 100644 index 0000000000000000000000000000000000000000..633e47a700e98034df89df6f51192a4c6117a790 --- /dev/null +++ b/connect/crypto_test.go @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the LICENSE file // +//////////////////////////////////////////////////////////////////////////////////////////// + +package connect + +import ( + "bytes" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/crypto/xx" + "gitlab.com/xx_network/primitives/id" + "testing" +) + +var expectedSig = []byte{139, 67, 63, 6, 185, 76, 60, 217, 163, 84, 251, 231, + 197, 6, 33, 179, 53, 66, 88, 75, 105, 191, 16, 71, 126, 4, 16, 11, 41, + 237, 34, 245, 242, 97, 44, 58, 154, 120, 58, 235, 240, 140, 223, 80, 232, + 51, 94, 247, 226, 217, 79, 194, 215, 46, 187, 157, 55, 167, 180, 179, 12, + 228, 205, 98, 132, 200, 146, 180, 142, 0, 230, 79, 0, 129, 39, 205, 67, + 79, 252, 62, 187, 125, 130, 232, 125, 41, 99, 63, 106, 79, 234, 131, 109, + 103, 189, 149, 45, 169, 227, 85, 164, 121, 103, 254, 19, 224, 236, 28, 187, + 38, 240, 132, 192, 227, 145, 140, 56, 196, 91, 48, 228, 242, 123, 142, 123, + 221, 159, 160} + +type CountingReader struct { + count uint8 +} + +// Read just counts until 254 then starts over again +func (c *CountingReader) Read(b []byte) (int, error) { + for i := 0; i < len(b); i++ { + c.count = (c.count + 1) % 255 + b[i] = c.count + } + return len(b), nil +} + +func TestSignVerify_Consistency(t *testing.T) { + // use insecure seeded rng to reproduce key + notRand := &CountingReader{count: uint8(0)} + + privKey, err := rsa.GenerateKey(notRand, 1024) + if err != nil { + t.Fatalf("SignVerify error: "+ + "Could not generate key: %v", err.Error()) + } + + connFp := []byte("connFp") + + signature, err := sign(notRand, privKey, connFp) + if err != nil { + t.Logf("Sign error: %v", err) + } + + salt := make([]byte, 32) + copy(salt, "salt") + + partnerId, err := xx.NewID(privKey.GetPublic(), salt, id.User) + if err != nil { + t.Fatalf("NewId error: %v", err) + } + + err = verify(partnerId, privKey.GetPublic(), signature, connFp, salt) + if err != nil { + t.Fatalf("Verify error: %v", err) + } + + if !bytes.Equal(signature, expectedSig) { + t.Errorf("Consistency test failed."+ + "\nExpected: %v"+ + "\nReceived: %v", expectedSig, signature) + } +} diff --git a/connect/server.go b/connect/server.go index 78e22eed6fedfbe965ac89dcd8cbdf15579d4a26..b9f77b28b05b5f9af692fce2f532808a82b02e99 100644 --- a/connect/server.go +++ b/connect/server.go @@ -9,11 +9,9 @@ package connect import ( "github.com/golang/protobuf/proto" - "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/e2e/receive" "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/crypto/xx" "gitlab.com/xx_network/primitives/id" ) @@ -41,7 +39,7 @@ type serverListener struct { // buildAuthConfirmationHandler returns a serverListener object. // This will handle incoming identity authentication confirmations // via the serverListener.Hear method. A successful AuthenticatedConnection -// will be passed along via the serverListener.connectionCallback +// will be passed along via the serverListener.connectionCallback. func buildAuthConfirmationHandler(cb AuthenticatedCallback, connection Connection) server { return &serverListener{ @@ -62,41 +60,19 @@ func (a serverListener) Hear(item receive.Message) { return } - // Process the PEM encoded public key to an rsa.PublicKey object - partnerPubKey, err := rsa.LoadPublicKeyFromPem(iar.RsaPubKey) - if err != nil { - a.handleAuthConfirmationErr(err, item.Sender) - return - } - - // Get the new partner + // Get the new partner's connection fingerprint newPartner := a.conn.GetPartner() + connectionFp := newPartner.ConnectionFingerprint().Bytes() - // Verify the partner's known ID against the information passed - // along the wire - partnerWireId, err := xx.NewID(partnerPubKey, iar.Salt, id.User) + // Process the PEM encoded public key to an rsa.PublicKey object + partnerPubKey, err := rsa.LoadPublicKeyFromPem(iar.RsaPubKey) if err != nil { a.handleAuthConfirmationErr(err, item.Sender) - return } - if !newPartner.PartnerId().Cmp(partnerWireId) { - err = errors.New("Failed confirm partner's ID over the wire") - a.handleAuthConfirmationErr(err, item.Sender) - return - } - - // The connection fingerprint (hashed) will be used as a nonce - connectionFp := newPartner.ConnectionFingerprint().Bytes() - - // Hash the connection fingerprint - opts := rsa.NewDefaultOptions() - h := opts.Hash.New() - h.Write(connectionFp) - nonce := h.Sum(nil) - - // Verify the signature - err = rsa.Verify(partnerPubKey, opts.Hash, nonce, iar.Signature, opts) + // Verify the signature within the message + err = verify(newPartner.PartnerId(), partnerPubKey, + iar.Signature, connectionFp, iar.Salt) if err != nil { a.handleAuthConfirmationErr(err, item.Sender) return