Adding new authorization level to peers; porting Blocked status to authorization; removing trusted; securing engine/peerapp message processing around authoriztion
the build failed Details

This commit is contained in:
Dan Ballard 2020-06-15 17:16:04 -07:00
parent e05d1addf6
commit e91e892eef
15 changed files with 152 additions and 173 deletions

View File

@ -121,10 +121,10 @@ func (app *application) CreateTaggedPeer(name string, password string, tag strin
p := peer.FromProfile(pc) p := peer.FromProfile(pc)
p.Init(app.eventBuses[profile.Onion]) p.Init(app.eventBuses[profile.Onion])
blockedPeers := profile.BlockedPeers() peerAuthorizations := profile.ContactsAuthorizations()
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations)
app.peers[profile.Onion] = p app.peers[profile.Onion] = p
app.engines[profile.Onion] = engine app.engines[profile.Onion] = engine
@ -213,9 +213,9 @@ func (app *application) LoadProfiles(password string) {
peer := peer.FromProfile(profile) peer := peer.FromProfile(profile)
peer.Init(app.eventBuses[profile.Onion]) peer.Init(app.eventBuses[profile.Onion])
blockedPeers := profile.BlockedPeers() peerAuthorizations := profile.ContactsAuthorizations()
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations)
app.appmutex.Lock() app.appmutex.Lock()
app.peers[profile.Onion] = peer app.peers[profile.Onion] = peer
app.storage[profile.Onion] = profileStore app.storage[profile.Onion] = profileStore
@ -246,10 +246,12 @@ func (ac *applicationCore) GetEventBus(onion string) event.Manager {
func (app *application) getACNStatusHandler() func(int, string) { func (app *application) getACNStatusHandler() func(int, string) {
return func(progress int, status string) { return func(progress int, status string) {
progStr := strconv.Itoa(progress) progStr := strconv.Itoa(progress)
app.peerLock.Lock()
app.appBus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)) app.appBus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status))
for _, bus := range app.eventBuses { for _, bus := range app.eventBuses {
bus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)) bus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status))
} }
app.peerLock.Unlock()
} }
} }

View File

@ -107,10 +107,10 @@ func (as *applicationService) createPeer(name, password, tag string) {
profileStore := storage.CreateProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile) profileStore := storage.CreateProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile)
blockedPeers := profile.BlockedPeers() peerAuthorizations := profile.ContactsAuthorizations()
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], peerAuthorizations)
as.storage[profile.Onion] = profileStore as.storage[profile.Onion] = profileStore
as.engines[profile.Onion] = engine as.engines[profile.Onion] = engine
@ -126,9 +126,9 @@ func (as *applicationService) loadProfiles(password string) {
as.applicationCore.LoadProfiles(password, false, func(profile *model.Profile, profileStore storage.ProfileStore) { as.applicationCore.LoadProfiles(password, false, func(profile *model.Profile, profileStore storage.ProfileStore) {
as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion])
blockedPeers := profile.BlockedPeers() peerAuthorizations := profile.ContactsAuthorizations()
identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], peerAuthorizations)
as.asmutex.Lock() as.asmutex.Lock()
as.storage[profile.Onion] = profileStore as.storage[profile.Onion] = profileStore
as.engines[profile.Onion] = engine as.engines[profile.Onion] = engine

View File

@ -48,7 +48,6 @@ var suggestionsSelectedProfile = []prompt.Suggest{
/*{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"}, /*{Text: "/list-servers", Description: "retrieve a list of servers and their connection status"},
{Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},*/ {Text: "/list-peers", Description: "retrieve a list of peers and their connection status"},*/
{Text: "/export-group", Description: "export a group invite: prints as a string"}, {Text: "/export-group", Description: "export a group invite: prints as a string"},
{Text: "/trust", Description: "trust a peer"},
{Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"}, {Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"},
} }
@ -411,7 +410,7 @@ func main() {
// think over. // think over.
for _, name := range p.GetContacts() { for _, name := range p.GetContacts() {
profile := p.GetContact(name) profile := p.GetContact(name)
if profile.Trusted && !profile.Blocked { if profile.Authorization == model.AuthApproved {
p.PeerWithOnion(profile.Onion) p.PeerWithOnion(profile.Onion)
} }
} }
@ -453,22 +452,16 @@ func main() {
contacts := peer.GetContacts() contacts := peer.GetContacts()
for _, onion := range contacts { for _, onion := range contacts {
c := peer.GetContact(onion) c := peer.GetContact(onion)
fmt.Printf("Name: %v Onion: %v Trusted: %v\n", c.Name, c.Onion, c.Trusted) fmt.Printf("Name: %v Onion: %v Authorization: %v\n", c.Name, c.Onion, c.Authorization)
} }
case "/list-groups": case "/list-groups":
for _, gid := range peer.GetGroups() { for _, gid := range peer.GetGroups() {
g := peer.GetGroup(gid) g := peer.GetGroup(gid)
fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted) fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted)
} }
case "/trust":
if len(commands) == 2 {
peer.TrustPeer(commands[1])
} else {
fmt.Printf("Error trusting peer, usage: %s\n", usages[commands[0]])
}
case "/block": case "/block":
if len(commands) == 2 { if len(commands) == 2 {
peer.BlockPeer(commands[1]) peer.SetContactAuthorization(commands[1], model.AuthBlocked)
} else { } else {
fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]]) fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]])
} }

View File

@ -19,11 +19,9 @@ const (
// RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd" // RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd"
RetryPeerRequest = Type("RetryPeerRequest") RetryPeerRequest = Type("RetryPeerRequest")
// Blocking Events both Block and Unblock have the same structure // RemotePeer
// attributes: // Authorization(model.peer.Auth_...)
// RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd" SetPeerAuthorization = Type("UpdatePeerAuthorization")
BlockPeer = Type("BlockPeer")
UnblockPeer = Type("UnblockPeer")
// Turn on/off blocking of unknown peers (if peers aren't in the contact list then they will be autoblocked // Turn on/off blocking of unknown peers (if peers aren't in the contact list then they will be autoblocked
BlockUnknownPeers = Type("BlockUnknownPeers") BlockUnknownPeers = Type("BlockUnknownPeers")
@ -92,6 +90,7 @@ const (
// a peer contact has been added // a peer contact has been added
// attributes: // attributes:
// RemotePeer [eg ""] // RemotePeer [eg ""]
// Authorization
PeerCreated = Type("PeerCreated") PeerCreated = Type("PeerCreated")
// Password, NewPassword // Password, NewPassword
@ -244,6 +243,8 @@ const (
Status = Field("Status") Status = Field("Status")
EventID = Field("EventID") EventID = Field("EventID")
EventContext = Field("EventContext") EventContext = Field("EventContext")
Authorization = Field("Authorization")
) )
// Defining Common errors // Defining Common errors

3
go.mod
View File

@ -4,7 +4,7 @@ go 1.14
require ( require (
cwtch.im/tapir v0.1.18 cwtch.im/tapir v0.1.18
git.openprivacy.ca/openprivacy/connectivity v1.1.1 git.openprivacy.ca/openprivacy/connectivity v1.1.2
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 git.openprivacy.ca/openprivacy/libricochet-go v1.0.13
git.openprivacy.ca/openprivacy/log v1.0.0 git.openprivacy.ca/openprivacy/log v1.0.0
github.com/c-bata/go-prompt v0.2.3 github.com/c-bata/go-prompt v0.2.3
@ -19,7 +19,6 @@ require (
golang.org/x/crypto v0.0.0-20200420104511-884d27f42877 golang.org/x/crypto v0.0.0-20200420104511-884d27f42877
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
golang.org/x/tools v0.0.0-20200420001825-978e26b7c37c // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect
) )

2
go.sum
View File

@ -3,6 +3,8 @@ cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg=
git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w= git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w=
git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
git.openprivacy.ca/openprivacy/connectivity v1.1.2 h1:Bk8ul3+4/awpQGvskfLpp7/K3Lj8OAxBwlmQqeZy3Ok=
git.openprivacy.ca/openprivacy/connectivity v1.1.2/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 h1:Z86uL9K47onznY1wP1P/wWfWMbbyvk6xnCp94R180os= git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 h1:Z86uL9K47onznY1wP1P/wWfWMbbyvk6xnCp94R180os=
git.openprivacy.ca/openprivacy/libricochet-go v1.0.13/go.mod h1:ZUuX1SOrgV4K18IEcp0hQJNPKszRr2oGb3UeK2iYe5U= git.openprivacy.ca/openprivacy/libricochet-go v1.0.13/go.mod h1:ZUuX1SOrgV4K18IEcp0hQJNPKszRr2oGb3UeK2iYe5U=
git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y=

View File

@ -18,12 +18,24 @@ import (
"time" "time"
) )
// Authorization is a type determining client assigned authorization to a peer
type Authorization string
const (
// AuthUnknown is an inital state for a new unseen peer
AuthUnknown Authorization = "unknown"
// AuthApproved means the client has approved the peer, it can send messages to us, perform GetVals, etc
AuthApproved Authorization = "approved"
// AuthBlocked means the client has blocked the peer, it's messages and connections should be rejected
AuthBlocked Authorization = "blocked"
)
// PublicProfile is a local copy of a CwtchIdentity // PublicProfile is a local copy of a CwtchIdentity
type PublicProfile struct { type PublicProfile struct {
Name string Name string
Ed25519PublicKey ed25519.PublicKey Ed25519PublicKey ed25519.PublicKey
Trusted bool Authorization Authorization
Blocked bool DeprecatedBlocked bool `json:"Blocked"`
Onion string Onion string
Attributes map[string]string Attributes map[string]string
Timeline Timeline `json:"-"` Timeline Timeline `json:"-"`
@ -239,64 +251,38 @@ func (p *Profile) GetContacts() []string {
return keys return keys
} }
// BlockPeer blocks a contact // SetContactAuthorization sets the authoirization level of a peer
func (p *Profile) BlockPeer(onion string) (err error) { func (p *Profile) SetContactAuthorization(onion string, auth Authorization) (err error) {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
contact, ok := p.Contacts[onion] contact, ok := p.Contacts[onion]
if ok { if ok {
contact.Blocked = true contact.Authorization = auth
} else { } else {
err = errors.New("peer does not exist") err = errors.New("peer does not exist")
} }
return return
} }
// UnblockPeer unblocks a contact // GetContactAuthorization returns the contact's authorization level
func (p *Profile) UnblockPeer(onion string) (err error) { func (p *Profile) GetContactAuthorization(onion string) Authorization {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
contact, ok := p.Contacts[onion] contact, ok := p.Contacts[onion]
if ok { if ok {
contact.Blocked = false return contact.Authorization
} else {
err = errors.New("peer does not exist")
} }
return return AuthUnknown
} }
// BlockedPeers calculates a list of Peers who have been Blocked. // ContactsAuthorizations calculates a list of Peers who are at the supplied auth levels
func (p *Profile) BlockedPeers() []string { func (p *Profile) ContactsAuthorizations(authorizationFilter ...Authorization) map[string]Authorization {
blockedPeers := []string{} authorizations := map[string]Authorization{}
for _, contact := range p.GetContacts() { for _, contact := range p.GetContacts() {
c, _ := p.GetContact(contact) c, _ := p.GetContact(contact)
if c.Blocked { authorizations[c.Onion] = c.Authorization
blockedPeers = append(blockedPeers, c.Onion)
}
} }
return blockedPeers return authorizations
}
// TrustPeer sets a contact to trusted
func (p *Profile) TrustPeer(onion string) (err error) {
p.lock.Lock()
defer p.lock.Unlock()
contact, ok := p.Contacts[onion]
if ok {
contact.Trusted = true
} else {
err = errors.New("peer does not exist")
}
return
}
// 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
} }
// GetContact returns a contact if the profile has it // GetContact returns a contact if the profile has it

View File

@ -31,13 +31,9 @@ func TestTrustPeer(t *testing.T) {
alice := GenerateNewProfile("Alice") alice := GenerateNewProfile("Alice")
sarah.AddContact(alice.Onion, &alice.PublicProfile) sarah.AddContact(alice.Onion, &alice.PublicProfile)
alice.AddContact(sarah.Onion, &sarah.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile)
alice.TrustPeer(sarah.Onion) alice.SetContactAuthorization(sarah.Onion, AuthApproved)
if alice.IsBlocked(sarah.Onion) { if alice.GetContactAuthorization(sarah.Onion) != AuthApproved {
t.Errorf("peer should not be blocked") t.Errorf("peer should be approved")
}
if alice.TrustPeer("") == nil {
t.Errorf("trusting a non existent peer should error")
} }
} }
@ -46,13 +42,13 @@ func TestBlockPeer(t *testing.T) {
alice := GenerateNewProfile("Alice") alice := GenerateNewProfile("Alice")
sarah.AddContact(alice.Onion, &alice.PublicProfile) sarah.AddContact(alice.Onion, &alice.PublicProfile)
alice.AddContact(sarah.Onion, &sarah.PublicProfile) alice.AddContact(sarah.Onion, &sarah.PublicProfile)
alice.BlockPeer(sarah.Onion) alice.SetContactAuthorization(sarah.Onion, AuthBlocked)
if !alice.IsBlocked(sarah.Onion) { if alice.GetContactAuthorization(sarah.Onion) != AuthBlocked {
t.Errorf("peer should not be blocked") t.Errorf("peer should be blocked")
} }
if alice.BlockPeer("") == nil { if alice.SetContactAuthorization("", AuthUnknown) == nil {
t.Errorf("blocking a non existent peer should error") t.Errorf("Seting Auth level of a non existent peer should error")
} }
} }

View File

@ -41,9 +41,7 @@ type CwtchPeer interface {
SendMessageToPeer(string, string) string SendMessageToPeer(string, string) string
SendGetValToPeer(string, string, string) SendGetValToPeer(string, string, string)
TrustPeer(string) error SetContactAuthorization(string, model.Authorization) error
BlockPeer(string) error
UnblockPeer(string) error
ProcessInvite(string, string) (string, error) ProcessInvite(string, string) (string, error)
AcceptInvite(string) error AcceptInvite(string) error
RejectInvite(string) RejectInvite(string)
@ -68,7 +66,7 @@ type CwtchPeer interface {
GetGroup(string) *model.Group GetGroup(string) *model.Group
GetGroupState(string) (connections.ConnectionState, bool) GetGroupState(string) (connections.ConnectionState, bool)
GetGroups() []string GetGroups() []string
AddContact(nick, onion string, trusted bool) AddContact(nick, onion string, authorization model.Authorization)
GetContacts() []string GetContacts() []string
GetContact(string) *model.PublicProfile GetContact(string) *model.PublicProfile
@ -191,17 +189,16 @@ func (cp *cwtchPeer) GetGroup(groupID string) *model.Group {
return cp.Profile.GetGroup(groupID) return cp.Profile.GetGroup(groupID)
} }
func (cp *cwtchPeer) AddContact(nick, onion string, trusted bool) { func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authorization) {
decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: decodedPub, Trusted: trusted, Blocked: false, Onion: onion, Attributes: map[string]string{"nick": nick}} pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: decodedPub, Authorization: authorization, Onion: onion, Attributes: map[string]string{"nick": nick}}
cp.mutex.Lock()
cp.Profile.AddContact(onion, pp) cp.Profile.AddContact(onion, pp)
cp.mutex.Unlock()
pd, _ := json.Marshal(pp) pd, _ := json.Marshal(pp)
cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{ cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{
event.Data: string(pd), event.Data: string(pd),
event.RemotePeer: onion, event.RemotePeer: onion,
})) }))
cp.eventBus.Publish(event.NewEventList(event.SetPeerAuthorization, event.RemotePeer, onion, event.Authorization, string(authorization)))
} }
// GetContacts returns an unordered list of onions // GetContacts returns an unordered list of onions
@ -257,10 +254,10 @@ func (cp *cwtchPeer) GetGroupState(groupid string) (connections.ConnectionState,
// PeerWithOnion is the entry point for cwtchPeer relationships // PeerWithOnion is the entry point for cwtchPeer relationships
func (cp *cwtchPeer) PeerWithOnion(onion string) { func (cp *cwtchPeer) PeerWithOnion(onion string) {
cp.mutex.Lock() cp.mutex.Lock()
if _, exists := cp.Profile.GetContact(onion); !exists {
cp.AddContact(onion, onion, false)
}
defer cp.mutex.Unlock() defer cp.mutex.Unlock()
if _, exists := cp.Profile.GetContact(onion); !exists {
cp.AddContact(onion, onion, model.AuthApproved)
}
cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion})) cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion}))
} }
@ -343,32 +340,12 @@ func (cp *cwtchPeer) SendGetValToPeer(onion string, scope string, path string) {
cp.eventBus.Publish(event) cp.eventBus.Publish(event)
} }
// TrustPeer sets an existing peer relationship to trusted
func (cp *cwtchPeer) TrustPeer(peer string) error {
cp.mutex.Lock()
defer cp.mutex.Unlock()
err := cp.Profile.TrustPeer(peer)
if err == nil {
cp.PeerWithOnion(peer)
}
return err
}
// BlockPeer blocks an existing peer relationship. // BlockPeer blocks an existing peer relationship.
func (cp *cwtchPeer) BlockPeer(peer string) error { func (cp *cwtchPeer) SetContactAuthorization(peer string, authorization model.Authorization) error {
cp.mutex.Lock() cp.mutex.Lock()
err := cp.Profile.BlockPeer(peer) err := cp.Profile.SetContactAuthorization(peer, authorization)
cp.mutex.Unlock() cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.BlockPeer, map[event.Field]string{event.RemotePeer: peer})) cp.eventBus.Publish(event.NewEvent(event.SetPeerAuthorization, map[event.Field]string{event.RemotePeer: peer, event.Authorization: string(authorization)}))
return err
}
// UnblockPeer blocks an existing peer relationship.
func (cp *cwtchPeer) UnblockPeer(peer string) error {
cp.mutex.Lock()
err := cp.Profile.UnblockPeer(peer)
cp.mutex.Unlock()
cp.eventBus.Publish(event.NewEvent(event.UnblockPeer, map[event.Field]string{event.RemotePeer: peer}))
return err return err
} }

View File

@ -82,7 +82,9 @@ func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error {
*x = ChannelResult_CommonError(value) *x = ChannelResult_CommonError(value)
return nil return nil
} }
func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} } func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) {
return fileDescriptor0, []int{2, 0}
}
type Packet struct { type Packet struct {
// Must contain exactly one field // Must contain exactly one field

View File

@ -2,6 +2,7 @@ package connections
import ( import (
"cwtch.im/cwtch/event" "cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/protocol" "cwtch.im/cwtch/protocol"
"cwtch.im/tapir" "cwtch.im/tapir"
"cwtch.im/tapir/networks/tor" "cwtch.im/tapir/networks/tor"
@ -28,8 +29,8 @@ type engine struct {
// Engine State // Engine State
started bool started bool
// Blocklist // Authorization list of contacts to authorization status
blocked sync.Map authorizations sync.Map // string(onion) => model.Authorization
// Block Unknown Contacts // Block Unknown Contacts
blockUnknownContacts bool blockUnknownContacts bool
@ -55,7 +56,7 @@ type Engine interface {
} }
// NewProtocolEngine initializes a new engine that runs Cwtch using the given parameters // NewProtocolEngine initializes a new engine that runs Cwtch using the given parameters
func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, knownPeers []string, blockedPeers []string) Engine { func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, peerAuthorizations map[string]model.Authorization) Engine {
engine := new(engine) engine := new(engine)
engine.identity = identity engine.identity = identity
engine.privateKey = privateKey engine.privateKey = privateKey
@ -83,17 +84,12 @@ func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateK
engine.eventManager.Subscribe(event.DeleteContact, engine.queue) engine.eventManager.Subscribe(event.DeleteContact, engine.queue)
engine.eventManager.Subscribe(event.DeleteGroup, engine.queue) engine.eventManager.Subscribe(event.DeleteGroup, engine.queue)
engine.eventManager.Subscribe(event.BlockPeer, engine.queue) engine.eventManager.Subscribe(event.SetPeerAuthorization, engine.queue)
engine.eventManager.Subscribe(event.UnblockPeer, engine.queue)
engine.eventManager.Subscribe(event.BlockUnknownPeers, engine.queue) engine.eventManager.Subscribe(event.BlockUnknownPeers, engine.queue)
engine.eventManager.Subscribe(event.AllowUnknownPeers, engine.queue) engine.eventManager.Subscribe(event.AllowUnknownPeers, engine.queue)
for _, peer := range knownPeers { for peer, authorization := range peerAuthorizations {
engine.blocked.Store(peer, false) engine.authorizations.Store(peer, authorization)
}
for _, peer := range blockedPeers {
engine.blocked.Store(peer, true)
} }
return engine return engine
} }
@ -115,7 +111,6 @@ func (e *engine) eventHandler() {
e.eventManager.Publish(event.Event{EventType: event.ProtocolEngineStatus, EventID: ev.EventID}) e.eventManager.Publish(event.Event{EventType: event.ProtocolEngineStatus, EventID: ev.EventID})
case event.PeerRequest: case event.PeerRequest:
if torProdider.IsValidHostname(ev.Data[event.RemotePeer]) { if torProdider.IsValidHostname(ev.Data[event.RemotePeer]) {
e.blocked.Store(ev.Data[event.RemotePeer], false)
go e.peerWithOnion(ev.Data[event.RemotePeer]) go e.peerWithOnion(ev.Data[event.RemotePeer])
} }
case event.RetryPeerRequest: case event.RetryPeerRequest:
@ -131,7 +126,7 @@ func (e *engine) eventHandler() {
case event.DeleteContact: case event.DeleteContact:
onion := ev.Data[event.RemotePeer] 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. // We remove this peer from out blocklist which will prevent them from contacting us if we have "block unknown peers" turned on.
e.blocked.Delete(ev.Data[event.RemotePeer]) e.authorizations.Delete(ev.Data[event.RemotePeer])
e.deleteConnection(onion) e.deleteConnection(onion)
case event.DeleteGroup: case event.DeleteGroup:
// TODO: There isn't a way here to determine if other Groups are using a server connection... // TODO: There isn't a way here to determine if other Groups are using a server connection...
@ -151,21 +146,19 @@ func (e *engine) eventHandler() {
e.sendGetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Scope], ev.Data[event.Path]) e.sendGetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Scope], ev.Data[event.Path])
case event.SendRetValMessageToPeer: case event.SendRetValMessageToPeer:
e.sendRetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Data], ev.Data[event.Exists]) e.sendRetValToPeer(ev.EventID, ev.Data[event.RemotePeer], ev.Data[event.Data], ev.Data[event.Exists])
case event.UnblockPeer: case event.SetPeerAuthorization:
// We simply remove the peer from our blocklist auth := model.Authorization(ev.Data[event.Authorization])
// The UI has the responsibility to reinitiate contact with the peer. e.authorizations.Store(ev.Data[event.RemotePeer], auth)
// (this should happen periodically in any case) if auth == model.AuthBlocked {
e.blocked.Store(ev.Data[event.RemotePeer], false) connection, err := e.service.GetConnection(ev.Data[event.RemotePeer])
case event.BlockPeer: if connection != nil && err == nil {
e.blocked.Store(ev.Data[event.RemotePeer], true) connection.Close()
connection, err := e.service.GetConnection(ev.Data[event.RemotePeer]) }
if connection != nil && err == nil { // Explicitly send a disconnected event (if we don't do this here then the UI can wait for a while before
connection.Close() // an ongoing Open() connection fails and so the user will see a blocked peer as still connecting (because
// there isn't an active connection and we are stuck waiting for tor to time out)
e.peerDisconnected(ev.Data[event.RemotePeer])
} }
// Explicitly send a disconnected event (if we don't do this here then the UI can wait for a while before
// an ongoing Open() connection fails and so the user will see a blocked peer as still connecting (because
// there isn't an active connection and we are stuck waiting for tor to time out)
e.peerDisconnected(ev.Data[event.RemotePeer])
case event.AllowUnknownPeers: case event.AllowUnknownPeers:
e.blockUnknownContacts = false e.blockUnknownContacts = false
case event.BlockUnknownPeers: case event.BlockUnknownPeers:
@ -178,16 +171,27 @@ func (e *engine) eventHandler() {
} }
} }
func (e *engine) isBlocked(onion string) bool {
authorization, known := e.authorizations.Load(onion)
if !known {
// if we block unknown peers we will block this contact
return e.blockUnknownContacts
}
return authorization.(model.Authorization) == model.AuthBlocked
}
func (e *engine) isApproved(onion string) bool {
authorization, known := e.authorizations.Load(onion)
if !known {
return false
}
return authorization.(model.Authorization) == model.AuthApproved
}
func (e *engine) createPeerTemplate() *PeerApp { func (e *engine) createPeerTemplate() *PeerApp {
peerAppTemplate := new(PeerApp) peerAppTemplate := new(PeerApp)
peerAppTemplate.IsBlocked = func(onion string) bool { peerAppTemplate.IsBlocked = e.isBlocked
blocked, known := e.blocked.Load(onion) peerAppTemplate.IsApproved = e.isApproved
if !known {
// if we block unknown peers we will block this contact
return e.blockUnknownContacts
}
return blocked.(bool)
}
peerAppTemplate.MessageHandler = e.handlePeerMessage peerAppTemplate.MessageHandler = e.handlePeerMessage
peerAppTemplate.OnAcknowledgement = e.ignoreOnShutdown2(e.peerAck) peerAppTemplate.OnAcknowledgement = e.ignoreOnShutdown2(e.peerAck)
peerAppTemplate.OnAuth = e.ignoreOnShutdown(e.peerAuthed) peerAppTemplate.OnAuth = e.ignoreOnShutdown(e.peerAuthed)
@ -217,8 +221,7 @@ func (e *engine) Shutdown() {
// peerWithOnion is the entry point for cwtchPeer relationships // peerWithOnion is the entry point for cwtchPeer relationships
// needs to be run in a goroutine as will block on Open. // needs to be run in a goroutine as will block on Open.
func (e *engine) peerWithOnion(onion string) { func (e *engine) peerWithOnion(onion string) {
blocked, known := e.blocked.Load(onion) if !e.isBlocked(onion) {
if known && !(blocked.(bool)) {
e.ignoreOnShutdown(e.peerConnecting)(onion) e.ignoreOnShutdown(e.peerConnecting)(onion)
connected, err := e.service.Connect(onion, e.createPeerTemplate()) connected, err := e.service.Connect(onion, e.createPeerTemplate())
@ -258,6 +261,10 @@ func (e *engine) ignoreOnShutdown2(f func(string, string)) func(string, string)
} }
func (e *engine) peerAuthed(onion string) { func (e *engine) peerAuthed(onion string) {
_, known := e.authorizations.Load(onion)
if !known {
e.authorizations.Store(onion, model.AuthUnknown)
}
e.eventManager.Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{ e.eventManager.Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{
event.RemotePeer: string(onion), event.RemotePeer: string(onion),
event.ConnectionState: ConnectionStateName[AUTHENTICATED], event.ConnectionState: ConnectionStateName[AUTHENTICATED],

View File

@ -18,6 +18,7 @@ type PeerApp struct {
MessageHandler func(string, string, string, []byte) MessageHandler func(string, string, string, []byte)
RetValHandler func(string, []byte, []byte) RetValHandler func(string, []byte, []byte)
IsBlocked func(string) bool IsBlocked func(string) bool
IsApproved func(string) bool
OnAcknowledgement func(string, string) OnAcknowledgement func(string, string)
OnAuth func(string) OnAuth func(string)
OnClose func(string) OnClose func(string)
@ -47,6 +48,7 @@ func (pa *PeerApp) NewInstance() tapir.Application {
newApp := new(PeerApp) newApp := new(PeerApp)
newApp.MessageHandler = pa.MessageHandler newApp.MessageHandler = pa.MessageHandler
newApp.IsBlocked = pa.IsBlocked newApp.IsBlocked = pa.IsBlocked
newApp.IsApproved = pa.IsApproved
newApp.OnAcknowledgement = pa.OnAcknowledgement newApp.OnAcknowledgement = pa.OnAcknowledgement
newApp.OnAuth = pa.OnAuth newApp.OnAuth = pa.OnAuth
newApp.OnClose = pa.OnClose newApp.OnClose = pa.OnClose
@ -99,10 +101,12 @@ func (pa *PeerApp) listen() {
pa.getValRequests.Delete(peerMessage.ID) pa.getValRequests.Delete(peerMessage.ID)
} }
default: default:
pa.MessageHandler(pa.connection.Hostname(), peerMessage.ID, peerMessage.Context, peerMessage.Data) if pa.IsApproved(pa.connection.Hostname()) {
pa.MessageHandler(pa.connection.Hostname(), peerMessage.ID, peerMessage.Context, peerMessage.Data)
// Acknowledge the message // Acknowledge the message
pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}}) pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}})
}
} }
} else { } else {
log.Errorf("Error unmarshalling PeerMessage package: %x %v", message, err) log.Errorf("Error unmarshalling PeerMessage package: %x %v", message, err)

View File

@ -78,7 +78,7 @@ func TestPeerServerConnection(t *testing.T) {
onionAddr := identity.Hostname() onionAddr := identity.Hostname()
manager := event.NewEventManager() manager := event.NewEventManager()
engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil, nil) engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil)
psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr) psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr)
numcalls := 0 numcalls := 0

View File

@ -66,8 +66,7 @@ func (ps *ProfileStoreV1) initProfileWriterStore() {
ps.queue = event.NewQueue() ps.queue = event.NewQueue()
go ps.eventHandler() go ps.eventHandler()
ps.eventManager.Subscribe(event.BlockPeer, ps.queue) ps.eventManager.Subscribe(event.SetPeerAuthorization, ps.queue)
ps.eventManager.Subscribe(event.UnblockPeer, ps.queue)
ps.eventManager.Subscribe(event.PeerCreated, ps.queue) ps.eventManager.Subscribe(event.PeerCreated, ps.queue)
ps.eventManager.Subscribe(event.GroupCreated, ps.queue) ps.eventManager.Subscribe(event.GroupCreated, ps.queue)
ps.eventManager.Subscribe(event.SetProfileName, ps.queue) ps.eventManager.Subscribe(event.SetProfileName, ps.queue)
@ -249,6 +248,18 @@ func (ps *ProfileStoreV1) load() error {
if err == nil { if err == nil {
ps.profile = cp ps.profile = cp
// TODO 2020.06.09: v1 update, Remove on v2
// if we already have the contact it can be assumed "approved" unless blocked
for _, contact := range cp.Contacts {
if contact.Authorization == "" {
if contact.DeprecatedBlocked {
contact.Authorization = model.AuthBlocked
} else {
contact.Authorization = model.AuthApproved
}
}
}
for gid, group := range cp.Groups { for gid, group := range cp.Groups {
ss := NewStreamStore(ps.directory, group.LocalID, ps.key) ss := NewStreamStore(ps.directory, group.LocalID, ps.key)
@ -272,16 +283,9 @@ func (ps *ProfileStoreV1) eventHandler() {
log.Debugf("eventHandler event %v %v\n", ev.EventType, ev.EventID) log.Debugf("eventHandler event %v %v\n", ev.EventType, ev.EventID)
switch ev.EventType { switch ev.EventType {
case event.BlockPeer: case event.SetPeerAuthorization:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer]) err := ps.profile.SetContactAuthorization(ev.Data[event.RemotePeer], model.Authorization(ev.Data[event.Authorization]))
if exists { if err == nil {
contact.Blocked = true
ps.save()
}
case event.UnblockPeer:
contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
if exists {
contact.Blocked = false
ps.save() ps.save()
} }
case event.PeerCreated: case event.PeerCreated:

View File

@ -188,9 +188,11 @@ func TestCwtchPeerIntegration(t *testing.T) {
app.LaunchPeers() app.LaunchPeers()
appClient.LaunchPeers() appClient.LaunchPeers()
fmt.Println("Waiting for Alice, Bob, and Carol to connect with onion network...") waitTime := time.Duration(60) * time.Second
time.Sleep(time.Second * 60) t.Logf("** Waiting for Alice, Bob, and Carol to connect with onion network... (%v)\n", waitTime)
time.Sleep(waitTime)
numGoRoutinesPostPeerStart := runtime.NumGoroutine() numGoRoutinesPostPeerStart := runtime.NumGoroutine()
fmt.Println("** Wait Done!")
// ***** Peering, server joining, group creation / invite ***** // ***** Peering, server joining, group creation / invite *****
@ -198,11 +200,9 @@ func TestCwtchPeerIntegration(t *testing.T) {
alice.JoinServer(serverAddr) alice.JoinServer(serverAddr)
fmt.Println("Alice peering with Bob...") fmt.Println("Alice peering with Bob...")
alice.AddContact("Bob", bob.GetOnion(), false) // Add contact so we can track connection state
alice.PeerWithOnion(bob.GetOnion()) alice.PeerWithOnion(bob.GetOnion())
fmt.Println("Alice peering with Carol...") fmt.Println("Alice peering with Carol...")
alice.AddContact("Carol", carol.GetOnion(), false)
alice.PeerWithOnion(carol.GetOnion()) alice.PeerWithOnion(carol.GetOnion())
fmt.Println("Creating group on ", serverAddr, "...") fmt.Println("Creating group on ", serverAddr, "...")
@ -218,9 +218,15 @@ func TestCwtchPeerIntegration(t *testing.T) {
fmt.Println("Waiting for alice and Bob to peer...") fmt.Println("Waiting for alice and Bob to peer...")
waitForPeerPeerConnection(t, alice, bob) waitForPeerPeerConnection(t, alice, bob)
bob.AddContact("alice?", alice.GetOnion(), true) // Need to add contact else SetContactAuth fails on peer peer doesnt exist
// Normal flow would be Bob app monitors for the new connection (a new connection state change to Auth
// and the adds the user to peer, and then approves or blocks it
bob.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
bob.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
waitForPeerPeerConnection(t, alice, carol) waitForPeerPeerConnection(t, alice, carol)
carol.AddContact("alice?", alice.GetOnion(), true) carol.AddContact("alice?", alice.GetOnion(), model.AuthApproved)
carol.SetContactAuthorization(alice.GetOnion(), model.AuthApproved)
fmt.Println("Alice and Bob getVal public.name...") fmt.Println("Alice and Bob getVal public.name...")
@ -234,13 +240,13 @@ func TestCwtchPeerIntegration(t *testing.T) {
aliceName, exists := bob.GetContactAttribute(alice.GetOnion(), attr.GetPeerScope("name")) aliceName, exists := bob.GetContactAttribute(alice.GetOnion(), attr.GetPeerScope("name"))
if !exists || aliceName != "Alice" { if !exists || aliceName != "Alice" {
t.Fatalf("alice GetKeyVal error on alice peer.name %v\n", exists) t.Fatalf("Bob: alice GetKeyVal error on alice peer.name %v\n", exists)
} }
fmt.Printf("Bob has alice's name as '%v'\n", aliceName) fmt.Printf("Bob has alice's name as '%v'\n", aliceName)
bobName, exists := alice.GetContactAttribute(bob.GetOnion(), attr.GetPeerScope("name")) bobName, exists := alice.GetContactAttribute(bob.GetOnion(), attr.GetPeerScope("name"))
if !exists || bobName != "Bob" { if !exists || bobName != "Bob" {
t.Fatalf("bob GetKeyVal error, alice peer.name\n") t.Fatalf("Alice: bob GetKeyVal error on bob peer.name\n")
} }
fmt.Printf("Alice has bob's name as '%v'\n", bobName) fmt.Printf("Alice has bob's name as '%v'\n", bobName)