From 99fa093df7f9fc0926ecc4a8efbc0ab6fdbc5a85 Mon Sep 17 00:00:00 2001 From: Benjamin Wenger <ben@elixxir.ioo> Date: Fri, 16 Oct 2020 16:17:23 -0700 Subject: [PATCH] finished ownership and callbacks --- auth/callback.go | 263 +++++++++++++++++++++++++++++++++- auth/confirm.go | 35 +++++ auth/fmt.go | 8 +- auth/request.go | 8 +- auth/verify.go | 13 ++ interfaces/message/type.go | 5 +- network/message/handler.go | 2 +- network/message/sendE2E.go | 5 +- network/message/sendUnsafe.go | 5 +- storage/auth/sentRequest.go | 51 ++++--- storage/auth/store.go | 18 ++- storage/e2e/manager.go | 8 ++ 12 files changed, 381 insertions(+), 40 deletions(-) create mode 100644 auth/confirm.go create mode 100644 auth/verify.go diff --git a/auth/callback.go b/auth/callback.go index 50ab60557..ffb89bf72 100644 --- a/auth/callback.go +++ b/auth/callback.go @@ -1,37 +1,288 @@ package auth import ( + "github.com/pkg/errors" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/contact" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" + "gitlab.com/elixxir/client/storage/auth" + "gitlab.com/elixxir/client/storage/e2e" + "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" - "time" + cAuth "gitlab.com/elixxir/crypto/e2e/auth" + jww "github.com/spf13/jwalterweatherman" + "io" + "strings" ) type RequestCallback func(requestor contact.Contact, message string) type ConfirmCallback func(partner contact.Contact) func RegisterCallbacks(rcb RequestCallback, ccb ConfirmCallback, - sw interfaces.Switchboard, storage *storage.Session) stoppable.Stoppable { + sw interfaces.Switchboard, storage *storage.Session, + net interfaces.NetworkManager, rng io.Reader) stoppable.Stoppable { rawMessages := make(chan message.Receive, 1000) sw.RegisterChannel("Auth", &id.ID{}, message.Raw, rawMessages) stop := stoppable.NewSingle("Auth") + authStore := storage.Auth() + grp := storage.E2e().GetGroup() go func() { select { case <-stop.Quit(): return case msg := <-rawMessages: - //check the message is well formed - if msglen(msg.Payload) != 2* - cmixMsg := format.Unmarshal(msg.Payload) - } + //lookup the message, check if it is an auth request + cmixMsg := format.Unmarshal(msg.Payload) + fp := cmixMsg.GetKeyFP() + fpType, sr, myHistoricalPrivKey, err := authStore.GetFingerprint(fp) + if err != nil { + // if the lookup fails, ignore the message. It is likely + // garbled or for a different protocol + break + } + + //denote that the message is not garbled + storage.GetGarbledMessages().Remove(cmixMsg) + switch fpType { + // if it is general, that means a new request has been received + case auth.General: + handleRequest(cmixMsg, myHistoricalPrivKey, grp, storage, rcb, + ccb, net) + // if it is specific, that means the original request was sent + // by this users and a confirmation has been received + case auth.Specific: + handleConfirm(cmixMsg, sr, ccb, storage, grp, net) + } + } }() + return stop +} + +func handleRequest(cmixMsg format.Message, myHistoricalPrivKey *cyclic.Int, + grp *cyclic.Group, storage *storage.Session, rcb RequestCallback, + ccb ConfirmCallback, net interfaces.NetworkManager) { + //decode the outer format + baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) + if err != nil { + jww.WARN.Printf("Failed to handle auth request: %s", err) + return + } + + //decrypt the message + success, payload, _ := cAuth.Decrypt(myHistoricalPrivKey, + partnerPubKey, baseFmt.GetSalt(), baseFmt.GetEcrPayload(), + cmixMsg.GetMac(), grp) + + if !success { + jww.WARN.Printf("Recieved auth request failed " + + "its mac check") + return + } + + //decode the ecr format + ecrFmt, err := unmarshalEcrFormat(payload) + if err != nil { + jww.WARN.Printf("Failed to unmarshal auth "+ + "request's encrypted payload: %s", err) + return + } + + //decode the request format + requestFmt, err := newRequestFormat(ecrFmt) + if err != nil { + jww.WARN.Printf("Failed to unmarshal auth "+ + "request's internal payload: %s", err) + return + } + + partnerID, err := requestFmt.GetID() + if err != nil { + jww.WARN.Printf("Failed to unmarshal auth "+ + "request's sender ID: %s", err) + return + } + + /*do state edge checks*/ + // check if a relationship already exists. + // if it does and the keys used are the same as we have, send a + // confirmation in case there are state issues. + // do not store + if _, err := storage.E2e().GetPartner(partnerID); err == nil { + jww.WARN.Printf("Recieved Auth request for %s, "+ + "channel already exists. Ignoring", partnerID) + //exit + return + } else { + //check if the relationship already exists, + rType, sr2, _, err := storage.Auth().GetRequest(partnerID) + if err != nil && !strings.Contains(err.Error(), auth.NoRequest) { + // if another error is recieved, print it and exist + jww.WARN.Printf("Recieved new Auth request for %s, "+ + "internal lookup produced bad result: %+v", + partnerID, err) + return + } else { + //handle the events where the relationship already exists + switch rType { + // if this is a duplicate, ignore the message + case auth.Receive: + jww.WARN.Printf("Recieved new Auth request for %s, "+ + "is a duplicate", partnerID) + return + // if we sent a request, then automatically confirm + // then exit, nothing else needed + case auth.Sent: + // do the confirmation + if err := doConfirm(sr2, grp, partnerPubKey, ecrFmt.GetOwnership(), + storage, ccb, net); err != nil { + jww.WARN.Printf("Confirmation failed: %s", err) + } + //exit + return + } + } + } + + //process the inner payload + facts, msg, err := contact.UnstringifyFacts( + string(requestFmt.msgPayload)) + if err != nil { + jww.WARN.Printf("failed to parse facts and message "+ + "from Auth Request: %s", err) + return + } + + //create the contact + c := contact.Contact{ + ID: partnerID, + DhPubKey: partnerPubKey, + OwnershipProof: copySlice(ecrFmt.ownership), + Facts: facts, + } + + //create the auth storage + if err = storage.Auth().AddReceived(c); err != nil { + jww.WARN.Printf("failed to store contact Auth "+ + "Request: %s", err) + return + } + + //call the callback + + go rcb(c, msg) + return +} + +func handleConfirm(cmixMsg format.Message, sr *auth.SentRequest, + ccb ConfirmCallback, storage *storage.Session, grp *cyclic.Group, + net interfaces.NetworkManager) { + // check if relationship already exists + 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()) + return + } + + // extract the message + baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp) + if err != nil { + jww.WARN.Printf("Failed to handle auth confirm: %s", err) + return + } + + // decrypt the payload + 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") + return + } + + ecrFmt, err := unmarshalEcrFormat(payload) + if err != nil { + jww.WARN.Printf("Failed to unmarshal auth confirmation's "+ + "encrypted payload: %s", err) + return + } + + // finalize the confirmation + if err := doConfirm(sr, grp, partnerPubKey, ecrFmt.GetOwnership(), + storage, ccb, net); err != nil { + jww.WARN.Printf("Confirmation failed: %s", err) + return + } +} + +func doConfirm(sr *auth.SentRequest, grp *cyclic.Group, + partnerPubKey *cyclic.Int, ownershipProof []byte, storage *storage.Session, + ccb ConfirmCallback, net interfaces.NetworkManager) error { + // verify the message came from the intended recipient + if !cAuth.VerifyOwnershipProof(sr.GetMyPrivKey(), + sr.GetPartnerHistoricalPubKey(), grp, ownershipProof) { + return errors.Errorf("Failed authenticate identity for auth "+ + "confirmation of %s", sr.GetPartner()) + } + // create the relationship + p := e2e.GetDefaultSessionParams() + if err := storage.E2e().AddPartner(sr.GetPartner(), + partnerPubKey, p, p); err != nil { + return errors.Errorf("Failed to create channel with partner (%s) "+ + "after confirmation: %+v", + sr.GetPartner(), err) + } + net.CheckGarbledMessages() + + // delete the in progress negotiation + if err := storage.Auth().Delete(sr.GetPartner()); err != nil { + return errors.Errorf("Failed to delete in progress negotiation "+ + "with partner (%s) after confirmation: %+v", + sr.GetPartner(), err) + } + + //notify the end point + c := contact.Contact{ + ID: sr.GetPartner().DeepCopy(), + DhPubKey: partnerPubKey.DeepCopy(), + OwnershipProof: copySlice(ownershipProof), + Facts: make([]contact.Fact, 0), + } + + go ccb(c) + + return nil } + +func copySlice(s []byte) []byte { + c := make([]byte, len(s)) + copy(c, s) + return c +} + +func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat, + *cyclic.Int, error) { + + baseFmt, err := unmarshalBaseFormat(cmixMsg.GetContents(), + grp.GetP().ByteLen()) + if err != nil { + return baseFormat{}, nil, errors.WithMessage(err, "Failed to"+ + " unmarshal auth") + } + + if !grp.BytesInside(baseFmt.pubkey) { + return baseFormat{}, nil, errors.WithMessage(err, "Received "+ + "auth confirmation public key is not in the e2e cyclic group") + } + partnerPubKey := grp.NewIntFromBytes(baseFmt.pubkey) + return baseFmt, partnerPubKey, nil +} \ No newline at end of file diff --git a/auth/confirm.go b/auth/confirm.go new file mode 100644 index 000000000..c8dcff3e0 --- /dev/null +++ b/auth/confirm.go @@ -0,0 +1,35 @@ +package auth + +import ( + "github.com/pkg/errors" + "gitlab.com/elixxir/client/interfaces" + "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/storage" + "io" +) + +func ConfirmRequestAuth(partner contact.Contact, rng io.Reader, + storage *storage.Session, net interfaces.NetworkManager) error { + + // check that messages can be sent over the network + if !net.GetHealthTracker().IsHealthy() { + return errors.New("Cannot confirm authenticated message " + + "when the network is not healthy") + } + + // check if the partner has an auth in progress + storedContact, err := storage.Auth().GetReceivedRequest(partner.ID) + if err == nil { + return errors.Errorf("failed to find a pending Auth Request: %s", + err) + } + + // verify the passed contact matches what is stored + if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 { + return errors.Errorf("Pending Auth Request has diferent pubkey than : %s", + err) + } + + // chec + +} diff --git a/auth/fmt.go b/auth/fmt.go index 39f13a002..5ee0c4874 100644 --- a/auth/fmt.go +++ b/auth/fmt.go @@ -40,7 +40,7 @@ func buildBaseFormat(data []byte, pubkeySize int) baseFormat { return f } -func unmarshalFormat(b []byte, pubkeySize int) (baseFormat, error) { +func unmarshalBaseFormat(b []byte, pubkeySize int) (baseFormat, error) { if len(b) < pubkeySize+saltSize { return baseFormat{}, errors.New("Received baseFormat too small") } @@ -167,9 +167,9 @@ type requestFormat struct { msgPayload []byte } -func newRequestFormat(ecrFmt ecrFormat) (requestFormat) { +func newRequestFormat(ecrFmt ecrFormat) (requestFormat, error) { if len(ecrFmt.payload) < id.ArrIDLen { - jww.FATAL.Panicf("Payload is not long enough") + return requestFormat{}, errors.New("Payload is not long enough") } rf := requestFormat{ @@ -179,7 +179,7 @@ func newRequestFormat(ecrFmt ecrFormat) (requestFormat) { rf.id = rf.payload[:id.ArrIDLen] rf.id = rf.payload[id.ArrIDLen:] - return rf + return rf, nil } func (rf requestFormat) GetID() (*id.ID, error) { diff --git a/auth/request.go b/auth/request.go index bafa9c5fb..102da56df 100644 --- a/auth/request.go +++ b/auth/request.go @@ -69,7 +69,10 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen()) baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen()) ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen()) - requestFmt := newRequestFormat(ecrFmt) + requestFmt, err := newRequestFormat(ecrFmt) + if err != nil { + return errors.Errorf("failed to make request format: %+v", err) + } //check the payload fits facts := me.StringifyFacts() @@ -117,7 +120,8 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader, /*store state*/ //fixme: channel is bricked if the first store succedes but the second fails //store the in progress auth - err = storage.Auth().AddSent(partner.ID, newPrivKey, newPrivKey, fp) + err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey, + newPrivKey, fp) if err != nil { return errors.Errorf("Failed to store auth request: %s", err) } diff --git a/auth/verify.go b/auth/verify.go new file mode 100644 index 000000000..c456d738d --- /dev/null +++ b/auth/verify.go @@ -0,0 +1,13 @@ +package auth + +import ( + "gitlab.com/elixxir/client/interfaces/contact" + "gitlab.com/elixxir/client/storage" + cAuth "gitlab.com/elixxir/crypto/e2e/auth" +) + +func VerifyOwnership(received, verified contact.Contact, storage *storage.Session) bool { + myHistoricalPrivKey := storage.E2e().GetDHPrivateKey() + return cAuth.VerifyOwnershipProof(myHistoricalPrivKey, verified.DhPubKey, + storage.E2e().GetGroup(), received.OwnershipProof) +} diff --git a/interfaces/message/type.go b/interfaces/message/type.go index 19c71c879..e6b569b08 100644 --- a/interfaces/message/type.go +++ b/interfaces/message/type.go @@ -10,8 +10,9 @@ const ( NoType Type = 0 // A message with no message structure - // this is a reserved type, a message sent with this type will be - // ignored on reception. To send a raw message use SendCMIX. + // this is a reserved type, messages sent via SendCmix automatically gain + // this type. Sent messages with this type will be rejected and received + // non Cmix messages will be ignored Raw Type = 1 //General text message, contains human readable text diff --git a/network/message/handler.go b/network/message/handler.go index f740f7756..8e3b34f30 100644 --- a/network/message/handler.go +++ b/network/message/handler.go @@ -68,7 +68,7 @@ func (m *Manager) handleMessage(ecrMsg format.Message) { raw := message.Receive{ Payload: msg.Marshal(), MessageType: message.Raw, - Sender: &id.ID{}, + Sender: msg.GetRecipientID(), Timestamp: time.Time{}, Encryption: message.None, } diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go index e728cdf14..447bc3ce7 100644 --- a/network/message/sendE2E.go +++ b/network/message/sendE2E.go @@ -19,7 +19,10 @@ import ( ) func (m *Manager) SendE2E(msg message.Send, param params.E2E) ([]id.Round, e2e.MessageID, error) { - + if msg.MessageType == message.Raw { + return nil, e2e.MessageID{}, errors.Errorf("Raw (%d) is a reserved "+ + "message type", msg.MessageType) + } //timestamp the message ts := time.Now() diff --git a/network/message/sendUnsafe.go b/network/message/sendUnsafe.go index 6e00fb157..88a1ef2d0 100644 --- a/network/message/sendUnsafe.go +++ b/network/message/sendUnsafe.go @@ -21,7 +21,10 @@ import ( // Sends using SendCMIX and returns a list of rounds the messages are in. Will // return an error if a single part of the message fails to send. func (m *Manager) SendUnsafe(msg message.Send, param params.Unsafe) ([]id.Round, error) { - + if msg.MessageType == message.Raw { + return nil, errors.Errorf("Raw (%d) is a reserved message type", + msg.MessageType) + } //timestamp the message ts := time.Now() diff --git a/storage/auth/sentRequest.go b/storage/auth/sentRequest.go index c2c67d728..20f12c42f 100644 --- a/storage/auth/sentRequest.go +++ b/storage/auth/sentRequest.go @@ -16,17 +16,19 @@ const currentSentRequestVersion = 0 type SentRequest struct { kv *versioned.KV - partner *id.ID - myPrivKey *cyclic.Int - myPubKey *cyclic.Int - fingerprint format.Fingerprint - sentMux sync.Mutex + partner *id.ID + partnerhistoricalPubKey *cyclic.Int + myPrivKey *cyclic.Int + myPubKey *cyclic.Int + fingerprint format.Fingerprint + sentMux sync.Mutex } type sentRequestDisk struct { - MyPrivKey []byte - MyPubKey []byte - Fingerprint []byte + PartnerhistoricalPubKey []byte + MyPrivKey []byte + MyPubKey []byte + Fingerprint []byte } func loadSentRequest(kv *versioned.KV, partner *id.ID, grp *cyclic.Group) (*SentRequest, error) { @@ -43,6 +45,12 @@ func loadSentRequest(kv *versioned.KV, partner *id.ID, grp *cyclic.Group) (*Sent "SentRequest Auth with %s", partner) } + historicalPrivKey := grp.NewInt(1) + if err = historicalPrivKey.GobDecode(srd.PartnerhistoricalPubKey); err != nil { + return nil, errors.WithMessagef(err, "Failed to decode historical"+ + " private key with %s for SentRequest Auth", partner) + } + myPrivKey := grp.NewInt(1) if err = myPrivKey.GobDecode(srd.MyPubKey); err != nil { return nil, errors.WithMessagef(err, "Failed to decode private "+ @@ -59,11 +67,12 @@ func loadSentRequest(kv *versioned.KV, partner *id.ID, grp *cyclic.Group) (*Sent copy(fp[:], srd.Fingerprint) return &SentRequest{ - kv: kv, - partner: partner, - myPrivKey: myPrivKey, - myPubKey: myPubKey, - fingerprint: fp, + kv: kv, + partner: partner, + partnerhistoricalPubKey: historicalPrivKey, + myPrivKey: myPrivKey, + myPubKey: myPubKey, + fingerprint: fp, }, nil } @@ -79,10 +88,16 @@ func (sr *SentRequest) save() error { return err } + historicalPrivKey, err := sr.myPubKey.GobEncode() + if err != nil { + return err + } + ipd := sentRequestDisk{ - MyPrivKey: privKey, - MyPubKey: pubKey, - Fingerprint: sr.fingerprint[:], + PartnerhistoricalPubKey: historicalPrivKey, + MyPrivKey: privKey, + MyPubKey: pubKey, + Fingerprint: sr.fingerprint[:], } data, err := json.Marshal(&ipd) @@ -107,6 +122,10 @@ func (sr *SentRequest) GetPartner() *id.ID { return sr.partner } +func (sr *SentRequest) GetPartnerHistoricalPubKey() *cyclic.Int { + return sr.partnerhistoricalPubKey +} + func (sr *SentRequest) GetMyPrivKey() *cyclic.Int { return sr.myPrivKey } diff --git a/storage/auth/store.go b/storage/auth/store.go index e7a15c07e..940a61f59 100644 --- a/storage/auth/store.go +++ b/storage/auth/store.go @@ -151,8 +151,8 @@ func (s *Store) save() error { return s.kv.Set(requestMapKey, &obj) } -func (s *Store) AddSent(partner *id.ID, myPrivKey, myPubKey *cyclic.Int, - fp format.Fingerprint) error { +func (s *Store) AddSent(partner *id.ID, partnerhistoricalPubKey, myPrivKey, + myPubKey *cyclic.Int, fp format.Fingerprint) error { s.mux.Lock() defer s.mux.Unlock() @@ -162,11 +162,12 @@ func (s *Store) AddSent(partner *id.ID, myPrivKey, myPubKey *cyclic.Int, } sr := &SentRequest{ - kv: s.kv, - partner: partner, - myPrivKey: myPrivKey, - myPubKey: myPubKey, - fingerprint: fp, + kv: s.kv, + partner: partner, + partnerhistoricalPubKey: partnerhistoricalPubKey, + myPrivKey: myPrivKey, + myPubKey: myPubKey, + fingerprint: fp, } if err := sr.save(); err != nil { @@ -253,6 +254,9 @@ func (s *Store) GetFingerprint(fp format.Fingerprint) (FingerprintType, //return the request return Specific, r.Request.sent, nil, nil default: + jww.WARN.Printf("Auth request message ignored due to "+ + "Unknown fingerprint type %d on lookup, should be "+ + "impossible", r.Type) return 0, nil, nil, errors.Errorf("Unknown fingerprint type") } } diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go index d395de5be..1ee144555 100644 --- a/storage/e2e/manager.go +++ b/storage/e2e/manager.go @@ -191,3 +191,11 @@ func (m *Manager) Confirm(sid SessionID) error { func (m *Manager) TriggerNegotiations() []*Session { return m.send.TriggerNegotiation() } + +func (m *Manager) GetMyOriginPrivateKey() *cyclic.Int { + return m.originMyPrivKey.DeepCopy() +} + +func (m *Manager) GetPartnerOriginPublicKey() *cyclic.Int { + return m.originPartnerPubKey.DeepCopy() +} -- GitLab