forked from cwtch.im/cwtch
Fixing some race conditions, adding more error checking
This commit is contained in:
parent
b4eb27ae1d
commit
ec0c341bb1
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/c-bata/go-prompt"
|
"github.com/c-bata/go-prompt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var app app2.Application
|
var app app2.Application
|
||||||
|
@ -38,7 +39,7 @@ var usages = map[string]string{
|
||||||
"peers": "",
|
"peers": "",
|
||||||
"contacts": "",
|
"contacts": "",
|
||||||
"groups": "",
|
"groups": "",
|
||||||
"info": "",
|
"info": "",
|
||||||
"send": "send [groupid] [message]",
|
"send": "send [groupid] [message]",
|
||||||
"timeline": "timeline [groupid]",
|
"timeline": "timeline [groupid]",
|
||||||
"accept-invite": "accept-invite [groupid]",
|
"accept-invite": "accept-invite [groupid]",
|
||||||
|
@ -46,8 +47,8 @@ var usages = map[string]string{
|
||||||
"invite-to-group": "invite-to-group [peerid] [groupid]",
|
"invite-to-group": "invite-to-group [peerid] [groupid]",
|
||||||
"new-group": "new-group [server]",
|
"new-group": "new-group [server]",
|
||||||
"help": "",
|
"help": "",
|
||||||
"trust": "trust [peerid]",
|
"trust": "trust [peerid]",
|
||||||
"block": "block [peerid]",
|
"block": "block [peerid]",
|
||||||
}
|
}
|
||||||
|
|
||||||
func completer(d prompt.Document) []prompt.Suggest {
|
func completer(d prompt.Document) []prompt.Suggest {
|
||||||
|
@ -326,6 +327,36 @@ func main() {
|
||||||
for _, command := range suggestions {
|
for _, command := range suggestions {
|
||||||
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
fmt.Printf("%-18s%-56s%s\n", command.Text, command.Description, usages[command.Text])
|
||||||
}
|
}
|
||||||
|
case "sendlots":
|
||||||
|
if len(commands) == 2 {
|
||||||
|
group := app.Peer.Profile.GetGroupByGroupId(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 := app.Peer.SendMessageToGroup(commands[1], fmt.Sprintf("this is message %v", i))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("could not send message %v because %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("Waiting 5 seconds for message to process...\n")
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
timeline := group.GetTimeline()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
found := false
|
||||||
|
for _, m := range timeline.Messages {
|
||||||
|
if m.Message == fmt.Sprintf("this is message %v", i) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fmt.Printf("message %v was never received\n", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.Peer.Save(profilefile)
|
app.Peer.Save(profilefile)
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
|
//Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
|
||||||
|
@ -22,7 +22,7 @@ type Group struct {
|
||||||
Timeline Timeline
|
Timeline Timeline
|
||||||
Accepted bool
|
Accepted bool
|
||||||
Owner string
|
Owner string
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGroup initializes a new group associated with a given CwtchServer
|
// NewGroup initializes a new group associated with a given CwtchServer
|
||||||
|
@ -70,7 +70,7 @@ func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified boo
|
||||||
timelineMessage := &Message{
|
timelineMessage := &Message{
|
||||||
Message: message.GetText(),
|
Message: message.GetText(),
|
||||||
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
||||||
Received: time.Now(),
|
Received: time.Now(),
|
||||||
Signature: message.GetSignature(),
|
Signature: message.GetSignature(),
|
||||||
Verified: verified,
|
Verified: verified,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.GetOnion(),
|
||||||
|
|
|
@ -14,7 +14,7 @@ type Timeline struct {
|
||||||
// Message is a local representation of a given message sent over a group chat channel.
|
// Message is a local representation of a given message sent over a group chat channel.
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Received time.Time
|
Received time.Time
|
||||||
PeerID string
|
PeerID string
|
||||||
Message string
|
Message string
|
||||||
Signature []byte
|
Signature []byte
|
||||||
|
|
|
@ -13,8 +13,8 @@ func TestTranscriptConsistency(t *testing.T) {
|
||||||
// Setup the Group
|
// Setup the Group
|
||||||
sarah := GenerateNewProfile("Sarah")
|
sarah := GenerateNewProfile("Sarah")
|
||||||
alice := GenerateNewProfile("Alice")
|
alice := GenerateNewProfile("Alice")
|
||||||
sarah.AddContact(alice.Onion, alice.PublicProfile)
|
sarah.AddContact(alice.Onion, &alice.PublicProfile)
|
||||||
alice.AddContact(sarah.Onion, sarah.PublicProfile)
|
alice.AddContact(sarah.Onion, &sarah.PublicProfile)
|
||||||
|
|
||||||
gid, invite := alice.StartGroup("aaa.onion")
|
gid, invite := alice.StartGroup("aaa.onion")
|
||||||
gci := &protocol.CwtchPeerPacket{}
|
gci := &protocol.CwtchPeerPacket{}
|
||||||
|
@ -23,21 +23,23 @@ func TestTranscriptConsistency(t *testing.T) {
|
||||||
|
|
||||||
group := alice.GetGroupByGroupId(gid)
|
group := alice.GetGroupByGroupId(gid)
|
||||||
|
|
||||||
c1 := sarah.EncryptMessageToGroup("Hello World 1", group.GroupID)
|
t.Logf("group: %v, sarah %v", group, sarah)
|
||||||
|
|
||||||
|
c1, _ := sarah.EncryptMessageToGroup("Hello World 1", group.GroupID)
|
||||||
alice.AttemptDecryption(c1)
|
alice.AttemptDecryption(c1)
|
||||||
|
|
||||||
c2 := alice.EncryptMessageToGroup("Hello World 2", group.GroupID)
|
c2, _ := alice.EncryptMessageToGroup("Hello World 2", group.GroupID)
|
||||||
alice.AttemptDecryption(c2)
|
alice.AttemptDecryption(c2)
|
||||||
|
|
||||||
c3 := alice.EncryptMessageToGroup("Hello World 3", group.GroupID)
|
c3, _ := alice.EncryptMessageToGroup("Hello World 3", group.GroupID)
|
||||||
alice.AttemptDecryption(c3)
|
alice.AttemptDecryption(c3)
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
c4 := alice.EncryptMessageToGroup("Hello World 4", group.GroupID)
|
c4, _ := alice.EncryptMessageToGroup("Hello World 4", group.GroupID)
|
||||||
alice.AttemptDecryption(c4)
|
alice.AttemptDecryption(c4)
|
||||||
|
|
||||||
c5 := alice.EncryptMessageToGroup("Hello World 5", group.GroupID)
|
c5, _ := alice.EncryptMessageToGroup("Hello World 5", group.GroupID)
|
||||||
|
|
||||||
_, m1 := sarah.AttemptDecryption(c1)
|
_, m1 := sarah.AttemptDecryption(c1)
|
||||||
_, m2 := sarah.AttemptDecryption(c2)
|
_, m2 := sarah.AttemptDecryption(c2)
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"git.mascherari.press/cwtch/protocol"
|
"git.mascherari.press/cwtch/protocol"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
// "log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -50,6 +50,7 @@ func GenerateNewProfile(name string) *Profile {
|
||||||
p.Onion = utils.GetTorHostname(publicKeyBytes)
|
p.Onion = utils.GetTorHostname(publicKeyBytes)
|
||||||
|
|
||||||
p.Contacts = make(map[string]*PublicProfile)
|
p.Contacts = make(map[string]*PublicProfile)
|
||||||
|
p.Contacts[p.Onion] = &p.PublicProfile
|
||||||
p.Groups = make(map[string]*Group)
|
p.Groups = make(map[string]*Group)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
@ -106,10 +107,10 @@ func (p *Profile) SignMessage(message string) []byte {
|
||||||
func (p *Profile) StartGroup(server string) (groupID string, invite []byte) {
|
func (p *Profile) StartGroup(server string) (groupID string, invite []byte) {
|
||||||
group := NewGroup(server)
|
group := NewGroup(server)
|
||||||
groupID = group.GroupID
|
groupID = group.GroupID
|
||||||
signedGroupId := p.SignMessage(groupID)
|
signedGroupId := p.SignMessage(groupID + server)
|
||||||
group.SignGroup(signedGroupId)
|
group.SignGroup(signedGroupId)
|
||||||
invite = group.Invite()
|
invite = group.Invite()
|
||||||
p.AddGroup(group)
|
p.Groups[group.GroupID] = group
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,8 +134,13 @@ func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname stri
|
||||||
func (p *Profile) AddGroup(group *Group) {
|
func (p *Profile) AddGroup(group *Group) {
|
||||||
existingGroup, exists := p.Groups[group.GroupID]
|
existingGroup, exists := p.Groups[group.GroupID]
|
||||||
if !exists {
|
if !exists {
|
||||||
// owned := ed25519.Verify(p.Contacts[group.Owner].Ed25519PublicKey,[]byte(group.GroupID),group.SignedGroupID)
|
owner, ok := p.Contacts[group.Owner]
|
||||||
p.Groups[group.GroupID] = group
|
if ok {
|
||||||
|
valid := ed25519.Verify(owner.Ed25519PublicKey, []byte(group.GroupID+group.GroupServer), group.SignedGroupID)
|
||||||
|
if valid {
|
||||||
|
p.Groups[group.GroupID] = group
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if exists && existingGroup.Owner == group.Owner {
|
} else if exists && existingGroup.Owner == group.Owner {
|
||||||
p.Groups[group.GroupID] = group
|
p.Groups[group.GroupID] = group
|
||||||
}
|
}
|
||||||
|
@ -161,26 +167,29 @@ func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
|
||||||
|
|
||||||
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
|
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
|
||||||
// profile
|
// profile
|
||||||
func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphertext []byte) {
|
func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, error) {
|
||||||
group := p.Groups[groupID]
|
group := p.Groups[groupID]
|
||||||
timestamp := time.Now().Unix()
|
if group != nil {
|
||||||
signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
|
timestamp := time.Now().Unix()
|
||||||
var prevSig []byte
|
signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
|
||||||
if len(group.Timeline.Messages) > 0 {
|
var prevSig []byte
|
||||||
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
|
if len(group.Timeline.Messages) > 0 {
|
||||||
} else {
|
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
|
||||||
prevSig = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
} else {
|
||||||
|
prevSig = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
}
|
||||||
|
dm := &protocol.DecryptedGroupMessage{
|
||||||
|
Onion: proto.String(p.Onion),
|
||||||
|
Text: proto.String(message),
|
||||||
|
SignedGroupId: group.SignedGroupID[:],
|
||||||
|
Timestamp: proto.Int32(int32(timestamp)),
|
||||||
|
Signature: signature,
|
||||||
|
PreviousMessageSig: prevSig,
|
||||||
|
}
|
||||||
|
ciphertext := group.EncryptMessage(dm)
|
||||||
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
dm := &protocol.DecryptedGroupMessage{
|
return nil, errors.New("group does not exist")
|
||||||
Onion: proto.String(p.Onion),
|
|
||||||
Text: proto.String(message),
|
|
||||||
SignedGroupId: group.SignedGroupID[:],
|
|
||||||
Timestamp: proto.Int32(int32(timestamp)),
|
|
||||||
Signature: signature,
|
|
||||||
PreviousMessageSig: prevSig,
|
|
||||||
}
|
|
||||||
ciphertext = group.EncryptMessage(dm)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save makes a opy of the profile in the given file
|
// Save makes a opy of the profile in the given file
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"Name":"Sarah","Ed25519PublicKey":"Q6dF7nCKD6BfqgGLBk0FCCYBdWBLv7Ts8uqw2zOf02A=","Trusted":false,"Blocked":false,"Onion":"lrkqzljkc5cgzmpz","Contacts":{},"Ed25519PrivateKey":"Fy9HObWaw9EH8N3jXNQtD9UgL0Yd7feY5L+oR7uNnXdDp0XucIoPoF+qAYsGTQUIJgF1YEu/tOzy6rDbM5/TYA==","OnionPrivateKey":{"N":107498703462635665473764971520727195625141645315352207633904323688914544197828221612438071393137838030896432787991102883141045406182879952109587171872209559960156106354960716467157362368090179490551994133414470776568822115434428749245942446198318143880845039325380939873177604289430737365718905329111360343777,"E":65537,"D":66650934838346851867814606295271813891877956204037743640936696656474260172643727314338596411016111670467760958042487061561457482564584036102063950773690623873787097606677998053221231028080886156972583586531026258726212233434099244761970868656273357504083063877021130260355845678915219099393211715317011636225,"Primes":[10332071347522235902804485468145791866057055098286157383486988408098332066944447770996594012391982280205811088346484428102348931474084528799982497053772929,10404370996568393072795074320321443437318186756296376448713064693902356814671757730321014302625680493962881120033122151552975123796575551159729969846473313],"Precomputed":{"Dp":1495964493593519637482822872686198926057271401309753992582938386017746814520589360193275258000633533070981909720002300048265697403872439900865677625513089,"Dq":9954139888243813632868821504775543966969614535400447071284280168032892482669848809815187112454533967864971071413045943887011813741384006488948667765441249,"Qinv":9010750810541828585160172405562139672487565596169846634421581721158830598191896975588357263133581085970784950793654150830626201363086339113136601110542205,"CRTValues":[]}},"Groups":{}}
|
{"Name":"Sarah","Ed25519PublicKey":"FH0bgqmhOuDPu/uXhm5ZT2BuA323JzPBd0Kf3B1YxZA=","Trusted":false,"Blocked":false,"Onion":"y6rv5psc5wruugyx","Contacts":{"y6rv5psc5wruugyx":{"Name":"Sarah","Ed25519PublicKey":"FH0bgqmhOuDPu/uXhm5ZT2BuA323JzPBd0Kf3B1YxZA=","Trusted":false,"Blocked":false,"Onion":"y6rv5psc5wruugyx"}},"Ed25519PrivateKey":"JMnZpsSi1hY6ziZqEaO2kk0ID9+XKm3nOiVoMJazbNIUfRuCqaE64M+7+5eGbllPYG4DfbcnM8F3Qp/cHVjFkA==","OnionPrivateKey":{"N":134057991576218556828017459230656270170181976065106440664426101908559409913157558701412664226096530310039819285641603796528668425856812146294423697678905284504161436672881228795123245277153307076066056625279711139065658846052615035565877781519956303024388960852968254724005382280308994893755778995867984769237,"E":65537,"D":119111293612512116271655044493966989822690945058076323906946181761988105028352909702660473288151745730711181119106925691927679973719306212959462470296819405321699089033715265196316348982251250275981258387949939751153694376595038421083397851309304042337114325061140172676796510776973712466297887452957497017473,"Primes":[11012092321802935235393019477644583097314933484252577291348472528127020714587871982369210353940819563472891086714033880185118457292396210254630513786328037,12173707562439882498035952564416877264727009547626505009495704590991075365448508893781170020376584913732483057163277808299913173595097515219539586115287601],"Precomputed":{"Dp":1484196888452100751243214383417529067528004142185376431854998822664235072889431515331297359603876576653738300028152971049562099779723449733420073062157797,"Dq":716821604337328632069224116851316498536422629114708986246607626480225824118677934923806935145539789463870059929400019259797746874337874959674737366966673,"Qinv":7022175560771039220943597791205895336996303269499099474897105311693825279448116080639519964317539885032301904250731793359527590562893328519648160444244943,"CRTValues":[]}},"Groups":{}}
|
|
@ -40,8 +40,8 @@ func TestProfileIdentity(t *testing.T) {
|
||||||
func TestProfileGroup(t *testing.T) {
|
func TestProfileGroup(t *testing.T) {
|
||||||
sarah := GenerateNewProfile("Sarah")
|
sarah := GenerateNewProfile("Sarah")
|
||||||
alice := GenerateNewProfile("Alice")
|
alice := GenerateNewProfile("Alice")
|
||||||
sarah.AddContact(alice.Onion, alice.PublicProfile)
|
sarah.AddContact(alice.Onion, &alice.PublicProfile)
|
||||||
alice.AddContact(sarah.Onion, sarah.PublicProfile)
|
alice.AddContact(sarah.Onion, &sarah.PublicProfile)
|
||||||
|
|
||||||
gid, invite := alice.StartGroup("aaa.onion")
|
gid, invite := alice.StartGroup("aaa.onion")
|
||||||
gci := &protocol.CwtchPeerPacket{}
|
gci := &protocol.CwtchPeerPacket{}
|
||||||
|
@ -49,7 +49,7 @@ func TestProfileGroup(t *testing.T) {
|
||||||
sarah.ProcessInvite(gci.GetGroupChatInvite(), alice.Onion)
|
sarah.ProcessInvite(gci.GetGroupChatInvite(), alice.Onion)
|
||||||
|
|
||||||
group := alice.GetGroupByGroupId(gid)
|
group := alice.GetGroupByGroupId(gid)
|
||||||
c := sarah.EncryptMessageToGroup("Hello World", group.GroupID)
|
c, _ := sarah.EncryptMessageToGroup("Hello World", group.GroupID)
|
||||||
alice.AttemptDecryption(c)
|
alice.AttemptDecryption(c)
|
||||||
|
|
||||||
gid2, invite2 := alice.StartGroup("bbb.onion")
|
gid2, invite2 := alice.StartGroup("bbb.onion")
|
||||||
|
@ -57,20 +57,25 @@ func TestProfileGroup(t *testing.T) {
|
||||||
proto.Unmarshal(invite2, gci2)
|
proto.Unmarshal(invite2, gci2)
|
||||||
sarah.ProcessInvite(gci2.GetGroupChatInvite(), alice.Onion)
|
sarah.ProcessInvite(gci2.GetGroupChatInvite(), alice.Onion)
|
||||||
group2 := alice.GetGroupByGroupId(gid2)
|
group2 := alice.GetGroupByGroupId(gid2)
|
||||||
c2 := sarah.EncryptMessageToGroup("Hello World", group2.GroupID)
|
c2, _ := sarah.EncryptMessageToGroup("Hello World", group2.GroupID)
|
||||||
alice.AttemptDecryption(c2)
|
alice.AttemptDecryption(c2)
|
||||||
|
|
||||||
bob := GenerateNewProfile("bob")
|
bob := GenerateNewProfile("bob")
|
||||||
|
bob.AddContact(alice.Onion, &alice.PublicProfile)
|
||||||
bob.ProcessInvite(gci2.GetGroupChatInvite(), alice.Onion)
|
bob.ProcessInvite(gci2.GetGroupChatInvite(), alice.Onion)
|
||||||
c3 := bob.EncryptMessageToGroup("Bobs Message", group2.GroupID)
|
c3, err := bob.EncryptMessageToGroup("Bobs Message", group2.GroupID)
|
||||||
ok, message := alice.AttemptDecryption(c3)
|
if err == nil {
|
||||||
if ok != true || message.Verified == true {
|
ok, message := alice.AttemptDecryption(c3)
|
||||||
t.Errorf("Bobs message to the group should be decrypted but not verified by alice instead %v %v", message, ok)
|
if ok != true || message.Verified == true {
|
||||||
}
|
t.Errorf("Bobs message to the group should be decrypted but not verified by alice instead %v %v", message, ok)
|
||||||
|
}
|
||||||
|
|
||||||
eve := GenerateNewProfile("eve")
|
eve := GenerateNewProfile("eve")
|
||||||
ok, _ = eve.AttemptDecryption(c3)
|
ok, _ = eve.AttemptDecryption(c3)
|
||||||
if ok {
|
if ok {
|
||||||
t.Errorf("Eves hould not be able to decrypt messages!")
|
t.Errorf("Eves hould not be able to decrypt messages!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Bob failed to encrypt a message to the group")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nothing in this file constitutes a cryptographic proof, but acts to prevent regressions in expected protocol
|
|
||||||
// behaviour.
|
|
||||||
|
|
||||||
// When a new member joins the group, they should be unable to decrypt any past messages
|
|
||||||
func TestGroupForwardSecrecy(t *testing.T) {
|
|
||||||
t.Fatalf("Failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a member leaves the group, they should be unable to decrypt any future messages.
|
|
||||||
func TestGroupBackwardSecrecy(t *testing.T) {
|
|
||||||
t.Fatalf("Failed")
|
|
||||||
}
|
|
|
@ -74,40 +74,39 @@ func (psc *PeerServerConnection) Break() error {
|
||||||
return psc.connection.Break()
|
return psc.connection.Break()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) {
|
func (psc *PeerServerConnection) SendGroupMessage(gm *protocol.GroupMessage) error {
|
||||||
for psc.state != AUTHENTICATED {
|
if psc.state != AUTHENTICATED {
|
||||||
time.Sleep(time.Second * 2)
|
return errors.New("peer is not yet connected & authenticated to server cannot send message")
|
||||||
}
|
}
|
||||||
log.Printf("Opening a Channel to Send")
|
|
||||||
psc.connection.Do(func() error {
|
err := psc.connection.Do(func() error {
|
||||||
psc.connection.RequestOpenChannel("im.cwtch.server.send", &send.CwtchPeerSendChannel{})
|
psc.connection.RequestOpenChannel("im.cwtch.server.send", &send.CwtchPeerSendChannel{})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
log.Printf("Waiting...")
|
|
||||||
// TODO We have to wait to receive the channel result before we can continue
|
errCount := 0
|
||||||
// We should have a better mechanism for this kindof interaction
|
for errCount < 5 {
|
||||||
log.Printf("CWTCH PEER Sending...")
|
time.Sleep(time.Second * 1)
|
||||||
send:
|
err = psc.connection.Do(func() error {
|
||||||
time.Sleep(time.Second * 2)
|
channel := psc.connection.Channel("im.cwtch.server.send", channels.Outbound)
|
||||||
err := psc.connection.Do(func() error {
|
if channel == nil {
|
||||||
channel := psc.connection.Channel("im.cwtch.server.send", channels.Outbound)
|
return errors.New("no channel found")
|
||||||
if channel == nil {
|
}
|
||||||
return errors.New("No Channel")
|
sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel)
|
||||||
}
|
if ok {
|
||||||
sendchannel, ok := channel.Handler.(*send.CwtchPeerSendChannel)
|
return sendchannel.SendGroupMessage(gm)
|
||||||
if ok {
|
}
|
||||||
sendchannel.SendGroupMessage(gm)
|
return errors.New("channel is not a peer send channel (this should definitely not happen)")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errCount++
|
||||||
} else {
|
} else {
|
||||||
return errors.New("Failed")
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
for err != nil {
|
|
||||||
log.Printf("CHANNEL ERROR %v", err)
|
|
||||||
goto send
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Done")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (psc *PeerServerConnection) HandleGroupMessage(gm *protocol.GroupMessage) {
|
func (psc *PeerServerConnection) HandleGroupMessage(gm *protocol.GroupMessage) {
|
||||||
|
|
|
@ -71,7 +71,6 @@ func (cp *CwtchPeer) Save(profilefile string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func LoadCwtchPeer(profilefile string) (*CwtchPeer, error) {
|
func LoadCwtchPeer(profilefile string) (*CwtchPeer, error) {
|
||||||
bytes, _ := ioutil.ReadFile(profilefile)
|
bytes, _ := ioutil.ReadFile(profilefile)
|
||||||
cp := new(CwtchPeer)
|
cp := new(CwtchPeer)
|
||||||
|
@ -123,14 +122,17 @@ func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) error {
|
||||||
}
|
}
|
||||||
psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
|
psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
|
||||||
if psc == nil {
|
if psc == nil {
|
||||||
return errors.New("could not find server to send message to")
|
return errors.New("could not find server connection to send message to")
|
||||||
|
}
|
||||||
|
ct, err := cp.Profile.EncryptMessageToGroup(message, groupid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
ct := cp.Profile.EncryptMessageToGroup(message, groupid)
|
|
||||||
gm := &protocol.GroupMessage{
|
gm := &protocol.GroupMessage{
|
||||||
Ciphertext: ct,
|
Ciphertext: ct,
|
||||||
}
|
}
|
||||||
psc.SendGroupMessage(gm)
|
err = psc.SendGroupMessage(gm)
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CwtchPeer) GetPeers() map[string]connections.ConnectionState {
|
func (cp *CwtchPeer) GetPeers() map[string]connections.ConnectionState {
|
||||||
|
@ -142,7 +144,7 @@ func (cp *CwtchPeer) GetServers() map[string]connections.ConnectionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CwtchPeer) TrustPeer(peer string) error {
|
func (cp *CwtchPeer) TrustPeer(peer string) error {
|
||||||
_,ok := cp.Profile.Contacts[peer]
|
_, ok := cp.Profile.Contacts[peer]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("peer does not exist")
|
return errors.New("peer does not exist")
|
||||||
}
|
}
|
||||||
|
@ -152,7 +154,7 @@ func (cp *CwtchPeer) TrustPeer(peer string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *CwtchPeer) BlockPeer(peer string) error {
|
func (cp *CwtchPeer) BlockPeer(peer string) error {
|
||||||
_,ok := cp.Profile.Contacts[peer]
|
_, ok := cp.Profile.Contacts[peer]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("peer does not exist")
|
return errors.New("peer does not exist")
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,15 +73,20 @@ func (cplc *CwtchPeerSendChannel) OpenOutboundResult(err error, crm *Protocol_Da
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendGroupMessage performs the spamguard proof of work and sends a message.
|
// SendGroupMessage performs the spamguard proof of work and sends a message.
|
||||||
func (cplc *CwtchPeerSendChannel) SendGroupMessage(gm *protocol.GroupMessage) {
|
func (cplc *CwtchPeerSendChannel) SendGroupMessage(gm *protocol.GroupMessage) error {
|
||||||
sgsolve := cplc.spamGuard.SolveChallenge(cplc.challenge, gm.GetCiphertext())
|
if cplc.channel.Pending == false {
|
||||||
gm.Spamguard = sgsolve[:]
|
sgsolve := cplc.spamGuard.SolveChallenge(cplc.challenge, gm.GetCiphertext())
|
||||||
csp := &protocol.CwtchServerPacket{
|
gm.Spamguard = sgsolve[:]
|
||||||
GroupMessage: gm,
|
csp := &protocol.CwtchServerPacket{
|
||||||
|
GroupMessage: gm,
|
||||||
|
}
|
||||||
|
packet, _ := proto.Marshal(csp)
|
||||||
|
cplc.channel.SendMessage(packet)
|
||||||
|
cplc.channel.CloseChannel()
|
||||||
|
} else {
|
||||||
|
return errors.New("channel isn't set up yet")
|
||||||
}
|
}
|
||||||
packet, _ := proto.Marshal(csp)
|
return nil
|
||||||
cplc.channel.SendMessage(packet)
|
|
||||||
cplc.channel.CloseChannel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet should never be
|
// Packet should never be
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"Profile":{"Name":"alice","Ed25519PublicKey":"zjwuZF+0Op1KvzzOhqeHSbvXu0U5djPgTwKpAXU//7I=","Trusted":false,"Blocked":false,"Onion":"cgb6lexebisn6vpt","Contacts":{},"Ed25519PrivateKey":"2lHp48zadAzBVtVEjviPijgcVx4Djle0DJ4A5thMgSnOPC5kX7Q6nUq/PM6Gp4dJu9e7RTl2M+BPAqkBdT//sg==","OnionPrivateKey":{"N":147157004733998659199661812977704646877524902240221974034556056889821412558230773103992139173929507205012402066934484772524414251527816699766158278653365683529647303492589077999418406692030398982986134250109986822607454250083546220790801592188854654036322143842183727153411958706262869490046244589596814484207,"E":65537,"D":69165161921072992345197107361524577532819621949517943546186584988284007065187336984028409340899806375574057721100797022263294508290146601200802195055611675878933653952917037806259598451163914910457010547243954518097830998995842836616848924363378592869926704087730180750445525914371624821666069356678694847337,"Primes":[11763039476614882926577453250579526149631348162928663166529586678426982975960723170685888779927126269335313361708713193749880911707050422735813878237869163,12510117391559317555937472239718194296052069062441314136877360692383786524485845690577241127778855759836876789006855569037256017877501872991377132347214989],"Precomputed":{"Dp":1278485896393301662969180760545614916212583623976392995333784669887779418309782735627135599423545789652798222003618781437667298382430080124924886013829471,"Dq":8164208322581015485411991511554498528192425558091078408139596209976876415647033281749066985597474111543452099818777372899635929087702444540354302920340953,"Qinv":2274603529611181793096917499650639342446510507100204826932356686902897257746923946968375987728128727917910029163224849482139316568187669695882958003364055,"CRTValues":[]}},"Groups":{}}}
|
{"Profile":{"Name":"alice","Ed25519PublicKey":"W6naGYWbCXL80XXDgpKE7V+siJo6o9bbGyTvUnUKILE=","Trusted":false,"Blocked":false,"Onion":"ycnqegkvvkz5gsvs","Contacts":{"ycnqegkvvkz5gsvs":{"Name":"alice","Ed25519PublicKey":"W6naGYWbCXL80XXDgpKE7V+siJo6o9bbGyTvUnUKILE=","Trusted":false,"Blocked":false,"Onion":"ycnqegkvvkz5gsvs"}},"Ed25519PrivateKey":"KvmZ7aWoB5RVmkloWri+316/wfA/CyGn0pcYS34WN7xbqdoZhZsJcvzRdcOCkoTtX6yImjqj1tsbJO9SdQogsQ==","OnionPrivateKey":{"N":138578445730948217399235853380745007937830927545752074120752000142489179414948896312808960302192046016583916437398543221534462954288785032720766262144773721364963084161409242148120269561063898622509706451288561156808651123385134272354840396173775759747294322189173896763873375202344132814058552323894612271379,"E":65537,"D":66260191577658320344575654997009713119915009011301814160670837488213389782059578785391177198063535925899470611750930777881256864604625902701343234975183920374575910046121511808611558969522048550963186520107773724296757371157728176877458332331908465537431591104876047516596838295433500859876997952335609406401,"Primes":[13275044288223673053268894137348396823019805659935753695111177485542609507887379320301691989583038422110723796993226398199751492392227091252491529012654327,10439019465560768307866940717986678867283694780656194070584524560148251882718648675994212565947390303977286149767780033032504469772695284927646948863796677],"Precomputed":{"Dp":5339834254331941789069908406682918611600288643166857483719438803635721376427469881773550883155897567537160699091130724771378745478796566513544740952921149,"Dq":6887615251084321564924765912178799470531367716104711656104878564862147484026383377308478349087250013492864037140982596218296928108413824489317205789392741,"Qinv":2094027238282876917718460475743388991144681081516298156361631729627217848136364125434066438976020876853031955793909233935538173930073006469995174800032241,"CRTValues":[]}},"Groups":{}}}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"github.com/s-rah/go-ricochet/wire/control"
|
"github.com/s-rah/go-ricochet/wire/control"
|
||||||
"io"
|
"io"
|
||||||
|
//"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Guard implements a spam protection mechanism for Cwtch Servers.
|
// Guard implements a spam protection mechanism for Cwtch Servers.
|
||||||
|
@ -69,6 +70,7 @@ func (sg *Guard) SolveChallenge(challenge []byte, message []byte) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("[SOLVED] %x\n",sha256.Sum256(solve))
|
||||||
return spamguard[:]
|
return spamguard[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,20 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
cwtchserver "git.mascherari.press/cwtch/server"
|
cwtchserver "git.mascherari.press/cwtch/server"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const privateKeyFile = "./private_key"
|
const privateKeyFile = "./private_key"
|
||||||
|
|
||||||
func checkAndGenPrivateKey(privateKeyFile string) {
|
func checkAndGenPrivateKey(privateKeyFile string) {
|
||||||
if _, err := os.Stat(privateKeyFile); os.IsNotExist(err) {
|
if _, err := os.Stat(privateKeyFile); os.IsNotExist(err) {
|
||||||
log.Printf("no private key found!")
|
log.Printf("no private key found!")
|
||||||
log.Printf("generating new private key...")
|
log.Printf("generating new private key...")
|
||||||
pk, pk_err := utils.GeneratePrivateKey()
|
pk, pk_err := utils.GeneratePrivateKey()
|
||||||
if pk_err != nil {
|
if pk_err != nil {
|
||||||
log.Fatalf("error generating new private key: %v\n", err)
|
log.Fatalf("error generating new private key: %v\n", err)
|
||||||
}
|
}
|
||||||
err := ioutil.WriteFile(privateKeyFile, []byte(utils.PrivateKeyToString(pk)), 0400)
|
err := ioutil.WriteFile(privateKeyFile, []byte(utils.PrivateKeyToString(pk)), 0400)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/s-rah/go-ricochet/channels"
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
"github.com/s-rah/go-ricochet/wire/control"
|
"github.com/s-rah/go-ricochet/wire/control"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CwtchChannel implements the ChannelHandler interface for a channel of
|
// CwtchChannel implements the ChannelHandler interface for a channel of
|
||||||
|
@ -92,8 +93,12 @@ func (cc *CwtchServerSendChannel) Packet(data []byte) {
|
||||||
ok := cc.spamguard.ValidateChallenge(gm.GetCiphertext(), gm.GetSpamguard())
|
ok := cc.spamguard.ValidateChallenge(gm.GetCiphertext(), gm.GetSpamguard())
|
||||||
if ok {
|
if ok {
|
||||||
cc.Handler.HandleGroupMessage(gm)
|
cc.Handler.HandleGroupMessage(gm)
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Failed to validate spamguard %v\n", gm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Failed to decode packet on SEND channel %v\n", err)
|
||||||
}
|
}
|
||||||
cc.channel.CloseChannel()
|
cc.channel.CloseChannel()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/s-rah/go-ricochet/channels"
|
"github.com/s-rah/go-ricochet/channels"
|
||||||
"github.com/s-rah/go-ricochet/utils"
|
"github.com/s-rah/go-ricochet/utils"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.mascherari.press/cwtch/protocol"
|
"git.mascherari.press/cwtch/protocol"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -61,7 +62,10 @@ func (ms *MessageStore) FetchMessages() (messages []*protocol.GroupMessage) {
|
||||||
func (ms *MessageStore) AddMessage(gm protocol.GroupMessage) {
|
func (ms *MessageStore) AddMessage(gm protocol.GroupMessage) {
|
||||||
ms.lock.Lock()
|
ms.lock.Lock()
|
||||||
ms.messages = append(ms.messages, &gm)
|
ms.messages = append(ms.messages, &gm)
|
||||||
s, _ := json.Marshal(gm)
|
s, err := json.Marshal(gm)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to unmarshal group message %v\n", err)
|
||||||
|
}
|
||||||
fmt.Fprintf(ms.file, "%s\n", s)
|
fmt.Fprintf(ms.file, "%s\n", s)
|
||||||
ms.lock.Unlock()
|
ms.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue