Tapir Server Refactor

This commit is contained in:
Sarah Jamie Lewis 2020-07-13 17:46:05 -07:00
parent c2114018f8
commit 0550a71244
48 changed files with 728 additions and 3410 deletions

View File

@ -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)
}

View File

@ -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")

15
go.mod
View File

@ -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
)

10
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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
}

46
model/keyBundle.go Normal file
View File

@ -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
}

View File

@ -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[:],
}

View File

@ -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.

View File

@ -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,
}

View File

@ -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;
}

View File

@ -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()
}

View File

@ -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())
}

View File

@ -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) {

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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) }

View File

@ -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;
}

View File

@ -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,
}

View File

@ -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;
}

91
protocol/groups/common.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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
})
})
}

View File

@ -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)
}

110
server/server_tokenboard.go Normal file
View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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 {

View File

@ -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