From 0550a71244ae4c1295960dc47fa707fa08d7bd22 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 13 Jul 2020 17:46:05 -0700 Subject: [PATCH 01/12] 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 From d230a1b62973f1f767c786091b287419fc4630c1 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 21 Jul 2020 11:44:01 -0700 Subject: [PATCH 02/12] Fix Data Race in Metrics --- server/metrics/metrics.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/metrics/metrics.go b/server/metrics/metrics.go index abbcac4..805515a 100644 --- a/server/metrics/metrics.go +++ b/server/metrics/metrics.go @@ -151,11 +151,13 @@ func (mh *monitorHistory) Months() []float64 { } func (mh *monitorHistory) Report(w *bufio.Writer) { + mh.lock.Lock() fmt.Fprintln(w, "Minutes:", reportLine(mh.monitorType, mh.perMinutePerHour[:])) fmt.Fprintln(w, "Hours: ", reportLine(mh.monitorType, mh.perHourForDay[:])) fmt.Fprintln(w, "Days: ", reportLine(mh.monitorType, mh.perDayForWeek[:])) fmt.Fprintln(w, "Weeks: ", reportLine(mh.monitorType, mh.perWeekForMonth[:])) fmt.Fprintln(w, "Months: ", reportLine(mh.monitorType, mh.perMonthForYear[:])) + mh.lock.Unlock() } func reportLine(t MonitorType, array []float64) string { From f74e8647ef4e1252d1774ff809747c16d261517c Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 22 Jul 2020 10:14:32 -0700 Subject: [PATCH 03/12] Fixup APIs, Error handling and formatting --- model/keyBundle.go | 25 +++++++++++--------- model/profile.go | 8 +++++++ peer/cwtch_peer.go | 57 +++++++++++++++++++++++++--------------------- server/server.go | 4 ++-- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/model/keyBundle.go b/model/keyBundle.go index 7ff63e9..d5b7765 100644 --- a/model/keyBundle.go +++ b/model/keyBundle.go @@ -2,15 +2,18 @@ package model import "errors" +// KeyType provides a wrapper for a generic public key type identifier (could be an onion address, a zcash address etc.) +type KeyType string + const ( - // KeyTypeOnion - a cwtch address - KeyTypeOnion = "onion" // bulletin board + // KeyTypeServerOnion - a cwtch address + KeyTypeServerOnion = KeyType("bulletin_board_onion") // bulletin board // KeyTypeTokenOnion - a cwtch peer with a PoW based token protocol - KeyTypeTokenOnion = "token_onion" + KeyTypeTokenOnion = KeyType("token_service_onion") //KeyTypePrivacyPass - a privacy pass based token server - KeyTypePrivacyPass = "pp_key" + KeyTypePrivacyPass = KeyType("privacy_pass_public_key") ) // Key provides a wrapper for a generic public key identifier (could be an onion address, a zcash address etc.) @@ -18,18 +21,18 @@ type Key string // KeyBundle manages a collection of related keys for various different services. type KeyBundle struct { - Keys map[string]Key + Keys map[KeyType]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] +// HasKeyType returns true if the bundle has a public key of a given type. +func (kb *KeyBundle) HasKeyType(keytype KeyType) bool { + _, exists := kb.Keys[keytype] 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] +func (kb *KeyBundle) GetKey(keytype KeyType) (Key, error) { + key, exists := kb.Keys[keytype] if exists { return key, nil } @@ -40,7 +43,7 @@ func (kb *KeyBundle) GetKey(name string) (Key, error) { func (kb *KeyBundle) AttributeBundle() map[string]string { ab := make(map[string]string) for k, v := range kb.Keys { - ab[k] = string(v) + ab[string(k)] = string(v) } return ab } diff --git a/model/profile.go b/model/profile.go index 00dbcb2..64f0577 100644 --- a/model/profile.go +++ b/model/profile.go @@ -78,6 +78,14 @@ func (p *PublicProfile) SetAttribute(name string, value string) { p.Attributes[name] = value } +// IsServer returns true if the onion address is associated with a server. +func (p *PublicProfile) IsServer(onion string) bool { + if _, isServer := p.GetAttribute(string(KeyTypeServerOnion)); isServer == true { + return true + } + return false +} + // GetAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false. func (p *PublicProfile) GetAttribute(name string) (value string, exists bool) { p.lock.Lock() diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 5a84107..68f84a8 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -48,7 +48,7 @@ type CwtchPeer interface { DeleteContact(string) DeleteGroup(string) - AddServer(string) + AddServer(string) error JoinServer(string) SendMessageToGroup(string, string) error SendMessageToGroupTracked(string, string) (string, error) @@ -205,36 +205,41 @@ 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) { +func (cp *cwtchPeer) AddServer(serverSpecification string) error { keyBundle := new(model.KeyBundle) - json.Unmarshal([]byte(serverSpecification), &keyBundle) - + err := json.Unmarshal([]byte(serverSpecification), &keyBundle) log.Debugf("Got new key bundle %v", keyBundle) - - if keyBundle.HasKey(model.KeyTypeOnion) { - onionKey, _ := keyBundle.GetKey(model.KeyTypeOnion) + if keyBundle.HasKeyType(model.KeyTypeServerOnion) { + onionKey, _ := keyBundle.GetKey(model.KeyTypeServerOnion) 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, - })) + if cp.GetContact(onion) == nil { + 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} - // 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)) + 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)) + return nil } - - // Default to Deleting Peer History - cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault)) + // Server Already Exists + return errors.New("a contact already exists with the given onion address") } + return err } // GetContacts returns an unordered list of onions @@ -332,8 +337,8 @@ 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) { if cp.GetContact(onion) != nil { - tokenY, yExists := cp.GetContact(onion).GetAttribute(model.KeyTypePrivacyPass) - tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(model.KeyTypeTokenOnion) + tokenY, yExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypePrivacyPass)) + tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(string(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})) } diff --git a/server/server.go b/server/server.go index ce6500b..2fa4792 100644 --- a/server/server.go +++ b/server/server.go @@ -84,9 +84,9 @@ func (s *Server) Run(acn connectivity.ACN) { // 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)} + kb := model.KeyBundle{Keys: make(map[model.KeyType]model.Key)} identity := s.config.Identity() - kb.Keys[model.KeyTypeOnion] = model.Key(identity.Hostname()) + kb.Keys[model.KeyTypeServerOnion] = 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 From 7c73df1f06938c560da89936446cebc8e3a191ae Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 22 Jul 2020 12:35:49 -0700 Subject: [PATCH 04/12] More considered server side sync capability --- server/server_tokenboard.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/server_tokenboard.go b/server/server_tokenboard.go index 198d33f..397a2c5 100644 --- a/server/server_tokenboard.go +++ b/server/server_tokenboard.go @@ -39,7 +39,6 @@ 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() @@ -85,6 +84,15 @@ func (ta *TokenboardServer) Listen() { data, _ = json.Marshal(message) ta.connection.Send(data) } + newMessages := ta.LegacyMessageStore.FetchMessages() + // Set sync and then send any new messages that might have happened while we were syncing + ta.connection.SetCapability(groups.CwtchServerSyncedCapability) + if len(newMessages) > len(messages) { + for _, message := range newMessages[len(messages):] { + data, _ = json.Marshal(groups.Message{MessageType: groups.NewMessageMessage, NewMessage: &groups.NewMessage{EGM: *message}}) + ta.connection.Send(data) + } + } } else { log.Debugf("Server Closing Connection Because of Malformed ReplayRequestMessage Packet") ta.connection.Close() From 8ed7dd471ad6d441aae55a8c1f0d6e02f3cea784 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 23 Jul 2020 14:40:44 -0700 Subject: [PATCH 05/12] Small Fixes --- model/profile.go | 10 ++++------ peer/cwtch_peer.go | 4 +++- server/server_tokenboard.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/model/profile.go b/model/profile.go index 64f0577..39df5ce 100644 --- a/model/profile.go +++ b/model/profile.go @@ -78,12 +78,10 @@ func (p *PublicProfile) SetAttribute(name string, value string) { p.Attributes[name] = value } -// IsServer returns true if the onion address is associated with a server. -func (p *PublicProfile) IsServer(onion string) bool { - if _, isServer := p.GetAttribute(string(KeyTypeServerOnion)); isServer == true { - return true - } - return false +// IsServer returns true if the profile is associated with a server. +func (p *PublicProfile) IsServer() (isServer bool) { + _, isServer = p.GetAttribute(string(KeyTypeServerOnion)) + return } // GetAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false. diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 68f84a8..31f0896 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -205,6 +205,9 @@ func (cp *cwtchPeer) AddContact(nick, onion string, authorization model.Authoriz cp.eventBus.Publish(event.NewEventList(event.SetPeerAttribute, event.RemotePeer, onion, event.SaveHistoryKey, event.DeleteHistoryDefault)) } +// AddServer takes in a serialized server specification (a bundle of related keys) and adds a contact for the +// server assuming there are no errors and the contact doesn't already exist. +// TODO in the future this function should also integrate with a trust provider to validate the key bundle. func (cp *cwtchPeer) AddServer(serverSpecification string) error { keyBundle := new(model.KeyBundle) err := json.Unmarshal([]byte(serverSpecification), &keyBundle) @@ -216,7 +219,6 @@ func (cp *cwtchPeer) AddServer(serverSpecification string) error { if cp.GetContact(onion) == nil { 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) diff --git a/server/server_tokenboard.go b/server/server_tokenboard.go index 397a2c5..db35e3e 100644 --- a/server/server_tokenboard.go +++ b/server/server_tokenboard.go @@ -84,9 +84,9 @@ func (ta *TokenboardServer) Listen() { data, _ = json.Marshal(message) ta.connection.Send(data) } - newMessages := ta.LegacyMessageStore.FetchMessages() // Set sync and then send any new messages that might have happened while we were syncing ta.connection.SetCapability(groups.CwtchServerSyncedCapability) + newMessages := ta.LegacyMessageStore.FetchMessages() if len(newMessages) > len(messages) { for _, message := range newMessages[len(messages):] { data, _ = json.Marshal(groups.Message{MessageType: groups.NewMessageMessage, NewMessage: &groups.NewMessage{EGM: *message}}) From d5fb0a579374b33e0bef1504a1289733ab3c7b24 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 21 Sep 2020 14:26:28 -0700 Subject: [PATCH 06/12] Tapir UI Integration First Pass --- app/applets.go | 2 - app/plugins/contactRetry.go | 9 +++- event/common.go | 6 +++ peer/cwtch_peer.go | 52 ++++++++++++--------- protocol/connections/engine.go | 21 +++++++-- protocol/connections/tokenboardclientapp.go | 3 +- server/app/main.go | 35 ++++++++++++++ server/server.go | 8 +++- server/serverConfig.go | 22 +++++---- 9 files changed, 115 insertions(+), 43 deletions(-) diff --git a/app/applets.go b/app/applets.go index c0993a9..d6579ed 100644 --- a/app/applets.go +++ b/app/applets.go @@ -58,8 +58,6 @@ func (ap *appletPeers) LaunchPeers() { log.Debugf("done Listen() for %v\n", pid) p.StartPeersConnections() log.Debugf("done StartPeersConnections() for %v\n", pid) - p.StartGroupConnections() - log.Debugf("done StartGroupConnections() for %v\n", pid) } ap.launched = true } diff --git a/app/plugins/contactRetry.go b/app/plugins/contactRetry.go index f5e879d..2592f68 100644 --- a/app/plugins/contactRetry.go +++ b/app/plugins/contactRetry.go @@ -3,6 +3,7 @@ package plugins import ( "cwtch.im/cwtch/event" "cwtch.im/cwtch/protocol/connections" + "git.openprivacy.ca/openprivacy/log" "sync" "time" ) @@ -56,11 +57,13 @@ func (cr *contactRetry) run() { case e := <-cr.queue.OutChan(): switch e.EventType { case event.PeerStateChange: + log.Errorf("PEER STATE CHANGE: %v", e) state := connections.ConnectionStateToType[e.Data[event.ConnectionState]] peer := e.Data[event.RemotePeer] cr.handleEvent(peer, state, peerConn) case event.ServerStateChange: + log.Errorf("SERVER STATE CHANGE: %v", e) state := connections.ConnectionStateToType[e.Data[event.ConnectionState]] server := e.Data[event.GroupServer] cr.handleEvent(server, state, serverConn) @@ -73,7 +76,9 @@ func (cr *contactRetry) run() { p := v.(*contact) p.ticks = 0 p.backoff = 1 - cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id})) + if p.ctype == peerConn { + cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id})) + } return true }) } else if prog != "100" { @@ -93,7 +98,7 @@ func (cr *contactRetry) run() { if p.ctype == peerConn { cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id})) } else { - cr.bus.Publish(event.NewEventList(event.JoinServer, event.GroupServer, p.id)) + //cr.bus.Publish(event.NewEventList(event.JoinServer, event.GroupServer, p.id)) } } } diff --git a/event/common.go b/event/common.go index 23b50e0..610bdc1 100644 --- a/event/common.go +++ b/event/common.go @@ -40,6 +40,7 @@ const ( // TimestampReceived [eg time.Now().Format(time.RFC3339Nano)] // RemotePeer: [eg "chpr7qm6op5vfcg2pi4vllco3h6aa7exexc4rqwnlupqhoogx2zgd6qd"] // GroupInvite: [eg "torv3....."] + // Imported NewGroupInvite = Type("NewGroupInvite") // GroupID @@ -247,6 +248,11 @@ const ( EventContext = Field("EventContext") Authorization = Field("Authorization") + + KeyBundle = Field("KeyBundle") + + // Indicate whether an event was triggered by a user import + Imported = Field("Imported") ) // Defining Common errors diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 31f0896..fe1691a 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "git.openprivacy.ca/openprivacy/log" + "runtime/debug" "strconv" "strings" "sync" @@ -49,7 +50,7 @@ type CwtchPeer interface { DeleteGroup(string) AddServer(string) error - JoinServer(string) + JoinServer(string) error SendMessageToGroup(string, string) error SendMessageToGroupTracked(string, string) (string, error) @@ -80,7 +81,6 @@ type CwtchPeer interface { Listen() StartPeersConnections() - StartGroupConnections() Shutdown() } @@ -106,7 +106,7 @@ func (cp *cwtchPeer) Init(eventBus event.Manager) { go cp.eventHandler() cp.eventBus = eventBus - cp.AutoHandleEvents([]event.Type{event.EncryptedGroupMessage, event.NewMessageFromPeer, event.PeerAcknowledgement, + cp.AutoHandleEvents([]event.Type{event.EncryptedGroupMessage, event.NewMessageFromPeer, event.PeerAcknowledgement, event.NewGroupInvite, event.PeerError, event.SendMessageToGroupError, event.NewGetValMessageFromPeer}) } @@ -128,6 +128,7 @@ func (cp *cwtchPeer) ImportGroup(exportedInvite string) (err error) { if err == nil { cp.eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{ event.GroupInvite: string(data), + event.Imported: "true", })) } else { log.Errorf("error decoding group invite: %v", err) @@ -298,7 +299,12 @@ func (cp *cwtchPeer) GetGroupState(groupid string) (connections.ConnectionState, func (cp *cwtchPeer) PeerWithOnion(onion string) { cp.mutex.Lock() defer cp.mutex.Unlock() - if _, exists := cp.Profile.GetContact(onion); !exists { + if contact, exists := cp.Profile.GetContact(onion); !exists { + if contact.IsServer() { + log.Debugf("Tried to peer with a server...should never happen so logging it to try and trace the path:") + debug.PrintStack() + return // ABORT + } cp.AddContact(onion, onion, model.AuthApproved) } cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion})) @@ -337,15 +343,16 @@ 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) { +func (cp *cwtchPeer) JoinServer(onion string) error { if cp.GetContact(onion) != nil { tokenY, yExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypePrivacyPass)) tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypeTokenOnion)) if yExists && onionExists { cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion})) + return nil } } - // TODO HANDLE ERROR + return errors.New("no keys found for server connection") } // SendMessageToGroup attempts to sent the given message to the given group id. @@ -436,23 +443,11 @@ func (cp *cwtchPeer) Listen() { // StartGroupConnections attempts to connect to all group servers (thus initiating reconnect attempts in the conectionsmanager) func (cp *cwtchPeer) StartPeersConnections() { for _, contact := range cp.GetContacts() { - cp.PeerWithOnion(contact) - } -} - -// StartPeerConnections attempts to connect to all peers (thus initiating reconnect attempts in the conectionsmanager) -func (cp *cwtchPeer) StartGroupConnections() { - joinedServers := map[string]bool{} - for _, groupID := range cp.GetGroups() { - // Only send a join server packet if we haven't joined this server yet... - group := cp.GetGroup(groupID) - cp.mutex.Lock() - if joined := joinedServers[groupID]; group.Accepted && !joined { - log.Infof("Join Server %v (%v)\n", group.GroupServer, joined) - cp.JoinServer(group.GroupServer) - joinedServers[group.GroupServer] = true + if cp.GetContact(contact).IsServer() { + cp.JoinServer(contact) + } else { + cp.PeerWithOnion(contact) } - cp.mutex.Unlock() } } @@ -611,7 +606,18 @@ func (cp *cwtchPeer) eventHandler() { } case event.NewGroupInvite: cp.mutex.Lock() - cp.Profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) + group, err := cp.Profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer]) + if err == nil { + if ev.Data[event.Imported] == "true" { + cp.Profile.GetGroup(group).Accepted = true + cp.mutex.Unlock() // TODO...seriously need a better way of handling these cases + err = cp.JoinServer(cp.Profile.GetGroup(group).GroupServer) + cp.mutex.Lock() + if err != nil { + log.Errorf("Joining Server should have worked %v", err) + } + } + } cp.mutex.Unlock() case event.PeerStateChange: cp.mutex.Lock() diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 476dd8e..4f03336 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -11,7 +11,7 @@ import ( "errors" "fmt" "git.openprivacy.ca/openprivacy/connectivity" - torProdider "git.openprivacy.ca/openprivacy/connectivity/tor" + torProvider "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" @@ -113,13 +113,14 @@ func (e *engine) eventHandler() { case event.StatusRequest: e.eventManager.Publish(event.Event{EventType: event.ProtocolEngineStatus, EventID: ev.EventID}) case event.PeerRequest: - if torProdider.IsValidHostname(ev.Data[event.RemotePeer]) { + if torProvider.IsValidHostname(ev.Data[event.RemotePeer]) { go e.peerWithOnion(ev.Data[event.RemotePeer]) } case event.RetryPeerRequest: // This event allows engine to treat (automated) retry peering requests differently to user-specified // peer events - if torProdider.IsValidHostname(ev.Data[event.RemotePeer]) { + if torProvider.IsValidHostname(ev.Data[event.RemotePeer]) { + log.Debugf("Retrying Peer Request: %v", ev.Data[event.RemotePeer]) go e.peerWithOnion(ev.Data[event.RemotePeer]) } case event.InvitePeerToGroup: @@ -134,7 +135,10 @@ func (e *engine) eventHandler() { case event.DeleteGroup: // TODO: There isn't a way here to determine if other Groups are using a server connection... case event.SendMessageToGroup: - e.sendMessageToGroup(ev.Data[event.GroupServer], []byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature])) + err := e.sendMessageToGroup(ev.Data[event.GroupServer], []byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature])) + if err != nil { + e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: ev.Data[event.GroupServer], event.EventID: ev.EventID, event.Error: err.Error()})) + } case event.SendMessageToPeer: // TODO: remove this passthrough once the UI is integrated. context, ok := ev.Data[event.EventContext] @@ -223,6 +227,7 @@ func (e *engine) Shutdown() { // peerWithOnion is the entry point for cwtchPeer relationships // needs to be run in a goroutine as will block on Open. func (e *engine) peerWithOnion(onion string) { + log.Debugf("Called PeerWithOnion for %v", onion) if !e.isBlocked(onion) { e.ignoreOnShutdown(e.peerConnecting)(onion) connected, err := e.service.Connect(onion, e.createPeerTemplate()) @@ -249,6 +254,7 @@ 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) { + log.Debugf("Peering with Token Server %v %v", onion, tokenServerOnion) e.ignoreOnShutdown(e.serverConnecting)(onion) // Create a new ephemeral service for this connection @@ -415,8 +421,15 @@ func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) error if err == nil { tokenApp, ok := (conn.App()).(*TokenBoardClient) if ok { + attempts := 0 for tokenApp.Post(ct, sig) == false { + // TODO This should eventually be wired back into the UI to allow it to error tokenApp.MakePayment() + time.Sleep(time.Second * 5) + attempts++ + if attempts == 5 { + return errors.New("failed to post to token board") + } } return nil } diff --git a/protocol/connections/tokenboardclientapp.go b/protocol/connections/tokenboardclientapp.go index cfb3241..cd46fe5 100644 --- a/protocol/connections/tokenboardclientapp.go +++ b/protocol/connections/tokenboardclientapp.go @@ -145,6 +145,7 @@ func (ta *TokenBoardClient) Post(ct []byte, sig []byte) bool { // MakePayment uses the PoW based token protocol to obtain more tokens func (ta *TokenBoardClient) MakePayment() { + log.Debugf("Making a Payment %v", ta) id, sk := primitives.InitializeEphemeralIdentity() var client tapir.Service client = new(tor.BaseOnionService) @@ -164,7 +165,7 @@ func (ta *TokenBoardClient) MakePayment() { conn.Close() return } - log.Debugf("Error making payment: %v", err) + log.Debugf("Error making payment: to %v %v", ta.tokenServiceOnion, err) } // NextToken retrieves the next token diff --git a/server/app/main.go b/server/app/main.go index 4ffd9af..5994fd0 100644 --- a/server/app/main.go +++ b/server/app/main.go @@ -1,7 +1,12 @@ package main import ( + "cwtch.im/cwtch/model" cwtchserver "cwtch.im/cwtch/server" + "cwtch.im/tapir/primitives" + "encoding/base64" + "encoding/json" + "fmt" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" "os" @@ -16,6 +21,24 @@ func main() { log.AddEverythingFromPattern("server/server") configDir := os.Getenv("CWTCH_CONFIG_DIR") + if len(os.Args) == 2 && os.Args[1] == "gen1" { + config := new(cwtchserver.Config) + id, pk := primitives.InitializeEphemeralIdentity() + tid, tpk := primitives.InitializeEphemeralIdentity() + config.PrivateKey = pk + config.PublicKey = id.PublicKey() + config.TokenServerPrivateKey = tpk + config.TokenServerPublicKey = tid.PublicKey() + config.MaxBufferLines = 100000 + config.ServerReporting = cwtchserver.Reporting{ + LogMetricsToFile: true, + ReportingGroupID: "", + ReportingServerAddr: "", + } + config.Save(".", "serverConfig.json") + return + } + serverConfig := cwtchserver.LoadConfig(configDir, serverConfigFile) acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"}) @@ -31,5 +54,17 @@ func main() { // TODO load params from .cwtch/server.conf or command line flag // TODO: respond to HUP so t.Close is gracefully called server.Setup(serverConfig) + + // TODO create a random group for testing + group, _ := model.NewGroup(tor.GetTorV3Hostname(serverConfig.PublicKey)) + group.SignGroup([]byte{}) + invite, err := group.Invite([]byte{}) + if err != nil { + panic(err) + } + fmt.Printf("%v", "torv3"+base64.StdEncoding.EncodeToString(invite)) + + bundle, _ := json.Marshal(server.KeyBundle()) + log.Infof("Server Config: server:%s", base64.StdEncoding.EncodeToString(bundle)) server.Run(acn) } diff --git a/server/server.go b/server/server.go index 2fa4792..5bb6777 100644 --- a/server/server.go +++ b/server/server.go @@ -8,6 +8,7 @@ import ( "cwtch.im/tapir" "cwtch.im/tapir/applications" tor2 "cwtch.im/tapir/networks/tor" + "cwtch.im/tapir/persistence" "cwtch.im/tapir/primitives" "cwtch.im/tapir/primitives/privacypass" "git.openprivacy.ca/openprivacy/connectivity" @@ -32,8 +33,11 @@ type Server struct { // 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() + bs := new(persistence.BoltPersistence) + bs.Open("./tokens.db") + s.tokenServer = privacypass.NewTokenServerFromStore(bs) + s.tokenService = s.config.TokenServiceIdentity() + s.tokenServicePrivKey = s.config.TokenServerPrivateKey } // Run starts a server with the given privateKey diff --git a/server/serverConfig.go b/server/serverConfig.go index 835107f..45e029f 100644 --- a/server/serverConfig.go +++ b/server/serverConfig.go @@ -5,7 +5,6 @@ import ( "cwtch.im/tapir/primitives" "encoding/json" "git.openprivacy.ca/openprivacy/log" - "github.com/gtank/ristretto255" "golang.org/x/crypto/ed25519" "io/ioutil" "path" @@ -20,20 +19,25 @@ 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"` - PrivacyPassPublicKey ristretto255.Element `json:"privacyPassPublicKey"` - PrivacyPassPrivateKey ristretto255.Scalar `json:"privacyPassPrivateKey"` - ServerReporting Reporting `json:"serverReporting"` + ConfigDir string `json:"-"` + MaxBufferLines int `json:"maxBufferLines"` + PublicKey ed25519.PublicKey `json:"publicKey"` + PrivateKey ed25519.PrivateKey `json:"privateKey"` + TokenServerPublicKey ed25519.PublicKey `json:"tokenServerPublicKey"` + TokenServerPrivateKey ed25519.PrivateKey `json:"tokenServerPrivateKey"` + ServerReporting Reporting `json:"serverReporting"` } -// Identity returns an encapsulation of the servers keys for running ricochet +// Identity returns an encapsulation of the servers keys func (config *Config) Identity() primitives.Identity { return primitives.InitializeIdentity("", &config.PrivateKey, &config.PublicKey) } +// TokenServiceIdentity returns an encapsulation of the servers token server (experimental) +func (config *Config) TokenServiceIdentity() primitives.Identity { + return primitives.InitializeIdentity("", &config.TokenServerPrivateKey, &config.TokenServerPublicKey) +} + // Save dumps the latest version of the config to a json file given by filename func (config *Config) Save(dir, filename string) { log.Infof("Saving config to %s\n", path.Join(dir, filename)) From 2d9050346b639cdcf1681b8e1e3fa6ee1dd1deb2 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 21 Sep 2020 15:39:15 -0700 Subject: [PATCH 07/12] Fixing Tor/Server Integration --- peer/cwtch_peer.go | 8 +---- server/serverConfig.go | 21 +++++++++----- testing/cwtch_peer_server_integration_test.go | 3 ++ testing/serverMonitorReport.txt | 29 +++++++++++++++++++ 4 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 testing/serverMonitorReport.txt diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index fe1691a..bc5df1e 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -10,7 +10,6 @@ import ( "encoding/json" "errors" "git.openprivacy.ca/openprivacy/log" - "runtime/debug" "strconv" "strings" "sync" @@ -299,12 +298,7 @@ func (cp *cwtchPeer) GetGroupState(groupid string) (connections.ConnectionState, func (cp *cwtchPeer) PeerWithOnion(onion string) { cp.mutex.Lock() defer cp.mutex.Unlock() - if contact, exists := cp.Profile.GetContact(onion); !exists { - if contact.IsServer() { - log.Debugf("Tried to peer with a server...should never happen so logging it to try and trace the path:") - debug.PrintStack() - return // ABORT - } + if _, exists := cp.Profile.GetContact(onion); !exists { cp.AddContact(onion, onion, model.AuthApproved) } cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion})) diff --git a/server/serverConfig.go b/server/serverConfig.go index 45e029f..4bed576 100644 --- a/server/serverConfig.go +++ b/server/serverConfig.go @@ -1,7 +1,6 @@ package server import ( - "crypto/rand" "cwtch.im/tapir/primitives" "encoding/json" "git.openprivacy.ca/openprivacy/log" @@ -49,9 +48,20 @@ func (config *Config) Save(dir, filename string) { func LoadConfig(configDir, filename string) Config { log.Infof("Loading config from %s\n", path.Join(configDir, filename)) config := Config{} - config.ConfigDir = configDir + + id, pk := primitives.InitializeEphemeralIdentity() + tid, tpk := primitives.InitializeEphemeralIdentity() + config.PrivateKey = pk + config.PublicKey = id.PublicKey() + config.TokenServerPrivateKey = tpk + config.TokenServerPublicKey = tid.PublicKey() config.MaxBufferLines = 100000 - config.ServerReporting.LogMetricsToFile = true + config.ServerReporting = Reporting{ + LogMetricsToFile: true, + ReportingGroupID: "", + ReportingServerAddr: "", + } + raw, err := ioutil.ReadFile(path.Join(configDir, filename)) if err == nil { err = json.Unmarshal(raw, &config) @@ -60,11 +70,6 @@ func LoadConfig(configDir, filename string) Config { log.Errorf("reading config: %v", err) } } - - if config.PrivateKey == nil { - config.PublicKey, config.PrivateKey, _ = ed25519.GenerateKey(rand.Reader) - } - // Always save (first time generation, new version with new variables populated) config.Save(configDir, filename) return config diff --git a/testing/cwtch_peer_server_integration_test.go b/testing/cwtch_peer_server_integration_test.go index 5e6f4ce..0173e56 100644 --- a/testing/cwtch_peer_server_integration_test.go +++ b/testing/cwtch_peer_server_integration_test.go @@ -116,6 +116,9 @@ func TestCwtchPeerIntegration(t *testing.T) { log.ExcludeFromPattern("pipeBridge") log.ExcludeFromPattern("tapir") os.RemoveAll("tor") + dataDir := path.Join(".", "tor") + os.MkdirAll(dataDir, 0700) + tor.GenerateTorrc("examplehashedpassword", "./tor/torrc") acn, err := tor.NewTorACNWithAuth(".", "", 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"}) if err != nil { t.Fatalf("Could not start Tor: %v", err) diff --git a/testing/serverMonitorReport.txt b/testing/serverMonitorReport.txt new file mode 100644 index 0000000..56aadd3 --- /dev/null +++ b/testing/serverMonitorReport.txt @@ -0,0 +1,29 @@ +Uptime: 3m0.015645854s + +messages: +Minutes: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Hours: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Days: 0 0 0 0 0 0 0 +Weeks: 0 0 0 0 +Months: 0 0 0 0 0 0 0 0 0 0 0 0 + +Client Connections: +Minutes: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Hours: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +Days: 0 0 0 0 0 0 0 +Weeks: 0 0 0 0 +Months: 0 0 0 0 0 0 0 0 0 0 0 0 + +CPU: +Minutes: 0.00 100.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 +Hours: 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 +Days: 0.00 0.00 0.00 0.00 0.00 0.00 0.00 +Weeks: 0.00 0.00 0.00 0.00 +Months: 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 + +Memory: +Minutes: 14MBs 14MBs 14MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs +Hours: 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs +Days: 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs +Weeks: 0MBs 0MBs 0MBs 0MBs +Months: 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs 0MBs From 1e34eb67a7a35598aad533c94f336b893965d6cc Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 28 Sep 2020 10:40:41 -0700 Subject: [PATCH 08/12] Fixing up ContactRetry and Integ Tests --- app/plugins/contactRetry.go | 5 ----- testing/cwtch_peer_server_integration_test.go | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/plugins/contactRetry.go b/app/plugins/contactRetry.go index 2592f68..369f9a8 100644 --- a/app/plugins/contactRetry.go +++ b/app/plugins/contactRetry.go @@ -3,7 +3,6 @@ package plugins import ( "cwtch.im/cwtch/event" "cwtch.im/cwtch/protocol/connections" - "git.openprivacy.ca/openprivacy/log" "sync" "time" ) @@ -57,13 +56,11 @@ func (cr *contactRetry) run() { case e := <-cr.queue.OutChan(): switch e.EventType { case event.PeerStateChange: - log.Errorf("PEER STATE CHANGE: %v", e) state := connections.ConnectionStateToType[e.Data[event.ConnectionState]] peer := e.Data[event.RemotePeer] cr.handleEvent(peer, state, peerConn) case event.ServerStateChange: - log.Errorf("SERVER STATE CHANGE: %v", e) state := connections.ConnectionStateToType[e.Data[event.ConnectionState]] server := e.Data[event.GroupServer] cr.handleEvent(server, state, serverConn) @@ -97,8 +94,6 @@ func (cr *contactRetry) run() { if cr.networkUp { if p.ctype == peerConn { cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id})) - } else { - //cr.bus.Publish(event.NewEventList(event.JoinServer, event.GroupServer, p.id)) } } } diff --git a/testing/cwtch_peer_server_integration_test.go b/testing/cwtch_peer_server_integration_test.go index 0173e56..4f5dc65 100644 --- a/testing/cwtch_peer_server_integration_test.go +++ b/testing/cwtch_peer_server_integration_test.go @@ -348,16 +348,18 @@ func TestCwtchPeerIntegration(t *testing.T) { fmt.Printf("%v> %v", bobName, bobLines[2]) bob.SendMessageToGroup(groupID, bobLines[2]) - time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition + // Bob should have enough tokens so we don't need to account for + // token acquisition here... fmt.Printf("%v> %v", carolName, carolLines[0]) carol.SendMessageToGroup(groupID, carolLines[0]) - time.Sleep(time.Second * 60) // we need to account for spam-based token acquisition + time.Sleep(time.Second * 30) // we need to account for spam-based token acquisition, but everything should + // be warmed-up and delays should be pretty small. // ***** Verify Test ***** fmt.Println("Final syncing time...") - time.Sleep(time.Second * 60) + time.Sleep(time.Second * 30) alicesGroup := alice.GetGroup(groupID) if alicesGroup == nil { From 6739df68c3a36bb5119971708eb92e2b75f8f557 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 28 Sep 2020 11:18:18 -0700 Subject: [PATCH 09/12] Group V2 Logic --- model/group.go | 5 +++++ storage/v1/profile_store.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/model/group.go b/model/group.go index 0ec8c86..aaa2ddb 100644 --- a/model/group.go +++ b/model/group.go @@ -14,6 +14,9 @@ import ( "time" ) +// CurrentGroupVersion is used to set the version of newly created groups and make sure group structs stored are correct and up to date +const CurrentGroupVersion = 2 + // Group defines and encapsulates Cwtch's conception of group chat. Which are sessions // tied to a server under a given group key. Each group has a set of Messages. type Group struct { @@ -31,11 +34,13 @@ type Group struct { LocalID string State string `json:"-"` unacknowledgedMessages []Message + Version int } // NewGroup initializes a new group associated with a given CwtchServer func NewGroup(server string) (*Group, error) { group := new(Group) + group.Version = CurrentGroupVersion group.LocalID = GenerateRandomID() if tor.IsValidHostname(server) == false { diff --git a/storage/v1/profile_store.go b/storage/v1/profile_store.go index 1c5f22b..709ffe5 100644 --- a/storage/v1/profile_store.go +++ b/storage/v1/profile_store.go @@ -247,6 +247,7 @@ func (ps *ProfileStoreV1) load() error { } cp := new(model.Profile) err = json.Unmarshal(decrypted, &cp) + if err == nil { ps.profile = cp @@ -275,12 +276,19 @@ func (ps *ProfileStoreV1) load() error { } for gid, group := range cp.Groups { + if group.Version == 0 { + log.Infof("group %v is of unsupported version 0. dropping group...\n", group.GroupID) + delete(cp.Groups, gid) + continue + } + ss := NewStreamStore(ps.directory, group.LocalID, ps.key) cp.Groups[gid].Timeline.SetMessages(ss.Read()) ps.streamStores[group.GroupID] = ss } + ps.save() } return err From 918c410ab3a50decb0c44a225ad1b2affddd940f Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 28 Sep 2020 11:19:33 -0700 Subject: [PATCH 10/12] Upgrade Connectivity --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4bbe0a8..b4e27c1 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( cwtch.im/tapir v0.2.0 - git.openprivacy.ca/openprivacy/connectivity v1.2.0 + git.openprivacy.ca/openprivacy/connectivity v1.2.1 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 // indirect diff --git a/go.sum b/go.sum index 854b96e..9911d1b 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ git.openprivacy.ca/openprivacy/connectivity v1.1.2 h1:Bk8ul3+4/awpQGvskfLpp7/K3L 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/connectivity v1.2.1 h1:oRL56TR9ZQnKkGkTIQ9wYbJ2IkOOsi/zLYExYiAS+sE= +git.openprivacy.ca/openprivacy/connectivity v1.2.1/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= From aed688c72f1e9b7b5bb6ad12bcae6e2969745f70 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 28 Sep 2020 11:46:18 -0700 Subject: [PATCH 11/12] Metrics Test... --- server/metrics/metrics_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/metrics/metrics_test.go b/server/metrics/metrics_test.go index 4841ecd..c17e4d0 100644 --- a/server/metrics/metrics_test.go +++ b/server/metrics/metrics_test.go @@ -32,6 +32,6 @@ func TestCounter(t *testing.T) { counterStart := c.GetStarttime() if counterStart.Sub(starttime) > time.Millisecond { - t.Error("counter's starttime was innaccurate") + t.Errorf("counter's starttime was innaccurate %v", counterStart.Sub(starttime)) } } From e5d21b25a333ad781b41b246dafcf294d8eb6614 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 28 Sep 2020 14:13:30 -0700 Subject: [PATCH 12/12] Assume Invites are v2 Groups --- model/profile.go | 1 + 1 file changed, 1 insertion(+) diff --git a/model/profile.go b/model/profile.go index 39df5ce..8113812 100644 --- a/model/profile.go +++ b/model/profile.go @@ -363,6 +363,7 @@ func (p *Profile) ProcessInvite(invite string, peerHostname string) (string, err err := json.Unmarshal([]byte(invite), &gci) if err == nil { group := new(Group) + group.Version = CurrentGroupVersion group.GroupID = gci.GroupName group.LocalID = GenerateRandomID() group.SignedGroupID = gci.SignedGroupID