diff --git a/event/common.go b/event/common.go index 92dc8f2..57cc35e 100644 --- a/event/common.go +++ b/event/common.go @@ -230,6 +230,10 @@ const ( // Onion: the local onion we attempt to check NetworkStatus = Type("NetworkError") + // Notify the UI that a Server has been added + // Onion = Server Onion + ServerCreated = Type("ServerAdded") + // For debugging. Allows test to emit a Syn and get a response Ack(eventID) when the subsystem is done processing a queue Syn = Type("Syn") Ack = Type("Ack") diff --git a/go.mod b/go.mod index 8e0063e..6653538 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module cwtch.im/cwtch go 1.14 require ( - git.openprivacy.ca/cwtch.im/tapir v0.4.1 + git.openprivacy.ca/cwtch.im/tapir v0.4.2 git.openprivacy.ca/openprivacy/connectivity v1.4.3 git.openprivacy.ca/openprivacy/log v1.0.2 github.com/gtank/ristretto255 v0.1.2 diff --git a/go.sum b/go.sum index 239cbd7..f3849e4 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ git.openprivacy.ca/cwtch.im/tapir v0.4.0 h1:clG8uORt0NKEhT4P+Dpw1pzyUuYzYBMevGqn git.openprivacy.ca/cwtch.im/tapir v0.4.0/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/cwtch.im/tapir v0.4.1 h1:9LMpQX41IzecNNlRc1FZKXHg6wlFss679tFsa3vzb3Y= git.openprivacy.ca/cwtch.im/tapir v0.4.1/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= +git.openprivacy.ca/cwtch.im/tapir v0.4.2 h1:bxMWZnVJXX4dqqOFS7ELW4iFkVL4GS8wiRkjRv5rJe8= +git.openprivacy.ca/cwtch.im/tapir v0.4.2/go.mod h1:eH6dZxXrhW0C4KZX18ksUa6XJCrEvtg8cJJ/Fy6gv+E= git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.4.0 h1:c7AANUCrlA4hIqXxIGDOWMtSe8CpDleD1877PShScbM= diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index f850510..9bfab3f 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -122,6 +122,7 @@ type ModifyGroups interface { // ModifyServers provides write-only access to servers type ModifyServers interface { AddServer(string) error + ResyncServer(onion string) error } // SendMessages enables a caller to sender messages to a contact @@ -326,6 +327,8 @@ func (cp *cwtchPeer) AddServer(serverSpecification string) error { cp.mutex.Unlock() pd, _ := json.Marshal(pp) + + // Sync the Storage Engine cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{ event.Data: string(pd), event.RemotePeer: onion, @@ -473,6 +476,20 @@ func (cp *cwtchPeer) JoinServer(onion string) error { return errors.New("no keys found for server connection") } +// ResyncServer completely tears down and resyncs a new server connection with the given onion address +func (cp *cwtchPeer) ResyncServer(onion string) error { + if cp.GetContact(onion) != nil { + tokenY, yExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypePrivacyPass)) + tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypeTokenOnion)) + if yExists && onionExists { + signature := base64.StdEncoding.EncodeToString([]byte{}) + cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion, event.Signature: signature})) + return nil + } + } + return errors.New("no keys found for server connection") +} + // SendMessageToGroupTracked attempts to sent the given message to the given group id. // It returns the signature of the message which can be used to identify it in any UX layer. func (cp *cwtchPeer) SendMessageToGroupTracked(groupid string, message string) (string, error) { @@ -684,7 +701,14 @@ func (cp *cwtchPeer) eventHandler() { ciphertext, _ := base64.StdEncoding.DecodeString(ev.Data[event.Ciphertext]) signature, _ := base64.StdEncoding.DecodeString(ev.Data[event.Signature]) + + // SECURITY NOTE: A malicious server could insert posts such that everyone always has a different lastKnownSignature + // However the server can always replace **all** messages in an attempt to track users + // This is mitigated somewhat by resync events which do wipe things entire. + // The security of cwtch groups are also not dependent on the servers inability to uniquely tag connections (as long as + // it learns nothing else about each connection). cp.SetContactAttribute(ev.Data[event.GroupServer], lastKnownSignature, ev.Data[event.Signature]) + cp.mutex.Lock() ok, groupID, message, seen := cp.Profile.AttemptDecryption(ciphertext, signature) cp.mutex.Unlock() diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index b4b4068..ab874cd 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -136,12 +136,7 @@ func (e *engine) eventHandler() { } go e.peerWithTokenServer(ev.Data[event.GroupServer], ev.Data[event.ServerTokenOnion], ev.Data[event.ServerTokenY], signature) case event.LeaveServer: - es, ok := e.ephemeralServices.Load(ev.Data[event.GroupServer]) - if ok { - ephemeralService := es.(tapir.Service) - ephemeralService.Shutdown() - e.ephemeralServices.Delete(ev.Data[event.GroupServer]) - } + e.leaveServer(ev.Data[event.GroupServer]) case event.DeleteContact: onion := ev.Data[event.RemotePeer] // We remove this peer from out blocklist which will prevent them from contacting us if we have "block unknown peers" turned on. @@ -279,8 +274,14 @@ func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, toke if exists { connection := service.(*tor.BaseOnionService) if conn, err := connection.GetConnection(onion); err == nil { - if conn.IsClosed() == false { + // We are already peered and synced so return... + // This will only not-trigger it lastKnownSignature has been wiped, which only happens when ResyncServer is called + // in CwtchPeer. + if conn.IsClosed() == false && len(lastKnownSignature) != 0 { return + } else { + // Otherwise...we are going to rebuild the connection(which will result in a bandwidth heavy resync)... + e.leaveServer(onion) } } // Otherwise...let's reconnect @@ -508,3 +509,12 @@ func (e *engine) handlePeerRetVal(hostname string, getValData, retValData []byte e.eventManager.Publish(event.NewEventList(event.NewRetValMessageFromPeer, event.RemotePeer, hostname, event.Scope, getVal.Scope, event.Path, getVal.Path, event.Exists, strconv.FormatBool(retVal.Exists), event.Data, retVal.Val)) } + +func (e *engine) leaveServer(server string) { + es, ok := e.ephemeralServices.Load(server) + if ok { + ephemeralService := es.(tapir.Service) + ephemeralService.Shutdown() + e.ephemeralServices.Delete(server) + } +}