diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 8b898acaf44a3a7b5378e00c66cf33fad952a5f3..4c06be977f9b4033a81aa963b4ead9cc945921a2 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -6,15 +6,32 @@ import ( "gitlab.com/elixxir/client/storage/e2e" ) -// CreateAuthenticatedChannel creates a 1-way authenticated channel +// CreateAuthenticatedChannelUNSAFE creates a 1-way authenticated channel // so this user can send messages to the desired recipient Contact. // To receive confirmation from the remote user, clients must // register a listener to do that. -func (c *Client) CreateAuthenticatedChannel(recipient contact.Contact) error { +func (c *Client) CreateAuthenticatedChannelUnsafe(recipient contact.Contact) error { jww.INFO.Printf("CreateAuthenticatedChannel(%v)", recipient) sesParam := e2e.GetDefaultSessionParams() return c.storage.E2e().AddPartner(recipient.ID, recipient.DhPubKey, - sesParam, sesParam) + c.storage.E2e().GetDHPrivateKey(), sesParam, sesParam) +} + +// RequestAuthenticatedChannel sends a request to another party to establish an +// authenticated channel +// It will not run if the network status is not healthy +// An error will be returned if a channel already exists, if a request was +// already received, or if a request was already sent +// When a confirmation occurs, the channel will be created and the callback +// will be called +func (c *Client) RequestAuthenticatedChannel(recipient contact.Contact) error { + jww.INFO.Printf("RequestAuthenticatedChannel(%v)", recipient) + + if c.network. + + sesParam := e2e.GetDefaultSessionParams() + return c.storage.E2e().AddPartner(recipient.ID, recipient.DhPubKey, + c.storage.E2e().GetDHPrivateKey(), sesParam, sesParam) } // RegisterAuthConfirmationCb registers a callback for channel @@ -38,7 +55,8 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co //add the precanned user as a e2e contact sesParam := e2e.GetDefaultSessionParams() - err := c.storage.E2e().AddPartner(precan.ID, precan.DhPubKey, sesParam, sesParam) + err := c.storage.E2e().AddPartner(precan.ID, precan.DhPubKey, + c.storage.E2e().GetDHPrivateKey(), sesParam, sesParam) return precan, err } diff --git a/auth/callback.go b/auth/callback.go index 13ff991ec5fc15c33b556524828065cd815def6b..23278de7df702a5cbab6ea08037950b5fba96ee9 100644 --- a/auth/callback.go +++ b/auth/callback.go @@ -40,6 +40,9 @@ func RegisterCallbacks(rcb RequestCallback, ccb ConfirmCallback, //lookup the message, check if it is an auth request cmixMsg := format.Unmarshal(msg.Payload) fp := cmixMsg.GetKeyFP() + // this takes the request lock if it is a specific fp, + // all exits after this need to call fail or Delete if it is + // specific fpType, sr, myHistoricalPrivKey, err := authStore.GetFingerprint(fp) if err != nil { // if the lookup fails, ignore the message. It is likely @@ -76,7 +79,7 @@ func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, } //decrypt the message - success, payload, _ := cAuth.Decrypt(myHistoricalPrivKey, + success, payload := cAuth.Decrypt(myHistoricalPrivKey, partnerPubKey, baseFmt.GetSalt(), baseFmt.GetEcrPayload(), cmixMsg.GetMac(), grp) @@ -189,6 +192,7 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if m, err := storage.E2e().GetPartner(sr.GetPartner()); m != nil || err == nil { jww.WARN.Printf("Cannot confirm auth for %s, channel already "+ "exists.", sr.GetPartner()) + storage.Auth().Fail(sr.GetPartner()) return } @@ -196,17 +200,19 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) if err != nil { jww.WARN.Printf("Failed to handle auth confirm: %s", err) + storage.Auth().Fail(sr.GetPartner()) return } // decrypt the payload - success, payload, _ := cAuth.Decrypt(sr.GetMyPrivKey(), + success, payload := cAuth.Decrypt(sr.GetMyPrivKey(), partnerPubKey, baseFmt.GetSalt(), baseFmt.GetEcrPayload(), cmixMsg.GetMac(), grp) if !success { jww.WARN.Printf("Recieved auth confirmation failed its mac " + "check") + storage.Auth().Fail(sr.GetPartner()) return } @@ -214,6 +220,7 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if err != nil { jww.WARN.Printf("Failed to unmarshal auth confirmation's "+ "encrypted payload: %s", err) + storage.Auth().Fail(sr.GetPartner()) return } @@ -221,6 +228,7 @@ func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, if err := doConfirm(sr, grp, partnerPubKey, ecrFmt.GetOwnership(), storage, ccb, net); err != nil { jww.WARN.Printf("Confirmation failed: %s", err) + storage.Auth().Fail(sr.GetPartner()) return } } @@ -239,16 +247,17 @@ func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, // the second does not p := e2e.GetDefaultSessionParams() if err := storage.E2e().AddPartner(sr.GetPartner(), - partnerPubKey, p, p); err != nil { + partnerPubKey, sr.GetMyPrivKey(), p, p); err != nil { return errors.Errorf("Failed to create channel with partner (%s) "+ "after confirmation: %+v", sr.GetPartner(), err) } // delete the in progress negotiation + // this undoes the request lock if err := storage.Auth().Delete(sr.GetPartner()); err != nil { - return errors.Errorf("Failed to delete in progress negotiation "+ - "with partner (%s) after confirmation: %+v", + return errors.Errorf("UNRECOVERABLE! Failed to delete in "+ + "progress negotiation with partner (%s) after confirmation: %+v", sr.GetPartner(), err) } diff --git a/auth/confirm.go b/auth/confirm.go index ec0c321c0d3298ba7753fbf49bfffe0adaf86166..c4bdf8a225bf8ad0863293ea2c23de73459d1c45 100644 --- a/auth/confirm.go +++ b/auth/confirm.go @@ -4,15 +4,25 @@ import ( "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/interfaces/params" + "gitlab.com/elixxir/client/interfaces/utility" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/e2e" "gitlab.com/elixxir/crypto/diffieHellman" + "gitlab.com/elixxir/primitives/format" + "gitlab.com/elixxir/primitives/states" "io" cAuth "gitlab.com/elixxir/crypto/e2e/auth" + "time" + ds "gitlab.com/elixxir/comms/network/dataStructures" + jww "github.com/spf13/jwalterweatherman" ) func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, storage *storage.Session, net interfaces.NetworkManager) error { + /*edge checking*/ + // check that messages can be sent over the network if !net.GetHealthTracker().IsHealthy() { return errors.New("Cannot confirm authenticated message " + @@ -20,6 +30,8 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, } // check if the partner has an auth in progress + // this takes the lock, from this point forward any errors need to release + // the lock storedContact, err := storage.Auth().GetReceivedRequest(partner.ID) if err != nil { return errors.Errorf("failed to find a pending Auth Request: %s", @@ -28,6 +40,7 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, // verify the passed contact matches what is stored if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 { + storage.Auth().Fail(partner.ID) return errors.Errorf("Pending Auth Request has different "+ "pubkey than stored", err) @@ -35,6 +48,8 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, grp := storage.E2e().GetGroup() + /*cryptographic generation*/ + //generate ownership proof ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(), partner.DhPubKey, storage.E2e().GetGroup()) @@ -43,5 +58,92 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng) newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp) + //generate salt + salt := make([]byte, saltSize) + _, err = rng.Read(salt) + if err != nil { + storage.Auth().Fail(partner.ID) + return errors.Wrap(err, "Failed to generate salt for "+ + "confirmation") + } + + /*construct message*/ + // we build the payload before we save because it is technically fallible + // which can get into a bricked state if it fails + cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen()) + baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen()) + ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen()) + + // setup the encrypted payload + ecrFmt.SetOwnership(ownership) + // confirmation has no custom payload + + //encrypt the payload + ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey, + salt, ecrFmt.payload, grp) + + //get the fingerprint from the old ownership proof + fp := cAuth.MakeOwnershipProofFP(storedContact.OwnershipProof) + + //final construction + baseFmt.SetEcrPayload(ecrPayload) + baseFmt.SetSalt(salt) + baseFmt.SetPubKey(newPubKey) + + cmixMsg.SetKeyFP(fp) + cmixMsg.SetMac(mac) + cmixMsg.SetContents(baseFmt.Marshal()) + + // fixme: channel can get into a bricked state if the first save occurs and + // the second does not or the two occur and the storage into critical + // messages does not occur + + //create local relationship + p := e2e.GetDefaultSessionParams() + if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey, newPrivKey, + p, p); err != nil { + storage.Auth().Fail(partner.ID) + return errors.Errorf("Failed to create channel with partner (%s) "+ + "on confirmation: %+v", + partner.ID, err) + } + + // delete the in progress negotiation + // this unlocks the request lock + if err := storage.Auth().Delete(partner.ID); err != nil { + return errors.Errorf("UNRECOVERABLE! Failed to delete in "+ + "progress negotiation with partner (%s) after creating confirmation: %+v", + partner.ID, err) + } + + //store the message as a critical message so it will always be sent + storage.GetCriticalRawMessages().AddProcessing(cmixMsg) + + /*send message*/ + round, err := net.SendCMIX(cmixMsg, params.GetDefaultCMIX()) + if err != nil { + // if the send fails just set it to failed, it will but automatically + // retried + jww.ERROR.Printf("request failed to transmit, will be "+ + "handled on reconnect: %+v", err) + storage.GetCriticalRawMessages().Failed(cmixMsg) + } + + /*check message delivery*/ + sendResults := make(chan ds.EventReturn, 1) + roundEvents := net.GetInstance().GetRoundEvents() + + roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute, + states.COMPLETED, states.FAILED) + + success, _, _ := utility.TrackResults(sendResults, 1) + if !success { + jww.ERROR.Printf("request failed to transmit, will be " + + "handled on reconnect") + storage.GetCriticalRawMessages().Failed(cmixMsg) + } else { + storage.GetCriticalRawMessages().Succeeded(cmixMsg) + } + return nil } diff --git a/auth/request.go b/auth/request.go index 87d6f07beeabfbc75796dfc3da83a8d52fc152b3..fe0b621f2e9f55b71b6a245f8a64e6b6ff2e17fc 100644 --- a/auth/request.go +++ b/auth/request.go @@ -105,8 +105,9 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, requestFmt.SetID(storage.GetUser().ID) requestFmt.SetMsgPayload(msgPayloadBytes) ecrFmt.SetOwnership(ownership) - ecrPayload, mac, fp := cAuth.Encrypt(newPrivKey, partner.DhPubKey, + ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey, salt, ecrFmt.payload, grp) + fp := cAuth.MakeOwnershipProofFP(ownership) /*construct message*/ baseFmt.SetEcrPayload(ecrPayload) @@ -125,7 +126,8 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, if err != nil { return errors.Errorf("Failed to store auth request: %s", err) } - //store the message as a critical message so it will alwasy be sent + + //store the message as a critical message so it will always be sent storage.GetCriticalRawMessages().AddProcessing(cmixMsg) /*send message*/ diff --git a/go.mod b/go.mod index bbba6b21afdfa3e5106a3ec82534a5b313210da8..96468ab1eb69ad331d37e1d3f82ebe22e421d614 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/comms v0.0.0-20201006010513-3353fb46569a - gitlab.com/elixxir/crypto v0.0.0-20201014182120-fb12f042858e + gitlab.com/elixxir/crypto v0.0.0-20201020171647-d7d62fef38cb gitlab.com/elixxir/ekv v0.1.3 gitlab.com/elixxir/primitives v0.0.0-20201007223554-4a62c355bb0b gitlab.com/xx_network/comms v0.0.0-20200924225518-0c867207b1e6 diff --git a/go.sum b/go.sum index da89ee35b6488d1a3032dbd838e65a89b31fa1b5..798efd00ae42e2960edd3df0809385cd76ac979a 100644 --- a/go.sum +++ b/go.sum @@ -344,6 +344,10 @@ gitlab.com/elixxir/crypto v0.0.0-20201006010428-67a8782d097e h1:EcK8J7n0QJ5UhDqk gitlab.com/elixxir/crypto v0.0.0-20201006010428-67a8782d097e/go.mod h1:W/lkTsgaqA+8A1FKZnXFtetNLHV9VNn6IPzMYzgOiBY= gitlab.com/elixxir/crypto v0.0.0-20201014182120-fb12f042858e h1:u8tp5BrblsTuaEjQMFlGSTrkjDr4P5KN9OFmxhWkSwE= gitlab.com/elixxir/crypto v0.0.0-20201014182120-fb12f042858e/go.mod h1:P180AwIwARAbBsmaC0NO92XhXngb1l12JVl3KlxWPsM= +gitlab.com/elixxir/crypto v0.0.0-20201020171315-ebe735c2a509 h1:+qswefNfUd2ZBrCdd9otMSBUzsL4kOqNth9MWo5ESkA= +gitlab.com/elixxir/crypto v0.0.0-20201020171315-ebe735c2a509/go.mod h1:P180AwIwARAbBsmaC0NO92XhXngb1l12JVl3KlxWPsM= +gitlab.com/elixxir/crypto v0.0.0-20201020171647-d7d62fef38cb h1:ysjivRX5wW203R1KfgmzO3jB5I8kMlY7l7g9AoIPlAc= +gitlab.com/elixxir/crypto v0.0.0-20201020171647-d7d62fef38cb/go.mod h1:P180AwIwARAbBsmaC0NO92XhXngb1l12JVl3KlxWPsM= gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842 h1:m1zDQ6UadpuMnV7nvnyR+DUXE3AisRnVjajTb1xZE4c= gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842/go.mod h1:bXY0kgbV5BHYda4YY5/hiG5bjimGK+R3PYub5yM9C/s= gitlab.com/elixxir/ekv v0.1.1 h1:Em3rF8sv+tNbQGXbcpYzAS2blWRAP708JGhYlkN74Kg= diff --git a/keyExchange/confirm_test.go b/keyExchange/confirm_test.go index a9314aa0577b0c95c8c4e126424266886dcedbc4..d9fd6aa61f37d9730261e184918a81bb5502fd99 100644 --- a/keyExchange/confirm_test.go +++ b/keyExchange/confirm_test.go @@ -23,7 +23,7 @@ func TestHandleConfirm(t *testing.T) { bobPubKey := bobSession.E2e().GetDHPublicKey() // Add bob as a partner - aliceSession.E2e().AddPartner(bobID, bobPubKey, + aliceSession.E2e().AddPartner(bobID, bobPubKey, alicePrivKey, e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams()) // Generate a session ID, bypassing some business logic here diff --git a/keyExchange/exchange_test.go b/keyExchange/exchange_test.go index 71530f0c0ddbdde955ba2b9e131076780d7dd026..a4952a4ed47cce5124d29764d14a6619dfa5eaf5 100644 --- a/keyExchange/exchange_test.go +++ b/keyExchange/exchange_test.go @@ -33,15 +33,17 @@ func TestFullExchange(t *testing.T) { // Pull alice's and bob's keys for later use alicePrivKey := aliceSession.E2e().GetDHPrivateKey() alicePubKey := aliceSession.E2e().GetDHPublicKey() + bobPrivKey := bobSession.E2e().GetDHPrivateKey() bobPubKey := bobSession.E2e().GetDHPublicKey() + // Generate bob's new keypair newBobPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, genericGroup, csprng.NewSystemRNG()) newBobPubKey := dh.GeneratePublicKey(newBobPrivKey, genericGroup) // Add Alice and Bob as partners - aliceSession.E2e().AddPartner(exchangeBobId, bobPubKey, + aliceSession.E2e().AddPartner(exchangeBobId, bobPubKey, alicePrivKey, e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams()) - bobSession.E2e().AddPartner(exchangeAliceId, alicePubKey, + bobSession.E2e().AddPartner(exchangeAliceId, alicePubKey, bobPrivKey, e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams()) // Start the listeners for alice and bob diff --git a/keyExchange/rekey_test.go b/keyExchange/rekey_test.go index 8b9876ca781a0d7a44f72df8c5298d8620be3a2a..1fa381935d5f51947551ebc38942a3fec20af13b 100644 --- a/keyExchange/rekey_test.go +++ b/keyExchange/rekey_test.go @@ -23,7 +23,7 @@ func TestRekey(t *testing.T) { sessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup) // Add bob as a partner - aliceSession.E2e().AddPartner(bobID, bobPubKey, + aliceSession.E2e().AddPartner(bobID, bobPubKey, alicePrivKey, e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams()) // Get Alice's manager for Bob diff --git a/keyExchange/trigger_test.go b/keyExchange/trigger_test.go index be1cdf8db5af52c194bfd74fc99c1c7cf9d898c5..31355f48028852f9d37d57b1fbc2538c9d5cb432 100644 --- a/keyExchange/trigger_test.go +++ b/keyExchange/trigger_test.go @@ -31,7 +31,8 @@ func TestHandleTrigger(t *testing.T) { // Add bob as a partner aliceSession.E2e().AddPartner(bobID, bobSession.E2e().GetDHPublicKey(), - e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams()) + alicePrivKey, e2e.GetDefaultSessionParams(), + e2e.GetDefaultSessionParams()) // Generate a session ID, bypassing some business logic here oldSessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup) diff --git a/storage/auth/store.go b/storage/auth/store.go index 940a61f59b41839940629620212d92a76ff846aa..01bb85631b28dd3914ffbf2f43f6bba694b175eb 100644 --- a/storage/auth/store.go +++ b/storage/auth/store.go @@ -329,21 +329,22 @@ func (s *Store) GetRequest(partner *id.ID) (RequestType, *SentRequest, contact.C } } - // One of two calls after using a request. This one to be used when the use // is unsuccessful. It will allow any thread waiting on access to continue // using the structure -func (s *Store) Fail(partner *id.ID) error { +// this does not return an error because an error is not handleable +func (s *Store) Fail(partner *id.ID) { s.mux.RLock() r, ok := s.requests[*partner] s.mux.RUnlock() if !ok { - return errors.Errorf("Request not found: %s", partner) + jww.ERROR.Panicf("Request cannot be failed, not found: %s", + partner) + return } r.mux.Unlock() - return nil } // One of two calls after using a request. This one to be used when the use diff --git a/storage/e2e/store.go b/storage/e2e/store.go index a3694aafc722dfbe08068c261f15868c9bb89fc1..8399c54054b0ea7fc697d226d47d6a4ceee4a1f9 100644 --- a/storage/e2e/store.go +++ b/storage/e2e/store.go @@ -152,7 +152,7 @@ func (s *Store) save() error { return s.kv.Set(storeKey, &obj) } -func (s *Store) AddPartner(partnerID *id.ID, partnerPubKey *cyclic.Int, +func (s *Store) AddPartner(partnerID *id.ID, partnerPubKey, myPrivKey *cyclic.Int, sendParams, receiveParams SessionParams) error { s.mux.Lock() defer s.mux.Unlock() diff --git a/storage/e2e/store_test.go b/storage/e2e/store_test.go index ce8c962a75efce22c9a79beff0dec5a0097713a3..5f9c84e88960a2d6808aabc31a93ac02dc989833 100644 --- a/storage/e2e/store_test.go +++ b/storage/e2e/store_test.go @@ -87,7 +87,7 @@ func TestStore_AddPartner(t *testing.T) { expectedManager := newManager(s.context, s.kv, partnerID, s.dhPrivateKey, pubKey, p, p) - s.AddPartner(partnerID, pubKey, p, p) + s.AddPartner(partnerID, pubKey, s.dhPrivateKey, p, p) m, exists := s.managers[*partnerID] if !exists { @@ -108,7 +108,7 @@ func TestStore_GetPartner(t *testing.T) { p := GetDefaultSessionParams() expectedManager := newManager(s.context, s.kv, partnerID, s.dhPrivateKey, pubKey, p, p) - s.AddPartner(partnerID, pubKey, p, p) + s.AddPartner(partnerID, pubKey, s.dhPrivateKey, p, p) m, err := s.GetPartner(partnerID) if err != nil {