From 0550a71244ae4c1295960dc47fa707fa08d7bd22 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 13 Jul 2020 17:46:05 -0700 Subject: [PATCH] Tapir Server Refactor --- app/cli/main.go | 618 ------------------ event/common.go | 8 +- go.mod | 15 +- go.sum | 10 + model/group.go | 42 +- model/group_test.go | 15 +- model/keyBundle.go | 46 ++ model/profile.go | 29 +- peer/cwtch_peer.go | 42 +- protocol/ControlChannel.pb.go | 327 --------- protocol/ControlChannel.proto | 53 -- protocol/connections/connectionsmanager.go | 74 --- protocol/connections/connectsmanager_test.go | 11 - protocol/connections/engine.go | 112 +++- .../connections/fetch/peer_fetch_channel.go | 104 --- .../fetch/peer_fetch_channel_test.go | 97 --- .../connections/listen/peer_listen_channel.go | 84 --- .../listen/peer_listen_channel_test.go | 89 --- protocol/connections/peerapp.go | 2 +- protocol/connections/peerserverconnection.go | 168 ----- .../connections/peerserverconnection_test.go | 105 --- .../connections/send/peer_send_channel.go | 95 --- .../send/peer_send_channel_test.go | 108 --- protocol/connections/spam/spamguard.go | 100 --- protocol/connections/spam/spamguard_test.go | 69 -- protocol/connections/tokenboardclientapp.go | 178 +++++ protocol/cwtch-profile.pb.go | 135 ---- protocol/cwtch-profile.proto | 21 - protocol/group_message.pb.go | 188 ------ protocol/group_message.proto | 35 - protocol/groups/common.go | 91 +++ server/app/main.go | 6 +- server/fetch/server_fetch_channel.go | 100 --- server/fetch/server_fetch_channel_test.go | 109 --- server/listen/server_listen_channel.go | 85 --- server/listen/server_listen_channel_test.go | 94 --- server/metrics/monitors.go | 8 +- server/send/server_send_channel.go | 99 --- server/send/server_send_channel_test.go | 174 ----- server/server.go | 114 ++-- server/serverConfig.go | 21 +- server/server_instance.go | 45 -- server/server_instance_test.go | 37 -- server/server_tokenboard.go | 110 ++++ server/storage/message_store.go | 24 +- server/storage/message_store_test.go | 8 +- testing/cwtch_peer_server_integration_test.go | 25 +- testing/tests.sh | 8 - 48 files changed, 728 insertions(+), 3410 deletions(-) delete mode 100644 app/cli/main.go create mode 100644 model/keyBundle.go delete mode 100644 protocol/ControlChannel.pb.go delete mode 100644 protocol/ControlChannel.proto delete mode 100644 protocol/connections/connectionsmanager.go delete mode 100644 protocol/connections/connectsmanager_test.go delete mode 100644 protocol/connections/fetch/peer_fetch_channel.go delete mode 100644 protocol/connections/fetch/peer_fetch_channel_test.go delete mode 100644 protocol/connections/listen/peer_listen_channel.go delete mode 100644 protocol/connections/listen/peer_listen_channel_test.go delete mode 100644 protocol/connections/peerserverconnection.go delete mode 100644 protocol/connections/peerserverconnection_test.go delete mode 100644 protocol/connections/send/peer_send_channel.go delete mode 100644 protocol/connections/send/peer_send_channel_test.go delete mode 100644 protocol/connections/spam/spamguard.go delete mode 100644 protocol/connections/spam/spamguard_test.go create mode 100644 protocol/connections/tokenboardclientapp.go delete mode 100644 protocol/cwtch-profile.pb.go delete mode 100644 protocol/cwtch-profile.proto delete mode 100644 protocol/group_message.pb.go delete mode 100644 protocol/group_message.proto create mode 100644 protocol/groups/common.go delete mode 100644 server/fetch/server_fetch_channel.go delete mode 100644 server/fetch/server_fetch_channel_test.go delete mode 100644 server/listen/server_listen_channel.go delete mode 100644 server/listen/server_listen_channel_test.go delete mode 100644 server/send/server_send_channel.go delete mode 100644 server/send/server_send_channel_test.go delete mode 100644 server/server_instance.go delete mode 100644 server/server_instance_test.go create mode 100644 server/server_tokenboard.go diff --git a/app/cli/main.go b/app/cli/main.go deleted file mode 100644 index 4475818..0000000 --- a/app/cli/main.go +++ /dev/null @@ -1,618 +0,0 @@ -package main - -import ( - app2 "cwtch.im/cwtch/app" - "cwtch.im/cwtch/event" - peer2 "cwtch.im/cwtch/peer" - - "bytes" - "cwtch.im/cwtch/model" - "fmt" - "git.openprivacy.ca/openprivacy/connectivity/tor" - "git.openprivacy.ca/openprivacy/log" - "github.com/c-bata/go-prompt" - "golang.org/x/crypto/ssh/terminal" - "os" - "os/user" - "path" - "strings" - "syscall" - "time" -) - -var app app2.Application -var peer peer2.CwtchPeer -var group *model.Group -var groupFollowBreakChan chan bool -var prmpt string - -var suggestionsBase = []prompt.Suggest{ - {Text: "/new-profile", Description: "create a new profile"}, - {Text: "/load-profiles", Description: "loads profiles with a password"}, - {Text: "/list-profiles", Description: "list active profiles"}, - {Text: "/select-profile", Description: "selects an active profile to use"}, - {Text: "/help", Description: "print list of commands"}, - {Text: "/quit", Description: "quit cwtch"}, -} - -var suggestionsSelectedProfile = []prompt.Suggest{ - {Text: "/info", Description: "show user info"}, - {Text: "/list-contacts", Description: "retrieve a list of contacts"}, - {Text: "/list-groups", Description: "retrieve a list of groups"}, - {Text: "/new-group", Description: "create a new group on a server"}, - {Text: "/select-group", Description: "selects a group to follow"}, - {Text: "/unselect-group", Description: "stop following the current group"}, - {Text: "/invite", Description: "invite a new contact"}, - {Text: "/invite-to-group", Description: "invite an existing contact to join an existing group"}, - {Text: "/accept-invite", Description: "accept the invite of a group"}, - /*{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: "/export-group", Description: "export a group invite: prints as a string"}, - {Text: "/block", Description: "block a peer - you will no longer see messages or connect to this peer"}, -} - -var suggestions = suggestionsBase - -var usages = map[string]string{ - "/new-profile": "/new-profile [name]", - "/load-profiles": "/load-profiles", - "/list-profiles": "", - "/select-profile": "/select-profile [onion]", - "/quit": "", - /* "/list-servers": "", - "/list-peers": "",*/ - "/list-contacts": "", - "/list-groups": "", - "/select-group": "/select-group [groupid]", - "/unselect-group": "", - "/export-group": "/export-group [groupid]", - "/info": "", - "/send": "/send [groupid] [message]", - "/timeline": "/timeline [groupid]", - "/accept-invite": "/accept-invite [groupid]", - "/invite": "/invite [peerid]", - "/invite-to-group": "/invite-to-group [groupid] [peerid]", - "/new-group": "/new-group [server]", - "/help": "", - "/trust": "/trust [peerid]", - "/block": "/block [peerid]", -} - -func printMessage(m model.Message) { - p := peer.GetContact(m.PeerID) - name := "unknown" - if p != nil { - name = p.Name - } else if peer.GetOnion() == m.PeerID { - name = peer.GetName() - } - - fmt.Printf("%v %v (%v): %v\n", m.Timestamp, name, m.PeerID, m.Message) -} - -func startGroupFollow() { - groupFollowBreakChan = make(chan bool) - go func() { - for { - l := len(group.Timeline.GetMessages()) - select { - case <-time.After(1 * time.Second): - if group == nil { - return - } - gms := group.Timeline.GetMessages() - if len(gms) != l { - fmt.Printf("\n") - for ; l < len(gms); l++ { - printMessage(gms[l]) - } - fmt.Printf(prmpt) - } - case <-groupFollowBreakChan: - return - } - } - }() -} - -func stopGroupFollow() { - if group != nil { - groupFollowBreakChan <- true - group = nil - } -} - -func completer(d prompt.Document) []prompt.Suggest { - - var s []prompt.Suggest - - if d.FindStartOfPreviousWord() == 0 { - return prompt.FilterHasPrefix(suggestions, d.GetWordBeforeCursor(), true) - } - - w := d.CurrentLine() - - // Suggest a profile id - if strings.HasPrefix(w, "/select-profile") { - s = []prompt.Suggest{} - peerlist := app.ListPeers() - for onion, peername := range peerlist { - s = append(s, prompt.Suggest{Text: onion, Description: peername}) - } - } - - if peer == nil { - return s - } - - // Suggest groupid - if /*strings.HasPrefix(w, "send") || strings.HasPrefix(w, "timeline") ||*/ strings.HasPrefix(w, "/export-group") || strings.HasPrefix(w, "/select-group") { - s = []prompt.Suggest{} - groups := peer.GetGroups() - for _, groupID := range groups { - group := peer.GetGroup(groupID) - s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer}) - } - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) - } - - // Suggest unaccepted group - if strings.HasPrefix(w, "/accept-invite") { - s = []prompt.Suggest{} - groups := peer.GetGroups() - for _, groupID := range groups { - group := peer.GetGroup(groupID) - if group.Accepted == false { - s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer}) - } - } - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) - } - - // suggest groupid AND peerid - if strings.HasPrefix(w, "/invite-to-group") { - - if d.FindStartOfPreviousWordWithSpace() == 0 { - s = []prompt.Suggest{} - groups := peer.GetGroups() - for _, groupID := range groups { - group := peer.GetGroup(groupID) - if group.Owner == "self" { - s = append(s, prompt.Suggest{Text: group.GroupID, Description: "Group owned by " + group.Owner + " on " + group.GroupServer}) - } - } - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) - } - - s = []prompt.Suggest{} - contacts := peer.GetContacts() - for _, onion := range contacts { - contact := peer.GetContact(onion) - s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name}) - } - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) - } - - // Suggest contact onion / peerid - if strings.HasPrefix(w, "/block") || strings.HasPrefix(w, "/trust") || strings.HasPrefix(w, "/invite") { - s = []prompt.Suggest{} - contacts := peer.GetContacts() - for _, onion := range contacts { - contact := peer.GetContact(onion) - s = append(s, prompt.Suggest{Text: contact.Onion, Description: contact.Name}) - } - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) - } - - return s -} - -func handleAppEvents(em event.Manager) { - queue := event.NewQueue() - em.Subscribe(event.NewPeer, queue) - em.Subscribe(event.PeerError, queue) - - for { - ev := queue.Next() - switch ev.EventType { - case event.NewPeer: - onion := ev.Data[event.Identity] - p := app.GetPeer(onion) - app.LaunchPeers() - - fmt.Printf("\nLoaded profile %v (%v)\n", p.GetName(), p.GetOnion()) - suggestions = append(suggestionsBase, suggestionsSelectedProfile...) - - profiles := app.ListPeers() - fmt.Printf("\n%v profiles active now\n", len(profiles)) - fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n") - case event.PeerError: - err := ev.Data[event.Error] - fmt.Printf("\nError creating profile: %v\n", err) - } - } -} - -func main() { - - cwtch := - ` - #, #' - @@@@@@: - @@@@@@. - @'@@+#' @@@@+ - ''''''@ #+@ : - @''''+;+' . ' - @''@' :+' , ; ##, +' - ,@@ ;' #'#@''. #''@''# - # ''''''#:,,#'''''@ - : @''''@ :+'''@ - ' @;+'@ @'# - .:# '#..# '# @ - @@@@@@ - @@@@@@ - '@@@@ - @# . . - +++, #'@+'@ - ''', ''''''# - .#+# ''', @'''+, - @''# ''', .#@ - :; '@''# .;. ''', ' : ;. , - @+'''@ '+'+ @++ @+'@+''''+@ #+'''#: ''';#''+@ @@@@ @@@@@@@@@ :@@@@# - #''''''# +''. +'': +'''''''''+ @'''''''# '''+'''''@ @@@@ @@@@@@@@@@@@@@@@: - @'''@@'''@ @''# ,'''@ ''+ @@''+#+ :'''@@+''' ''''@@'''' @@@@ @@@@@@@@@@@@@@@@@ - '''# @''# +''@ @'''# ;''@ +''+ @''@ ,+'', '''@ #'''. @@@@ @@@@ '@@@# @@@@ -;''' @@; '''# #'@'' @''@ @''+ +''# .@@ ''', '''. @@@@ @@@ @@@ .@@@ -@''# #'' ''#''#@''. #''# '''. '''. +'', @@@@ @@@ @@@ @@@ -@''# @''@'' #'@+'+ #''# '''. ''', +'', +@@@.@@@ @@@@ @@@, @@@ ,@@@ -;''+ @, +''@'# @'+''@ @''# +''; '+ ''', +'', @@@@@@@@# @@@@ @@@. .@@@ .@@@ - '''# ++'+ ''''@ ,''''# #''' @''@ '@''+ ''', ''', @@@@@@@@: @@@@ @@@; .@@@' ;@@@ - @'''@@'''@ #'''. +'''' ;'''#@ :'''#@+''+ ''', ''', @@@@@@# @@@@ @@@+ ,@@@. @@@@ - #''''''# @''+ @''+ +'''' @'''''''# ''', ''', #@@@. @@@@ @@@+ @@@ @@@@ - @+''+@ '++@ ;++@ '#''@ ##'''@: +++, +++, :@ @@@@ @@@' @@@ '@@@ - :' ' '` - fmt.Printf("%v\n\n", cwtch) - quit := false - - usr, err := user.Current() - if err != nil { - log.Errorf("\nError: could not load current user: %v\n", err) - os.Exit(1) - } - - acn, err := tor.NewTorACN(path.Join(usr.HomeDir, ".cwtch"), "") - if err != nil { - log.Errorf("\nError connecting to Tor: %v\n", err) - os.Exit(1) - } - app = app2.NewApp(acn, path.Join(usr.HomeDir, ".cwtch")) - go handleAppEvents(app.GetPrimaryBus()) - if err != nil { - log.Errorf("Error initializing application: %v", err) - os.Exit(1) - } - log.SetLevel(log.LevelDebug) - fmt.Printf("\nWelcome to Cwtch!\n") - fmt.Printf("If this if your first time you should create a profile by running `/new-profile`\n") - fmt.Printf("`/load-profiles` will prompt you for a password and load profiles from storage\n") - fmt.Printf("`/help` will show you other available commands\n") - fmt.Printf("There is full [TAB] completion support\n\n") - - var history []string - for !quit { - - prmpt = "cwtch> " - if group != nil { - prmpt = fmt.Sprintf("cwtch %v (%v) [%v] say> ", peer.GetName(), peer.GetOnion(), group.GroupID) - } else if peer != nil { - prmpt = fmt.Sprintf("cwtch %v (%v)> ", peer.GetName(), peer.GetOnion()) - } - - text := prompt.Input(prmpt, completer, prompt.OptionSuggestionBGColor(prompt.Purple), - prompt.OptionDescriptionBGColor(prompt.White), - prompt.OptionPrefixTextColor(prompt.White), - prompt.OptionInputTextColor(prompt.Purple), - prompt.OptionHistory(history)) - - commands := strings.Split(text[0:], " ") - history = append(history, text) - - if peer == nil { - if commands[0] != "/help" && commands[0] != "/quit" && commands[0] != "/new-profile" && commands[0] != "/load-profiles" && commands[0] != "/select-profile" && commands[0] != "/list-profiles" { - fmt.Printf("Profile needs to be set\n") - continue - } - } - - // Send - if group != nil && !strings.HasPrefix(commands[0], "/") { - err := peer.SendMessageToGroup(group.GroupID, text) - if err != nil { - fmt.Printf("Error sending message: %v\n", err) - } - } - - switch commands[0] { - case "/quit": - quit = true - case "/new-profile": - if len(commands) == 2 { - name := strings.Trim(commands[1], " ") - if name == "" { - fmt.Printf("Error creating profile, usage: %v\n", usages[commands[0]]) - break - } - - fmt.Print("** WARNING: PASSWORDS CANNOT BE RECOVERED! **\n") - - password := "" - failcount := 0 - for ; failcount < 3; failcount++ { - fmt.Print("Enter a password to encrypt the profile: ") - bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin)) - if string(bytePassword) == "" { - fmt.Print("\nBlank password not allowed.") - continue - } - fmt.Print("\nRe-enter password: ") - bytePassword2, _ := terminal.ReadPassword(int(syscall.Stdin)) - if bytes.Equal(bytePassword, bytePassword2) { - password = string(bytePassword) - break - } else { - fmt.Print("\nPASSWORDS DIDN'T MATCH! Try again.\n") - } - } - - if failcount >= 3 { - fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name) - } else { - app.CreatePeer(name, password) - } - } else { - fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]]) - } - case "/load-profiles": - fmt.Print("Enter a password to decrypt the profile: ") - bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) - - if err != nil { - fmt.Printf("\nError loading profiles: %v\n", err) - continue - } - - app.LoadProfiles(string(bytePassword)) - - if err == nil { - - } else { - fmt.Printf("\nError loading profiles: %v\n", err) - } - - case "/list-profiles": - peerlist := app.ListPeers() - for onion, peername := range peerlist { - fmt.Printf(" %v\t%v\n", onion, peername) - } - case "/select-profile": - if len(commands) == 2 { - p := app.GetPeer(commands[1]) - if p == nil { - fmt.Printf("Error: profile '%v' does not exist\n", commands[1]) - } else { - stopGroupFollow() - peer = p - suggestions = append(suggestionsBase, suggestionsSelectedProfile...) - } - - // Auto cwtchPeer / Join Server - // TODO There are some privacy implications with this that we should - // think over. - for _, name := range p.GetContacts() { - profile := p.GetContact(name) - if profile.Authorization == model.AuthApproved { - p.PeerWithOnion(profile.Onion) - } - } - - for _, groupid := range p.GetGroups() { - group := p.GetGroup(groupid) - if group.Accepted || group.Owner == "self" { - p.JoinServer(group.GroupServer) - } - } - - } else { - fmt.Printf("Error selecting profile, usage: %s\n", usages[commands[0]]) - } - case "/info": - if peer != nil { - fmt.Printf("Address cwtch:%v\n", peer.GetOnion()) - } else { - fmt.Printf("Profile needs to be set\n") - } - case "/invite": - if len(commands) == 2 { - fmt.Printf("Inviting cwtch:%v\n", commands[1]) - peer.PeerWithOnion(commands[1]) - } else { - fmt.Printf("Error inviting peer, usage: %s\n", usages[commands[0]]) - } - /*case "/list-peers": - peers := peer.GetPeers() - for p, s := range peers { - fmt.Printf("Name: %v Status: %v\n", p, connections.ConnectionStateName[s]) - } - case "/list-servers": - servers := peer.GetServers() - for s, st := range servers { - fmt.Printf("Name: %v Status: %v\n", s, connections.ConnectionStateName[st]) - }*/ - case "/list-contacts": - contacts := peer.GetContacts() - for _, onion := range contacts { - c := peer.GetContact(onion) - fmt.Printf("Name: %v Onion: %v Authorization: %v\n", c.Name, c.Onion, c.Authorization) - } - case "/list-groups": - for _, gid := range peer.GetGroups() { - g := peer.GetGroup(gid) - fmt.Printf("Group Id: %v Owner: %v Accepted:%v\n", gid, g.Owner, g.Accepted) - } - case "/block": - if len(commands) == 2 { - peer.SetContactAuthorization(commands[1], model.AuthBlocked) - } else { - fmt.Printf("Error blocking peer, usage: %s\n", usages[commands[0]]) - } - case "/accept-invite": - if len(commands) == 2 { - groupID := commands[1] - err := peer.AcceptInvite(groupID) - if err != nil { - fmt.Printf("Error: %v\n", err) - } else { - group := peer.GetGroup(groupID) - if group == nil { - fmt.Printf("Error: group does not exist\n") - } else { - peer.JoinServer(group.GroupServer) - } - } - } else { - fmt.Printf("Error accepting invite, usage: %s\n", usages[commands[0]]) - } - case "/invite-to-group": - if len(commands) == 3 { - fmt.Printf("Inviting %v to %v\n", commands[1], commands[2]) - err := peer.InviteOnionToGroup(commands[2], commands[1]) - if err != nil { - fmt.Printf("Error: %v\n", err) - } - } else { - fmt.Printf("Error inviting peer to group, usage: %s\n", usages[commands[0]]) - } - case "/new-group": - if len(commands) == 2 && commands[1] != "" { - fmt.Printf("Setting up a new group on server:%v\n", commands[1]) - id, _, err := peer.StartGroup(commands[1]) - if err == nil { - fmt.Printf("New Group [%v] created for server %v\n", id, commands[1]) - group := peer.GetGroup(id) - if group == nil { - fmt.Printf("Error: group does not exist\n") - } else { - peer.JoinServer(group.GroupServer) - } - } else { - fmt.Printf("Error creating new group: %v", err) - } - } else { - fmt.Printf("Error creating a new group, usage: %s\n", usages[commands[0]]) - } - case "/select-group": - if len(commands) == 2 { - g := peer.GetGroup(commands[1]) - if g == nil { - fmt.Printf("Error: group %s not found!\n", commands[1]) - } else { - stopGroupFollow() - group = g - - fmt.Printf("--------------- %v ---------------\n", group.GroupID) - gms := group.Timeline.GetMessages() - max := 20 - if len(gms) < max { - max = len(gms) - } - for i := len(gms) - max; i < len(gms); i++ { - printMessage(gms[i]) - } - fmt.Printf("------------------------------\n") - - startGroupFollow() - } - } else { - fmt.Printf("Error selecting a group, usage: %s\n", usages[commands[0]]) - } - case "/unselect-group": - stopGroupFollow() - case "/export-group": - if len(commands) == 2 { - group := peer.GetGroup(commands[1]) - if group == nil { - fmt.Printf("Error: group does not exist\n") - } else { - invite, _ := peer.ExportGroup(commands[1]) - fmt.Printf("Invite: %v\n", invite) - } - } else { - fmt.Printf("Error exporting group, usage: %s\n", usages[commands[0]]) - } - case "/import-group": - if len(commands) == 2 { - err := peer.ImportGroup(commands[1]) - if err != nil { - fmt.Printf("Error importing group: %v\n", err) - } else { - fmt.Printf("Imported group!\n") - } - } else { - fmt.Printf("%v", commands) - fmt.Printf("Error importing group, usage: %s\n", usages[commands[0]]) - } - case "/help": - for _, command := range suggestions { - fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text]) - } - case "/sendlots": - if len(commands) == 2 { - group := peer.GetGroup(commands[1]) - if group == nil { - fmt.Printf("Error: group does not exist\n") - } else { - for i := 0; i < 100; i++ { - fmt.Printf("Sending message: %v\n", i) - err := peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i)) - if err != nil { - fmt.Printf("could not send message %v because %v\n", i, err) - } - } - fmt.Printf("Waiting 5 seconds for message to process...\n") - time.Sleep(time.Second * 5) - timeline := group.GetTimeline() - totalLatency := time.Duration(0) - maxLatency := time.Duration(0) - totalMessages := 0 - for i := 0; i < 100; i++ { - found := false - for _, m := range timeline { - if m.Message == fmt.Sprintf("this is message %v", i) && m.PeerID == peer.GetOnion() { - found = true - latency := m.Received.Sub(m.Timestamp) - fmt.Printf("Latency for Message %v was %v\n", i, latency) - totalLatency = totalLatency + latency - if maxLatency < latency { - maxLatency = latency - } - totalMessages++ - } - } - - if !found { - fmt.Printf("message %v was never received\n", i) - } - } - - fmt.Printf("Average Latency for %v messages was: %vms\n", totalMessages, time.Duration(int64(totalLatency)/int64(totalMessages))) - fmt.Printf("Max Latency for %v messages was: %vms\n", totalMessages, maxLatency) - - } - } - } - } - - app.Shutdown() - acn.Close() - os.Exit(0) -} diff --git a/event/common.go b/event/common.go index ab950a8..23b50e0 100644 --- a/event/common.go +++ b/event/common.go @@ -219,9 +219,11 @@ const ( Identity = Field("Identity") - GroupID = Field("GroupID") - GroupServer = Field("GroupServer") - GroupInvite = Field("GroupInvite") + GroupID = Field("GroupID") + GroupServer = Field("GroupServer") + ServerTokenY = Field("ServerTokenY") + ServerTokenOnion = Field("ServerTokenOnion") + GroupInvite = Field("GroupInvite") ProfileName = Field("ProfileName") Password = Field("Password") diff --git a/go.mod b/go.mod index d2d13fe..4bbe0a8 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,23 @@ module cwtch.im/cwtch go 1.14 require ( - cwtch.im/tapir v0.1.18 - git.openprivacy.ca/openprivacy/connectivity v1.1.2 + cwtch.im/tapir v0.2.0 + git.openprivacy.ca/openprivacy/connectivity v1.2.0 git.openprivacy.ca/openprivacy/libricochet-go v1.0.13 git.openprivacy.ca/openprivacy/log v1.0.1 - github.com/c-bata/go-prompt v0.2.3 - github.com/golang/protobuf v1.3.5 + github.com/c-bata/go-prompt v0.2.3 // indirect + github.com/golang/protobuf v1.3.5 // indirect github.com/google/go-cmp v0.4.0 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect + github.com/gtank/ristretto255 v0.1.2 + github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 // indirect github.com/struCoder/pidusage v0.1.3 - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/crypto v0.0.0-20200420104511-884d27f42877 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect google.golang.org/genproto v0.0.0-20180831171423-11092d34479b // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 2c539c3..854b96e 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ +cwtch.im v0.3.16 h1:uXJO1tUlKVDVQ/0wa4n8C8fzcW2Kol8IYg+tsNFmZOU= cwtch.im/tapir v0.1.18 h1:Fs/jL9ZRyel/A1D/BYzIPEVQau8y5BJg44yA+GQDbSM= cwtch.im/tapir v0.1.18/go.mod h1:/IrAI6CBHfgzsfgRT8WHVb1P9fCCz7+45hfsdkKn8Zg= +cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc= +cwtch.im/tapir v0.2.0/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ= 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/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/connectivity v1.2.0 h1:dbZ5CRl11vg3BNHdzRKSlDP8OUtDB+mf6FkxMVf73qw= +git.openprivacy.ca/openprivacy/connectivity v1.2.0/go.mod h1:B7vzuVmChJtSKoh0ezph5vu6DQ0gIk0zHUNG6IgXCcA= 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/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= @@ -13,6 +18,7 @@ git.openprivacy.ca/openprivacy/log v1.0.1 h1:NWV5oBTatvlSzUE6wtB+UQCulgyMOtm4BXG git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= github.com/c-bata/go-prompt v0.2.3 h1:jjCS+QhG/sULBhAaBdjb2PlMRVaKXQgn+4yzaauvs2s= github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -20,6 +26,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= @@ -30,6 +37,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= @@ -84,6 +93,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200420001825-978e26b7c37c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/model/group.go b/model/group.go index 49d7cb2..0ec8c86 100644 --- a/model/group.go +++ b/model/group.go @@ -2,12 +2,12 @@ package model import ( "crypto/rand" - "cwtch.im/cwtch/protocol" + "cwtch.im/cwtch/protocol/groups" + "encoding/json" "errors" "fmt" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" - "github.com/golang/protobuf/proto" "golang.org/x/crypto/nacl/secretbox" "io" "sync" @@ -89,29 +89,29 @@ func (g *Group) Invite(initialMessage []byte) ([]byte, error) { g.InitialMessage = initialMessage[:] - gci := &protocol.GroupChatInvite{ + gci := &groups.GroupInvite{ GroupName: g.GroupID, - GroupSharedKey: g.GroupKey[:], + SharedKey: g.GroupKey[:], ServerHost: g.GroupServer, - SignedGroupId: g.SignedGroupID[:], + SignedGroupID: g.SignedGroupID[:], InitialMessage: initialMessage[:], } - invite, err := proto.Marshal(gci) + invite, err := json.Marshal(gci) return invite, err } // AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline -func (g *Group) AddSentMessage(message *protocol.DecryptedGroupMessage, sig []byte) Message { +func (g *Group) AddSentMessage(message *groups.DecryptedGroupMessage, sig []byte) Message { g.lock.Lock() defer g.lock.Unlock() timelineMessage := Message{ - Message: message.GetText(), - Timestamp: time.Unix(int64(message.GetTimestamp()), 0), + Message: message.Text, + Timestamp: time.Unix(int64(message.Timestamp), 0), Received: time.Unix(0, 0), Signature: sig, - PeerID: message.GetOnion(), - PreviousMessageSig: message.GetPreviousMessageSig(), + PeerID: message.Onion, + PreviousMessageSig: message.PreviousMessageSig, ReceivedByServer: false, } g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage) @@ -139,7 +139,7 @@ func (g *Group) ErrorSentMessage(sig []byte, error string) bool { } // AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline -func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) { +func (g *Group) AddMessage(message *groups.DecryptedGroupMessage, sig []byte) (*Message, bool) { g.lock.Lock() defer g.lock.Unlock() @@ -153,12 +153,12 @@ func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) } timelineMessage := &Message{ - Message: message.GetText(), - Timestamp: time.Unix(int64(message.GetTimestamp()), 0), + Message: message.Text, + Timestamp: time.Unix(int64(message.Timestamp), 0), Received: time.Now(), Signature: sig, - PeerID: message.GetOnion(), - PreviousMessageSig: message.GetPreviousMessageSig(), + PeerID: message.Onion, + PreviousMessageSig: message.PreviousMessageSig, ReceivedByServer: true, Error: "", } @@ -175,13 +175,13 @@ func (g *Group) GetTimeline() (timeline []Message) { } //EncryptMessage takes a message and encrypts the message under the group key. -func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte, error) { +func (g *Group) EncryptMessage(message *groups.DecryptedGroupMessage) ([]byte, error) { var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { log.Errorf("Cannot read from random: %v\n", err) return nil, err } - wire, err := proto.Marshal(message) + wire, err := json.Marshal(message) if err != nil { return nil, err } @@ -191,14 +191,14 @@ func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte, // DecryptMessage takes a ciphertext and returns true and the decrypted message if the // cipher text can be successfully decrypted,else false. -func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) { +func (g *Group) DecryptMessage(ciphertext []byte) (bool, *groups.DecryptedGroupMessage) { if len(ciphertext) > 24 { var decryptNonce [24]byte copy(decryptNonce[:], ciphertext[:24]) decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey) if ok { - dm := &protocol.DecryptedGroupMessage{} - err := proto.Unmarshal(decrypted, dm) + dm := &groups.DecryptedGroupMessage{} + err := json.Unmarshal(decrypted, dm) if err == nil { return true, dm } diff --git a/model/group_test.go b/model/group_test.go index 00b52ae..782e364 100644 --- a/model/group_test.go +++ b/model/group_test.go @@ -1,25 +1,24 @@ package model import ( - "cwtch.im/cwtch/protocol" - "github.com/golang/protobuf/proto" + "cwtch.im/cwtch/protocol/groups" "testing" "time" ) func TestGroup(t *testing.T) { g, _ := NewGroup("2c3kmoobnyghj2zw6pwv7d57yzld753auo3ugauezzpvfak3ahc4bdyd") - dgm := &protocol.DecryptedGroupMessage{ - Onion: proto.String("onion"), - Text: proto.String("Hello World!"), - Timestamp: proto.Int32(int32(time.Now().Unix())), - SignedGroupId: []byte{}, + dgm := &groups.DecryptedGroupMessage{ + Onion: "onion", + Text: "Hello World!", + Timestamp: uint64(time.Now().Unix()), + SignedGroupID: []byte{}, PreviousMessageSig: []byte{}, Padding: []byte{}, } encMessage, _ := g.EncryptMessage(dgm) ok, message := g.DecryptMessage(encMessage) - if !ok || message.GetText() != "Hello World!" { + if !ok || message.Text != "Hello World!" { t.Errorf("group encryption was invalid, or returned wrong message decrypted:%v message:%v", ok, message) return } diff --git a/model/keyBundle.go b/model/keyBundle.go new file mode 100644 index 0000000..7ff63e9 --- /dev/null +++ b/model/keyBundle.go @@ -0,0 +1,46 @@ +package model + +import "errors" + +const ( + // KeyTypeOnion - a cwtch address + KeyTypeOnion = "onion" // bulletin board + + // KeyTypeTokenOnion - a cwtch peer with a PoW based token protocol + KeyTypeTokenOnion = "token_onion" + + //KeyTypePrivacyPass - a privacy pass based token server + KeyTypePrivacyPass = "pp_key" +) + +// Key provides a wrapper for a generic public key identifier (could be an onion address, a zcash address etc.) +type Key string + +// KeyBundle manages a collection of related keys for various different services. +type KeyBundle struct { + Keys map[string]Key +} + +// HasKey returns true if the bundle has a public key of a given type. +func (kb *KeyBundle) HasKey(name string) bool { + _, exists := kb.Keys[name] + return exists +} + +// GetKey retrieves a key with a given type from the bundle +func (kb *KeyBundle) GetKey(name string) (Key, error) { + key, exists := kb.Keys[name] + if exists { + return key, nil + } + return "", errors.New("no such key") +} + +// AttributeBundle returns a map that can be used as part of a peer attribute bundle +func (kb *KeyBundle) AttributeBundle() map[string]string { + ab := make(map[string]string) + for k, v := range kb.Keys { + ab[k] = string(v) + } + return ab +} diff --git a/model/profile.go b/model/profile.go index 0fb2c69..00dbcb2 100644 --- a/model/profile.go +++ b/model/profile.go @@ -2,14 +2,13 @@ package model import ( "crypto/rand" - "cwtch.im/cwtch/protocol" + "cwtch.im/cwtch/protocol/groups" "encoding/base32" "encoding/hex" "encoding/json" "errors" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "github.com/golang/protobuf/proto" "golang.org/x/crypto/ed25519" "io" "path/filepath" @@ -354,16 +353,16 @@ func (p *Profile) GetGroup(groupID string) (g *Group) { // ProcessInvite adds a new group invite to the profile. returns the new group ID func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, error) { - var gci protocol.GroupChatInvite - err := proto.Unmarshal([]byte(invite), &gci) + var gci groups.GroupInvite + err := json.Unmarshal([]byte(invite), &gci) if err == nil { group := new(Group) - group.GroupID = gci.GetGroupName() + group.GroupID = gci.GroupName group.LocalID = GenerateRandomID() - group.SignedGroupID = gci.GetSignedGroupId() - copy(group.GroupKey[:], gci.GetGroupSharedKey()[:]) - group.GroupServer = gci.GetServerHost() - group.InitialMessage = gci.GetInitialMessage()[:] + group.SignedGroupID = gci.SignedGroupID + copy(group.GroupKey[:], gci.SharedKey[:]) + group.GroupServer = gci.ServerHost + group.InitialMessage = []byte(gci.InitialMessage) group.Accepted = false group.Owner = peerHostname group.Attributes = make(map[string]string) @@ -389,7 +388,7 @@ func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool, for _, group := range p.Groups { success, dgm := group.DecryptMessage(ciphertext) if success { - verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), ciphertext, signature) + verified := p.VerifyGroupMessage(dgm.Onion, group.GroupID, dgm.Text, int32(dgm.Timestamp), ciphertext, signature) // So we have a message that has a valid group key, but the signature can't be verified. // The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious) @@ -436,11 +435,11 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, padding := make([]byte, lenPadding) getRandomness(&padding) - dm := &protocol.DecryptedGroupMessage{ - Onion: proto.String(p.Onion), - Text: proto.String(message), - SignedGroupId: group.SignedGroupID[:], - Timestamp: proto.Int32(int32(timestamp)), + dm := &groups.DecryptedGroupMessage{ + Onion: p.Onion, + Text: message, + SignedGroupID: group.SignedGroupID[:], + Timestamp: uint64(timestamp), PreviousMessageSig: prevSig, Padding: padding[:], } diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 24566e5..5a84107 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -48,6 +48,7 @@ type CwtchPeer interface { DeleteContact(string) DeleteGroup(string) + AddServer(string) JoinServer(string) SendMessageToGroup(string, string) error SendMessageToGroupTracked(string, string) (string, error) @@ -204,6 +205,38 @@ func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authoriz cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault)) } +func (cp *cwtchPeer) AddServer(serverSpecification string) { + keyBundle := new(model.KeyBundle) + json.Unmarshal([]byte(serverSpecification), &keyBundle) + + log.Debugf("Got new key bundle %v", keyBundle) + + if keyBundle.HasKey(model.KeyTypeOnion) { + onionKey, _ := keyBundle.GetKey(model.KeyTypeOnion) + onion := string(onionKey) + decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) + ab := keyBundle.AttributeBundle() + ab["nick"] = onion + pp := &model.PublicProfile{Name: onion, Ed25519PublicKey: decodedPub, Authorization: model.AuthUnknown, Onion: onion, Attributes: ab} + + cp.Profile.AddContact(onion, pp) + pd, _ := json.Marshal(pp) + cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{ + event.Data: string(pd), + event.RemotePeer: onion, + })) + + // Publish every key as an attribute + for k, v := range ab { + log.Debugf("Server (%v) has %v key %v", onion, k, v) + cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, k, v)) + } + + // Default to Deleting Peer History + cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault)) + } +} + // GetContacts returns an unordered list of onions func (cp *cwtchPeer) GetContacts() []string { cp.mutex.Lock() @@ -298,7 +331,14 @@ func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error { // JoinServer manages a new server connection with the given onion address func (cp *cwtchPeer) JoinServer(onion string) { - cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion})) + if cp.GetContact(onion) != nil { + tokenY, yExists := cp.GetContact(onion).GetAttribute(model.KeyTypePrivacyPass) + tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(model.KeyTypeTokenOnion) + if yExists && onionExists { + cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion})) + } + } + // TODO HANDLE ERROR } // SendMessageToGroup attempts to sent the given message to the given group id. diff --git a/protocol/ControlChannel.pb.go b/protocol/ControlChannel.pb.go deleted file mode 100644 index 1c7c302..0000000 --- a/protocol/ControlChannel.pb.go +++ /dev/null @@ -1,327 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: ControlChannel.proto - -/* -Package protocol is a generated protocol buffer package. - -It is generated from these files: - ControlChannel.proto - cwtch-profile.proto - group_message.proto - -It has these top-level messages: - Packet - OpenChannel - ChannelResult - KeepAlive - EnableFeatures - FeaturesEnabled - CwtchPeerPacket - CwtchIdentity - GroupChatInvite - CwtchServerPacket - FetchMessage - GroupMessage - DecryptedGroupMessage -*/ -package protocol - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -type ChannelResult_CommonError int32 - -const ( - ChannelResult_GenericError ChannelResult_CommonError = 0 - ChannelResult_UnknownTypeError ChannelResult_CommonError = 1 - ChannelResult_UnauthorizedError ChannelResult_CommonError = 2 - ChannelResult_BadUsageError ChannelResult_CommonError = 3 - ChannelResult_FailedError ChannelResult_CommonError = 4 -) - -var ChannelResult_CommonError_name = map[int32]string{ - 0: "GenericError", - 1: "UnknownTypeError", - 2: "UnauthorizedError", - 3: "BadUsageError", - 4: "FailedError", -} -var ChannelResult_CommonError_value = map[string]int32{ - "GenericError": 0, - "UnknownTypeError": 1, - "UnauthorizedError": 2, - "BadUsageError": 3, - "FailedError": 4, -} - -func (x ChannelResult_CommonError) Enum() *ChannelResult_CommonError { - p := new(ChannelResult_CommonError) - *p = x - return p -} -func (x ChannelResult_CommonError) String() string { - return proto.EnumName(ChannelResult_CommonError_name, int32(x)) -} -func (x *ChannelResult_CommonError) UnmarshalJSON(data []byte) error { - value, err := proto.UnmarshalJSONEnum(ChannelResult_CommonError_value, data, "ChannelResult_CommonError") - if err != nil { - return err - } - *x = ChannelResult_CommonError(value) - return nil -} -func (ChannelResult_CommonError) EnumDescriptor() ([]byte, []int) { - return fileDescriptor0, []int{2, 0} -} - -type Packet struct { - // Must contain exactly one field - OpenChannel *OpenChannel `protobuf:"bytes,1,opt,name=open_channel,json=openChannel" json:"open_channel,omitempty"` - ChannelResult *ChannelResult `protobuf:"bytes,2,opt,name=channel_result,json=channelResult" json:"channel_result,omitempty"` - KeepAlive *KeepAlive `protobuf:"bytes,3,opt,name=keep_alive,json=keepAlive" json:"keep_alive,omitempty"` - EnableFeatures *EnableFeatures `protobuf:"bytes,4,opt,name=enable_features,json=enableFeatures" json:"enable_features,omitempty"` - FeaturesEnabled *FeaturesEnabled `protobuf:"bytes,5,opt,name=features_enabled,json=featuresEnabled" json:"features_enabled,omitempty"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *Packet) Reset() { *m = Packet{} } -func (m *Packet) String() string { return proto.CompactTextString(m) } -func (*Packet) ProtoMessage() {} -func (*Packet) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -func (m *Packet) GetOpenChannel() *OpenChannel { - if m != nil { - return m.OpenChannel - } - return nil -} - -func (m *Packet) GetChannelResult() *ChannelResult { - if m != nil { - return m.ChannelResult - } - return nil -} - -func (m *Packet) GetKeepAlive() *KeepAlive { - if m != nil { - return m.KeepAlive - } - return nil -} - -func (m *Packet) GetEnableFeatures() *EnableFeatures { - if m != nil { - return m.EnableFeatures - } - return nil -} - -func (m *Packet) GetFeaturesEnabled() *FeaturesEnabled { - if m != nil { - return m.FeaturesEnabled - } - return nil -} - -type OpenChannel struct { - ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"` - ChannelType *string `protobuf:"bytes,2,req,name=channel_type,json=channelType" json:"channel_type,omitempty"` - proto.XXX_InternalExtensions `json:"-"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *OpenChannel) Reset() { *m = OpenChannel{} } -func (m *OpenChannel) String() string { return proto.CompactTextString(m) } -func (*OpenChannel) ProtoMessage() {} -func (*OpenChannel) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -var extRange_OpenChannel = []proto.ExtensionRange{ - {Start: 100, End: 536870911}, -} - -func (*OpenChannel) ExtensionRangeArray() []proto.ExtensionRange { - return extRange_OpenChannel -} - -func (m *OpenChannel) GetChannelIdentifier() int32 { - if m != nil && m.ChannelIdentifier != nil { - return *m.ChannelIdentifier - } - return 0 -} - -func (m *OpenChannel) GetChannelType() string { - if m != nil && m.ChannelType != nil { - return *m.ChannelType - } - return "" -} - -type ChannelResult struct { - ChannelIdentifier *int32 `protobuf:"varint,1,req,name=channel_identifier,json=channelIdentifier" json:"channel_identifier,omitempty"` - Opened *bool `protobuf:"varint,2,req,name=opened" json:"opened,omitempty"` - CommonError *ChannelResult_CommonError `protobuf:"varint,3,opt,name=common_error,json=commonError,enum=protocol.ChannelResult_CommonError" json:"common_error,omitempty"` - proto.XXX_InternalExtensions `json:"-"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *ChannelResult) Reset() { *m = ChannelResult{} } -func (m *ChannelResult) String() string { return proto.CompactTextString(m) } -func (*ChannelResult) ProtoMessage() {} -func (*ChannelResult) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -var extRange_ChannelResult = []proto.ExtensionRange{ - {Start: 100, End: 536870911}, -} - -func (*ChannelResult) ExtensionRangeArray() []proto.ExtensionRange { - return extRange_ChannelResult -} - -func (m *ChannelResult) GetChannelIdentifier() int32 { - if m != nil && m.ChannelIdentifier != nil { - return *m.ChannelIdentifier - } - return 0 -} - -func (m *ChannelResult) GetOpened() bool { - if m != nil && m.Opened != nil { - return *m.Opened - } - return false -} - -func (m *ChannelResult) GetCommonError() ChannelResult_CommonError { - if m != nil && m.CommonError != nil { - return *m.CommonError - } - return ChannelResult_GenericError -} - -type KeepAlive struct { - ResponseRequested *bool `protobuf:"varint,1,req,name=response_requested,json=responseRequested" json:"response_requested,omitempty"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *KeepAlive) Reset() { *m = KeepAlive{} } -func (m *KeepAlive) String() string { return proto.CompactTextString(m) } -func (*KeepAlive) ProtoMessage() {} -func (*KeepAlive) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -func (m *KeepAlive) GetResponseRequested() bool { - if m != nil && m.ResponseRequested != nil { - return *m.ResponseRequested - } - return false -} - -type EnableFeatures struct { - Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` - proto.XXX_InternalExtensions `json:"-"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *EnableFeatures) Reset() { *m = EnableFeatures{} } -func (m *EnableFeatures) String() string { return proto.CompactTextString(m) } -func (*EnableFeatures) ProtoMessage() {} -func (*EnableFeatures) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } - -var extRange_EnableFeatures = []proto.ExtensionRange{ - {Start: 100, End: 536870911}, -} - -func (*EnableFeatures) ExtensionRangeArray() []proto.ExtensionRange { - return extRange_EnableFeatures -} - -func (m *EnableFeatures) GetFeature() []string { - if m != nil { - return m.Feature - } - return nil -} - -type FeaturesEnabled struct { - Feature []string `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` - proto.XXX_InternalExtensions `json:"-"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *FeaturesEnabled) Reset() { *m = FeaturesEnabled{} } -func (m *FeaturesEnabled) String() string { return proto.CompactTextString(m) } -func (*FeaturesEnabled) ProtoMessage() {} -func (*FeaturesEnabled) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } - -var extRange_FeaturesEnabled = []proto.ExtensionRange{ - {Start: 100, End: 536870911}, -} - -func (*FeaturesEnabled) ExtensionRangeArray() []proto.ExtensionRange { - return extRange_FeaturesEnabled -} - -func (m *FeaturesEnabled) GetFeature() []string { - if m != nil { - return m.Feature - } - return nil -} - -func init() { - proto.RegisterType((*Packet)(nil), "protocol.Packet") - proto.RegisterType((*OpenChannel)(nil), "protocol.OpenChannel") - proto.RegisterType((*ChannelResult)(nil), "protocol.ChannelResult") - proto.RegisterType((*KeepAlive)(nil), "protocol.KeepAlive") - proto.RegisterType((*EnableFeatures)(nil), "protocol.EnableFeatures") - proto.RegisterType((*FeaturesEnabled)(nil), "protocol.FeaturesEnabled") - proto.RegisterEnum("protocol.ChannelResult_CommonError", ChannelResult_CommonError_name, ChannelResult_CommonError_value) -} - -func init() { proto.RegisterFile("ControlChannel.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 461 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0x4d, 0x8f, 0xd3, 0x30, - 0x10, 0x25, 0xe9, 0xee, 0x92, 0x4e, 0xfa, 0x91, 0x9a, 0x5d, 0x30, 0xb7, 0x12, 0x2e, 0x15, 0x12, - 0x3d, 0x54, 0x20, 0x21, 0x0e, 0x48, 0x4b, 0xd9, 0x22, 0xc4, 0x01, 0x64, 0xd1, 0x73, 0x64, 0x92, - 0x29, 0x1b, 0x35, 0x6b, 0x1b, 0xc7, 0x05, 0x2d, 0xa7, 0xfe, 0x0e, 0xfe, 0x0c, 0x7f, 0x0d, 0xc5, - 0x89, 0x9b, 0x14, 0x09, 0x09, 0x4e, 0xc9, 0x9b, 0xf7, 0xde, 0x8c, 0xfc, 0x66, 0xe0, 0x7c, 0x29, - 0x85, 0xd1, 0xb2, 0x58, 0x5e, 0x73, 0x21, 0xb0, 0x98, 0x2b, 0x2d, 0x8d, 0x24, 0x81, 0xfd, 0xa4, - 0xb2, 0x88, 0x7f, 0xf9, 0x70, 0xf6, 0x91, 0xa7, 0x5b, 0x34, 0xe4, 0x05, 0x0c, 0xa4, 0x42, 0x91, - 0xa4, 0xb5, 0x94, 0x7a, 0x53, 0x6f, 0x16, 0x2e, 0x2e, 0xe6, 0x4e, 0x3b, 0xff, 0xa0, 0x50, 0x34, - 0x7d, 0x58, 0x28, 0x5b, 0x40, 0x5e, 0xc1, 0xa8, 0x31, 0x25, 0x1a, 0xcb, 0x5d, 0x61, 0xa8, 0x6f, - 0xbd, 0x0f, 0x5a, 0xaf, 0xf3, 0x59, 0x9a, 0x0d, 0xd3, 0x2e, 0x24, 0x0b, 0x80, 0x2d, 0xa2, 0x4a, - 0x78, 0x91, 0x7f, 0x43, 0xda, 0xb3, 0xde, 0x7b, 0xad, 0xf7, 0x3d, 0xa2, 0xba, 0xac, 0x28, 0xd6, - 0xdf, 0xba, 0x5f, 0x72, 0x09, 0x63, 0x14, 0xfc, 0x73, 0x81, 0xc9, 0x06, 0xb9, 0xd9, 0x69, 0x2c, - 0xe9, 0x89, 0x35, 0xd2, 0xd6, 0x78, 0x65, 0x05, 0xab, 0x86, 0x67, 0x23, 0x3c, 0xc2, 0xe4, 0x0d, - 0x44, 0xce, 0x9b, 0xd4, 0x54, 0x46, 0x4f, 0x6d, 0x8f, 0x87, 0x6d, 0x0f, 0xa7, 0xae, 0x7b, 0x65, - 0x6c, 0xbc, 0x39, 0x2e, 0xc4, 0x39, 0x84, 0x9d, 0x60, 0xc8, 0x53, 0x20, 0x2e, 0x8b, 0x3c, 0x43, - 0x61, 0xf2, 0x4d, 0x8e, 0x9a, 0x7a, 0x53, 0x7f, 0x76, 0xca, 0x26, 0x0d, 0xf3, 0xee, 0x40, 0x90, - 0x47, 0x30, 0x70, 0x72, 0x73, 0xab, 0x90, 0xfa, 0x53, 0x7f, 0xd6, 0x67, 0x61, 0x53, 0xfb, 0x74, - 0xab, 0xf0, 0x49, 0x10, 0x64, 0xd1, 0x7e, 0xbf, 0xdf, 0xfb, 0xf1, 0x4f, 0x1f, 0x86, 0x47, 0x41, - 0xfe, 0xef, 0xb4, 0xfb, 0x70, 0x56, 0xed, 0x0d, 0x33, 0x3b, 0x27, 0x60, 0x0d, 0x22, 0x2b, 0x18, - 0xa4, 0xf2, 0xe6, 0x46, 0x8a, 0x04, 0xb5, 0x96, 0xda, 0xae, 0x60, 0xb4, 0x78, 0xfc, 0x97, 0xf5, - 0xcd, 0x97, 0x56, 0x7b, 0x55, 0x49, 0x59, 0x98, 0xb6, 0x20, 0x56, 0x10, 0x76, 0x38, 0x12, 0xc1, - 0xe0, 0x2d, 0x0a, 0xd4, 0x79, 0x6a, 0x71, 0x74, 0x87, 0x9c, 0x43, 0xb4, 0x16, 0x5b, 0x21, 0xbf, - 0x8b, 0xea, 0x69, 0x75, 0xd5, 0x23, 0x17, 0x30, 0x59, 0x0b, 0xbe, 0x33, 0xd7, 0x52, 0xe7, 0x3f, - 0x30, 0xab, 0xcb, 0x3e, 0x99, 0xc0, 0xf0, 0x35, 0xcf, 0xd6, 0x25, 0xff, 0xd2, 0x28, 0x7b, 0x64, - 0x0c, 0xe1, 0x8a, 0xe7, 0x85, 0xd3, 0x9c, 0x74, 0xc2, 0x79, 0x09, 0xfd, 0xc3, 0xa1, 0x54, 0xb9, - 0x68, 0x2c, 0x95, 0x14, 0x25, 0x26, 0x1a, 0xbf, 0xee, 0xb0, 0x34, 0x98, 0xd9, 0x5c, 0x02, 0x36, - 0x71, 0x0c, 0x73, 0x44, 0xfc, 0x0c, 0x46, 0xc7, 0xb7, 0x42, 0x28, 0xdc, 0x6d, 0x16, 0x4d, 0xbd, - 0x69, 0x6f, 0xd6, 0x67, 0x0e, 0x76, 0x26, 0x3e, 0x87, 0xf1, 0x1f, 0xd7, 0xf1, 0x2f, 0xb6, 0xdf, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x32, 0x16, 0x1e, 0x93, 0x03, 0x00, 0x00, -} diff --git a/protocol/ControlChannel.proto b/protocol/ControlChannel.proto deleted file mode 100644 index a9efedf..0000000 --- a/protocol/ControlChannel.proto +++ /dev/null @@ -1,53 +0,0 @@ -syntax = "proto2"; -package protocol; - -message Packet { - // Must contain exactly one field - optional OpenChannel open_channel = 1; - optional ChannelResult channel_result = 2; - optional KeepAlive keep_alive = 3; - optional EnableFeatures enable_features = 4; - optional FeaturesEnabled features_enabled = 5; -} - -message OpenChannel { - required int32 channel_identifier = 1; // Arbitrary unique identifier for this channel instance - required string channel_type = 2; // String identifying channel type; e.g. im.ricochet.chat - - // It is valid to extend the OpenChannel message to add fields specific - // to the requested channel_type. - extensions 100 to max; -} - -message ChannelResult { - required int32 channel_identifier = 1; // Matching the value from OpenChannel - required bool opened = 2; // If the channel is now open - - enum CommonError { - GenericError = 0; - UnknownTypeError = 1; - UnauthorizedError = 2; - BadUsageError = 3; - FailedError = 4; - } - - optional CommonError common_error = 3; - - // As with OpenChannel, it is valid to extend this message with fields specific - // to the channel type. - extensions 100 to max; -} - -message KeepAlive { - required bool response_requested = 1; -} - -message EnableFeatures { - repeated string feature = 1; - extensions 100 to max; -} - -message FeaturesEnabled { - repeated string feature = 1; - extensions 100 to max; -} diff --git a/protocol/connections/connectionsmanager.go b/protocol/connections/connectionsmanager.go deleted file mode 100644 index b9c465b..0000000 --- a/protocol/connections/connectionsmanager.go +++ /dev/null @@ -1,74 +0,0 @@ -package connections - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/connectivity" - "git.openprivacy.ca/openprivacy/log" - - "sync" -) - -// Manager encapsulates all the logic necessary to manage outgoing peer and server connections. -type Manager struct { - serverConnections map[string]*PeerServerConnection - lock sync.Mutex - acn connectivity.ACN -} - -// NewConnectionsManager creates a new instance of Manager. -func NewConnectionsManager(acn connectivity.ACN) *Manager { - m := new(Manager) - m.acn = acn - m.serverConnections = make(map[string]*PeerServerConnection) - return m -} - -// ManageServerConnection creates a new ServerConnection for Host with the given callback handler. -// If there is an establish connection, it is replaced with a new one, assuming this came from -// a new JoinServer from a new Group being joined. If it is still connecting to a server, the second request will be abandonded -func (m *Manager) ManageServerConnection(host string, engine Engine, messageHandler func(string, *protocol.GroupMessage)) { - m.lock.Lock() - defer m.lock.Unlock() - - psc, exists := m.serverConnections[host] - - if exists { - if psc.GetState() == DISCONNECTED || psc.GetState() == CONNECTING || psc.GetState() == CONNECTED { - log.Infof("Already connecting to %v, abandoning fresh attempt\n", host) - return - } - } - - newPsc := NewPeerServerConnection(engine, host) - newPsc.GroupMessageHandler = messageHandler - go newPsc.Run() - m.serverConnections[host] = newPsc - - if exists { - log.Infof("Closing connection to %v, replacing with this one\n", host) - psc.Close() - } -} - -// SetServerSynced is a helper for peerserver connections and engine to call when a Fetch is done to set the state of the connection to SYNCED -func (m *Manager) SetServerSynced(onion string) { - m.serverConnections[onion].setState(SYNCED) -} - -// GetPeerServerConnectionForOnion safely returns a given host connection -func (m *Manager) GetPeerServerConnectionForOnion(host string) (psc *PeerServerConnection) { - m.lock.Lock() - psc = m.serverConnections[host] - m.lock.Unlock() - return -} - -// Shutdown closes all connections under management (freeing their goroutines) -func (m *Manager) Shutdown() { - m.lock.Lock() - for onion, psc := range m.serverConnections { - psc.Close() - delete(m.serverConnections, onion) - } - m.lock.Unlock() -} diff --git a/protocol/connections/connectsmanager_test.go b/protocol/connections/connectsmanager_test.go deleted file mode 100644 index 80540d8..0000000 --- a/protocol/connections/connectsmanager_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package connections - -import ( - "git.openprivacy.ca/openprivacy/connectivity" - "testing" -) - -func TestConnectionsManager(t *testing.T) { - // TODO We need to encapsulate connections behind a well defined interface for tesintg - NewConnectionsManager(connectivity.NewLocalACN()) -} diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 7044e61..476dd8e 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -3,15 +3,17 @@ package connections import ( "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" - "cwtch.im/cwtch/protocol" + "cwtch.im/cwtch/protocol/groups" "cwtch.im/tapir" "cwtch.im/tapir/networks/tor" "cwtch.im/tapir/primitives" "encoding/json" "errors" + "fmt" "git.openprivacy.ca/openprivacy/connectivity" torProdider "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" + "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" "strconv" "sync" @@ -19,8 +21,7 @@ import ( ) type engine struct { - queue event.Queue - connectionsManager *Manager + queue event.Queue // Engine Attributes identity primitives.Identity @@ -41,6 +42,9 @@ type engine struct { // Nextgen Tapir Service service tapir.Service + // Nextgen Tapir Service + ephemeralServices sync.Map // string(onion) => tapir.Service + // Required for listen(), inaccessible from identity privateKey ed25519.PrivateKey @@ -64,7 +68,6 @@ func NewProtocolEngine(identity primitives.Identity, privateKey ed25519.PrivateK go engine.eventHandler() engine.acn = acn - engine.connectionsManager = NewConnectionsManager(engine.acn) // Init the Server running the Simple App. engine.service = new(tor.BaseOnionService) @@ -122,7 +125,7 @@ func (e *engine) eventHandler() { case event.InvitePeerToGroup: e.sendMessageToPeer(ev.EventID, ev.Data[event.RemotePeer], event.ContextInvite, []byte(ev.Data[event.GroupInvite])) case event.JoinServer: - e.joinServer(ev.Data[event.GroupServer]) + e.peerWithTokenServer(ev.Data[event.GroupServer], ev.Data[event.ServerTokenOnion], ev.Data[event.ServerTokenY]) 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. @@ -213,7 +216,6 @@ func (e *engine) listenFn() { // Shutdown tears down the eventHandler goroutine func (e *engine) Shutdown() { e.shuttingDown = true - e.connectionsManager.Shutdown() e.service.Shutdown() e.queue.Shutdown() } @@ -244,6 +246,38 @@ func (e *engine) peerWithOnion(onion string) { } } +// peerWithTokenServer is the entry point for cwtchPeer - server relationships +// needs to be run in a goroutine as will block on Open. +func (e *engine) peerWithTokenServer(onion string, tokenServerOnion string, tokenServerY string) { + e.ignoreOnShutdown(e.serverConnecting)(onion) + + // Create a new ephemeral service for this connection + ephemeralService := new(tor.BaseOnionService) + eid, epk := primitives.InitializeEphemeralIdentity() + ephemeralService.Init(e.acn, epk, &eid) + + Y := ristretto255.NewElement() + Y.UnmarshalText([]byte(tokenServerY)) + connected, err := ephemeralService.Connect(onion, NewTokenBoardClient(e.acn, Y, tokenServerOnion, e.receiveGroupMessage, e.serverSynced)) + e.ephemeralServices.Store(onion, ephemeralService) + // If we are already connected...check if we are authed and issue an auth event + // (This allows the ui to be stateless) + if connected && err != nil { + conn, err := ephemeralService.GetConnection(onion) + if err == nil { + if conn.HasCapability(groups.CwtchServerSyncedCapability) { + e.ignoreOnShutdown(e.serverConnected)(onion) + return + } + } + } + + // Only issue a disconnected error if we are disconnected (Connect will fail if a connection already exists) + if !connected && err != nil { + e.ignoreOnShutdown(e.serverDisconnected)(onion) + } +} + func (e *engine) ignoreOnShutdown(f func(string)) func(string) { return func(x string) { if !e.shuttingDown { @@ -278,6 +312,35 @@ func (e *engine) peerConnecting(onion string) { })) } +func (e *engine) serverConnecting(onion string) { + e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ + event.GroupServer: string(onion), + event.ConnectionState: ConnectionStateName[CONNECTING], + })) +} + +func (e *engine) serverConnected(onion string) { + e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ + event.GroupServer: onion, + event.ConnectionState: ConnectionStateName[CONNECTED], + })) +} + +func (e *engine) serverSynced(onion string) { + log.Debugf("SERVER SYNCED: %v", onion) + e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ + event.GroupServer: onion, + event.ConnectionState: ConnectionStateName[SYNCED], + })) +} + +func (e *engine) serverDisconnected(onion string) { + e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ + event.GroupServer: onion, + event.ConnectionState: ConnectionStateName[DISCONNECTED], + })) +} + func (e *engine) peerAck(onion string, eventID string) { e.eventManager.Publish(event.NewEvent(event.PeerAcknowledgement, map[event.Field]string{ event.EventID: eventID, @@ -335,32 +398,31 @@ func (e *engine) deleteConnection(id string) { } // receiveGroupMessage is a callback function that processes GroupMessages from a given server -func (e *engine) receiveGroupMessage(server string, gm *protocol.GroupMessage) { +func (e *engine) receiveGroupMessage(server string, gm *groups.EncryptedGroupMessage) { // Publish Event so that a Profile Engine can deal with it. // Note: This technically means that *multiple* Profile Engines could listen to the same ProtocolEngine! - e.eventManager.Publish(event.NewEvent(event.EncryptedGroupMessage, map[event.Field]string{event.Ciphertext: string(gm.GetCiphertext()), event.Signature: string(gm.GetSignature())})) -} - -// joinServer manages a new server connection with the given onion address -func (e *engine) joinServer(onion string) { - e.connectionsManager.ManageServerConnection(onion, e, e.receiveGroupMessage) + e.eventManager.Publish(event.NewEvent(event.EncryptedGroupMessage, map[event.Field]string{event.Ciphertext: string(gm.Ciphertext), event.Signature: string(gm.Signature)})) } // sendMessageToGroup attempts to sent the given message to the given group id. -func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) { - psc := e.connectionsManager.GetPeerServerConnectionForOnion(server) - if psc == nil { - e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: "server is offline or the connection has yet to finalize"})) +func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) error { + es, ok := e.ephemeralServices.Load(server) + if !ok { + return fmt.Errorf("no service exists for group %v", server) } - gm := &protocol.GroupMessage{ - Ciphertext: ct, - Signature: sig, - } - err := psc.SendGroupMessage(gm) - - if err != nil { - e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: err.Error()})) + ephemeralService := es.(tapir.Service) + conn, err := ephemeralService.WaitForCapabilityOrClose(server, groups.CwtchServerSyncedCapability) + if err == nil { + tokenApp, ok := (conn.App()).(*TokenBoardClient) + if ok { + for tokenApp.Post(ct, sig) == false { + tokenApp.MakePayment() + } + return nil + } + return errors.New("failed type assertion conn.App != TokenBoardClientApp") } + return err } func (e *engine) handlePeerMessage(hostname string, eventID string, context string, message []byte) { diff --git a/protocol/connections/fetch/peer_fetch_channel.go b/protocol/connections/fetch/peer_fetch_channel.go deleted file mode 100644 index 8f61850..0000000 --- a/protocol/connections/fetch/peer_fetch_channel.go +++ /dev/null @@ -1,104 +0,0 @@ -package fetch - -import ( - "cwtch.im/cwtch/protocol" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" -) - -// CwtchPeerFetchChannel is the peer implementation of the im.cwtch.server.fetch -// channel. -type CwtchPeerFetchChannel struct { - channel *channels.Channel - Handler CwtchPeerFetchChannelHandler -} - -// CwtchPeerFetchChannelHandler should be implemented by peers to receive new messages. -type CwtchPeerFetchChannelHandler interface { - HandleGroupMessage(*protocol.GroupMessage) - HandleFetchDone() -} - -// Type returns the type string for this channel, e.g. "im.ricochet.server.fetch) -func (cpfc *CwtchPeerFetchChannel) Type() string { - return "im.cwtch.server.fetch" -} - -// Closed is called when the channel is closed for any reason. -func (cpfc *CwtchPeerFetchChannel) Closed(err error) { - cpfc.Handler.HandleFetchDone() -} - -// OnlyClientCanOpen - for Cwtch server channels only client can open -func (cpfc *CwtchPeerFetchChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cpfc *CwtchPeerFetchChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cpfc *CwtchPeerFetchChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch server channels require no auth. -func (cpfc *CwtchPeerFetchChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound - cwtch server peer implementations shouldnever respond to inbound requests -func (cpfc *CwtchPeerFetchChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - return nil, errors.New("client does not receive inbound listen channels") -} - -// OpenOutbound sets up a new cwtch fetch channel -func (cpfc *CwtchPeerFetchChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - cpfc.channel = channel - messageBuilder := new(utils.MessageBuilder) - return messageBuilder.OpenChannel(channel.ID, cpfc.Type()), nil -} - -// OpenOutboundResult confirms a previous open channel request -func (cpfc *CwtchPeerFetchChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - if err == nil { - if crm.GetOpened() { - cpfc.channel.Pending = false - cpfc.FetchRequest() - } - } -} - -// FetchRequest sends a FetchMessage to the Server. -func (cpfc *CwtchPeerFetchChannel) FetchRequest() error { - if cpfc.channel.Pending == false { - fm := &protocol.FetchMessage{} - csp := &protocol.CwtchServerPacket{ - FetchMessage: fm, - } - packet, _ := proto.Marshal(csp) - cpfc.channel.SendMessage(packet) - } else { - return errors.New("channel isn't set up yet") - } - return nil -} - -// Packet is called for each raw packet received on this channel. -func (cpfc *CwtchPeerFetchChannel) Packet(data []byte) { - csp := &protocol.CwtchServerPacket{} - err := proto.Unmarshal(data, csp) - if err == nil { - if csp.GetGroupMessage() != nil { - gm := csp.GetGroupMessage() - // We create a new go routine here to avoid leaking any information about processing time - // TODO Server can probably try to use this to DoS a peer - cpfc.Handler.HandleGroupMessage(gm) - } - } -} diff --git a/protocol/connections/fetch/peer_fetch_channel_test.go b/protocol/connections/fetch/peer_fetch_channel_test.go deleted file mode 100644 index 1d9652a..0000000 --- a/protocol/connections/fetch/peer_fetch_channel_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package fetch - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" - "time" -) - -type TestHandler struct { - Received bool - Closed bool -} - -func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) { - th.Received = true -} - -func (th *TestHandler) HandleFetchDone() { - th.Closed = true -} - -func TestPeerFetchChannelAttributes(t *testing.T) { - cssc := new(CwtchPeerFetchChannel) - if cssc.Type() != "im.cwtch.server.fetch" { - t.Errorf("cwtch channel type is incorrect %v", cssc.Type()) - } - - if !cssc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.Fetch channel") - } - - if cssc.Bidirectional() { - t.Errorf("im.cwtch.server.fetch should not be bidirectional") - } - - if !cssc.Singleton() { - t.Errorf("im.cwtch.server.fetch should be a Singleton") - } - - if cssc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication()) - } - -} -func TestPeerFetchChannelOpenInbound(t *testing.T) { - cssc := new(CwtchPeerFetchChannel) - channel := new(channels.Channel) - _, err := cssc.OpenInbound(channel, nil) - if err == nil { - t.Errorf("client implementation of im.cwtch.server.Fetch should never open an inbound channel") - } -} - -func TestPeerFetchChannel(t *testing.T) { - pfc := new(CwtchPeerFetchChannel) - th := new(TestHandler) - pfc.Handler = th - channel := new(channels.Channel) - channel.ID = 3 - channel.SendMessage = func([]byte) {} - channel.CloseChannel = func() {} - result, err := pfc.OpenOutbound(channel) - if err != nil { - t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err) - } - - cr := &Protocol_Data_Control.ChannelResult{ - ChannelIdentifier: proto.Int32(3), - Opened: proto.Bool(true), - } - - pfc.OpenOutboundResult(nil, cr) - if channel.Pending { - t.Errorf("once opened channel should no longer be pending") - } - - csp := &protocol.CwtchServerPacket{ - GroupMessage: &protocol.GroupMessage{ - Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}, - }, - } - packet, _ := proto.Marshal(csp) - - pfc.Packet(packet) - - time.Sleep(time.Second * 2) - - if th.Received != true { - t.Errorf("group message should not have been received") - } - - pfc.Closed(nil) - -} diff --git a/protocol/connections/listen/peer_listen_channel.go b/protocol/connections/listen/peer_listen_channel.go deleted file mode 100644 index 0aacc0a..0000000 --- a/protocol/connections/listen/peer_listen_channel.go +++ /dev/null @@ -1,84 +0,0 @@ -package listen - -import ( - "cwtch.im/cwtch/protocol" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" -) - -// CwtchPeerListenChannel is the peer implementation of im.cwtch.server.listen -type CwtchPeerListenChannel struct { - channel *channels.Channel - Handler CwtchPeerSendChannelHandler -} - -// CwtchPeerSendChannelHandler is implemented by peers who want to listen to new messages -type CwtchPeerSendChannelHandler interface { - HandleGroupMessage(*protocol.GroupMessage) -} - -// Type returns the type string for this channel, e.g. "im.ricochet.server.listen". -func (cplc *CwtchPeerListenChannel) Type() string { - return "im.cwtch.server.listen" -} - -// Closed is called when the channel is closed for any reason. -func (cplc *CwtchPeerListenChannel) Closed(err error) { - -} - -// OnlyClientCanOpen - for Cwtch server channels can only be opened by peers -func (cplc *CwtchPeerListenChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cplc *CwtchPeerListenChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cplc *CwtchPeerListenChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch channels require no auth channels -func (cplc *CwtchPeerListenChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound - peers should never respond to open inbound requests from servers -func (cplc *CwtchPeerListenChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - return nil, errors.New("client does not receive inbound listen channels") -} - -// OpenOutbound sets up a new server listen channel -func (cplc *CwtchPeerListenChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - cplc.channel = channel - messageBuilder := new(utils.MessageBuilder) - return messageBuilder.OpenChannel(channel.ID, cplc.Type()), nil -} - -// OpenOutboundResult confirms a previous open channel request -func (cplc *CwtchPeerListenChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - if err == nil { - if crm.GetOpened() { - cplc.channel.Pending = false - } - } -} - -// Packet is called for each server packet received on this channel. -func (cplc *CwtchPeerListenChannel) Packet(data []byte) { - csp := &protocol.CwtchServerPacket{} - err := proto.Unmarshal(data, csp) - if err == nil { - if csp.GetGroupMessage() != nil { - gm := csp.GetGroupMessage() - cplc.Handler.HandleGroupMessage(gm) - } - } -} diff --git a/protocol/connections/listen/peer_listen_channel_test.go b/protocol/connections/listen/peer_listen_channel_test.go deleted file mode 100644 index 6f8ddf7..0000000 --- a/protocol/connections/listen/peer_listen_channel_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package listen - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" - "time" -) - -type TestHandler struct { - Received bool -} - -func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) { - th.Received = true -} - -func TestPeerListenChannelAttributes(t *testing.T) { - cssc := new(CwtchPeerListenChannel) - if cssc.Type() != "im.cwtch.server.listen" { - t.Errorf("cwtch channel type is incorrect %v", cssc.Type()) - } - - if !cssc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.listen channel") - } - - if cssc.Bidirectional() { - t.Errorf("im.cwtch.server.listen should not be bidirectional") - } - - if !cssc.Singleton() { - t.Errorf("im.cwtch.server.listen should be a Singleton") - } - - if cssc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication()) - } - -} -func TestPeerListenChannelOpenInbound(t *testing.T) { - cssc := new(CwtchPeerListenChannel) - channel := new(channels.Channel) - _, err := cssc.OpenInbound(channel, nil) - if err == nil { - t.Errorf("client implementation of im.cwtch.server.Listen should never open an inbound channel") - } -} - -func TestPeerListenChannel(t *testing.T) { - pfc := new(CwtchPeerListenChannel) - th := new(TestHandler) - pfc.Handler = th - channel := new(channels.Channel) - channel.ID = 3 - result, err := pfc.OpenOutbound(channel) - if err != nil { - t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err) - } - - cr := &Protocol_Data_Control.ChannelResult{ - ChannelIdentifier: proto.Int32(3), - Opened: proto.Bool(true), - } - - pfc.OpenOutboundResult(nil, cr) - if channel.Pending { - t.Errorf("once opened channel should no longer be pending") - } - - csp := &protocol.CwtchServerPacket{ - GroupMessage: &protocol.GroupMessage{Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}, - } - packet, _ := proto.Marshal(csp) - - pfc.Packet(packet) - - // Wait for goroutine to run - time.Sleep(time.Second * 1) - - if !th.Received { - t.Errorf("group message should have been received") - } - - pfc.Closed(nil) - -} diff --git a/protocol/connections/peerapp.go b/protocol/connections/peerapp.go index 581d971..45b601c 100644 --- a/protocol/connections/peerapp.go +++ b/protocol/connections/peerapp.go @@ -83,7 +83,7 @@ func (pa *PeerApp) listen() { for { message := pa.connection.Expect() if len(message) == 0 { - log.Errorf("0 byte read, socket has likely failed. Closing the listen goroutine") + log.Debugf("0 byte read, socket has likely failed. Closing the listen goroutine") pa.OnClose(pa.connection.Hostname()) return } diff --git a/protocol/connections/peerserverconnection.go b/protocol/connections/peerserverconnection.go deleted file mode 100644 index f150bc9..0000000 --- a/protocol/connections/peerserverconnection.go +++ /dev/null @@ -1,168 +0,0 @@ -package connections - -import ( - "crypto/rand" - "cwtch.im/cwtch/event" - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/protocol/connections/fetch" - "cwtch.im/cwtch/protocol/connections/listen" - "cwtch.im/cwtch/protocol/connections/send" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/connection" - "git.openprivacy.ca/openprivacy/libricochet-go/identity" - "git.openprivacy.ca/openprivacy/log" - "golang.org/x/crypto/ed25519" - "sync" - "time" -) - -// PeerServerConnection encapsulates a single Peer->Server connection -type PeerServerConnection struct { - connection.AutoConnectionHandler - Server string - stateMutex sync.Mutex - state ConnectionState - connection *connection.Connection - protocolEngine Engine - - GroupMessageHandler func(string, *protocol.GroupMessage) -} - -// NewPeerServerConnection creates a new Peer->Server outbound connection -func NewPeerServerConnection(engine Engine, serverhostname string) *PeerServerConnection { - psc := new(PeerServerConnection) - psc.protocolEngine = engine - psc.Server = serverhostname - psc.setState(DISCONNECTED) - psc.Init() - return psc -} - -// GetState returns the current connection state -func (psc *PeerServerConnection) GetState() ConnectionState { - psc.stateMutex.Lock() - defer psc.stateMutex.Unlock() - return psc.state -} - -func (psc *PeerServerConnection) setState(state ConnectionState) { - log.Debugf("Setting State to %v for %v\n", ConnectionStateName[state], psc.Server) - psc.stateMutex.Lock() - defer psc.stateMutex.Unlock() - psc.state = state - psc.protocolEngine.EventManager().Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ - event.GroupServer: string(psc.Server), - event.ConnectionState: ConnectionStateName[state], - })) -} - -// WaitTilSynced waits until the underlying connection is authenticated -func (psc *PeerServerConnection) WaitTilSynced() { - for { - if psc.GetState() == SYNCED { - break - } - time.Sleep(time.Second * 1) - } -} - -// Run manages the setup and teardown of a peer server connection -func (psc *PeerServerConnection) Run() error { - log.Infof("Connecting to %v", psc.Server) - psc.setState(CONNECTING) - - rc, err := goricochet.Open(psc.protocolEngine.ACN(), psc.Server) - if err == nil { - psc.connection = rc - if psc.GetState() == KILLED { - return nil - } - psc.setState(CONNECTED) - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err == nil { - _, err := connection.HandleOutboundConnection(psc.connection).ProcessAuthAsV3Client(identity.InitializeV3("cwtchpeer", &priv, &pub)) - if err == nil { - if psc.GetState() == KILLED { - return nil - } - psc.setState(AUTHENTICATED) - - go func() { - psc.connection.Do(func() error { - psc.connection.RequestOpenChannel("im.cwtch.server.fetch", &fetch.CwtchPeerFetchChannel{Handler: psc}) - return nil - }) - - psc.connection.Do(func() error { - psc.connection.RequestOpenChannel("im.cwtch.server.listen", &listen.CwtchPeerListenChannel{Handler: psc}) - return nil - }) - }() - psc.connection.Process(psc) - } - } - } - psc.setState(FAILED) - return err -} - -// Break makes Run() return and prevents processing, but doesn't close the connection. -func (psc *PeerServerConnection) Break() error { - return psc.connection.Break() -} - -// SendGroupMessage sends the given protocol message to the Server. -func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) error { - if psc.state != SYNCED { - return errors.New("peer is not yet connected & authenticated & synced to server cannot send message") - } - - err := psc.connection.Do(func() error { - psc.connection.RequestOpenChannel("im.cwtch.server.send", &send.CwtchPeerSendChannel{}) - return nil - }) - - errCount := 0 - for errCount < 5 { - time.Sleep(time.Second * time.Duration(errCount+1)) // back off retry - err = psc.connection.Do(func() error { - channel := psc.connection.Channel("im.cwtch.server.send", channels.Outbound) - if channel == nil { - return errors.New("no channel found") - } - sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel) - if ok { - return sendchannel.SendGroupMessage(gm) - } - return errors.New("channel is not a peer send channel (this should definitely not happen)") - }) - - if err != nil { - errCount++ - } else { - return nil - } - } - - return err -} - -// Close shuts down the connection (freeing the handler goroutines) -func (psc *PeerServerConnection) Close() { - psc.setState(KILLED) - if psc.connection != nil { - psc.connection.Close() - } -} - -// HandleGroupMessage passes the given group message back to the profile. -func (psc *PeerServerConnection) HandleGroupMessage(gm *protocol.GroupMessage) { - psc.GroupMessageHandler(psc.Server, gm) -} - -// HandleFetchDone calls the supplied callback for when a fetch connection is closed -func (psc *PeerServerConnection) HandleFetchDone() { - psc.setState(SYNCED) -} diff --git a/protocol/connections/peerserverconnection_test.go b/protocol/connections/peerserverconnection_test.go deleted file mode 100644 index e29f9af..0000000 --- a/protocol/connections/peerserverconnection_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package connections - -import ( - "cwtch.im/cwtch/event" - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/server/fetch" - "cwtch.im/cwtch/server/send" - "cwtch.im/tapir/primitives" - "git.openprivacy.ca/openprivacy/connectivity" - "git.openprivacy.ca/openprivacy/libricochet-go" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/connection" - identityOld "git.openprivacy.ca/openprivacy/libricochet-go/identity" - "golang.org/x/crypto/ed25519" - "net" - "testing" - "time" -) - -func ServerAuthValid(hostname string, key ed25519.PublicKey) (allowed, known bool) { - return true, true -} - -type TestServer struct { - connection.AutoConnectionHandler - Received chan bool -} - -func (ts *TestServer) HandleGroupMessage(gm *protocol.GroupMessage) { - ts.Received <- true -} - -func (ts *TestServer) HandleFetchRequest() []*protocol.GroupMessage { - return []*protocol.GroupMessage{{Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}, {Ciphertext: []byte("hello"), Signature: []byte{}, Spamguard: []byte{}}} -} - -func runtestserver(t *testing.T, ts *TestServer, priv ed25519.PrivateKey, identity primitives.Identity, listenChan chan bool) { - ln, _ := net.Listen("tcp", "127.0.0.1:5451") - listenChan <- true - conn, _ := ln.Accept() - defer conn.Close() - - rc, err := goricochet.NegotiateVersionInbound(conn) - if err != nil { - t.Errorf("Negotiate Version Error: %v", err) - } - // TODO switch from old identity to new tapir identity. - pub := identity.PublicKey() - err = connection.HandleInboundConnection(rc).ProcessAuthAsV3Server(identityOld.InitializeV3("", &priv, &pub), ServerAuthValid) - if err != nil { - t.Errorf("ServerAuth Error: %v", err) - } - - ts.RegisterChannelHandler("im.cwtch.server.send", func() channels.Handler { - server := new(send.CwtchServerSendChannel) - server.Handler = ts - return server - }) - - ts.RegisterChannelHandler("im.cwtch.server.fetch", func() channels.Handler { - server := new(fetch.CwtchServerFetchChannel) - server.Handler = ts - return server - }) - - rc.Process(ts) -} - -func TestPeerServerConnection(t *testing.T) { - identity, priv := primitives.InitializeEphemeralIdentity() - t.Logf("Launching Server....\n") - ts := new(TestServer) - ts.Init() - ts.Received = make(chan bool) - listenChan := make(chan bool) - go runtestserver(t, ts, priv, identity, listenChan) - <-listenChan - onionAddr := identity.Hostname() - - manager := event.NewEventManager() - engine := NewProtocolEngine(identity, priv, connectivity.NewLocalACN(), manager, nil) - - psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr) - numcalls := 0 - psc.GroupMessageHandler = func(s string, gm *protocol.GroupMessage) { - numcalls++ - } - state := psc.GetState() - if state != DISCONNECTED { - t.Errorf("new connections should start in disconnected state") - } - time.Sleep(time.Second * 1) - go psc.Run() - psc.WaitTilSynced() - - gm := &protocol.GroupMessage{Ciphertext: []byte("hello"), Signature: []byte{}} - psc.SendGroupMessage(gm) - - // Wait until message is received - <-ts.Received - - if numcalls != 2 { - t.Errorf("Should have received 2 calls from fetch request, instead received %v", numcalls) - } -} diff --git a/protocol/connections/send/peer_send_channel.go b/protocol/connections/send/peer_send_channel.go deleted file mode 100644 index a374b94..0000000 --- a/protocol/connections/send/peer_send_channel.go +++ /dev/null @@ -1,95 +0,0 @@ -package send - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/protocol/connections/spam" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" -) - -// CwtchPeerSendChannel is the peer implementation of im.cwtch.server.send -type CwtchPeerSendChannel struct { - channel *channels.Channel - spamGuard spam.Guard - challenge []byte -} - -// Type returns the type string for this channel, e.g. "im.ricochet.server.send". -func (cpsc *CwtchPeerSendChannel) Type() string { - return "im.cwtch.server.send" -} - -// Closed is called when the channel is closed for any reason. -func (cpsc *CwtchPeerSendChannel) Closed(err error) { - -} - -// OnlyClientCanOpen - for Cwtch server channels only peers may open. -func (cpsc *CwtchPeerSendChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cpsc *CwtchPeerSendChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cpsc *CwtchPeerSendChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch channels require no auth -func (cpsc *CwtchPeerSendChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound should never be called on peers. -func (cpsc *CwtchPeerSendChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - return nil, errors.New("client does not receive inbound listen channels") -} - -// OpenOutbound is used to set up a new send channel and initialize spamguard -func (cpsc *CwtchPeerSendChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - cpsc.spamGuard.Difficulty = 2 - cpsc.channel = channel - messageBuilder := new(utils.MessageBuilder) - return messageBuilder.OpenChannel(channel.ID, cpsc.Type()), nil -} - -// OpenOutboundResult confirms the open channel request and sets the spamguard challenge -func (cpsc *CwtchPeerSendChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - if err == nil { - if crm.GetOpened() { - ce, _ := proto.GetExtension(crm, protocol.E_ServerNonce) - cpsc.challenge = ce.([]byte)[:] - cpsc.channel.Pending = false - } - } -} - -// SendGroupMessage performs the spamguard proof of work and sends a message. -func (cpsc *CwtchPeerSendChannel) SendGroupMessage(gm *protocol.GroupMessage) error { - if cpsc.channel.Pending == false { - sgsolve := cpsc.spamGuard.SolveChallenge(cpsc.challenge, gm.GetCiphertext()) - gm.Spamguard = sgsolve[:] - csp := &protocol.CwtchServerPacket{ - GroupMessage: gm, - } - packet, _ := proto.Marshal(csp) - cpsc.channel.SendMessage(packet) - cpsc.channel.CloseChannel() - } else { - return errors.New("channel isn't set up yet") - } - return nil -} - -// Packet should never be -func (cpsc *CwtchPeerSendChannel) Packet(data []byte) { - // If we receive a packet on this channel, close the connection - cpsc.channel.CloseChannel() -} diff --git a/protocol/connections/send/peer_send_channel_test.go b/protocol/connections/send/peer_send_channel_test.go deleted file mode 100644 index 5ec16ba..0000000 --- a/protocol/connections/send/peer_send_channel_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package send - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/protocol/connections/spam" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" -) - -func TestPeerSendChannelAttributes(t *testing.T) { - cssc := new(CwtchPeerSendChannel) - if cssc.Type() != "im.cwtch.server.send" { - t.Errorf("cwtch channel type is incorrect %v", cssc.Type()) - } - - if !cssc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.send channel") - } - - if cssc.Bidirectional() { - t.Errorf("im.cwtch.server.listen should not be bidirectional") - } - - if !cssc.Singleton() { - t.Errorf("im.cwtch.server.listen should be a Singleton") - } - - if cssc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication()) - } - -} - -func TestPeerSendChannelOpenInbound(t *testing.T) { - cssc := new(CwtchPeerSendChannel) - channel := new(channels.Channel) - _, err := cssc.OpenInbound(channel, nil) - if err == nil { - t.Errorf("client implementation of im.cwtch.server.Listen should never open an inbound channel") - } -} - -func TestPeerSendChannelClosesOnPacket(t *testing.T) { - pfc := new(CwtchPeerSendChannel) - channel := new(channels.Channel) - closed := false - channel.CloseChannel = func() { - closed = true - } - - pfc.OpenOutbound(channel) - pfc.Packet([]byte{}) - if !closed { - t.Errorf("send channel should close if server attempts to send packets") - } -} - -func TestPeerSendChannel(t *testing.T) { - pfc := new(CwtchPeerSendChannel) - - channel := new(channels.Channel) - channel.ID = 3 - success := false - - var sg spam.Guard - sg.Difficulty = 2 - - closed := false - channel.CloseChannel = func() { - closed = true - } - - channel.SendMessage = func(message []byte) { - packet := new(protocol.CwtchServerPacket) - proto.Unmarshal(message[:], packet) - if packet.GetGroupMessage() != nil { - success = sg.ValidateChallenge(packet.GetGroupMessage().GetCiphertext(), packet.GetGroupMessage().GetSpamguard()) - } - } - result, err := pfc.OpenOutbound(channel) - if err != nil { - t.Errorf("expected result but also got non-nil error: result:%v, err: %v", result, err) - } - - challenge := sg.GenerateChallenge(3) - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(challenge[:], control) - - pfc.OpenOutboundResult(nil, control.GetChannelResult()) - if channel.Pending { - t.Errorf("once opened channel should no longer be pending") - } - - gm := &protocol.GroupMessage{Ciphertext: []byte("hello")} - pfc.SendGroupMessage(gm) - if !success { - t.Errorf("send channel should have successfully sent a valid group message") - } - - if !closed { - t.Errorf("send channel should have successfully closed after a valid group message") - } - - pfc.Closed(nil) - -} diff --git a/protocol/connections/spam/spamguard.go b/protocol/connections/spam/spamguard.go deleted file mode 100644 index 4150665..0000000 --- a/protocol/connections/spam/spamguard.go +++ /dev/null @@ -1,100 +0,0 @@ -package spam - -import ( - "crypto/rand" - "crypto/sha256" - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "io" - //"fmt" -) - -// Guard implements a spam protection mechanism for Cwtch Servers. -type Guard struct { - Difficulty int - nonce [24]byte -} - -func getRandomness(arr *[24]byte) { - if _, err := io.ReadFull(rand.Reader, arr[:]); err != nil { - utils.CheckError(err) - } -} - -//GenerateChallenge returns a channel result packet with a spamguard challenge nonce -func (sg *Guard) GenerateChallenge(channelID int32) []byte { - - cr := &Protocol_Data_Control.ChannelResult{ - ChannelIdentifier: proto.Int32(channelID), - Opened: proto.Bool(true), - } - - var nonce [24]byte - getRandomness(&nonce) - sg.nonce = nonce - err := proto.SetExtension(cr, protocol.E_ServerNonce, sg.nonce[:]) - utils.CheckError(err) - - pc := &Protocol_Data_Control.Packet{ - ChannelResult: cr, - } - ret, err := proto.Marshal(pc) - utils.CheckError(err) - return ret -} - -// SolveChallenge takes in a challenge and a message and returns a solution -// The solution is a 24 byte nonce which when hashed with the challenge and the message -// produces a sha256 hash with Difficulty leading 0s -func (sg *Guard) SolveChallenge(challenge []byte, message []byte) []byte { - solved := false - var spamguard [24]byte - var sum [32]byte - solve := make([]byte, len(challenge)+len(message)+len(spamguard)) - for !solved { - - getRandomness(&spamguard) - - copy(solve[0:], challenge[:]) - copy(solve[len(challenge):], message[:]) - copy(solve[len(challenge)+len(message):], spamguard[:]) - - sum = sha256.Sum256(solve) - - solved = true - for i := 0; i < sg.Difficulty; i++ { - if sum[i] != 0x00 { - solved = false - } - } - } - //fmt.Printf("[SOLVED] %x\n",sha256.Sum256(solve)) - return spamguard[:] -} - -// ValidateChallenge returns true if the message and spamguard pass the challenge -func (sg *Guard) ValidateChallenge(message []byte, spamguard []byte) bool { - if len(spamguard) != 24 { - return false - } - - // If the message is too large just throw it away. - if len(message) > 2048 { - return false - } - - solve := make([]byte, len(sg.nonce)+len(message)+len(spamguard)) - copy(solve[0:], sg.nonce[:]) - copy(solve[len(sg.nonce):], message[:]) - copy(solve[len(sg.nonce)+len(message):], spamguard[:]) - sum := sha256.Sum256(solve) - - for i := 0; i < sg.Difficulty; i++ { - if sum[i] != 0x00 { - return false - } - } - return true -} diff --git a/protocol/connections/spam/spamguard_test.go b/protocol/connections/spam/spamguard_test.go deleted file mode 100644 index aada34f..0000000 --- a/protocol/connections/spam/spamguard_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package spam - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" -) - -func TestSpamGuard(t *testing.T) { - var spamGuard Guard - spamGuard.Difficulty = 2 - challenge := spamGuard.GenerateChallenge(3) - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(challenge[:], control) - - if control.GetChannelResult() != nil { - ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce) - challenge := ce.([]byte)[:] - - sgsolve := spamGuard.SolveChallenge(challenge, []byte("Hello")) - t.Logf("Solved: %v %v", challenge, sgsolve) - result := spamGuard.ValidateChallenge([]byte("Hello"), sgsolve) - if result != true { - t.Errorf("Validating Guard Failed") - } - return - } - - t.Errorf("Failed SpamGaurd") - -} - -func TestSpamGuardBadLength(t *testing.T) { - var spamGuard Guard - spamGuard.Difficulty = 2 - spamGuard.GenerateChallenge(3) - result := spamGuard.ValidateChallenge([]byte("test"), []byte{0x00, 0x00}) - if result { - t.Errorf("Validating Guard should have failed") - } -} - -func TestSpamGuardFail(t *testing.T) { - var spamGuard Guard - spamGuard.Difficulty = 2 - challenge := spamGuard.GenerateChallenge(3) - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(challenge[:], control) - - if control.GetChannelResult() != nil { - ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce) - challenge := ce.([]byte)[:] - - var spamGuard2 Guard - spamGuard2.Difficulty = 1 - sgsolve := spamGuard2.SolveChallenge(challenge, []byte("Hello")) - t.Logf("Solved: %v %v", challenge, sgsolve) - result := spamGuard.ValidateChallenge([]byte("Hello"), sgsolve) - if result { - t.Errorf("Validating Guard successes") - } - return - } - - t.Errorf("Failed SpamGaurd") -} diff --git a/protocol/connections/tokenboardclientapp.go b/protocol/connections/tokenboardclientapp.go new file mode 100644 index 0000000..cfb3241 --- /dev/null +++ b/protocol/connections/tokenboardclientapp.go @@ -0,0 +1,178 @@ +package connections + +import ( + "cwtch.im/cwtch/protocol/groups" + "cwtch.im/tapir" + "cwtch.im/tapir/applications" + "cwtch.im/tapir/networks/tor" + "cwtch.im/tapir/primitives" + "cwtch.im/tapir/primitives/privacypass" + "encoding/json" + "errors" + "git.openprivacy.ca/openprivacy/connectivity" + "git.openprivacy.ca/openprivacy/log" + "github.com/gtank/ristretto255" +) + +// NewTokenBoardClient generates a new Client for Token Board +func NewTokenBoardClient(acn connectivity.ACN, Y *ristretto255.Element, tokenServiceOnion string, groupMessageHandler func(server string, gm *groups.EncryptedGroupMessage), serverSyncedHandler func(server string)) tapir.Application { + tba := new(TokenBoardClient) + tba.acn = acn + tba.tokenService = privacypass.NewTokenServer() + tba.tokenService.Y = Y + tba.tokenServiceOnion = tokenServiceOnion + tba.receiveGroupMessageHandler = groupMessageHandler + tba.serverSyncedHandler = serverSyncedHandler + return tba +} + +// TokenBoardClient defines a client for the TokenBoard server +type TokenBoardClient struct { + applications.AuthApp + connection tapir.Connection + receiveGroupMessageHandler func(server string, gm *groups.EncryptedGroupMessage) + serverSyncedHandler func(server string) + + // Token service handling + acn connectivity.ACN + tokens []*privacypass.Token + tokenService *privacypass.TokenServer + tokenServiceOnion string +} + +// NewInstance Client a new TokenBoardApp +func (ta *TokenBoardClient) NewInstance() tapir.Application { + tba := new(TokenBoardClient) + tba.serverSyncedHandler = ta.serverSyncedHandler + tba.receiveGroupMessageHandler = ta.receiveGroupMessageHandler + tba.acn = ta.acn + tba.tokenService = ta.tokenService + tba.tokenServiceOnion = ta.tokenServiceOnion + return tba +} + +// Init initializes the cryptographic TokenBoardApp +func (ta *TokenBoardClient) Init(connection tapir.Connection) { + ta.AuthApp.Init(connection) + if connection.HasCapability(applications.AuthCapability) { + ta.connection = connection + ta.connection.SetCapability(groups.CwtchServerSyncedCapability) + log.Debugf("Successfully Initialized Connection") + go ta.Listen() + ta.Replay() + } else { + connection.Close() + } +} + +// Listen processes the messages for this application +func (ta *TokenBoardClient) Listen() { + for { + log.Debugf("Client waiting...") + data := ta.connection.Expect() + if len(data) == 0 { + log.Debugf("Server closed the connection...") + return // connection is closed + } + + // We always expect the server to follow protocol, and the second it doesn't we close the connection + // TODO issue an error so the client is aware + var message groups.Message + if err := json.Unmarshal(data, &message); err != nil { + log.Debugf("Server sent an unexpected message, closing the connection: %v", err) + ta.connection.Close() + return + } + + switch message.MessageType { + case groups.NewMessageMessage: + if message.NewMessage != nil { + ta.receiveGroupMessageHandler(ta.connection.Hostname(), &message.NewMessage.EGM) + } else { + // TODO: Send this error to the UI + log.Debugf("Server sent an unexpected NewMessage, closing the connection: %s", data) + ta.connection.Close() + return + } + case groups.PostResultMessage: + // TODO handle failure + case groups.ReplayResultMessage: + if message.ReplayResult != nil { + log.Debugf("Replaying %v Messages...", message.ReplayResult.NumMessages) + for i := 0; i < message.ReplayResult.NumMessages; i++ { + data := ta.connection.Expect() + egm := &groups.EncryptedGroupMessage{} + if err := json.Unmarshal(data, egm); err == nil { + ta.receiveGroupMessageHandler(ta.connection.Hostname(), egm) + } else { + // TODO: Send this error to the UI + log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection: %v", err) + ta.connection.Close() + return + } + } + ta.serverSyncedHandler(ta.connection.Hostname()) + } + } + } +} + +// Replay posts a Replay Message to the server. +func (ta *TokenBoardClient) Replay() { + // TODO - Allow configurable ranges + data, _ := json.Marshal(groups.Message{MessageType: groups.ReplayRequestMessage, ReplayRequest: &groups.ReplayRequest{LastCommit: []byte{}}}) + ta.connection.Send(data) +} + +// PurchaseTokens purchases the given number of tokens from the server (using the provided payment handler) +func (ta *TokenBoardClient) PurchaseTokens() { + ta.MakePayment() +} + +// Post sends a Post Request to the server +func (ta *TokenBoardClient) Post(ct []byte, sig []byte) bool { + egm := groups.EncryptedGroupMessage{Ciphertext: ct, Signature: sig} + token, err := ta.NextToken(egm.ToBytes(), ta.connection.Hostname()) + if err == nil { + data, _ := json.Marshal(groups.Message{MessageType: groups.PostRequestMessage, PostRequest: &groups.PostRequest{EGM: egm, Token: token}}) + log.Debugf("Message Length: %s %v", data, len(data)) + ta.connection.Send(data) + return true + } + log.Debugf("No Valid Tokens: %v", err) + return false +} + +// MakePayment uses the PoW based token protocol to obtain more tokens +func (ta *TokenBoardClient) MakePayment() { + id, sk := primitives.InitializeEphemeralIdentity() + var client tapir.Service + client = new(tor.BaseOnionService) + client.Init(ta.acn, sk, &id) + + tokenApplication := new(applications.TokenApplication) + tokenApplication.TokenService = ta.tokenService + powTokenApp := new(applications.ApplicationChain). + ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). + ChainApplication(tokenApplication, applications.HasTokensCapability) + client.Connect(ta.tokenServiceOnion, powTokenApp) + conn, err := client.WaitForCapabilityOrClose(ta.tokenServiceOnion, applications.HasTokensCapability) + if err == nil { + powtapp, _ := conn.App().(*applications.TokenApplication) + ta.tokens = append(ta.tokens, powtapp.Tokens...) + log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit()) + conn.Close() + return + } + log.Debugf("Error making payment: %v", err) +} + +// NextToken retrieves the next token +func (ta *TokenBoardClient) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) { + if len(ta.tokens) == 0 { + return privacypass.SpentToken{}, errors.New("No more tokens") + } + token := ta.tokens[0] + ta.tokens = ta.tokens[1:] + return token.SpendToken(append(data, hostname...)), nil +} diff --git a/protocol/cwtch-profile.pb.go b/protocol/cwtch-profile.pb.go deleted file mode 100644 index b78b7b2..0000000 --- a/protocol/cwtch-profile.pb.go +++ /dev/null @@ -1,135 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: cwtch-profile.proto - -/* -Package protocol is a generated protocol buffer package. - -It is generated from these files: - cwtch-profile.proto - -It has these top-level messages: - CwtchPeerPacket - CwtchIdentity - GroupChatInvite -*/ -package protocol - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -type CwtchPeerPacket struct { - CwtchIdentify *CwtchIdentity `protobuf:"bytes,1,opt,name=cwtch_identify,json=cwtchIdentify" json:"cwtch_identify,omitempty"` - GroupChatInvite *GroupChatInvite `protobuf:"bytes,2,opt,name=group_chat_invite,json=groupChatInvite" json:"group_chat_invite,omitempty"` -} - -func (m *CwtchPeerPacket) Reset() { *m = CwtchPeerPacket{} } -func (m *CwtchPeerPacket) String() string { return proto.CompactTextString(m) } -func (*CwtchPeerPacket) ProtoMessage() {} -func (*CwtchPeerPacket) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -func (m *CwtchPeerPacket) GetCwtchIdentify() *CwtchIdentity { - if m != nil { - return m.CwtchIdentify - } - return nil -} - -func (m *CwtchPeerPacket) GetGroupChatInvite() *GroupChatInvite { - if m != nil { - return m.GroupChatInvite - } - return nil -} - -type CwtchIdentity struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Ed25519PublicKey []byte `protobuf:"bytes,2,opt,name=ed25519_public_key,json=ed25519PublicKey,proto3" json:"ed25519_public_key,omitempty"` -} - -func (m *CwtchIdentity) Reset() { *m = CwtchIdentity{} } -func (m *CwtchIdentity) String() string { return proto.CompactTextString(m) } -func (*CwtchIdentity) ProtoMessage() {} -func (*CwtchIdentity) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -func (m *CwtchIdentity) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *CwtchIdentity) GetEd25519PublicKey() []byte { - if m != nil { - return m.Ed25519PublicKey - } - return nil -} - -// [name] has invited you to join a group chat: [message] -type GroupChatInvite struct { - GroupName string `protobuf:"bytes,1,opt,name=group_name,json=groupName" json:"group_name,omitempty"` - GroupSharedKey []byte `protobuf:"bytes,2,opt,name=group_shared_key,json=groupSharedKey,proto3" json:"group_shared_key,omitempty"` - ServerHost string `protobuf:"bytes,3,opt,name=server_host,json=serverHost" json:"server_host,omitempty"` - SignedGroupId []byte `protobuf:"bytes,4,opt,name=signed_group_id,json=signedGroupId,proto3" json:"signed_group_id,omitempty"` - InitialMessage []byte `protobuf:"bytes,5,opt,name=initial_message,json=initialMessage,proto3" json:"initial_message,omitempty"` -} - -func (m *GroupChatInvite) Reset() { *m = GroupChatInvite{} } -func (m *GroupChatInvite) String() string { return proto.CompactTextString(m) } -func (*GroupChatInvite) ProtoMessage() {} -func (*GroupChatInvite) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -func (m *GroupChatInvite) GetGroupName() string { - if m != nil { - return m.GroupName - } - return "" -} - -func (m *GroupChatInvite) GetGroupSharedKey() []byte { - if m != nil { - return m.GroupSharedKey - } - return nil -} - -func (m *GroupChatInvite) GetServerHost() string { - if m != nil { - return m.ServerHost - } - return "" -} - -func (m *GroupChatInvite) GetSignedGroupId() []byte { - if m != nil { - return m.SignedGroupId - } - return nil -} - -func (m *GroupChatInvite) GetInitialMessage() []byte { - if m != nil { - return m.InitialMessage - } - return nil -} - -func init() { - proto.RegisterType((*CwtchPeerPacket)(nil), "protocol.CwtchPeerPacket") - proto.RegisterType((*CwtchIdentity)(nil), "protocol.CwtchIdentity") - proto.RegisterType((*GroupChatInvite)(nil), "protocol.GroupChatInvite") -} - -func init() { proto.RegisterFile("cwtch-profile.proto", fileDescriptor0) } diff --git a/protocol/cwtch-profile.proto b/protocol/cwtch-profile.proto deleted file mode 100644 index 3de2ef5..0000000 --- a/protocol/cwtch-profile.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; -package protocol; - -message CwtchPeerPacket { - CwtchIdentity cwtch_identify = 1; - GroupChatInvite group_chat_invite = 2; -} - -message CwtchIdentity { - string name = 1; - bytes ed25519_public_key = 2; -} - -// [name] has invited you to join a group chat: [message] -message GroupChatInvite { - string group_name = 1; - bytes group_shared_key = 2; - string server_host = 3; - bytes signed_group_id = 4; - bytes initial_message = 5; -} diff --git a/protocol/group_message.pb.go b/protocol/group_message.pb.go deleted file mode 100644 index c11e4a0..0000000 --- a/protocol/group_message.pb.go +++ /dev/null @@ -1,188 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: group_message.proto - -package protocol - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import control "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type CwtchServerPacket struct { - GroupMessage *GroupMessage `protobuf:"bytes,1,opt,name=group_message,json=groupMessage" json:"group_message,omitempty"` - FetchMessage *FetchMessage `protobuf:"bytes,2,opt,name=fetch_message,json=fetchMessage" json:"fetch_message,omitempty"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *CwtchServerPacket) Reset() { *m = CwtchServerPacket{} } -func (m *CwtchServerPacket) String() string { return proto.CompactTextString(m) } -func (*CwtchServerPacket) ProtoMessage() {} -func (*CwtchServerPacket) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } - -func (m *CwtchServerPacket) GetGroupMessage() *GroupMessage { - if m != nil { - return m.GroupMessage - } - return nil -} - -func (m *CwtchServerPacket) GetFetchMessage() *FetchMessage { - if m != nil { - return m.FetchMessage - } - return nil -} - -type FetchMessage struct { - XXX_unrecognized []byte `json:"-"` -} - -func (m *FetchMessage) Reset() { *m = FetchMessage{} } -func (m *FetchMessage) String() string { return proto.CompactTextString(m) } -func (*FetchMessage) ProtoMessage() {} -func (*FetchMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } - -type GroupMessage struct { - Ciphertext []byte `protobuf:"bytes,1,req,name=ciphertext" json:"ciphertext,omitempty"` - Spamguard []byte `protobuf:"bytes,2,req,name=spamguard" json:"spamguard,omitempty"` - Signature []byte `protobuf:"bytes,3,req,name=signature" json:"signature,omitempty"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *GroupMessage) Reset() { *m = GroupMessage{} } -func (m *GroupMessage) String() string { return proto.CompactTextString(m) } -func (*GroupMessage) ProtoMessage() {} -func (*GroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} } - -func (m *GroupMessage) GetCiphertext() []byte { - if m != nil { - return m.Ciphertext - } - return nil -} - -func (m *GroupMessage) GetSpamguard() []byte { - if m != nil { - return m.Spamguard - } - return nil -} - -func (m *GroupMessage) GetSignature() []byte { - if m != nil { - return m.Signature - } - return nil -} - -// DecryptedGroupMessage is *never* sent in the clear on the wire -// and is only ever sent when encrypted in the ciphertext parameter of -// GroupMessage -type DecryptedGroupMessage struct { - Onion *string `protobuf:"bytes,1,req,name=onion" json:"onion,omitempty"` - Timestamp *int32 `protobuf:"varint,2,req,name=timestamp" json:"timestamp,omitempty"` - Text *string `protobuf:"bytes,3,req,name=text" json:"text,omitempty"` - SignedGroupId []byte `protobuf:"bytes,4,req,name=signed_group_id,json=signedGroupId" json:"signed_group_id,omitempty"` - PreviousMessageSig []byte `protobuf:"bytes,5,req,name=previous_message_sig,json=previousMessageSig" json:"previous_message_sig,omitempty"` - // Used to prevent analysis on text length, length is 1024 - len(text) - Padding []byte `protobuf:"bytes,6,req,name=padding" json:"padding,omitempty"` - XXX_unrecognized []byte `json:"-"` -} - -func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} } -func (m *DecryptedGroupMessage) String() string { return proto.CompactTextString(m) } -func (*DecryptedGroupMessage) ProtoMessage() {} -func (*DecryptedGroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} } - -func (m *DecryptedGroupMessage) GetOnion() string { - if m != nil && m.Onion != nil { - return *m.Onion - } - return "" -} - -func (m *DecryptedGroupMessage) GetTimestamp() int32 { - if m != nil && m.Timestamp != nil { - return *m.Timestamp - } - return 0 -} - -func (m *DecryptedGroupMessage) GetText() string { - if m != nil && m.Text != nil { - return *m.Text - } - return "" -} - -func (m *DecryptedGroupMessage) GetSignedGroupId() []byte { - if m != nil { - return m.SignedGroupId - } - return nil -} - -func (m *DecryptedGroupMessage) GetPreviousMessageSig() []byte { - if m != nil { - return m.PreviousMessageSig - } - return nil -} - -func (m *DecryptedGroupMessage) GetPadding() []byte { - if m != nil { - return m.Padding - } - return nil -} - -var E_ServerNonce = &proto.ExtensionDesc{ - ExtendedType: (*control.ChannelResult)(nil), - ExtensionType: ([]byte)(nil), - Field: 8200, - Name: "protocol.server_nonce", - Tag: "bytes,8200,opt,name=server_nonce,json=serverNonce", - Filename: "group_message.proto", -} - -func init() { - proto.RegisterType((*CwtchServerPacket)(nil), "protocol.CwtchServerPacket") - proto.RegisterType((*FetchMessage)(nil), "protocol.FetchMessage") - proto.RegisterType((*GroupMessage)(nil), "protocol.GroupMessage") - proto.RegisterType((*DecryptedGroupMessage)(nil), "protocol.DecryptedGroupMessage") - proto.RegisterExtension(E_ServerNonce) -} - -func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) } - -var fileDescriptor2 = []byte{ - // 360 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0x4d, 0x6e, 0xdb, 0x30, - 0x10, 0x85, 0x21, 0xff, 0xb4, 0xf5, 0x58, 0x6e, 0x51, 0xd6, 0x6d, 0x89, 0xa2, 0x28, 0x0c, 0x2d, - 0x0a, 0xaf, 0x8c, 0x20, 0xcb, 0x78, 0x13, 0xc0, 0x41, 0x82, 0x2c, 0x12, 0x04, 0xf2, 0x01, 0x04, - 0x42, 0x1a, 0x53, 0x4c, 0x24, 0x92, 0x20, 0x29, 0x27, 0xb9, 0x41, 0x36, 0x39, 0x5d, 0x2e, 0x14, - 0x88, 0xb2, 0x6c, 0x39, 0x2b, 0x69, 0xde, 0x37, 0x6f, 0xde, 0x80, 0x24, 0xfc, 0xe0, 0x46, 0x55, - 0x3a, 0x29, 0xd1, 0x5a, 0xc6, 0x71, 0xa1, 0x8d, 0x72, 0x8a, 0x7c, 0xf1, 0x9f, 0x54, 0x15, 0x7f, - 0xa6, 0x2b, 0x25, 0x9d, 0x51, 0xc5, 0x2a, 0x67, 0x52, 0x62, 0xd1, 0xf0, 0xe8, 0x35, 0x80, 0xef, - 0xab, 0x47, 0x97, 0xe6, 0x6b, 0x34, 0x5b, 0x34, 0x77, 0x2c, 0x7d, 0x40, 0x47, 0x96, 0x30, 0x39, - 0x1a, 0x46, 0x83, 0x59, 0x30, 0x1f, 0x9f, 0xfe, 0x5a, 0xb4, 0xd3, 0x16, 0x57, 0x35, 0xbe, 0x69, - 0x68, 0x1c, 0xf2, 0x4e, 0x55, 0x9b, 0x37, 0xe8, 0xd2, 0x7c, 0x6f, 0xee, 0x7d, 0x34, 0x5f, 0xd6, - 0x78, 0x6f, 0xde, 0x74, 0xaa, 0xe8, 0x2b, 0x84, 0x5d, 0x1a, 0xdd, 0x43, 0xd8, 0x8d, 0x22, 0xff, - 0x00, 0x52, 0xa1, 0x73, 0x34, 0x0e, 0x9f, 0x1c, 0x0d, 0x66, 0xbd, 0x79, 0x18, 0x77, 0x14, 0xf2, - 0x17, 0x46, 0x56, 0xb3, 0x92, 0x57, 0xcc, 0x64, 0xb4, 0xe7, 0xf1, 0x41, 0xf0, 0x54, 0x70, 0xc9, - 0x5c, 0x65, 0x90, 0xf6, 0x77, 0xb4, 0x15, 0xa2, 0xb7, 0x00, 0x7e, 0x5e, 0x60, 0x6a, 0x9e, 0xb5, - 0xc3, 0xec, 0x28, 0x75, 0x0a, 0x43, 0x25, 0x85, 0x92, 0x3e, 0x70, 0x14, 0x37, 0x45, 0x3d, 0xcd, - 0x89, 0x12, 0xad, 0x63, 0xa5, 0xf6, 0x59, 0xc3, 0xf8, 0x20, 0x10, 0x02, 0x03, 0xbf, 0x63, 0xdf, - 0x5b, 0xfc, 0x3f, 0xf9, 0x0f, 0xdf, 0xea, 0x38, 0xcc, 0x92, 0xe6, 0x78, 0x45, 0x46, 0x07, 0x7e, - 0x8b, 0x49, 0x23, 0xfb, 0xd0, 0xeb, 0x8c, 0x9c, 0xc0, 0x54, 0x1b, 0xdc, 0x0a, 0x55, 0xd9, 0xf6, - 0x14, 0x13, 0x2b, 0x38, 0x1d, 0xfa, 0x66, 0xd2, 0xb2, 0xdd, 0x7a, 0x6b, 0xc1, 0x09, 0x85, 0xcf, - 0x9a, 0x65, 0x99, 0x90, 0x9c, 0x7e, 0xf2, 0x4d, 0x6d, 0x79, 0xb6, 0x84, 0xd0, 0xfa, 0xbb, 0x4d, - 0xa4, 0x92, 0x29, 0x92, 0xdf, 0x87, 0x7b, 0xd8, 0x3d, 0x85, 0x18, 0x6d, 0x55, 0x38, 0xfa, 0x72, - 0x3e, 0x0b, 0xe6, 0x61, 0x3c, 0x6e, 0xba, 0x6f, 0xeb, 0xe6, 0xf7, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x33, 0x30, 0xca, 0x8b, 0x54, 0x02, 0x00, 0x00, -} diff --git a/protocol/group_message.proto b/protocol/group_message.proto deleted file mode 100644 index 372ed59..0000000 --- a/protocol/group_message.proto +++ /dev/null @@ -1,35 +0,0 @@ -syntax = "proto2"; -package protocol; - -import "ControlChannel.proto"; - -message CwtchServerPacket { - optional GroupMessage group_message = 1; - optional FetchMessage fetch_message = 2; -} - -extend protocol.ChannelResult { - optional bytes server_nonce = 8200; // 32 random bytes -} - -message FetchMessage { -} - -message GroupMessage { - required bytes ciphertext = 1; - required bytes spamguard = 2; - required bytes signature = 3; -} - -// DecryptedGroupMessage is *never* sent in the clear on the wire -// and is only ever sent when encrypted in the ciphertext parameter of -// GroupMessage -message DecryptedGroupMessage { - required string onion = 1; - required int32 timestamp = 2; - required string text = 3; - required bytes signed_group_id = 4; - required bytes previous_message_sig =5; - // Used to prevent analysis on text length, length is 1024 - len(text) - required bytes padding = 6; -} diff --git a/protocol/groups/common.go b/protocol/groups/common.go new file mode 100644 index 0000000..ddc4a9e --- /dev/null +++ b/protocol/groups/common.go @@ -0,0 +1,91 @@ +package groups + +import ( + "cwtch.im/tapir" + "cwtch.im/tapir/primitives/privacypass" + "encoding/json" +) + +// CwtchServerSyncedCapability is used to indicate that a given cwtch server is synced +const CwtchServerSyncedCapability = tapir.Capability("CwtchServerSyncedCapability") + +// GroupInvite provides a structured type for communicating group information to peers +type GroupInvite struct { + GroupName string + SignedGroupID []byte + Timestamp uint64 + SharedKey []byte + ServerHost string + InitialMessage []byte +} + +// DecryptedGroupMessage is the main encapsulation of group message data +type DecryptedGroupMessage struct { + Text string + Onion string + Timestamp uint64 + SignedGroupID []byte + PreviousMessageSig []byte + Padding []byte +} + +// EncryptedGroupMessage provides an encapsulation of the encrypted group message stored on the server +type EncryptedGroupMessage struct { + Ciphertext []byte + Signature []byte +} + +// ToBytes converts the encrypted group message to a set of bytes for serialization +func (egm EncryptedGroupMessage) ToBytes() []byte { + data, _ := json.Marshal(egm) + return data +} + +// MessageType defines the enum for TokenBoard messages +type MessageType int + +// Message Types +const ( + ReplayRequestMessage MessageType = iota + ReplayResultMessage + PostRequestMessage + PostResultMessage + NewMessageMessage +) + +// Message encapsulates the application protocol +type Message struct { + MessageType MessageType + PostRequest *PostRequest `json:",omitempty"` + PostResult *PostResult `json:",omitempty"` + NewMessage *NewMessage `json:",omitempty"` + ReplayRequest *ReplayRequest `json:",omitempty"` + ReplayResult *ReplayResult `json:",omitempty"` +} + +// ReplayRequest requests a reply from the given Commit +type ReplayRequest struct { + LastCommit []byte +} + +// PostRequest requests to post the message to the board with the given token +type PostRequest struct { + Token privacypass.SpentToken + EGM EncryptedGroupMessage +} + +// PostResult returns the success of a given post attempt +type PostResult struct { + Success bool +} + +// ReplayResult is sent by the server before a stream of replayed messages +type ReplayResult struct { + NumMessages int +} + +// NewMessage is used to send a new bulletin board message to interested peers. +type NewMessage struct { + //Token privacypass.SpentToken + EGM EncryptedGroupMessage +} diff --git a/server/app/main.go b/server/app/main.go index 406af21..4ffd9af 100644 --- a/server/app/main.go +++ b/server/app/main.go @@ -5,7 +5,6 @@ import ( "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" "os" - "path" ) const ( @@ -19,7 +18,7 @@ func main() { serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile) - acn, err := tor.NewTorACN(path.Join(configDir, "tor"), "") + acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"}) if err != nil { log.Errorf("\nError connecting to Tor: %v\n", err) os.Exit(1) @@ -31,5 +30,6 @@ func main() { // TODO load params from .cwtch/server.conf or command line flag // TODO: respond to HUP so t.Close is gracefully called - server.Run(acn, serverConfig) + server.Setup(serverConfig) + server.Run(acn) } diff --git a/server/fetch/server_fetch_channel.go b/server/fetch/server_fetch_channel.go deleted file mode 100644 index f2eebc1..0000000 --- a/server/fetch/server_fetch_channel.go +++ /dev/null @@ -1,100 +0,0 @@ -package fetch - -import ( - "cwtch.im/cwtch/protocol" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" -) - -// CwtchServerFetchChannel implements the ChannelHandler interface for a channel of -// type "im.cwtch.server.fetch" - this implementation only handles server side logic. -type CwtchServerFetchChannel struct { - Handler CwtchServerFetchHandler - channel *channels.Channel -} - -// CwtchServerFetchHandler defines the interface for interacting with this Channel -type CwtchServerFetchHandler interface { - HandleFetchRequest() []*protocol.GroupMessage -} - -// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch". -func (cc *CwtchServerFetchChannel) Type() string { - return "im.cwtch.server.fetch" -} - -// Closed is called when the channel is closed for any reason. -func (cc *CwtchServerFetchChannel) Closed(err error) { - -} - -// OnlyClientCanOpen - for Cwtch channels any side can open -func (cc *CwtchServerFetchChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cc *CwtchServerFetchChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cc *CwtchServerFetchChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch channels require hidden service auth -func (cc *CwtchServerFetchChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound is the first method called for an inbound channel request. -// If an error is returned, the channel is rejected. If a RawMessage is -// returned, it will be sent as the ChannelResult message. -func (cc *CwtchServerFetchChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - cc.channel = channel - messageBuilder := new(utils.MessageBuilder) - return messageBuilder.AckOpenChannel(channel.ID), nil -} - -// OpenOutbound is the first method called for an outbound channel request. -// If an error is returned, the channel is not opened. If a RawMessage is -// returned, it will be sent as the OpenChannel message. -func (cc *CwtchServerFetchChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - return nil, errors.New("server does not open Fetch channels") -} - -// OpenOutboundResult is called when a response is received for an -// outbound OpenChannel request. If `err` is non-nil, the channel was -// rejected and Closed will be called immediately afterwards. `raw` -// contains the raw protocol message including any extension data. -func (cc *CwtchServerFetchChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - // NOTE: Should never be called -} - -// SendGroupMessages sends a series of group messages to the client. -func (cc *CwtchServerFetchChannel) SendGroupMessages(gms []*protocol.GroupMessage) { - for _, gm := range gms { - csp := &protocol.CwtchServerPacket{ - GroupMessage: gm, - } - packet, _ := proto.Marshal(csp) - cc.channel.SendMessage(packet) - } -} - -// Packet is called for each raw packet received on this channel. -func (cc *CwtchServerFetchChannel) Packet(data []byte) { - csp := &protocol.CwtchServerPacket{} - err := proto.Unmarshal(data, csp) - if err == nil { - if csp.GetFetchMessage() != nil { - cc.SendGroupMessages(cc.Handler.HandleFetchRequest()) - } - } - // If we receive a packet on this channel, close the connection - cc.channel.CloseChannel() -} diff --git a/server/fetch/server_fetch_channel_test.go b/server/fetch/server_fetch_channel_test.go deleted file mode 100644 index 7f1aef3..0000000 --- a/server/fetch/server_fetch_channel_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package fetch - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" -) - -func TestServerFetchChannelAttributes(t *testing.T) { - cslc := new(CwtchServerFetchChannel) - if cslc.Type() != "im.cwtch.server.fetch" { - t.Errorf("cwtch channel type is incorrect %v", cslc.Type()) - } - - if !cslc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.fetch channel") - } - - if cslc.Bidirectional() { - t.Errorf("im.cwtch.server.fetch should not be bidirectional") - } - - if !cslc.Singleton() { - t.Errorf("im.cwtch.server.fetch should be a Singleton") - } - - if cslc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cslc.RequiresAuthentication()) - } - -} -func TestServerFetchChannelOpenOutbound(t *testing.T) { - cslc := new(CwtchServerFetchChannel) - channel := new(channels.Channel) - _, err := cslc.OpenOutbound(channel) - if err == nil { - t.Errorf("server implementation of im.cwtch.server.fetch should never open an outbound channel") - } -} - -type TestHandler struct { -} - -func (th *TestHandler) HandleFetchRequest() []*protocol.GroupMessage { - gm := &protocol.GroupMessage{ - Ciphertext: []byte("Hello"), - Spamguard: []byte{}, - } - return []*protocol.GroupMessage{gm} -} - -func TestServerFetchChannel(t *testing.T) { - cslc := new(CwtchServerFetchChannel) - th := new(TestHandler) - cslc.Handler = th - channel := new(channels.Channel) - channel.ID = 1 - closed := false - channel.CloseChannel = func() { - closed = true - } - gotgm := false - channel.SendMessage = func([]byte) { - gotgm = true - } - - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(1), - ChannelType: proto.String(cslc.Type()), - } - - resp, err := cslc.OpenInbound(channel, oc) - if err != nil { - t.Errorf("OpenInbound for im.cwtch.server.Fetch should have succeeded, instead: %v", err) - } - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(resp[:], control) - - if control.GetChannelResult() != nil { - - fm := &protocol.FetchMessage{} - - csp := &protocol.CwtchServerPacket{ - FetchMessage: fm, - } - - packet, _ := proto.Marshal(csp) - cslc.Packet(packet) - - if !gotgm { - t.Errorf("Did not receive packet on wire as expected in Fetch channel") - } - - if !closed { - t.Errorf("Fetch channel should be cosed") - } - - if !closed { - t.Errorf("Fetch channel should be closed after incorrect packet received") - } - - } else { - t.Errorf("Expected ChannelResult from im.cwtch.server.Fetch, instead: %v", control) - } - -} diff --git a/server/listen/server_listen_channel.go b/server/listen/server_listen_channel.go deleted file mode 100644 index af9b3d9..0000000 --- a/server/listen/server_listen_channel.go +++ /dev/null @@ -1,85 +0,0 @@ -package listen - -import ( - "cwtch.im/cwtch/protocol" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/utils" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" -) - -// CwtchServerListenChannel implements the ChannelHandler interface for a channel of -// type "im.cwtch.server.listen" - this implementation only handles server side logic. -type CwtchServerListenChannel struct { - channel *channels.Channel -} - -// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch". -func (cc *CwtchServerListenChannel) Type() string { - return "im.cwtch.server.listen" -} - -// Closed is called when the channel is closed for any reason. -func (cc *CwtchServerListenChannel) Closed(err error) { - -} - -// OnlyClientCanOpen - for Cwtch channels any side can open -func (cc *CwtchServerListenChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cc *CwtchServerListenChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cc *CwtchServerListenChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch channels require hidden service auth -func (cc *CwtchServerListenChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound is the first method called for an inbound channel request. -// If an error is returned, the channel is rejected. If a RawMessage is -// returned, it will be sent as the ChannelResult message. -func (cc *CwtchServerListenChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - cc.channel = channel - messageBuilder := new(utils.MessageBuilder) - return messageBuilder.AckOpenChannel(channel.ID), nil -} - -// OpenOutbound is the first method called for an outbound channel request. -// If an error is returned, the channel is not opened. If a RawMessage is -// returned, it will be sent as the OpenChannel message. -func (cc *CwtchServerListenChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - return nil, errors.New("server does not open listen channels") -} - -// OpenOutboundResult is called when a response is received for an -// outbound OpenChannel request. If `err` is non-nil, the channel was -// rejected and Closed will be called immediately afterwards. `raw` -// contains the raw protocol message including any extension data. -func (cc *CwtchServerListenChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - // NOTE: Should never be called -} - -// SendGroupMessage sends a single group message to the peer -func (cc *CwtchServerListenChannel) SendGroupMessage(gm *protocol.GroupMessage) { - csp := &protocol.CwtchServerPacket{ - GroupMessage: gm, - } - packet, _ := proto.Marshal(csp) - cc.channel.SendMessage(packet) -} - -// Packet is called for each raw packet received on this channel. -func (cc *CwtchServerListenChannel) Packet(data []byte) { - // If we receive a packet on this channel, close the connection - cc.channel.CloseChannel() -} diff --git a/server/listen/server_listen_channel_test.go b/server/listen/server_listen_channel_test.go deleted file mode 100644 index ee1bfa8..0000000 --- a/server/listen/server_listen_channel_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package listen - -import ( - "cwtch.im/cwtch/protocol" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" -) - -func TestServerListenChannelAttributes(t *testing.T) { - cslc := new(CwtchServerListenChannel) - if cslc.Type() != "im.cwtch.server.listen" { - t.Errorf("cwtch channel type is incorrect %v", cslc.Type()) - } - - if !cslc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.listen channel") - } - - if cslc.Bidirectional() { - t.Errorf("im.cwtch.server.listen should not be bidirectional") - } - - if !cslc.Singleton() { - t.Errorf("im.cwtch.server.listen should be a Singleton") - } - - if cslc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cslc.RequiresAuthentication()) - } - -} -func TestServerListenChannelOpenOutbound(t *testing.T) { - cslc := new(CwtchServerListenChannel) - channel := new(channels.Channel) - _, err := cslc.OpenOutbound(channel) - if err == nil { - t.Errorf("server implementation of im.cwtch.server.listen should never open an outbound channel") - } -} - -func TestServerListenChannel(t *testing.T) { - cslc := new(CwtchServerListenChannel) - channel := new(channels.Channel) - channel.ID = 1 - closed := false - channel.CloseChannel = func() { - closed = true - } - gotgm := false - channel.SendMessage = func([]byte) { - gotgm = true - } - - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(1), - ChannelType: proto.String(cslc.Type()), - } - - resp, err := cslc.OpenInbound(channel, oc) - if err != nil { - t.Errorf("OpenInbound for im.cwtch.server.listen should have succeeded, instead: %v", err) - } - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(resp[:], control) - - if control.GetChannelResult() != nil { - gm := &protocol.GroupMessage{ - Ciphertext: []byte("Hello"), - Signature: []byte{}, - Spamguard: []byte{}, - } - cslc.SendGroupMessage(gm) - if !gotgm { - t.Errorf("Did not receive packet on wire as expected in listen channel") - } - - if closed { - t.Errorf("listen channel should not be cosed") - } - - cslc.Packet(nil) - - if !closed { - t.Errorf("listen channel should be closed after incorrect packet received") - } - - } else { - t.Errorf("Expected ChannelResult from im.cwtch.server.listen, instead: %v", control) - } - -} diff --git a/server/metrics/monitors.go b/server/metrics/monitors.go index 90f8ede..5ba4c85 100644 --- a/server/metrics/monitors.go +++ b/server/metrics/monitors.go @@ -2,8 +2,8 @@ package metrics import ( "bufio" + "cwtch.im/tapir" "fmt" - "git.openprivacy.ca/openprivacy/libricochet-go/application" "git.openprivacy.ca/openprivacy/log" "github.com/struCoder/pidusage" "os" @@ -30,7 +30,7 @@ type Monitors struct { } // Start initializes a Monitors's monitors -func (mp *Monitors) Start(ra *application.RicochetApplication, configDir string, log bool) { +func (mp *Monitors) Start(ts tapir.Service, configDir string, log bool) { mp.log = log mp.configDir = configDir mp.starttime = time.Now() @@ -50,7 +50,9 @@ func (mp *Monitors) Start(ra *application.RicochetApplication, configDir string, sysInfo, _ := pidusage.GetStat(os.Getpid()) return float64(sysInfo.Memory) }) - mp.ClientConns = NewMonitorHistory(Count, Average, func() float64 { return float64(ra.ConnectionCount()) }) + + // TODO: replace with ts. + mp.ClientConns = NewMonitorHistory(Count, Average, func() float64 { return float64(ts.Metrics().ConnectionCount) }) if mp.log { go mp.run() diff --git a/server/send/server_send_channel.go b/server/send/server_send_channel.go deleted file mode 100644 index 01aea8d..0000000 --- a/server/send/server_send_channel.go +++ /dev/null @@ -1,99 +0,0 @@ -package send - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/protocol/connections/spam" - "errors" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "git.openprivacy.ca/openprivacy/log" - "github.com/golang/protobuf/proto" -) - -// CwtchServerSendChannel implements the ChannelHandler interface for a channel of -// type "im.cwtch.server.send - this implementation only handles server-side logic. -type CwtchServerSendChannel struct { - // Methods of Handler are called for Cwtch events on this channel - Handler CwtchServerSendChannelHandler - channel *channels.Channel - spamguard spam.Guard -} - -// CwtchServerSendChannelHandler defines the interface needed to interact with this channel -type CwtchServerSendChannelHandler interface { - HandleGroupMessage(*protocol.GroupMessage) -} - -// Type returns the type string for this channel, e.g. "im.ricochet.Cwtch". -func (cc *CwtchServerSendChannel) Type() string { - return "im.cwtch.server.send" -} - -// Closed is called when the channel is closed for any reason. -func (cc *CwtchServerSendChannel) Closed(err error) { - -} - -// OnlyClientCanOpen - for Cwtch channels any side can open -func (cc *CwtchServerSendChannel) OnlyClientCanOpen() bool { - return true -} - -// Singleton - for Cwtch channels there can only be one instance per direction -func (cc *CwtchServerSendChannel) Singleton() bool { - return true -} - -// Bidirectional - for Cwtch channels are not bidrectional -func (cc *CwtchServerSendChannel) Bidirectional() bool { - return false -} - -// RequiresAuthentication - Cwtch channels require hidden service auth -func (cc *CwtchServerSendChannel) RequiresAuthentication() string { - return "none" -} - -// OpenInbound is the first method called for an inbound channel request. -// If an error is returned, the channel is rejected. If a RawMessage is -// returned, it will be sent as the ChannelResult message. -func (cc *CwtchServerSendChannel) OpenInbound(channel *channels.Channel, raw *Protocol_Data_Control.OpenChannel) ([]byte, error) { - cc.channel = channel - cc.spamguard.Difficulty = 2 - return cc.spamguard.GenerateChallenge(channel.ID), nil -} - -// OpenOutbound is the first method called for an outbound channel request. -// If an error is returned, the channel is not opened. If a RawMessage is -// returned, it will be sent as the OpenChannel message. -func (cc *CwtchServerSendChannel) OpenOutbound(channel *channels.Channel) ([]byte, error) { - return nil, errors.New("server does not open send channel") -} - -// OpenOutboundResult is called when a response is received for an -// outbound OpenChannel request. If `err` is non-nil, the channel was -// rejected and Closed will be called immediately afterwards. `raw` -// contains the raw protocol message including any extension data. -func (cc *CwtchServerSendChannel) OpenOutboundResult(err error, crm *Protocol_Data_Control.ChannelResult) { - // NOTE: Should never be called -} - -// Packet is called for each raw packet received on this channel. -func (cc *CwtchServerSendChannel) Packet(data []byte) { - csp := &protocol.CwtchServerPacket{} - err := proto.Unmarshal(data, csp) - if err == nil { - if csp.GetGroupMessage() != nil { - gm := csp.GetGroupMessage() - ok := cc.spamguard.ValidateChallenge(gm.GetCiphertext(), gm.GetSpamguard()) - if ok { - cc.Handler.HandleGroupMessage(gm) - } else { - log.Errorf("Failed to validate spamguard %v\n", gm) - } - } - } else { - log.Errorf("Failed to decode packet on SEND channel %v\n", err) - } - cc.channel.CloseChannel() -} diff --git a/server/send/server_send_channel_test.go b/server/send/server_send_channel_test.go deleted file mode 100644 index 443375f..0000000 --- a/server/send/server_send_channel_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package send - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/protocol/connections/spam" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" - "git.openprivacy.ca/openprivacy/libricochet-go/wire/control" - "github.com/golang/protobuf/proto" - "testing" -) - -type TestHandler struct { - Received bool -} - -func (th *TestHandler) HandleGroupMessage(m *protocol.GroupMessage) { - th.Received = true -} - -func TestServerSendChannelAttributes(t *testing.T) { - cssc := new(CwtchServerSendChannel) - if cssc.Type() != "im.cwtch.server.send" { - t.Errorf("cwtch channel type is incorrect %v", cssc.Type()) - } - - if !cssc.OnlyClientCanOpen() { - t.Errorf("only clients should be able to open im.cwtch.server.send channel") - } - - if cssc.Bidirectional() { - t.Errorf("im.cwtch.server.send should not be bidirectional") - } - - if !cssc.Singleton() { - t.Errorf("im.cwtch.server.send should be a Singleton") - } - - if cssc.RequiresAuthentication() != "none" { - t.Errorf("cwtch channel required auth is incorrect %v", cssc.RequiresAuthentication()) - } - -} -func TestServerSendChannelOpenOutbound(t *testing.T) { - cssc := new(CwtchServerSendChannel) - channel := new(channels.Channel) - _, err := cssc.OpenOutbound(channel) - if err == nil { - t.Errorf("server implementation of im.cwtch.server.send should never open an outbound channel") - } -} - -func TestServerSendChannel(t *testing.T) { - cssc := new(CwtchServerSendChannel) - th := new(TestHandler) - cssc.Handler = th - channel := new(channels.Channel) - channel.ID = 1 - closed := false - channel.CloseChannel = func() { - closed = true - } - - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(1), - ChannelType: proto.String(cssc.Type()), - } - - resp, err := cssc.OpenInbound(channel, oc) - if err != nil { - t.Errorf("OpenInbound for im.cwtch.server.send should have succeeded, instead: %v", err) - } - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(resp[:], control) - - if control.GetChannelResult() != nil { - - var spamguard spam.Guard - spamguard.Difficulty = 2 - - ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce) - challenge := ce.([]byte)[:] - - sgsolve := spamguard.SolveChallenge(challenge, []byte("Hello")) - //t.Logf("Solved: %x", sgsolve) - - gm := &protocol.GroupMessage{ - Ciphertext: []byte("Hello"), - Signature: []byte{}, - Spamguard: sgsolve, - } - - csp := &protocol.CwtchServerPacket{ - GroupMessage: gm, - } - packet, _ := proto.Marshal(csp) - - cssc.Packet(packet) - - if !th.Received { - t.Errorf("group message should have been received") - } - - if !closed { - t.Errorf("im.cwtch.server.send should have been closed after use") - } - } else { - t.Errorf("Expected ChannelResult from im.cwtch.server.send, instead: %v", control) - } - -} - -func TestServerSendChannelNoSpamGuard(t *testing.T) { - cssc := new(CwtchServerSendChannel) - th := new(TestHandler) - th.Received = false - cssc.Handler = th - channel := new(channels.Channel) - channel.ID = 1 - closed := false - channel.CloseChannel = func() { - closed = true - } - - oc := &Protocol_Data_Control.OpenChannel{ - ChannelIdentifier: proto.Int32(1), - ChannelType: proto.String(cssc.Type()), - } - - resp, err := cssc.OpenInbound(channel, oc) - if err != nil { - t.Errorf("OpenInbound for im.cwtch.server.send should have succeeded, instead: %v", err) - } - - control := new(Protocol_Data_Control.Packet) - proto.Unmarshal(resp[:], control) - - if control.GetChannelResult() != nil { - - var spamguard spam.Guard - spamguard.Difficulty = 2 - - ce, _ := proto.GetExtension(control.GetChannelResult(), protocol.E_ServerNonce) - challenge := ce.([]byte)[:] - - sgsolve := spamguard.SolveChallenge(challenge, []byte("4234")) - //t.Logf("Solved: %x", sgsolve) - - gm := &protocol.GroupMessage{ - Ciphertext: []byte("hello"), - Signature: []byte{}, - Spamguard: sgsolve, - } - - csp := &protocol.CwtchServerPacket{ - GroupMessage: gm, - } - packet, _ := proto.Marshal(csp) - - cssc.Packet(packet) - - if th.Received == true { - t.Errorf("group message should not have been received") - } - - if !closed { - t.Errorf("im.cwtch.server.send should have been closed after use") - } - - } else { - t.Errorf("Expected ChannelResult from im.cwtch.server.send, instead: %v", control) - } - -} diff --git a/server/server.go b/server/server.go index 3ef283e..ce6500b 100644 --- a/server/server.go +++ b/server/server.go @@ -1,89 +1,80 @@ package server import ( - "cwtch.im/cwtch/server/fetch" - "cwtch.im/cwtch/server/listen" + "crypto/ed25519" + "cwtch.im/cwtch/model" "cwtch.im/cwtch/server/metrics" - "cwtch.im/cwtch/server/send" "cwtch.im/cwtch/server/storage" + "cwtch.im/tapir" + "cwtch.im/tapir/applications" + tor2 "cwtch.im/tapir/networks/tor" + "cwtch.im/tapir/primitives" + "cwtch.im/tapir/primitives/privacypass" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/connectivity/tor" - "git.openprivacy.ca/openprivacy/libricochet-go/application" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" "git.openprivacy.ca/openprivacy/log" "os" - "strconv" "time" ) // Server encapsulates a complete, compliant Cwtch server. type Server struct { - app *application.RicochetApplication - config Config - metricsPack metrics.Monitors - closed bool + service tapir.Service + config Config + metricsPack metrics.Monitors + closed bool + tokenTapirService tapir.Service + tokenServer *privacypass.TokenServer + tokenService primitives.Identity + tokenServicePrivKey ed25519.PrivateKey +} + +// Setup initialized a server from a given configuration +func (s *Server) Setup(serverConfig Config) { + s.config = serverConfig + s.tokenServer = privacypass.NewTokenServer() + s.tokenService, s.tokenServicePrivKey = primitives.InitializeEphemeralIdentity() } // Run starts a server with the given privateKey // TODO: surface errors // TODO: handle HUP/KILL signals to exit and close Tor gracefully // TODO: handle user input to exit -func (s *Server) Run(acn connectivity.ACN, serverConfig Config) { +func (s *Server) Run(acn connectivity.ACN) { s.closed = false - s.config = serverConfig - cwtchserver := new(application.RicochetApplication) - s.metricsPack.Start(cwtchserver, serverConfig.ConfigDir, s.config.ServerReporting.LogMetricsToFile) - af := application.InstanceFactory{} - af.Init() + addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey) + + //tokenService := privacypass.NewTokenServer() + identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey) + var service tapir.Service + service = new(tor2.BaseOnionService) + service.Init(acn, s.config.PrivateKey, &identity) + s.service = service + log.Infof("cwtch server running on cwtch:%s\n", addressIdentity+".onion:") + s.metricsPack.Start(service, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile) + ms := new(storage.MessageStore) - err := ms.Init(serverConfig.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter) + err := ms.Init(s.config.ConfigDir, s.config.MaxBufferLines, s.metricsPack.MessageCounter) if err != nil { log.Errorln(err) acn.Close() os.Exit(1) } - af.AddHandler("im.cwtch.server.listen", func(rai *application.Instance) func() channels.Handler { - return func() channels.Handler { - cslc := new(listen.CwtchServerListenChannel) - return cslc - } - }) - af.AddHandler("im.cwtch.server.fetch", func(rai *application.Instance) func() channels.Handler { - si := new(Instance) - si.Init(rai, cwtchserver, ms) - return func() channels.Handler { - cssc := new(fetch.CwtchServerFetchChannel) - cssc.Handler = si - return cssc - } - }) - - af.AddHandler("im.cwtch.server.send", func(rai *application.Instance) func() channels.Handler { - si := new(Instance) - si.Init(rai, cwtchserver, ms) - return func() channels.Handler { - cssc := new(send.CwtchServerSendChannel) - cssc.Handler = si - return cssc - } - }) - - addressIdentity := tor.GetTorV3Hostname(s.config.PublicKey) - cwtchserver.Init(acn, "cwtch server for "+addressIdentity, s.config.Identity(), af, new(application.AcceptAllContactManager)) - port := strconv.Itoa(application.RicochetPort) - log.Infof("cwtch server running on cwtch:%s\n", addressIdentity+".onion:"+port) - - s.app = cwtchserver + go func() { + s.tokenTapirService = new(tor2.BaseOnionService) + s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService) + tokenApplication := new(applications.TokenApplication) + tokenApplication.TokenService = s.tokenServer + powTokenApp := new(applications.ApplicationChain). + ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). + ChainApplication(tokenApplication, applications.HasTokensCapability) + s.tokenTapirService.Listen(powTokenApp) + }() for true { - listenService, err := acn.Listen(s.config.PrivateKey, application.RicochetPort) - if err != nil { - log.Errorf("Listen() error setting up onion service: %v\n", err) - } else { - s.app.Run(listenService) - } + s.service.Listen(NewTokenBoardServer(ms, s.tokenServer)) if s.closed { return } @@ -91,9 +82,20 @@ func (s *Server) Run(acn connectivity.ACN, serverConfig Config) { } } +// KeyBundle provides the keybundle of the server (mostly used for testing) +func (s *Server) KeyBundle() model.KeyBundle { + kb := model.KeyBundle{Keys: make(map[string]model.Key)} + identity := s.config.Identity() + kb.Keys[model.KeyTypeOnion] = model.Key(identity.Hostname()) + kb.Keys[model.KeyTypeTokenOnion] = model.Key(tor.GetTorV3Hostname(s.tokenService.PublicKey())) + kb.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String()) + return kb +} + // Shutdown kills the app closing all connections and freeing all goroutines func (s *Server) Shutdown() { s.closed = true - s.app.Shutdown() + s.service.Shutdown() + s.tokenTapirService.Shutdown() s.metricsPack.Stop() } diff --git a/server/serverConfig.go b/server/serverConfig.go index f5bc4e9..835107f 100644 --- a/server/serverConfig.go +++ b/server/serverConfig.go @@ -2,9 +2,10 @@ package server import ( "crypto/rand" + "cwtch.im/tapir/primitives" "encoding/json" - "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/log" + "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" "io/ioutil" "path" @@ -19,16 +20,18 @@ type Reporting struct { // Config is a struct for storing basic server configuration type Config struct { - ConfigDir string `json:"-"` - MaxBufferLines int `json:"maxBufferLines"` - PublicKey ed25519.PublicKey `json:"publicKey"` - PrivateKey ed25519.PrivateKey `json:"privateKey"` - ServerReporting Reporting `json:"serverReporting"` + ConfigDir string `json:"-"` + MaxBufferLines int `json:"maxBufferLines"` + PublicKey ed25519.PublicKey `json:"publicKey"` + PrivateKey ed25519.PrivateKey `json:"privateKey"` + PrivacyPassPublicKey ristretto255.Element `json:"privacyPassPublicKey"` + PrivacyPassPrivateKey ristretto255.Scalar `json:"privacyPassPrivateKey"` + ServerReporting Reporting `json:"serverReporting"` } // Identity returns an encapsulation of the servers keys for running ricochet -func (config *Config) Identity() identity.Identity { - return identity.InitializeV3("", &config.PrivateKey, &config.PublicKey) +func (config *Config) Identity() primitives.Identity { + return primitives.InitializeIdentity("", &config.PrivateKey, &config.PublicKey) } // Save dumps the latest version of the config to a json file given by filename @@ -44,7 +47,7 @@ func LoadConfig(configDir, filename string) Config { config := Config{} config.ConfigDir = configDir config.MaxBufferLines = 100000 - config.ServerReporting.LogMetricsToFile = false + config.ServerReporting.LogMetricsToFile = true raw, err := ioutil.ReadFile(path.Join(configDir, filename)) if err == nil { err = json.Unmarshal(raw, &config) diff --git a/server/server_instance.go b/server/server_instance.go deleted file mode 100644 index d3e9712..0000000 --- a/server/server_instance.go +++ /dev/null @@ -1,45 +0,0 @@ -package server - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/server/listen" - "cwtch.im/cwtch/server/storage" - "git.openprivacy.ca/openprivacy/libricochet-go/application" - "git.openprivacy.ca/openprivacy/libricochet-go/channels" -) - -// Instance encapsulates the Ricochet application. -type Instance struct { - rai *application.Instance - ra *application.RicochetApplication - msi storage.MessageStoreInterface -} - -// Init sets up a Server Instance -func (si *Instance) Init(rai *application.Instance, ra *application.RicochetApplication, msi storage.MessageStoreInterface) { - si.rai = rai - si.ra = ra - si.msi = msi -} - -// HandleFetchRequest returns a list of all messages in the servers buffer -func (si *Instance) HandleFetchRequest() []*protocol.GroupMessage { - return si.msi.FetchMessages() -} - -// HandleGroupMessage takes in a group message and distributes it to all listening peers -func (si *Instance) HandleGroupMessage(gm *protocol.GroupMessage) { - si.msi.AddMessage(*gm) - go si.ra.Broadcast(func(rai *application.Instance) { - rai.Connection.Do(func() error { - channel := rai.Connection.Channel("im.cwtch.server.listen", channels.Inbound) - if channel != nil { - cslc, ok := channel.Handler.(*listen.CwtchServerListenChannel) - if ok { - cslc.SendGroupMessage(gm) - } - } - return nil - }) - }) -} diff --git a/server/server_instance_test.go b/server/server_instance_test.go deleted file mode 100644 index 950bab3..0000000 --- a/server/server_instance_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package server - -import ( - "cwtch.im/cwtch/protocol" - "cwtch.im/cwtch/server/metrics" - "cwtch.im/cwtch/server/storage" - "git.openprivacy.ca/openprivacy/libricochet-go/application" - "os" - "testing" - "time" -) - -func TestServerInstance(t *testing.T) { - si := new(Instance) - ai := new(application.Instance) - ra := new(application.RicochetApplication) - msi := new(storage.MessageStore) - os.RemoveAll("messages") - msi.Init(".", 5, metrics.NewCounter()) - gm := protocol.GroupMessage{ - Ciphertext: []byte("Hello this is a fairly average length message that we are writing here."), - Spamguard: []byte{}, - } - - si.Init(ai, ra, msi) - msi.AddMessage(gm) - res := si.HandleFetchRequest() - - if len(res) != 1 { - t.Errorf("Expected 1 Group messages Instead got %v", res) - } - - // ra.HandleApplicationInstance(ai) - si.HandleGroupMessage(&gm) - - time.Sleep(time.Second * 2) -} diff --git a/server/server_tokenboard.go b/server/server_tokenboard.go new file mode 100644 index 0000000..198d33f --- /dev/null +++ b/server/server_tokenboard.go @@ -0,0 +1,110 @@ +package server + +import ( + "cwtch.im/cwtch/protocol/groups" + "cwtch.im/cwtch/server/storage" + "cwtch.im/tapir" + "cwtch.im/tapir/applications" + "cwtch.im/tapir/primitives/privacypass" + "encoding/json" + "git.openprivacy.ca/openprivacy/log" +) + +// NewTokenBoardServer generates new Server for Token Board +func NewTokenBoardServer(store storage.MessageStoreInterface, tokenService *privacypass.TokenServer) tapir.Application { + tba := new(TokenboardServer) + tba.TokenService = tokenService + tba.LegacyMessageStore = store + return tba +} + +// TokenboardServer defines the token board server +type TokenboardServer struct { + applications.AuthApp + connection tapir.Connection + TokenService *privacypass.TokenServer + LegacyMessageStore storage.MessageStoreInterface +} + +// NewInstance creates a new TokenBoardApp +func (ta *TokenboardServer) NewInstance() tapir.Application { + tba := new(TokenboardServer) + tba.TokenService = ta.TokenService + tba.LegacyMessageStore = ta.LegacyMessageStore + return tba +} + +// Init initializes the cryptographic TokenBoardApp +func (ta *TokenboardServer) Init(connection tapir.Connection) { + ta.AuthApp.Init(connection) + if connection.HasCapability(applications.AuthCapability) { + ta.connection = connection + ta.connection.SetCapability(groups.CwtchServerSyncedCapability) + go ta.Listen() + } else { + connection.Close() + } +} + +// Listen processes the messages for this application +func (ta *TokenboardServer) Listen() { + for { + data := ta.connection.Expect() + if len(data) == 0 { + log.Debugf("Server Closing Connection") + ta.connection.Close() + return // connection is closed + } + + var message groups.Message + if err := json.Unmarshal(data, &message); err != nil { + log.Debugf("Server Closing Connection Because of Malformed Client Packet %v", err) + ta.connection.Close() + return // connection is closed + } + + switch message.MessageType { + case groups.PostRequestMessage: + if message.PostRequest != nil { + postrequest := *message.PostRequest + log.Debugf("Received a Post Message Request: %v", ta.connection.Hostname()) + ta.postMessageRequest(postrequest) + } else { + log.Debugf("Server Closing Connection Because of PostRequestMessage Client Packet") + ta.connection.Close() + return // connection is closed + } + case groups.ReplayRequestMessage: + if message.ReplayRequest != nil { + log.Debugf("Received Replay Request %v", message.ReplayRequest) + messages := ta.LegacyMessageStore.FetchMessages() + response, _ := json.Marshal(groups.Message{MessageType: groups.ReplayResultMessage, ReplayResult: &groups.ReplayResult{NumMessages: len(messages)}}) + log.Debugf("Sending Replay Response %v", groups.ReplayResult{NumMessages: len(messages)}) + ta.connection.Send(response) + for _, message := range messages { + data, _ = json.Marshal(message) + ta.connection.Send(data) + } + } else { + log.Debugf("Server Closing Connection Because of Malformed ReplayRequestMessage Packet") + ta.connection.Close() + return // connection is closed + } + } + } +} + +func (ta *TokenboardServer) postMessageRequest(pr groups.PostRequest) { + if err := ta.TokenService.SpendToken(pr.Token, append(pr.EGM.ToBytes(), ta.connection.ID().Hostname()...)); err == nil { + log.Debugf("Token is valid") + ta.LegacyMessageStore.AddMessage(pr.EGM) + data, _ := json.Marshal(groups.Message{MessageType: groups.PostResultMessage, PostResult: &groups.PostResult{Success: true}}) + ta.connection.Send(data) + data, _ = json.Marshal(groups.Message{MessageType: groups.NewMessageMessage, NewMessage: &groups.NewMessage{EGM: pr.EGM}}) + ta.connection.Broadcast(data, groups.CwtchServerSyncedCapability) + } else { + log.Debugf("Attempt to spend an invalid token: %v", err) + data, _ := json.Marshal(groups.Message{MessageType: groups.PostResultMessage, PostResult: &groups.PostResult{Success: false}}) + ta.connection.Send(data) + } +} diff --git a/server/storage/message_store.go b/server/storage/message_store.go index 174a405..2fb1c93 100644 --- a/server/storage/message_store.go +++ b/server/storage/message_store.go @@ -2,7 +2,7 @@ package storage import ( "bufio" - "cwtch.im/cwtch/protocol" + "cwtch.im/cwtch/protocol/groups" "cwtch.im/cwtch/server/metrics" "encoding/json" "fmt" @@ -20,8 +20,8 @@ const ( // MessageStoreInterface defines an interface to interact with a store of cwtch messages. type MessageStoreInterface interface { - AddMessage(protocol.GroupMessage) - FetchMessages() []*protocol.GroupMessage + AddMessage(groups.EncryptedGroupMessage) + FetchMessages() []*groups.EncryptedGroupMessage } // MessageStore is a file-backed implementation of MessageStoreInterface @@ -30,7 +30,7 @@ type MessageStore struct { filePos int storeDirectory string lock sync.Mutex - messages []*protocol.GroupMessage + messages []*groups.EncryptedGroupMessage messageCounter metrics.Counter maxBufferLines int bufferPos int @@ -45,7 +45,7 @@ func (ms *MessageStore) Close() { ms.lock.Unlock() } -func (ms *MessageStore) updateBuffer(gm *protocol.GroupMessage) { +func (ms *MessageStore) updateBuffer(gm *groups.EncryptedGroupMessage) { ms.messages[ms.bufferPos] = gm ms.bufferPos++ if ms.bufferPos == ms.maxBufferLines { @@ -70,7 +70,7 @@ func (ms *MessageStore) initAndLoadFiles() error { for scanner.Scan() { gms := scanner.Text() ms.filePos++ - gm := &protocol.GroupMessage{} + gm := &groups.EncryptedGroupMessage{} err := json.Unmarshal([]byte(gms), gm) if err == nil { ms.updateBuffer(gm) @@ -83,7 +83,7 @@ func (ms *MessageStore) initAndLoadFiles() error { return nil } -func (ms *MessageStore) updateFile(gm *protocol.GroupMessage) { +func (ms *MessageStore) updateFile(gm *groups.EncryptedGroupMessage) { s, err := json.Marshal(gm) if err != nil { log.Errorf("Failed to unmarshal group message %v\n", err) @@ -118,7 +118,7 @@ func (ms *MessageStore) Init(appDirectory string, maxBufferLines int, messageCou ms.bufferPos = 0 ms.maxBufferLines = maxBufferLines - ms.messages = make([]*protocol.GroupMessage, maxBufferLines) + ms.messages = make([]*groups.EncryptedGroupMessage, maxBufferLines) ms.bufferRotated = false ms.messageCounter = messageCounter @@ -127,13 +127,13 @@ func (ms *MessageStore) Init(appDirectory string, maxBufferLines int, messageCou } // FetchMessages returns all messages from the backing file. -func (ms *MessageStore) FetchMessages() (messages []*protocol.GroupMessage) { +func (ms *MessageStore) FetchMessages() (messages []*groups.EncryptedGroupMessage) { ms.lock.Lock() if !ms.bufferRotated { - messages = make([]*protocol.GroupMessage, ms.bufferPos) + messages = make([]*groups.EncryptedGroupMessage, ms.bufferPos) copy(messages, ms.messages[0:ms.bufferPos]) } else { - messages = make([]*protocol.GroupMessage, ms.maxBufferLines) + messages = make([]*groups.EncryptedGroupMessage, ms.maxBufferLines) copy(messages, ms.messages[ms.bufferPos:ms.maxBufferLines]) copy(messages[ms.bufferPos:], ms.messages[0:ms.bufferPos]) } @@ -142,7 +142,7 @@ func (ms *MessageStore) FetchMessages() (messages []*protocol.GroupMessage) { } // AddMessage adds a GroupMessage to the store -func (ms *MessageStore) AddMessage(gm protocol.GroupMessage) { +func (ms *MessageStore) AddMessage(gm groups.EncryptedGroupMessage) { ms.messageCounter.Add(1) ms.lock.Lock() ms.updateBuffer(&gm) diff --git a/server/storage/message_store_test.go b/server/storage/message_store_test.go index 85a9dd1..d6ce82e 100644 --- a/server/storage/message_store_test.go +++ b/server/storage/message_store_test.go @@ -1,7 +1,7 @@ package storage import ( - "cwtch.im/cwtch/protocol" + "cwtch.im/cwtch/protocol/groups" "cwtch.im/cwtch/server/metrics" "os" "strconv" @@ -14,9 +14,8 @@ func TestMessageStore(t *testing.T) { counter := metrics.NewCounter() ms.Init("./", 1000, counter) for i := 0; i < 499; i++ { - gm := protocol.GroupMessage{ + gm := groups.EncryptedGroupMessage{ Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)), - Spamguard: []byte{}, } ms.AddMessage(gm) } @@ -33,9 +32,8 @@ func TestMessageStore(t *testing.T) { counter.Reset() for i := 0; i < 1000; i++ { - gm := protocol.GroupMessage{ + gm := groups.EncryptedGroupMessage{ Ciphertext: []byte("Hello this is a fairly average length message that we are writing here. " + strconv.Itoa(i)), - Spamguard: []byte{}, } ms.AddMessage(gm) } diff --git a/testing/cwtch_peer_server_integration_test.go b/testing/cwtch_peer_server_integration_test.go index 32493ef..5e6f4ce 100644 --- a/testing/cwtch_peer_server_integration_test.go +++ b/testing/cwtch_peer_server_integration_test.go @@ -10,6 +10,7 @@ import ( "cwtch.im/cwtch/peer" "cwtch.im/cwtch/protocol/connections" cwtchserver "cwtch.im/cwtch/server" + "encoding/json" "fmt" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" @@ -115,7 +116,7 @@ func TestCwtchPeerIntegration(t *testing.T) { log.ExcludeFromPattern("pipeBridge") log.ExcludeFromPattern("tapir") os.RemoveAll("tor") - acn, err := tor.NewTorACN(".", "") + acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"}) if err != nil { t.Fatalf("Could not start Tor: %v", err) } @@ -125,7 +126,7 @@ func TestCwtchPeerIntegration(t *testing.T) { serverOnline := false var serverAddr string - + var serverKeyBundle []byte if !serverOnline { // launch app with new key fmt.Println("No server found!") @@ -135,7 +136,10 @@ func TestCwtchPeerIntegration(t *testing.T) { config := cwtchserver.LoadConfig(".", "server-test.json") identity := config.Identity() serverAddr = identity.Hostname() - go server.Run(acn, config) + server.Setup(config) + serverKeyBundle, _ = json.Marshal(server.KeyBundle()) + log.Debugf("server key bundle %s", serverKeyBundle) + go server.Run(acn) // let tor get established fmt.Printf("Establishing Tor hidden service: %v...\n", serverAddr) @@ -197,6 +201,7 @@ func TestCwtchPeerIntegration(t *testing.T) { // ***** Peering, server joining, group creation / invite ***** fmt.Println("Alice joining server...") + alice.AddServer(string(serverKeyBundle)) alice.JoinServer(serverAddr) fmt.Println("Alice peering with Bob...") @@ -222,10 +227,12 @@ func TestCwtchPeerIntegration(t *testing.T) { // 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.AddServer(string(serverKeyBundle)) bob.SetContactAuthorization(alice.GetOnion(), model.AuthApproved) waitForPeerPeerConnection(t, alice, carol) carol.AddContact("alice?", alice.GetOnion(), model.AuthApproved) + carol.AddServer(string(serverKeyBundle)) carol.SetContactAuthorization(alice.GetOnion(), model.AuthApproved) fmt.Println("Alice and Bob getVal public.name...") @@ -315,7 +322,7 @@ func TestCwtchPeerIntegration(t *testing.T) { if err != nil { t.Fatalf("Error for Alice inviting Carol to group: %v", err) } - time.Sleep(time.Second * 10) + time.Sleep(time.Second * 60) // Account for some token acquisition in Alice and Bob flows. fmt.Println("Carol examining groups and accepting invites...") for _, groupID := range carol.GetGroups() { group := carol.GetGroup(groupID) @@ -338,16 +345,16 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Printf("%v> %v", bobName, bobLines[2]) bob.SendMessageToGroup(groupID, bobLines[2]) - time.Sleep(time.Second * 10) + time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition fmt.Printf("%v> %v", carolName, carolLines[0]) carol.SendMessageToGroup(groupID, carolLines[0]) - time.Sleep(time.Second * 10) + time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition // ***** Verify Test ***** fmt.Println("Final syncing time...") - time.Sleep(time.Second * 30) + time.Sleep(time.Second * 60) alicesGroup := alice.GetGroup(groupID) if alicesGroup == nil { @@ -369,14 +376,14 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Printf("Bob's TimeLine:\n") bobVerified := printAndCountVerifedTimeline(t, bobsGroup.GetTimeline()) if bobVerified != 6 { - t.Errorf("Bob did not have 5 verified messages") + t.Errorf("Bob did not have 6 verified messages") } carolsGroup := carol.GetGroup(groupID) fmt.Printf("Carol's TimeLine:\n") carolVerified := printAndCountVerifedTimeline(t, carolsGroup.GetTimeline()) if carolVerified != 6 { - t.Errorf("Carol did not have 3 verified messages") + t.Errorf("Carol did not have 6 verified messages") } if len(alicesGroup.GetTimeline()) != 4 { diff --git a/testing/tests.sh b/testing/tests.sh index f7e704a..e5ed0eb 100755 --- a/testing/tests.sh +++ b/testing/tests.sh @@ -9,16 +9,8 @@ go test -race ${1} -coverprofile=storage.v0.cover.out -v ./storage/v0 go test -race ${1} -coverprofile=storage.v1.cover.out -v ./storage/v1 go test -race ${1} -coverprofile=storage.cover.out -v ./storage go test -race ${1} -coverprofile=peer.connections.cover.out -v ./protocol/connections -go test -race ${1} -coverprofile=protocol.spam.cover.out -v ./protocol/connections/spam -go test -race ${1} -coverprofile=peer.fetch.cover.out -v ./protocol/connections/fetch -go test -race ${1} -coverprofile=peer.listen.cover.out -v ./protocol/connections/listen -go test -race ${1} -coverprofile=peer.send.cover.out -v ./protocol/connections/send go test -race ${1} -coverprofile=peer.cover.out -v ./peer -go test -race ${1} -coverprofile=server.fetch.cover.out -v ./server/fetch -go test -race ${1} -coverprofile=server.listen.cover.out -v ./server/listen -go test -race ${1} -coverprofile=server.send.cover.out -v ./server/send go test -race ${1} -coverprofile=server.metrics.cover.out -v ./server/metrics -go test -race ${1} -coverprofile=server.cover.out -v ./server echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out rm -rf *.cover.out