diff --git a/model/profile.go b/model/profile.go index df8e420..518be14 100644 --- a/model/profile.go +++ b/model/profile.go @@ -13,6 +13,7 @@ import ( "io" "io/ioutil" "strconv" + "sync" "time" ) @@ -32,6 +33,7 @@ type Profile struct { Ed25519PrivateKey ed25519.PrivateKey OnionPrivateKey *rsa.PrivateKey Groups map[string]*Group + lock sync.Mutex } // GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name. @@ -78,7 +80,74 @@ func (p *Profile) AddCwtchIdentity(onion string, ci *protocol.CwtchIdentity) { // AddContact allows direct manipulation of cwtch contacts func (p *Profile) AddContact(onion string, profile *PublicProfile) { + p.lock.Lock() p.Contacts[onion] = profile + p.lock.Unlock() +} + +// RejectInvite rejects and removes a group invite +func (p *Profile) RejectInvite(groupID string) { + p.lock.Lock() + delete(p.Groups, groupID) + p.lock.Unlock() +} + +// AcceptInvite accepts a group invite +func (p *Profile) AcceptInvite(groupID string) error { + p.lock.Lock() + group, ok := p.Groups[groupID] + if ok { + group.Accepted = true + } + p.lock.Unlock() + if !ok { + return errors.New("group does not exist") + } + return nil +} + +// BlockPeer blocks a contact +func (p *Profile) BlockPeer(onion string) error { + p.lock.Lock() + contact, ok := p.Contacts[onion] + if ok { + contact.Blocked = true + } + p.lock.Unlock() + if !ok { + return errors.New("peer does not exist") + } + return nil +} + +// TrustPeer sets a contact to trusted +func (p *Profile) TrustPeer(onion string) error { + p.lock.Lock() + contact, ok := p.Contacts[onion] + if ok { + contact.Trusted = true + } + p.lock.Unlock() + if !ok { + return errors.New("peer does not exist") + } + return nil +} + +// IsBlocked returns true if the contact has been blocked, false otherwise +func (p *Profile) IsBlocked(onion string) bool { + contact, ok := p.GetContact(onion) + if ok { + return contact.Blocked + } + return false +} + +func (p *Profile) GetContact(onion string) (*PublicProfile, bool) { + p.lock.Lock() + contact, ok := p.Contacts[onion] + p.lock.Unlock() + return contact, ok } // VerifyGroupMessage confirms the authenticity of a message given an onion, message and signature. @@ -89,7 +158,7 @@ func (p *Profile) VerifyGroupMessage(onion string, groupID string, message strin return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature) } - contact, found := p.Contacts[onion] + contact, found := p.GetContact(onion) if found { m := message + groupID + strconv.Itoa(int(timestamp)) return ed25519.Verify(contact.Ed25519PublicKey, []byte(m), signature) @@ -111,13 +180,18 @@ func (p *Profile) StartGroup(server string) (groupID string, invite []byte, err signedGroupID := p.SignMessage(groupID + server) group.SignGroup(signedGroupID) invite, err = group.Invite() + p.lock.Lock() p.Groups[group.GroupID] = group + p.lock.Unlock() return } // GetGroupByGroupID a pointer to a Group by the group Id, returns nil if no group found. -func (p *Profile) GetGroupByGroupID(groupID string) *Group { - return p.Groups[groupID] +func (p *Profile) GetGroupByGroupID(groupID string) (g *Group) { + p.lock.Lock() + g = p.Groups[groupID] + p.lock.Unlock() + return g } // ProcessInvite adds a new group invite to the profile. @@ -134,17 +208,23 @@ func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname stri // AddGroup is a convenience method for adding a group to a profile. func (p *Profile) AddGroup(group *Group) { + p.lock.Lock() existingGroup, exists := p.Groups[group.GroupID] + p.lock.Unlock() if !exists { - owner, ok := p.Contacts[group.Owner] + owner, ok := p.GetContact(group.Owner) if ok { valid := ed25519.Verify(owner.Ed25519PublicKey, []byte(group.GroupID+group.GroupServer), group.SignedGroupID) if valid { + p.lock.Lock() p.Groups[group.GroupID] = group + p.lock.Unlock() } } } else if exists && existingGroup.Owner == group.Owner { + p.lock.Lock() p.Groups[group.GroupID] = group + p.lock.Unlock() } // If we are sent an invite or group update by someone who is not an owner @@ -187,7 +267,7 @@ func getRandomness(arr *[]byte) { // EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and // profile func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, error) { - group := p.Groups[groupID] + group := p.GetGroupByGroupID(groupID) if group != nil { timestamp := time.Now().Unix() signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp))) @@ -219,7 +299,9 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, // Save makes a opy of the profile in the given file func (p *Profile) Save(profilefile string) error { + p.lock.Lock() bytes, _ := json.Marshal(p) + p.lock.Unlock() return ioutil.WriteFile(profilefile, bytes, 0600) } diff --git a/peer/connections/connectionsmanager.go b/peer/connections/connectionsmanager.go index 1c6206b..4d7a960 100644 --- a/peer/connections/connectionsmanager.go +++ b/peer/connections/connectionsmanager.go @@ -50,6 +50,17 @@ func (m *Manager) ManageServerConnection(host string, handler func(string, *prot m.lock.Unlock() } +// TearDownPeerConnection closes an existing peer connection +func (m *Manager) TearDownPeerConnection(onion string) { + m.lock.Lock() + pc, ok := m.peerConnections[onion] + if ok { + pc.Kill() + delete(m.peerConnections, onion) + } + m.lock.Unlock() +} + // GetPeers returns a map of all peer connections with their state func (m *Manager) GetPeers() map[string]ConnectionState { rm := make(map[string]ConnectionState) diff --git a/peer/connections/peerpeerconnection.go b/peer/connections/peerpeerconnection.go index b88a397..570f9a6 100644 --- a/peer/connections/peerpeerconnection.go +++ b/peer/connections/peerpeerconnection.go @@ -103,3 +103,10 @@ func (ppc *PeerPeerConnection) Run() error { ppc.state = FAILED return err } + +// Kill closes the connection +func (ppc *PeerPeerConnection) Kill() { + ppc.state = KILLED + ppc.connection.Break() + // TODO We should kill the connection outright, but we need to add that to libricochet-go +} diff --git a/peer/connections/state.go b/peer/connections/state.go index 0ccfbc4..e8c9cb0 100644 --- a/peer/connections/state.go +++ b/peer/connections/state.go @@ -14,4 +14,5 @@ const ( CONNECTED AUTHENTICATED FAILED + KILLED ) diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index c23c7b5..b97d6d1 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -1,6 +1,7 @@ package peer import ( + "crypto/rsa" "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer/connections" "cwtch.im/cwtch/peer/peer" @@ -147,44 +148,39 @@ func (cp *CwtchPeer) GetServers() map[string]connections.ConnectionState { // TrustPeer sets an existing peer relationship to trusted func (cp *CwtchPeer) TrustPeer(peer string) error { - _, ok := cp.Profile.Contacts[peer] - if !ok { - return errors.New("peer does not exist") + err := cp.Profile.TrustPeer(peer) + if err == nil { + cp.PeerWithOnion(peer) } - cp.Profile.Contacts[peer].Trusted = true - cp.PeerWithOnion(peer) - return nil + return err } // BlockPeer blocks an existing peer relationship. func (cp *CwtchPeer) BlockPeer(peer string) error { - _, ok := cp.Profile.Contacts[peer] - if !ok { - return errors.New("peer does not exist") - } - cp.Profile.Contacts[peer].Blocked = true - return nil + err := cp.Profile.BlockPeer(peer) + cp.connectionsManager.TearDownConnection(peer) + return err } // AcceptInvite accepts a given existing group invite func (cp *CwtchPeer) AcceptInvite(groupID string) error { - g := cp.Profile.GetGroupByGroupID(groupID) - if g == nil { - return errors.New("group invite does not exit") - } - g.Accepted = true - return nil + return cp.Profile.AcceptInvite(groupID) } // RejectInvite rejects a given group invite. -func (cp *CwtchPeer) RejectInvite(groupID string) error { - g := cp.Profile.GetGroupByGroupID(groupID) - if g == nil { - return errors.New("group invite does not exit") - } - g.Accepted = false - // TODO delete group from Profile - return nil +func (cp *CwtchPeer) RejectInvite(groupID string) { + cp.Profile.RejectInvite(groupID) +} + +// LookupContact returns that a contact is known and allowed to communicate for all cases. +func (cp *CwtchPeer) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { + blocked := cp.Profile.IsBlocked(hostname) + return !blocked, true +} + +// ContactRequest needed to implement ContactRequestHandler Interface +func (cp *CwtchPeer) ContactRequest(name string, message string) string { + return "Accepted" } // Listen sets up an onion listener to process incoming cwtch messages @@ -208,7 +204,7 @@ func (cp *CwtchPeer) Listen() error { } }) - cwtchpeer.Init(cp.Profile.Name, cp.Profile.OnionPrivateKey, af, new(application.AcceptAllContactManager)) + cwtchpeer.Init(cp.Profile.Name, cp.Profile.OnionPrivateKey, af, cp) log.Printf("Running cwtch peer on %v", l.Addr().String()) cwtchpeer.Run(l) return nil diff --git a/server/server.go b/server/server.go index f5de798..5072e04 100644 --- a/server/server.go +++ b/server/server.go @@ -66,7 +66,7 @@ func (s *Server) Run(privateKeyFile string) { } }) - cwtchserver.Init("cwtch server for " + l.Addr().String()[0:16], pk, af, new(application.AcceptAllContactManager)) + cwtchserver.Init("cwtch server for "+l.Addr().String()[0:16], pk, af, new(application.AcceptAllContactManager)) log.Printf("cwtch server running on cwtch:%s", l.Addr().String()[0:16]) cwtchserver.Run(l) }