Browse Source

Fixing up messaging to use a proper ordering of messages based on timeline and previous hash.

This isn't perfect, but under normal conditions should prevent malicous server reordering. Still need a second order function
pull/8/head
Sarah Jamie Lewis 2 years ago
parent
commit
f1d0a8e900
14 changed files with 315 additions and 62 deletions
  1. +49
    -0
      attacks.md
  2. +8
    -7
      model/group.go
  3. +64
    -5
      model/message.go
  4. +58
    -0
      model/message_test.go
  5. +16
    -10
      model/profile.go
  6. +1
    -1
      model/profile_test
  7. +23
    -0
      model/threat_model_test.go
  8. +10
    -0
      peer/connections/connectsmanager_test.go
  9. +1
    -1
      peer/test_profile
  10. +38
    -28
      protocol/group_message.pb.go
  11. +1
    -0
      protocol/group_message.proto
  12. +9
    -9
      protocol/spam/spamguard.go
  13. +36
    -0
      protocol/spam/spamguard_test.go
  14. +1
    -1
      testing/cwtch_peer_server_intergration_test.go

+ 49
- 0
attacks.md View File

@@ -0,0 +1,49 @@
# Attacks On Cwtch


## Server Censorship

Servers must keep things for as long as possible. This messes with bandwidth requirements, means syncing takes really long.

We could improve fetch to say something like...fetch messages sent within the last day to improve that.

We should already restrict length to 1kb.

Force servers to keep things forever? Have clients do checks? Is this potentially creating a bigger issue down the line?

This means that secure key rotation is essential! We can't just rely on kdf because the rotation rate is known. Send secret
salt on invite!

## Subgroup Attack

* Alice invites Bob and Carol to her Group
* Carol invites Eve to the group, pretending that she is the Owner.
* After some time passes Carol send Eve a group key update
* Carol can now selectively reencrypt messages from Alice and Bob to Carol under the new group key.

Defenses
--------

Eve rejects the initial group invitation because the signed group id doesn't match Carol
Carol can create a new group with all the sam parameters and sign it herself though.
However Carol will notice messages she can decrypt but are intended for another group, and if she tries to send
a message to the group, Alice and Bob will discover their group has been compromised.

## Key Rotation Attacks

* Alice invites Bob and Carol to a new Group
* Alice invites Eve
* Alice rotates the key (using a kdf), sends the new key to the Group
* Alice sends invite to Eve with new Key

Now there is a window where Bob and Carol send messages without receiving the new Key. There is also a possibility that
Bob or Carol miss the Key rotation message by being offline during the entire Server buffer period.

Alice should then technically rebroadcast the key rotation message, along with the iteration, until she received confirmation from Bob and Carol?

RotateKey, 1
AckRotateKey

Invite
mAckInvite


+ 8
- 7
model/group.go View File

@@ -19,7 +19,7 @@ type Group struct {
SignedGroupID []byte
GroupKey [32]byte
GroupServer string
Timeline []Message
Timeline Timeline
Accepted bool
Owner string
}
@@ -66,13 +66,14 @@ func (g *Group) Invite() []byte {

func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified bool) *Message {
timelineMessage := &Message{
Message: message.GetText(),
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
Signature: message.GetSignature(),
Verified: verified,
PeerID: message.GetOnion(),
Message: message.GetText(),
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
Signature: message.GetSignature(),
Verified: verified,
PeerID: message.GetOnion(),
PreviousMessageSig: message.GetPreviousMessageSig(),
}
g.Timeline = append(g.Timeline, *timelineMessage)
g.Timeline.Insert(timelineMessage)
return timelineMessage
}



+ 64
- 5
model/message.go View File

@@ -1,14 +1,73 @@
package model

import (
"log"
"sync"
"time"
)

type Timeline struct {
Messages []Message
lock sync.Mutex
}

// Message is a local representation of a given message sent over a group chat channel.
type Message struct {
Timestamp time.Time
PeerID string
Message string
Signature []byte
Verified bool
Timestamp time.Time
PeerID string
Message string
Signature []byte
Verified bool
PreviousMessageSig []byte
}

func (m *Message) IsBefore(message *Message) bool {
if m.Timestamp.Before(message.Timestamp) {
return true
}
return false
}

func compareSignatures(a []byte, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func (t *Timeline) Insert(mi *Message) {
t.lock.Lock()
insert := false
place := len(t.Messages)
for i, m := range t.Messages {

if compareSignatures(m.Signature, mi.PreviousMessageSig) {
log.Printf("comp %d %v %v %x %x", place, mi.Message, m.Message, m.Signature[0:3], mi.PreviousMessageSig[0:3])
insert = true
place = i + 1
break
}

if mi.IsBefore(&m) {
place = i
insert = true
break
}
}

if insert == false {
t.Messages = append(t.Messages, *mi)
} else {
temp := make([]Message, len(t.Messages)+1)
copy(temp[0:place], t.Messages[0:place])
temp[place] = *mi
copy(temp[place+1:], t.Messages[place:])
t.Messages = temp
}
t.lock.Unlock()
}

+ 58
- 0
model/message_test.go View File

@@ -0,0 +1,58 @@
package model

import (
"git.mascherari.press/cwtch/protocol"
"github.com/golang/protobuf/proto"
"testing"
"time"
)

func TestMessageInsert(t *testing.T) {
timeline := new(Timeline)

// Setup the Group
sarah := GenerateNewProfile("Sarah")
alice := GenerateNewProfile("Alice")
sarah.AddContact(alice.Onion, alice.PublicProfile)
alice.AddContact(sarah.Onion, sarah.PublicProfile)

gid, invite := alice.StartGroup("aaa.onion")
gci := &protocol.CwtchPeerPacket{}
proto.Unmarshal(invite, gci)
sarah.ProcessInvite(gci.GetGroupChatInvite(), alice.Onion)

group := alice.GetGroupByGroupId(gid)

c1 := sarah.EncryptMessageToGroup("Hello World 1", group.GroupID)
alice.AttemptDecryption(c1)

c2 := alice.EncryptMessageToGroup("Hello World 2", group.GroupID)
alice.AttemptDecryption(c2)

c3 := alice.EncryptMessageToGroup("Hello World 3", group.GroupID)
alice.AttemptDecryption(c3)

time.Sleep(time.Second * 1)

c4 := alice.EncryptMessageToGroup("Hello World 4", group.GroupID)
alice.AttemptDecryption(c4)

c5 := alice.EncryptMessageToGroup("Hello World 5", group.GroupID)

_, m1 := sarah.AttemptDecryption(c1)
_, m2 := sarah.AttemptDecryption(c2)
_, m3 := sarah.AttemptDecryption(c3)
_, m4 := sarah.AttemptDecryption(c4)
_, m5 := sarah.AttemptDecryption(c5)

// Now we simulate a client receiving these messages completely out of order
timeline.Insert(m1)
timeline.Insert(m5)
timeline.Insert(m4)
timeline.Insert(m3)
timeline.Insert(m2)

for i, m := range timeline.Messages {
t.Logf("Messages %v: %v %x %x", i, m.Message, m.Signature, m.PreviousMessageSig)
}
}

+ 16
- 10
model/profile.go View File

@@ -10,7 +10,7 @@ import (
"github.com/s-rah/go-ricochet/utils"
"golang.org/x/crypto/ed25519"
"io/ioutil"
"log"
// "log"
"strconv"
"time"
)
@@ -127,10 +127,9 @@ func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname stri
func (p *Profile) AddGroup(group *Group) {
existingGroup, exists := p.Groups[group.GroupID]
if !exists {
// owned := ed25519.Verify(p.Contacts[group.Owner].Ed25519PublicKey,[]byte(group.GroupID),group.SignedGroupID)
p.Groups[group.GroupID] = group
}

if exists && existingGroup.Owner == group.Owner {
} else if exists && existingGroup.Owner == group.Owner {
p.Groups[group.GroupID] = group
}

@@ -145,7 +144,7 @@ func (p *Profile) AddGroup(group *Group) {
func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
for _, group := range p.Groups {
success, dgm := group.DecryptMessage(ciphertext)
log.Printf("Decrypt Attempt %v %v", success, dgm)
//log.Printf("Decrypt Attempt %v %v", success, dgm)
if success {
verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
return true, group.AddMessage(dgm, verified)
@@ -160,12 +159,19 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphert
group := p.Groups[groupID]
timestamp := time.Now().Unix()
signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
var prevSig []byte
if len(group.Timeline.Messages) > 0 {
prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
} 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,
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


+ 1
- 1
model/profile_test View File

@@ -1 +1 @@
{"Name":"Sarah","Ed25519PublicKey":"UsCWNjTraCR3Z2dqtsW1A++ubJD5E5FnC+ACSIP/8hk=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"n89RBVJD3T2KAxOUFsBgJcyBcOtgu0eeGs464orKNT9SwJY2NOtoJHdnZ2q2xbUD765skPkTkWcL4AJIg//yGQ==","OnionPrivateKey":{"N":123805553263348528457396531577890431199849926799850215350792942327090732394534595405543245927983354464965037398489749312360344572108681888509184005620780768892052483022600381118025061152186760477945751128712525497823295804735134482165349819329437248988124357774675993218222587847682647131583478848460913040243,"E":65537,"D":36374504906934646313489635099749458363262131933581273121740667172866198517734465027903858894110494685794311535589363611540022197170341482875998871297559138745889655544439413518334368519721005053246504047688085489173938252813690494094575739926433903954150053038634347562274030322729843548978331756633920952393,"Primes":[11696469543379902568303131723781955090117870500969155291133497643805055498454292242391505499241962759128566231590491957401660779652639301001660955427807509,10584865185531250093066719901396357065936568829189765800518363959386090914453659853194245194887656726848216858880202829552571699371710179494672080022887527],"Precomputed":{"Dp":10160546066712868045776669547990150376665097357075773683255583172245687391587977961328574691812932875168964159645365177332406882316926439191991697109426877,"Dq":9841112737610664672944159437140589723997848725150538012987853468625559479370285520771957035184623948003655181733807144213151331097197684620433635655501209,"Qinv":1336220763953049451525874385731316715417274408983287245077551897537966825948439529869489565316701741855547495791046015721525313869844240508900847744658372,"CRTValues":[]}},"Onion":"ln6hn7x65lfagspl","Groups":{}}
{"Name":"Sarah","Ed25519PublicKey":"j8RkHDyZPCFwuXjVxGtw8Qgv1l45moymwACxEXjQJnI=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"/TkSp0/IJ07+jLw/UAkyFC3mgd5WIAyJ7dRSz7M9kVePxGQcPJk8IXC5eNXEa3DxCC/WXjmajKbAALEReNAmcg==","OnionPrivateKey":{"N":110864111714001498673773375154532122354477779797956408709787762036989130004412378714867422979598016165479959916442576967379813396145904497664600025217339110732358507374657769751998789538776326442350416409397455856063370247022405840568758973841985645812850695658944529625449004363327908735536586292681231570813,"E":65537,"D":29258368192095608908884817685777310347483828667553504814753332196953842753808024509091764161544277089249452778045849081096193791289493937647541419902636631139610918217176285511499379852356646785160340611490902468063399499748079623910235136509595342699975150851412254552332235937599936863279394875342688545217,"Primes":[10479919459766681018465636257798402760984111495664175755942255404420416305820714717114953497775485113275270637658830475401064210738386560406839990913034719,10578717912825421118563933365610407240275592456669407680773574905407145537212719150546363664213151033473733068380589365500463635725956703879386470742958627],"Precomputed":{"Dp":8284056755927383160071597514918996216378258022687682149573348508872845365565133068650383233169949895375519101941128107453074315841465367073200569590456743,"Dq":6372702186525895688861316344573277352428100007466136918640473889031754670020876025200580396770300788890901041238776082975392592557803541040300560979782513,"Qinv":10360189342388704857040075408428853576974943026521455545260795237873844460252519600072106320342798867873256125020528899224644053095341386063420556723083365,"CRTValues":[]}},"Onion":"p2c3insyliefym26","Groups":{}}

+ 23
- 0
model/threat_model_test.go View File

@@ -0,0 +1,23 @@
package model

import (
"testing"
)

// Nothing in this file constitutes a cryptographic proof, but acts to prevent aggressions 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")
}

// If a server reorders stuff, we will still be able to put things in the right order.
func TestTranscriptConsistency(t *testing.T) {
t.Fatalf("Failed")
}

+ 10
- 0
peer/connections/connectsmanager_test.go View File

@@ -0,0 +1,10 @@
package connections

import (
"testing"
)

func TestConnectionsManager(t *testing.T) {
// TODO We need to encapsulate connections behind a well defined interface for tesintg
NewConnectionsManager()
}

+ 1
- 1
peer/test_profile View File

@@ -1 +1 @@
{"Profile":{"Name":"alice","Ed25519PublicKey":"GQjDT/ADqudCIZq/7i4flbRaLzBnPCj2IYI8S1qIWGM=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"hbG3RyQQ+r1PpW1UnE8B5kf54zDfHOvBz4bMgdW2IsAZCMNP8AOq50Ihmr/uLh+VtFovMGc8KPYhgjxLWohYYw==","OnionPrivateKey":{"N":118949437147999046779871097106577144824161312908242780099796773587901402109754265146160871793580686203857212353483161141299741736678609148375472482302726670540249479081752805338231525747022348201429435662507924057367176907320212979720249273484193414490356905146774628953953268592916404780858351871034973364651,"E":65537,"D":94181992508763271685525597753290425592727080704359767797710520442647537373960641663479161362904853560650542536805082850652072851335729546948313816304847468284377523922190377615541996345142801491115074550379060687848886736978676340143503908396698667066436989395508107231468852742294096428057812643803322726345,"Primes":[11093306403644218894981011451014980780451441495773111118759223267959318051098530285527407597744590710343913726393148960031173198769212333898347445271695669,10722631541929052848558646606992517878067322185889508722580532907231300392570442727893142668003376876119846036715047467381256380482736518424207582333117279],"Precomputed":{"Dp":288432398672654363139106817714108476889226792633129092670792322636109037018354450257697217549731946387933976071134257410212843595262795321158796507972129,"Dq":2883332097340673182326762426644935571740855072431310743824659832219009213395012164024304640710186775224072610968297015695864644296309957187387433410997235,"Qinv":9556280274383976646018218885445548987752711277953338363729408513927401398507573496665859225463898071345877217574624168387272500232882404725411489440703861,"CRTValues":[]}},"Onion":"kuzx57bbs6q7nymu","Groups":{}}}
{"Profile":{"Name":"alice","Ed25519PublicKey":"HePVPZDgQV5CPsKsfk8k9APAbFAaHo3h5IgcOLk8JkU=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"vW4vk9yJONpqLoooQrJLT3BsiJjNseagLkQelqIQppgd49U9kOBBXkI+wqx+TyT0A8BsUBoejeHkiBw4uTwmRQ==","OnionPrivateKey":{"N":124180052987038276908488971306097118499288790640484988517147166769877815582841930618153187145182425589345556238768813257991033435204827844882770722270092969225417271833713672261037744190852387384632814869090061694598739251480895491455885307591459276013910000175922066726291096488800388268492421362423280138243,"E":65537,"D":8323892957749960334757343957576401751184455456973168661303195196912785813440111711026549142145450594534309299432555604351047650653139581039260444984245820156701213023813296520282033216231123406154310401988822683123845588539154050789818510723459725427699399566036015352152852003341066008723635142189324534393,"Primes":[10318605891039665378109893427013360413384828451813995161940801274879978602208412774582196558435137474501959569416200754358158605387475412222231908504565973,12034576598653904425712782466302887117485091173704960260057943271715893247294740735187656822073438435145528143604740414620929110675044452637939150841583991],"Precomputed":{"Dp":1751913083442915553995892155002176805769763434141543313958760635756740801482719821517251340551257681596400569585029308539347708960532812179330369805305485,"Dq":11627835564337130720737672022387833102726565807121862361526909743375403088411089504151133028477566391355801045353607466539915973361080989821621113410604413,"Qinv":5168969236254874132993321993214963606790484960035248152574500117234718152121266178902971986905125868496286012732445791508496771810043600157469938696477522,"CRTValues":[]}},"Onion":"lxjwzgx5jwl7otgt","Groups":{}}}

+ 38
- 28
protocol/group_message.pb.go View File

@@ -84,12 +84,13 @@ func (m *GroupMessage) GetSpamguard() []byte {
// 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"`
Signature []byte `protobuf:"bytes,4,req,name=signature" json:"signature,omitempty"`
SignedGroupId []byte `protobuf:"bytes,5,req,name=signed_group_id,json=signedGroupId" json:"signed_group_id,omitempty"`
XXX_unrecognized []byte `json:"-"`
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"`
Signature []byte `protobuf:"bytes,4,req,name=signature" json:"signature,omitempty"`
SignedGroupId []byte `protobuf:"bytes,5,req,name=signed_group_id,json=signedGroupId" json:"signed_group_id,omitempty"`
PreviousMessageSig []byte `protobuf:"bytes,6,req,name=previous_message_sig,json=previousMessageSig" json:"previous_message_sig,omitempty"`
XXX_unrecognized []byte `json:"-"`
}

func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} }
@@ -132,6 +133,13 @@ func (m *DecryptedGroupMessage) GetSignedGroupId() []byte {
return nil
}

func (m *DecryptedGroupMessage) GetPreviousMessageSig() []byte {
if m != nil {
return m.PreviousMessageSig
}
return nil
}

var E_ServerNonce = &proto.ExtensionDesc{
ExtendedType: (*control.ChannelResult)(nil),
ExtensionType: ([]byte)(nil),
@@ -152,26 +160,28 @@ func init() {
func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) }

var fileDescriptor2 = []byte{
// 332 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0xdb, 0x4a, 0xf3, 0x30,
0x1c, 0xa7, 0x3b, 0xc0, 0xb7, 0xff, 0xba, 0x7d, 0x18, 0xa7, 0x06, 0x11, 0x29, 0xbd, 0x90, 0x5d,
0xed, 0xc2, 0x4b, 0x87, 0x20, 0x4c, 0x14, 0x41, 0x45, 0xea, 0x03, 0x8c, 0x90, 0xfe, 0xd7, 0x16,
0xdb, 0xa4, 0x24, 0xa9, 0x87, 0x37, 0xf0, 0x45, 0x7c, 0x1b, 0x1f, 0x4a, 0x9a, 0xee, 0x90, 0x7a,
0xe1, 0x55, 0xf8, 0x9d, 0x43, 0x02, 0xfb, 0x89, 0x92, 0x55, 0xb9, 0x2c, 0x50, 0x6b, 0x96, 0xe0,
0xac, 0x54, 0xd2, 0x48, 0xf2, 0xcf, 0x1e, 0x5c, 0xe6, 0xc7, 0x93, 0x85, 0x14, 0x46, 0xc9, 0x7c,
0x91, 0x32, 0x21, 0x30, 0x6f, 0xf4, 0xf0, 0xdb, 0x83, 0xbd, 0xc5, 0x9b, 0xe1, 0xe9, 0x33, 0xaa,
0x57, 0x54, 0x4f, 0x8c, 0xbf, 0xa0, 0x21, 0x73, 0x18, 0xb5, 0xca, 0xa8, 0x17, 0x78, 0xd3, 0xe1,
0xf9, 0xe1, 0x6c, 0xd3, 0x36, 0xbb, 0xad, 0xe5, 0x87, 0x46, 0x8d, 0xfc, 0xc4, 0x41, 0x75, 0x78,
0x85, 0x86, 0xa7, 0xdb, 0x70, 0xe7, 0x77, 0xf8, 0xa6, 0x96, 0xb7, 0xe1, 0x95, 0x83, 0xc8, 0x25,
0x8c, 0x5b, 0xcb, 0x9a, 0x76, 0x83, 0xee, 0x1f, 0xd3, 0x23, 0x77, 0x5a, 0x87, 0x63, 0xf0, 0xdd,
0xf2, 0xf0, 0x1e, 0x7c, 0xd7, 0x4e, 0x4e, 0x01, 0x78, 0x56, 0xa6, 0xa8, 0x0c, 0xbe, 0x1b, 0xea,
0x05, 0x9d, 0xa9, 0x1f, 0x39, 0x0c, 0x39, 0x81, 0x81, 0x2e, 0x59, 0x91, 0x54, 0x4c, 0xc5, 0xb4,
0x63, 0xe5, 0x1d, 0x11, 0x7e, 0x79, 0x70, 0x70, 0x8d, 0x5c, 0x7d, 0x94, 0x06, 0xe3, 0x56, 0xef,
0x04, 0xfa, 0x52, 0x64, 0x52, 0xd8, 0xca, 0x41, 0xd4, 0x80, 0xba, 0xcd, 0x64, 0x05, 0x6a, 0xc3,
0x8a, 0xd2, 0xb6, 0xf5, 0xa3, 0x1d, 0x41, 0x08, 0xf4, 0xec, 0x2d, 0xba, 0x36, 0xd2, 0xdb, 0xee,
0x67, 0x89, 0x60, 0xa6, 0x52, 0x48, 0x7b, 0xeb, 0xfd, 0x0d, 0x41, 0xce, 0xe0, 0x7f, 0x0d, 0x30,
0x5e, 0x36, 0x6f, 0x94, 0xc5, 0xb4, 0x6f, 0x3d, 0xa3, 0x86, 0xb6, 0x57, 0xba, 0x8b, 0x2f, 0xe6,
0xe0, 0x6b, 0xfb, 0x9d, 0x4b, 0x21, 0x05, 0x47, 0x72, 0xb4, 0x7b, 0xbc, 0xf5, 0xef, 0x47, 0xa8,
0xab, 0xdc, 0xd0, 0xcf, 0xab, 0xc0, 0x9b, 0xfa, 0xd1, 0xb0, 0x71, 0x3f, 0xd6, 0xe6, 0x9f, 0x00,
0x00, 0x00, 0xff, 0xff, 0xbf, 0x17, 0x09, 0x79, 0x47, 0x02, 0x00, 0x00,
// 358 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xcd, 0x4a, 0xeb, 0x40,
0x14, 0x26, 0xfd, 0xb9, 0xdc, 0x9e, 0xa6, 0xbd, 0xdc, 0xb9, 0xbd, 0x3a, 0x88, 0x48, 0xc8, 0x42,
0xba, 0x2a, 0xe2, 0xd2, 0x22, 0x08, 0x15, 0x45, 0x50, 0x91, 0xf4, 0x01, 0x42, 0x48, 0x4e, 0x93,
0xc1, 0x64, 0x66, 0x98, 0x99, 0x54, 0x7d, 0x03, 0x1f, 0xce, 0xbd, 0xaf, 0x23, 0x99, 0x34, 0x6d,
0xea, 0xc2, 0xd5, 0x70, 0xbe, 0xdf, 0xc3, 0x61, 0xe0, 0x5f, 0xaa, 0x44, 0x29, 0xc3, 0x02, 0xb5,
0x8e, 0x52, 0x9c, 0x49, 0x25, 0x8c, 0x20, 0xbf, 0xed, 0x13, 0x8b, 0xfc, 0x68, 0xb2, 0x10, 0xdc,
0x28, 0x91, 0x2f, 0xb2, 0x88, 0x73, 0xcc, 0x6b, 0xde, 0xff, 0x70, 0xe0, 0xef, 0xe2, 0xc5, 0xc4,
0xd9, 0x12, 0xd5, 0x1a, 0xd5, 0x53, 0x14, 0x3f, 0xa3, 0x21, 0x73, 0x18, 0xed, 0x85, 0x51, 0xc7,
0x73, 0xa6, 0xc3, 0xf3, 0x83, 0x59, 0x93, 0x36, 0xbb, 0xad, 0xe8, 0x87, 0x9a, 0x0d, 0xdc, 0xb4,
0x35, 0x55, 0xe6, 0x15, 0x9a, 0x38, 0xdb, 0x9a, 0x3b, 0xdf, 0xcd, 0x37, 0x15, 0xbd, 0x35, 0xaf,
0x5a, 0x13, 0xb9, 0x84, 0xf1, 0x5e, 0xb3, 0xa6, 0x5d, 0xaf, 0xfb, 0x43, 0xf5, 0xa8, 0x5d, 0xad,
0xfd, 0x31, 0xb8, 0xed, 0x70, 0xff, 0x1e, 0xdc, 0xb6, 0x9c, 0x9c, 0x00, 0xc4, 0x4c, 0x66, 0xa8,
0x0c, 0xbe, 0x1a, 0xea, 0x78, 0x9d, 0xa9, 0x1b, 0xb4, 0x10, 0x72, 0x0c, 0x03, 0x2d, 0xa3, 0x22,
0x2d, 0x23, 0x95, 0xd0, 0x8e, 0xa5, 0x77, 0x80, 0xff, 0xe9, 0xc0, 0xff, 0x6b, 0x8c, 0xd5, 0x9b,
0x34, 0x98, 0xec, 0xe5, 0x4e, 0xa0, 0x2f, 0x38, 0x13, 0xdc, 0x46, 0x0e, 0x82, 0x7a, 0xa8, 0xd2,
0x0c, 0x2b, 0x50, 0x9b, 0xa8, 0x90, 0x36, 0xad, 0x1f, 0xec, 0x00, 0x42, 0xa0, 0x67, 0xb7, 0xe8,
0x5a, 0x4b, 0x6f, 0xdb, 0xcf, 0x52, 0x1e, 0x99, 0x52, 0x21, 0xed, 0x6d, 0xfa, 0x1b, 0x80, 0x9c,
0xc2, 0x9f, 0x6a, 0xc0, 0x24, 0xac, 0x6f, 0xc4, 0x12, 0xda, 0xb7, 0x9a, 0x51, 0x0d, 0xdb, 0x95,
0xee, 0x12, 0x72, 0x06, 0x13, 0xa9, 0x70, 0xcd, 0x44, 0xa9, 0x9b, 0x3b, 0x86, 0x9a, 0xa5, 0xf4,
0x97, 0x15, 0x93, 0x86, 0xdb, 0x2c, 0xbf, 0x64, 0xe9, 0xc5, 0x1c, 0x5c, 0x6d, 0x3f, 0x40, 0xc8,
0x05, 0x8f, 0x91, 0x1c, 0xee, 0xce, 0xbd, 0xf9, 0x2f, 0x01, 0xea, 0x32, 0x37, 0xf4, 0xfd, 0xca,
0x73, 0xa6, 0x6e, 0x30, 0xac, 0xd5, 0x8f, 0x95, 0xf8, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x54, 0x1a,
0x6d, 0xda, 0x79, 0x02, 0x00, 0x00,
}

+ 1
- 0
protocol/group_message.proto View File

@@ -30,4 +30,5 @@ message DecryptedGroupMessage {
required string text = 3;
required bytes signature = 4;
required bytes signed_group_id = 5;
required bytes previous_message_sig =6;
}

+ 9
- 9
protocol/spam/spamguard.go View File

@@ -16,6 +16,12 @@ type Guard struct {
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 {

@@ -25,9 +31,7 @@ func (sg *Guard) GenerateChallenge(channelID int32) []byte {
}

var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
utils.CheckError(err)
}
getRandomness(&nonce)
sg.nonce = nonce
err := proto.SetExtension(cr, protocol.E_ServerNonce, sg.nonce[:])
utils.CheckError(err)
@@ -50,9 +54,7 @@ func (sg *Guard) SolveChallenge(challenge []byte, message []byte) []byte {
solve := make([]byte, len(challenge)+len(message)+len(spamguard))
for !solved {

if _, err := io.ReadFull(rand.Reader, spamguard[:]); err != nil {
utils.CheckError(err)
}
getRandomness(&spamguard)

copy(solve[0:], challenge[:])
copy(solve[len(challenge):], message[:])
@@ -67,8 +69,6 @@ func (sg *Guard) SolveChallenge(challenge []byte, message []byte) []byte {
}
}
}

//log.Printf("Solved answer: %v %x %x\n", len(solve), solve, sum)
return spamguard[:]
}

@@ -82,7 +82,7 @@ func (sg *Guard) ValidateChallenge(message []byte, spamguard []byte) bool {
copy(solve[len(sg.nonce):], message[:])
copy(solve[len(sg.nonce)+len(message):], spamguard[:])
sum := sha256.Sum256(solve)
//log.Printf("Got answer: %v %x %x\n", len(solve), solve, sum)
for i := 0; i < sg.Difficulty; i++ {
if sum[i] != 0x00 {
return false


+ 36
- 0
protocol/spam/spamguard_test.go View File

@@ -31,3 +31,39 @@ func TestSpamGuard(t *testing.T) {
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")
}

+ 1
- 1
testing/cwtch_peer_server_intergration_test.go View File

@@ -1,9 +1,9 @@
package testing

import (
"git.mascherari.press/cwtch/peer"
"testing"
"time"
"git.mascherari.press/cwtch/peer"
)

func TestCwtchPeerIntegration(t *testing.T) {


Loading…
Cancel
Save