Upgrade Dependencies + Clean up Groups #350

Merged
dan merged 5 commits from upgrade into master 2021-05-04 20:24:21 +00:00
22 changed files with 135 additions and 245 deletions

View File

@ -69,7 +69,7 @@ func (ap *appletPeers) ListPeers() map[string]string {
ap.peerLock.Lock() ap.peerLock.Lock()
defer ap.peerLock.Unlock() defer ap.peerLock.Unlock()
for k, p := range ap.peers { for k, p := range ap.peers {
keys[k] = p.GetName() keys[k] = p.GetOnion()
} }
return keys return keys
} }

View File

@ -2,6 +2,7 @@ package utils
import ( import (
app2 "cwtch.im/cwtch/app" app2 "cwtch.im/cwtch/app"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer" "cwtch.im/cwtch/peer"
"time" "time"
) )
@ -12,9 +13,11 @@ import (
// may fill that usecase better // may fill that usecase better
func WaitGetPeer(app app2.Application, name string) peer.CwtchPeer { func WaitGetPeer(app app2.Application, name string) peer.CwtchPeer {
dan marked this conversation as resolved
Review

this function... seems.. not great, and is only used by app/servermon? do we just want to remove that?

this function... seems.. not great, and is only used by app/servermon? do we just want to remove that?
Review

See below for my comment on App/Name and Profile.

See below for my comment on App/Name and Profile.
for true { for true {
for id, n := range app.ListPeers() { for id := range app.ListPeers() {
if n == name { peer := app.GetPeer(id)
return app.GetPeer(id) localName, _ := peer.GetAttribute(attr.GetLocalScope("name"))
if localName == name {
return peer
} }
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)

View File

@ -13,8 +13,6 @@ import (
"sync" "sync"
) )
// Event is the core struct type passed around between various subsystems. Events consist of a type which can be // Event is the core struct type passed around between various subsystems. Events consist of a type which can be
// filtered on, an event ID for tracing and a map of Fields to string values. // filtered on, an event ID for tracing and a map of Fields to string values.
type Event struct { type Event struct {
@ -60,7 +58,7 @@ type manager struct {
mapMutex sync.Mutex mapMutex sync.Mutex
internal chan bool internal chan bool
closed bool closed bool
trace bool trace bool
} }
// Manager is an interface for an event bus // Manager is an interface for an event bus
@ -86,7 +84,6 @@ func (em *manager) initialize() {
em.internal = make(chan bool) em.internal = make(chan bool)
em.closed = false em.closed = false
_, em.trace = os.LookupEnv("CWTCH_EVENT_SOURCE") _, em.trace = os.LookupEnv("CWTCH_EVENT_SOURCE")
go em.eventBus() go em.eventBus()

4
go.mod
View File

@ -3,8 +3,8 @@ module cwtch.im/cwtch
go 1.14 go 1.14
require ( require (
git.openprivacy.ca/cwtch.im/tapir v0.3.4 git.openprivacy.ca/cwtch.im/tapir v0.3.5
git.openprivacy.ca/openprivacy/connectivity v1.4.2 git.openprivacy.ca/openprivacy/connectivity v1.4.3
git.openprivacy.ca/openprivacy/log v1.0.2 git.openprivacy.ca/openprivacy/log v1.0.2
github.com/gtank/ristretto255 v0.1.2 github.com/gtank/ristretto255 v0.1.2
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect

4
go.sum
View File

@ -6,6 +6,8 @@ git.openprivacy.ca/cwtch.im/tapir v0.3.3 h1:Q7F8JijgOMMYSy3IdZl7+r6qkWckEWV1+EY7
git.openprivacy.ca/cwtch.im/tapir v0.3.3/go.mod h1:ZMg9Jzh0n3Os2aSF4z+bx/n8WBCJBN7KCQESXperYts= git.openprivacy.ca/cwtch.im/tapir v0.3.3/go.mod h1:ZMg9Jzh0n3Os2aSF4z+bx/n8WBCJBN7KCQESXperYts=
git.openprivacy.ca/cwtch.im/tapir v0.3.4 h1:g7yZkfz/vWr/t2tFXa/t0Ebr/w665uIKpxpCZ3lIPCo= git.openprivacy.ca/cwtch.im/tapir v0.3.4 h1:g7yZkfz/vWr/t2tFXa/t0Ebr/w665uIKpxpCZ3lIPCo=
git.openprivacy.ca/cwtch.im/tapir v0.3.4/go.mod h1:+Niy2AHhQC351ZTtfhC0uLjViCICyOxCJZsIlGKKNAU= git.openprivacy.ca/cwtch.im/tapir v0.3.4/go.mod h1:+Niy2AHhQC351ZTtfhC0uLjViCICyOxCJZsIlGKKNAU=
git.openprivacy.ca/cwtch.im/tapir v0.3.5 h1:AlqAhluY4ivznGoHh37Khyxy0u9IbtYskP93wgtmYx8=
git.openprivacy.ca/cwtch.im/tapir v0.3.5/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 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
git.openprivacy.ca/openprivacy/connectivity v1.4.0 h1:c7AANUCrlA4hIqXxIGDOWMtSe8CpDleD1877PShScbM= git.openprivacy.ca/openprivacy/connectivity v1.4.0 h1:c7AANUCrlA4hIqXxIGDOWMtSe8CpDleD1877PShScbM=
@ -14,6 +16,8 @@ git.openprivacy.ca/openprivacy/connectivity v1.4.1 h1:zoM+j7PFj8mQeUCNiDNMe7Uq9d
git.openprivacy.ca/openprivacy/connectivity v1.4.1/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo= git.openprivacy.ca/openprivacy/connectivity v1.4.1/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo=
git.openprivacy.ca/openprivacy/connectivity v1.4.2 h1:rQFIjWunLlRmXL5Efsv+7+1cA70T6Uza6RCy2PRm9zc= git.openprivacy.ca/openprivacy/connectivity v1.4.2 h1:rQFIjWunLlRmXL5Efsv+7+1cA70T6Uza6RCy2PRm9zc=
git.openprivacy.ca/openprivacy/connectivity v1.4.2/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo= git.openprivacy.ca/openprivacy/connectivity v1.4.2/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo=
git.openprivacy.ca/openprivacy/connectivity v1.4.3 h1:i2Ad/U9FlL9dKr2bhRck7lJ8NoWyGtoEfUwoCyMT0fU=
git.openprivacy.ca/openprivacy/connectivity v1.4.3/go.mod h1:bR0Myx9nm2YzWtsThRelkNMV4Pp7sPDa123O1qsAbVo=
git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM=
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=

View File

@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/protocol/groups" "cwtch.im/cwtch/protocol/groups"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -27,9 +28,7 @@ type Group struct {
GroupServer string GroupServer string
Timeline Timeline `json:"-"` Timeline Timeline `json:"-"`
Accepted bool Accepted bool
Owner string
IsCompromised bool IsCompromised bool
InitialMessage []byte
Attributes map[string]string Attributes map[string]string
lock sync.Mutex lock sync.Mutex
LocalID string LocalID string
@ -63,7 +62,6 @@ func NewGroup(server string) (*Group, error) {
return nil, err return nil, err
} }
copy(group.GroupKey[:], groupKey[:]) copy(group.GroupKey[:], groupKey[:])
group.Owner = "self"
group.Attributes = make(map[string]string) group.Attributes = make(map[string]string)
// By default we set the "name" of the group to a random string, we can override this later, but to simplify the // By default we set the "name" of the group to a random string, we can override this later, but to simplify the
// codes around invite, we assume that this is always set. // codes around invite, we assume that this is always set.
@ -82,33 +80,24 @@ func (g *Group) Compromised() {
g.IsCompromised = true g.IsCompromised = true
} }
// GetInitialMessage returns the first message of the group, if one was sent with the invite.
func (g *Group) GetInitialMessage() []byte {
g.lock.Lock()
defer g.lock.Unlock()
return g.InitialMessage
}
// Invite generates a invitation that can be sent to a cwtch peer // Invite generates a invitation that can be sent to a cwtch peer
func (g *Group) Invite(initialMessage []byte) ([]byte, error) { func (g *Group) Invite() (string, error) {
if g.SignedGroupID == nil { if g.SignedGroupID == nil {
return nil, errors.New("group isn't signed") return "", errors.New("group isn't signed")
} }
g.InitialMessage = initialMessage[:]
gci := &groups.GroupInvite{ gci := &groups.GroupInvite{
GroupID: g.GroupID, GroupID: g.GroupID,
GroupName: g.Attributes[attr.GetLocalScope("name")], GroupName: g.Attributes[attr.GetLocalScope("name")],
SharedKey: g.GroupKey[:], SharedKey: g.GroupKey[:],
ServerHost: g.GroupServer, ServerHost: g.GroupServer,
SignedGroupID: g.SignedGroupID[:], SignedGroupID: g.SignedGroupID[:],
InitialMessage: initialMessage[:],
} }
invite, err := json.Marshal(gci) invite, err := json.Marshal(gci)
return invite, err serializedInvite := fmt.Sprintf("torv3%v", base64.StdEncoding.EncodeToString(invite))
return serializedInvite, err
} }
// AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline // AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline

View File

@ -16,7 +16,7 @@ func TestMessagePadding(t *testing.T) {
gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
sarah.ProcessInvite(string(invite), alice.Onion) sarah.ProcessInvite(invite)
group := alice.GetGroup(gid) group := alice.GetGroup(gid)
@ -49,7 +49,7 @@ func TestTranscriptConsistency(t *testing.T) {
gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
sarah.ProcessInvite(string(invite), alice.Onion) sarah.ProcessInvite(invite)
group := alice.GetGroup(gid) group := alice.GetGroup(gid)

View File

@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"cwtch.im/cwtch/protocol/groups" "cwtch.im/cwtch/protocol/groups"
"encoding/base32" "encoding/base32"
"encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
@ -333,22 +334,15 @@ func (p *Profile) SignMessage(message string) []byte {
// StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed // StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed
// invite which can be sent on the wire. // invite which can be sent on the wire.
func (p *Profile) StartGroup(server string) (groupID string, invite []byte, err error) { func (p *Profile) StartGroup(server string) (groupID string, invite string, err error) {
return p.StartGroupWithMessage(server, []byte{})
}
// StartGroupWithMessage when given a server, and an initial message creates a new Group under this profile and returns the group id an a precomputed
// invite which can be sent on the wire.
func (p *Profile) StartGroupWithMessage(server string, initialMessage []byte) (groupID string, invite []byte, err error) {
group, err := NewGroup(server) group, err := NewGroup(server)
if err != nil { if err != nil {
return "", nil, err return "", "", err
} }
groupID = group.GroupID groupID = group.GroupID
group.Owner = p.Onion
signedGroupID := p.SignMessage(groupID + server) signedGroupID := p.SignMessage(groupID + server)
group.SignGroup(signedGroupID) group.SignGroup(signedGroupID)
invite, err = group.Invite(initialMessage) invite, err = group.Invite()
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
p.Groups[group.GroupID] = group p.Groups[group.GroupID] = group
@ -364,33 +358,36 @@ func (p *Profile) GetGroup(groupID string) (g *Group) {
} }
// ProcessInvite adds a new group invite to the profile. returns the new group ID // ProcessInvite adds a new group invite to the profile. returns the new group ID
func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, string, error) { func (p *Profile) ProcessInvite(invite string) (string, error) {
var gci groups.GroupInvite if strings.HasPrefix(invite, "torv3") {
err := json.Unmarshal([]byte(invite), &gci) data, err := base64.StdEncoding.DecodeString(invite[5:])
if err == nil { if err == nil {
group := new(Group) var gci groups.GroupInvite
group.Version = CurrentGroupVersion err := json.Unmarshal(data, &gci)
group.GroupID = gci.GroupID if err == nil {
group.LocalID = GenerateRandomID() group := new(Group)
group.SignedGroupID = gci.SignedGroupID group.Version = CurrentGroupVersion
copy(group.GroupKey[:], gci.SharedKey[:]) group.GroupID = gci.GroupID
group.GroupServer = gci.ServerHost group.LocalID = GenerateRandomID()
group.InitialMessage = []byte(gci.InitialMessage) group.SignedGroupID = gci.SignedGroupID
group.Accepted = false copy(group.GroupKey[:], gci.SharedKey[:])
group.Owner = peerHostname group.GroupServer = gci.ServerHost
group.Attributes = make(map[string]string) group.Accepted = false
p.AddGroup(group) group.Attributes = make(map[string]string)
return group.GroupID, gci.GroupName, nil p.AddGroup(group)
return group.GroupID, nil
}
}
} }
return "", "", err return "", errors.New("unsupported exported group type")
} }
// AddGroup is a convenience method for adding a group to a profile. // AddGroup is a convenience method for adding a group to a profile.
func (p *Profile) AddGroup(group *Group) { func (p *Profile) AddGroup(group *Group) {
p.lock.Lock()
defer p.lock.Unlock()
_, exists := p.Groups[group.GroupID] _, exists := p.Groups[group.GroupID]
if !exists { if !exists {
p.lock.Lock()
defer p.lock.Unlock()
p.Groups[group.GroupID] = group p.Groups[group.GroupID] = group
} }
} }

View File

@ -64,7 +64,7 @@ func TestRejectGroupInvite(t *testing.T) {
alice.AddContact(sarah.Onion, &sarah.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile)
gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
sarah.ProcessInvite(string(invite), alice.Onion) sarah.ProcessInvite(invite)
group := alice.GetGroup(gid) group := alice.GetGroup(gid)
if len(sarah.Groups) == 1 { if len(sarah.Groups) == 1 {
if sarah.GetGroup(group.GroupID).Accepted { if sarah.GetGroup(group.GroupID).Accepted {
@ -85,8 +85,8 @@ func TestProfileGroup(t *testing.T) {
sarah.AddContact(alice.Onion, &alice.PublicProfile) sarah.AddContact(alice.Onion, &alice.PublicProfile)
alice.AddContact(sarah.Onion, &sarah.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile)
gid, invite, _ := alice.StartGroupWithMessage("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd", []byte("Hello World")) gid, invite, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
sarah.ProcessInvite(string(invite), alice.Onion) sarah.ProcessInvite(invite)
if len(sarah.GetGroups()) != 1 { if len(sarah.GetGroups()) != 1 {
t.Errorf("sarah should only be in 1 group instead: %v", sarah.GetGroups()) t.Errorf("sarah should only be in 1 group instead: %v", sarah.GetGroups())
} }
@ -97,17 +97,11 @@ func TestProfileGroup(t *testing.T) {
alice.AttemptDecryption(c, s1) alice.AttemptDecryption(c, s1)
gid2, invite2, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") gid2, invite2, _ := alice.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
sarah.ProcessInvite(string(invite2), alice.Onion) sarah.ProcessInvite(invite2)
group2 := alice.GetGroup(gid2) group2 := alice.GetGroup(gid2)
c2, s2, _ := sarah.EncryptMessageToGroup("Hello World", group2.GroupID) c2, s2, _ := sarah.EncryptMessageToGroup("Hello World", group2.GroupID)
alice.AttemptDecryption(c2, s2) alice.AttemptDecryption(c2, s2)
sarahGroup := sarah.GetGroup(group.GroupID)
im := sarahGroup.GetInitialMessage()
if string(im) != "Hello World" {
t.Errorf("Initial Message was not stored properly: %v", im)
}
_, _, err := sarah.EncryptMessageToGroup(string(make([]byte, MaxGroupMessageLength*2)), group2.GroupID) _, _, err := sarah.EncryptMessageToGroup(string(make([]byte, MaxGroupMessageLength*2)), group2.GroupID)
if err == nil { if err == nil {
t.Errorf("Overly long message should have returned an error") t.Errorf("Overly long message should have returned an error")
@ -115,7 +109,7 @@ func TestProfileGroup(t *testing.T) {
bob := GenerateNewProfile("bob") bob := GenerateNewProfile("bob")
bob.AddContact(alice.Onion, &alice.PublicProfile) bob.AddContact(alice.Onion, &alice.PublicProfile)
bob.ProcessInvite(string(invite2), alice.Onion) bob.ProcessInvite(invite2)
c3, s3, err := bob.EncryptMessageToGroup("Bobs Message", group2.GroupID) c3, s3, err := bob.EncryptMessageToGroup("Bobs Message", group2.GroupID)
if err == nil { if err == nil {
ok, _, message, _ := alice.AttemptDecryption(c3, s3) ok, _, message, _ := alice.AttemptDecryption(c3, s3)

View File

@ -109,8 +109,8 @@ type ReadGroups interface {
// ModifyGroups provides write-only access add/edit/remove new groups // ModifyGroups provides write-only access add/edit/remove new groups
type ModifyGroups interface { type ModifyGroups interface {
ImportGroup(string) error ImportGroup(string) (string, error)
StartGroup(string) (string, []byte, error) StartGroup(string) (string, string, error)
AcceptInvite(string) error AcceptInvite(string) error
RejectInvite(string) RejectInvite(string)
DeleteGroup(string) DeleteGroup(string)
@ -172,14 +172,6 @@ type CwtchPeer interface {
SendMessages SendMessages
SendMessagesToGroup SendMessagesToGroup
// Deprecated
// TODO Should be removed
GetName() string
SetName(string)
// TODO Should not be exposed...
ProcessInvite(string, string) (string, string, error)
} }
// NewCwtchPeer creates and returns a new cwtchPeer with the given name. // NewCwtchPeer creates and returns a new cwtchPeer with the given name.
@ -223,20 +215,16 @@ func (cp *cwtchPeer) AutoHandleEvents(events []event.Type) {
} }
// ImportGroup initializes a group from an imported source rather than a peer invite // ImportGroup initializes a group from an imported source rather than a peer invite
func (cp *cwtchPeer) ImportGroup(exportedInvite string) (err error) { func (cp *cwtchPeer) ImportGroup(exportedInvite string) (string, error) {
if strings.HasPrefix(exportedInvite, "torv3") { cp.mutex.Lock()
data, err := base64.StdEncoding.DecodeString(exportedInvite[5:]) defer cp.mutex.Unlock()
if err == nil { gid, err := cp.Profile.ProcessInvite(exportedInvite)
cp.eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{
event.GroupInvite: string(data), if err == nil {
event.Imported: "true", cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.GroupID: gid, event.GroupInvite: exportedInvite}))
}))
} else {
log.Errorf("error decoding group invite: %v", err)
}
return nil
} }
return errors.New("unsupported exported group type")
return gid, err
} }
// ExportGroup serializes a group invite so it can be given offline // ExportGroup serializes a group invite so it can be given offline
@ -245,24 +233,15 @@ func (cp *cwtchPeer) ExportGroup(groupID string) (string, error) {
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
group := cp.Profile.GetGroup(groupID) group := cp.Profile.GetGroup(groupID)
if group != nil { if group != nil {
invite, err := group.Invite(group.GetInitialMessage()) return group.Invite()
if err == nil {
exportedInvite := "torv3" + base64.StdEncoding.EncodeToString(invite)
return exportedInvite, err
}
} }
return "", errors.New("group id could not be found") return "", errors.New("group id could not be found")
} }
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error. // StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
func (cp *cwtchPeer) StartGroup(server string) (string, []byte, error) { func (cp *cwtchPeer) StartGroup(server string) (string, string, error) {
return cp.StartGroupWithMessage(server, []byte{})
}
// StartGroupWithMessage create a new group linked to the given server and returns the group ID, an invite or an error.
func (cp *cwtchPeer) StartGroupWithMessage(server string, initialMessage []byte) (groupID string, invite []byte, err error) {
cp.mutex.Lock() cp.mutex.Lock()
groupID, invite, err = cp.Profile.StartGroupWithMessage(server, initialMessage) groupID, invite, err := cp.Profile.StartGroup(server)
cp.mutex.Unlock() cp.mutex.Unlock()
if err == nil { if err == nil {
group := cp.GetGroup(groupID) group := cp.GetGroup(groupID)
@ -275,7 +254,7 @@ func (cp *cwtchPeer) StartGroupWithMessage(server string, initialMessage []byte)
} else { } else {
log.Errorf("error creating group: %v", err) log.Errorf("error creating group: %v", err)
} }
return return groupID, invite, err
} }
// GetGroups returns an unordered list of all group IDs. // GetGroups returns an unordered list of all group IDs.
@ -400,18 +379,6 @@ func (cp *cwtchPeer) GetContact(onion string) *model.PublicProfile {
return contact return contact
} }
func (cp *cwtchPeer) GetName() string {
cp.mutex.Lock()
defer cp.mutex.Unlock()
return cp.Profile.Name
}
func (cp *cwtchPeer) SetName(newName string) {
cp.mutex.Lock()
defer cp.mutex.Unlock()
cp.Profile.Name = newName
}
func (cp *cwtchPeer) GetOnion() string { func (cp *cwtchPeer) GetOnion() string {
cp.mutex.Lock() cp.mutex.Lock()
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
@ -465,14 +432,14 @@ func (cp *cwtchPeer) DeleteGroup(groupID string) {
func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error { func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
cp.mutex.Lock() cp.mutex.Lock()
group := cp.Profile.GetGroup(groupid) group := cp.Profile.GetGroup(groupid)
defer cp.mutex.Unlock()
if group == nil { if group == nil {
cp.mutex.Unlock()
return errors.New("invalid group id") return errors.New("invalid group id")
} }
invite, err := group.Invite()
invite, err := group.Invite(group.InitialMessage) cp.mutex.Unlock()
if err == nil { if err == nil {
cp.eventBus.Publish(event.NewEvent(event.InvitePeerToGroup, map[event.Field]string{event.RemotePeer: onion, event.GroupInvite: string(invite)})) cp.SendMessageToPeer(onion, invite)
} }
return err return err
} }
@ -537,13 +504,6 @@ func (cp *cwtchPeer) SetContactAuthorization(peer string, authorization model.Au
return err return err
} }
// ProcessInvite adds a new group invite to the profile. returns the new group ID
func (cp *cwtchPeer) ProcessInvite(invite string, remotePeer string) (string, string, error) {
cp.mutex.Lock()
defer cp.mutex.Unlock()
return cp.Profile.ProcessInvite(invite, remotePeer)
}
// AcceptInvite accepts a given existing group invite // AcceptInvite accepts a given existing group invite
func (cp *cwtchPeer) AcceptInvite(groupID string) error { func (cp *cwtchPeer) AcceptInvite(groupID string) error {
cp.mutex.Lock() cp.mutex.Lock()
@ -615,6 +575,11 @@ func (cp *cwtchPeer) GetAttribute(key string) (string, bool) {
if val, exists := cp.Profile.GetAttribute(key); exists { if val, exists := cp.Profile.GetAttribute(key); exists {
return val, true return val, true
} }
if key == attr.GetLocalScope("name") {
dan marked this conversation as resolved
Review

wait, why this? why not using the attribute map like all others?

do you have a paired override for setAttribute? which also we prolly shouldn't do? I thougt we were just gonna delete Profile.Name

wait, why this? why not using the attribute map like all others? do you have a paired override for setAttribute? which also we prolly shouldn't do? I thougt we were just gonna delete Profile.Name
Review

this code will use default to set attribute first..this is a fallback for the case where we create a profile (which is prior to InitForEvents) and then immediately try to look it up (like in the integration test)...we don't yet have any way to alert the storage engine to save name as an attribute.

At some point we should reconcile this - probably by removing Name from profile, and any reference in App(Client/Server) - but that is a bigger change that touches a lot more of the integration test and app so I felt this was a good compromise that removes the troublesome APIs while preserving the speciality of Name until we have the time to redo Application to not rely on it.

this code will use default to set attribute first..this is a fallback for the case where we create a profile (which is prior to InitForEvents) and then immediately try to look it up (like in the integration test)...we don't yet have any way to alert the storage engine to save name as an attribute. At some point we should reconcile this - probably by removing Name from profile, and any reference in App(Client/Server) - but that is a bigger change that touches a lot more of the integration test and app so I felt this was a good compromise that removes the troublesome APIs while preserving the speciality of Name until we have the time to redo Application to not rely on it.
Review

ah i see, I missed its a fall through if we didnt get the attribute which we check first!
yeah the UI has all that code around name getting and falling back to onion but yeah putting it here is prolly wrong. ok thats fine

ah i see, I missed its a fall through if we didnt get the attribute which we check first! yeah the UI has all that code around name getting and falling back to onion but yeah putting it here is prolly wrong. ok thats fine
return cp.Profile.Name, true
}
return "", false return "", false
} }
@ -772,23 +737,6 @@ func (cp *cwtchPeer) eventHandler() {
cp.SetContactAttribute(onion, attr.GetPeerScope(path), val) cp.SetContactAttribute(onion, attr.GetPeerScope(path), val)
} }
} }
case event.NewGroupInvite:
cp.mutex.Lock()
group, groupName, err := cp.Profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer])
if err == nil {
if ev.Data[event.Imported] == "true" {
cp.Profile.GetGroup(group).Accepted = true
cp.mutex.Unlock() // TODO...seriously need a better way of handling these cases
cp.SetGroupAttribute(group, attr.GetLocalScope("name"), groupName)
err = cp.JoinServer(cp.Profile.GetGroup(group).GroupServer)
cp.mutex.Lock()
if err != nil {
log.Errorf("Joining Server should have worked %v", err)
}
}
cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.GroupID: group}))
}
cp.mutex.Unlock()
case event.PeerStateChange: case event.PeerStateChange:
cp.mutex.Lock() cp.mutex.Lock()
if _, exists := cp.Profile.Contacts[ev.Data[event.RemotePeer]]; exists { if _, exists := cp.Profile.Contacts[ev.Data[event.RemotePeer]]; exists {

View File

@ -468,9 +468,7 @@ func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) error
func (e *engine) handlePeerMessage(hostname string, eventID string, context string, message []byte) { func (e *engine) handlePeerMessage(hostname string, eventID string, context string, message []byte) {
log.Debugf("New message from peer: %v %v", hostname, context) log.Debugf("New message from peer: %v %v", hostname, context)
if context == event.ContextInvite { if context == event.ContextGetVal {
e.eventManager.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: hostname, event.GroupInvite: string(message)}))
} else if context == event.ContextGetVal {
var getVal peerGetVal var getVal peerGetVal
err := json.Unmarshal(message, &getVal) err := json.Unmarshal(message, &getVal)
if err == nil { if err == nil {

View File

@ -1,36 +0,0 @@
package groups
import (
"crypto/rand"
"cwtch.im/cwtch/model"
"golang.org/x/crypto/nacl/secretbox"
"io"
)
// Fuzz various group related functions
func Fuzz(data []byte) int {
profile := model.GenerateNewProfile("fuzz")
inviteid, _, err := profile.ProcessInvite(string(data), profile.Onion)
if err != nil {
if inviteid != "" {
panic("should not have added a group on err")
}
return 1
}
id, _, _ := profile.StartGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd")
var nonce [24]byte
io.ReadFull(rand.Reader, nonce[:])
encrypted := secretbox.Seal(nonce[:], data, &nonce, &profile.GetGroup(id).GroupKey)
ok, _, _, _ := profile.AttemptDecryption(encrypted, data)
if ok {
panic("this probably shouldn't happen")
}
ok = profile.VerifyGroupMessage(string(data), string(data), string(data), 0, encrypted, data)
if ok {
panic("this probably shouldn't happen")
}
return 0
}

View File

@ -1,20 +0,0 @@
package invites
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/peer"
)
// Fuzz import group function
func Fuzz(data []byte) int {
peer := peer.NewCwtchPeer("fuzz")
peer.Init(event.NewEventManager())
err := peer.ImportGroup(string(data))
if err != nil {
if len(peer.GetGroups()) > 0 {
panic("group added despite error")
}
return 0
}
return 1
}

View File

@ -11,13 +11,12 @@ const CwtchServerSyncedCapability = tapir.Capability("CwtchServerSyncedCapabilit
// GroupInvite provides a structured type for communicating group information to peers // GroupInvite provides a structured type for communicating group information to peers
type GroupInvite struct { type GroupInvite struct {
GroupID string GroupID string
GroupName string GroupName string
SignedGroupID []byte SignedGroupID []byte
Timestamp uint64 Timestamp uint64
SharedKey []byte SharedKey []byte
ServerHost string ServerHost string
InitialMessage []byte
} }
// DecryptedGroupMessage is the main encapsulation of group message data // DecryptedGroupMessage is the main encapsulation of group message data

View File

@ -21,6 +21,7 @@ const (
func main() { func main() {
log.AddEverythingFromPattern("server/app/main") log.AddEverythingFromPattern("server/app/main")
log.AddEverythingFromPattern("server/server") log.AddEverythingFromPattern("server/server")
log.SetLevel(log.LevelDebug)
configDir := os.Getenv("CWTCH_CONFIG_DIR") configDir := os.Getenv("CWTCH_CONFIG_DIR")
if len(os.Args) == 2 && os.Args[1] == "gen1" { if len(os.Args) == 2 && os.Args[1] == "gen1" {
@ -73,14 +74,17 @@ func main() {
// TODO create a random group for testing // TODO create a random group for testing
group, _ := model.NewGroup(tor.GetTorV3Hostname(serverConfig.PublicKey)) group, _ := model.NewGroup(tor.GetTorV3Hostname(serverConfig.PublicKey))
group.SignGroup([]byte{}) group.SignGroup([]byte{})
invite, err := group.Invite([]byte{}) invite, err := group.Invite()
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Printf("%v\n", "torv3"+base64.StdEncoding.EncodeToString(invite)) fmt.Printf("Invite: %v", invite)
bundle := server.KeyBundle().Serialize() bundle := server.KeyBundle().Serialize()
log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle)) log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle))
log.Infof("Server Tofu Bundle: tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite)
server.Run(acn) server.Run(acn)
for { for {
time.Sleep(time.Second) time.Sleep(time.Second)

View File

@ -39,7 +39,7 @@ type Server struct {
func (s *Server) Setup(serverConfig Config) { func (s *Server) Setup(serverConfig Config) {
s.config = serverConfig s.config = serverConfig
bs := new(persistence.BoltPersistence) bs := new(persistence.BoltPersistence)
bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db")) bs.Open(path.Join(serverConfig.ConfigDir, "tokens1.db"))
s.tokenServer = privacypass.NewTokenServerFromStore(bs) s.tokenServer = privacypass.NewTokenServerFromStore(bs)
s.tokenService = s.config.TokenServiceIdentity() s.tokenService = s.config.TokenServiceIdentity()
s.tokenServicePrivKey = s.config.TokenServerPrivateKey s.tokenServicePrivKey = s.config.TokenServerPrivateKey

View File

@ -42,7 +42,7 @@ func TestProfileStoreUpgradeV0toV1(t *testing.T) {
t.Errorf("Creating group invite: %v\n", err) t.Errorf("Creating group invite: %v\n", err)
} }
ps1.AddGroup(invite, profile.Onion) ps1.AddGroup(invite)
fmt.Println("Sending 200 messages...") fmt.Println("Sending 200 messages...")

View File

@ -53,8 +53,8 @@ func ReadProfile(directory, password string) (*model.Profile, error) {
/********************************************************************************************/ /********************************************************************************************/
// AddGroup For testing, adds a group to the profile (and starts a stream store) // AddGroup For testing, adds a group to the profile (and starts a stream store)
func (ps *ProfileStoreV0) AddGroup(invite []byte, peer string) { func (ps *ProfileStoreV0) AddGroup(invite string) {
gid, _, err := ps.profile.ProcessInvite(string(invite), peer) gid, err := ps.profile.ProcessInvite(invite)
if err == nil { if err == nil {
ps.save() ps.save()
group := ps.profile.Groups[gid] group := ps.profile.Groups[gid]

View File

@ -40,7 +40,7 @@ func TestProfileStoreWriteRead(t *testing.T) {
t.Errorf("Creating group invite: %v\n", err) t.Errorf("Creating group invite: %v\n", err)
} }
ps1.AddGroup(invite, profile.Onion) ps1.AddGroup(invite)
ps1.AddGroupMessage(groupid, time.Now().Format(time.RFC3339Nano), time.Now().Format(time.RFC3339Nano), ps1.getProfileCopy(true).Onion, testMessage) ps1.AddGroupMessage(groupid, time.Now().Format(time.RFC3339Nano), time.Now().Format(time.RFC3339Nano), ps1.getProfileCopy(true).Onion, testMessage)

View File

@ -76,7 +76,7 @@ func (ps *ProfileStoreV1) initProfileWriterStore() {
ps.eventManager.Subscribe(event.SetGroupAttribute, ps.queue) ps.eventManager.Subscribe(event.SetGroupAttribute, ps.queue)
ps.eventManager.Subscribe(event.AcceptGroupInvite, ps.queue) ps.eventManager.Subscribe(event.AcceptGroupInvite, ps.queue)
ps.eventManager.Subscribe(event.RejectGroupInvite, ps.queue) ps.eventManager.Subscribe(event.RejectGroupInvite, ps.queue)
ps.eventManager.Subscribe(event.NewGroupInvite, ps.queue) ps.eventManager.Subscribe(event.NewGroup, ps.queue)
ps.eventManager.Subscribe(event.NewMessageFromGroup, ps.queue) ps.eventManager.Subscribe(event.NewMessageFromGroup, ps.queue)
ps.eventManager.Subscribe(event.SendMessageToPeer, ps.queue) ps.eventManager.Subscribe(event.SendMessageToPeer, ps.queue)
ps.eventManager.Subscribe(event.PeerAcknowledgement, ps.queue) ps.eventManager.Subscribe(event.PeerAcknowledgement, ps.queue)
@ -377,8 +377,8 @@ func (ps *ProfileStoreV1) eventHandler() {
case event.RejectGroupInvite: case event.RejectGroupInvite:
ps.profile.RejectInvite(ev.Data[event.GroupID]) ps.profile.RejectInvite(ev.Data[event.GroupID])
ps.save() ps.save()
case event.NewGroupInvite: case event.NewGroup:
gid, _, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) gid, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite])
if err == nil { if err == nil {
ps.save() ps.save()
group := ps.profile.Groups[gid] group := ps.profile.Groups[gid]

View File

@ -35,7 +35,7 @@ func TestProfileStoreWriteRead(t *testing.T) {
t.Errorf("Creating group invite: %v\n", err) t.Errorf("Creating group invite: %v\n", err)
} }
eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)})) eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)}))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{ eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{
@ -89,7 +89,7 @@ func TestProfileStoreChangePassword(t *testing.T) {
t.Errorf("Creating group invite: %v\n", err) t.Errorf("Creating group invite: %v\n", err)
} }
eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)})) eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: ps1.GetProfileCopy(true).Onion, event.GroupInvite: string(invite)}))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
fmt.Println("Sending 200 messages...") fmt.Println("Sending 200 messages...")

View File

@ -23,6 +23,7 @@ import (
"path" "path"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"strings"
"testing" "testing"
"time" "time"
) )
@ -66,23 +67,25 @@ func serverCheck(t *testing.T, serverAddr string) bool {
} }
func waitForPeerGroupConnection(t *testing.T, peer peer.CwtchPeer, groupID string) { func waitForPeerGroupConnection(t *testing.T, peer peer.CwtchPeer, groupID string) {
peerName, _ := peer.GetAttribute(attr.GetLocalScope("name"))
for { for {
fmt.Printf("%v checking group connection...\n", peer.GetName()) fmt.Printf("%v checking group connection...\n", peerName)
state, ok := peer.GetGroupState(groupID) state, ok := peer.GetGroupState(groupID)
if ok { if ok {
fmt.Printf("Waiting for Peer %v to join group %v - state: %v\n", peer.GetName(), groupID, state) fmt.Printf("Waiting for Peer %v to join group %v - state: %v\n", peerName, groupID, state)
if state == connections.FAILED { if state == connections.FAILED {
t.Fatalf("%v could not connect to %v", peer.GetOnion(), groupID) t.Fatalf("%v could not connect to %v", peer.GetOnion(), groupID)
} }
if state != connections.SYNCED { if state != connections.SYNCED {
fmt.Printf("peer %v %v waiting connect to group %v, currently: %v\n", peer.GetName(), peer.GetOnion(), groupID, connections.ConnectionStateName[state]) fmt.Printf("peer %v %v waiting connect to group %v, currently: %v\n", peerName, peer.GetOnion(), groupID, connections.ConnectionStateName[state])
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
continue continue
} else { } else {
fmt.Printf("peer %v %v CONNECTED to group %v\n", peer.GetName(), peer.GetOnion(), groupID) fmt.Printf("peer %v %v CONNECTED to group %v\n", peerName, peer.GetOnion(), groupID)
break break
} }
} }
time.Sleep(time.Second * 2)
} }
return return
} }
@ -100,7 +103,9 @@ func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.Cw
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
continue continue
} else { } else {
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peera.GetName(), peerb.GetName()) peerAName, _ := peera.GetAttribute(attr.GetLocalScope("name"))
peerBName, _ := peerb.GetAttribute(attr.GetLocalScope("name"))
fmt.Printf("%v CONNECTED and AUTHED to %v\n", peerAName, peerBName)
break break
} }
} }
@ -300,12 +305,16 @@ func TestCwtchPeerIntegration(t *testing.T) {
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
fmt.Println("Bob examining groups and accepting invites...") fmt.Println("Bob examining groups and accepting invites...")
for _, groupID := range bob.GetGroups() { for _, message := range bob.GetContact(alice.GetOnion()).Timeline.GetMessages() {
group := bob.GetGroup(groupID) fmt.Printf("Found message from Alice: %v", message.Message)
fmt.Printf("Bob group: %v (Accepted: %v)\n", group.GroupID, group.Accepted) if strings.HasPrefix(message.Message, "torv3") {
if group.Accepted == false { gid, err := bob.ImportGroup(message.Message)
fmt.Printf("Bob received and accepting group invite: %v\n", group.GroupID) if err == nil {
bob.AcceptInvite(group.GroupID) fmt.Printf("Bob found invite...now accepting %v...", gid)
bob.AcceptInvite(gid)
} else {
t.Fatalf("Bob could not accept invite...%v", gid)
}
} }
} }
@ -347,12 +356,16 @@ func TestCwtchPeerIntegration(t *testing.T) {
} }
time.Sleep(time.Second * 60) // Account for some token acquisition in Alice and Bob flows. time.Sleep(time.Second * 60) // Account for some token acquisition in Alice and Bob flows.
fmt.Println("Carol examining groups and accepting invites...") fmt.Println("Carol examining groups and accepting invites...")
for _, groupID := range carol.GetGroups() { for _, message := range carol.GetContact(alice.GetOnion()).Timeline.GetMessages() {
group := carol.GetGroup(groupID) fmt.Printf("Found message from Alice: %v", message.Message)
fmt.Printf("Carol group: %v (Accepted: %v)\n", group.GroupID, group.Accepted) if strings.HasPrefix(message.Message, "torv3") {
if group.Accepted == false { gid, err := carol.ImportGroup(message.Message)
fmt.Printf("Carol received and accepting group invite: %v\n", group.GroupID) if err == nil {
carol.AcceptInvite(group.GroupID) fmt.Printf("Carol found invite...now accepting %v...", gid)
carol.AcceptInvite(gid)
} else {
t.Fatalf("Carol could not accept invite...%v", gid)
}
} }
} }