From f1d0a8e9000f9cd86946e858823e3085d2864806 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 2 Apr 2018 14:10:29 -0700 Subject: [PATCH] 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 --- attacks.md | 49 +++++++++++++ model/group.go | 15 ++-- model/message.go | 69 +++++++++++++++++-- model/message_test.go | 58 ++++++++++++++++ model/profile.go | 26 ++++--- model/profile_test | 2 +- model/threat_model_test.go | 23 +++++++ peer/connections/connectsmanager_test.go | 10 +++ peer/test_profile | 2 +- protocol/group_message.pb.go | 66 ++++++++++-------- protocol/group_message.proto | 1 + protocol/spam/spamguard.go | 18 ++--- protocol/spam/spamguard_test.go | 36 ++++++++++ .../cwtch_peer_server_intergration_test.go | 2 +- 14 files changed, 315 insertions(+), 62 deletions(-) create mode 100644 model/message_test.go create mode 100644 model/threat_model_test.go create mode 100644 peer/connections/connectsmanager_test.go diff --git a/attacks.md b/attacks.md index e69de29..004aeb9 100644 --- a/attacks.md +++ b/attacks.md @@ -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 + diff --git a/model/group.go b/model/group.go index 28c6b1e..9a9969c 100644 --- a/model/group.go +++ b/model/group.go @@ -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 } diff --git a/model/message.go b/model/message.go index ad3d902..e9ee555 100644 --- a/model/message.go +++ b/model/message.go @@ -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() } diff --git a/model/message_test.go b/model/message_test.go new file mode 100644 index 0000000..085ce78 --- /dev/null +++ b/model/message_test.go @@ -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) + } +} diff --git a/model/profile.go b/model/profile.go index 7b7b4c7..8a6e109 100644 --- a/model/profile.go +++ b/model/profile.go @@ -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 diff --git a/model/profile_test b/model/profile_test index 0fba87b..9bb01a0 100644 --- a/model/profile_test +++ b/model/profile_test @@ -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":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file diff --git a/model/threat_model_test.go b/model/threat_model_test.go new file mode 100644 index 0000000..b1d60c0 --- /dev/null +++ b/model/threat_model_test.go @@ -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") +} diff --git a/peer/connections/connectsmanager_test.go b/peer/connections/connectsmanager_test.go new file mode 100644 index 0000000..87a1c79 --- /dev/null +++ b/peer/connections/connectsmanager_test.go @@ -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() +} diff --git a/peer/test_profile b/peer/test_profile index 8ff825d..76663c5 100644 --- a/peer/test_profile +++ b/peer/test_profile @@ -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":{}}} \ No newline at end of file +{"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":{}}} \ No newline at end of file diff --git a/protocol/group_message.pb.go b/protocol/group_message.pb.go index a4543b2..51a3620 100644 --- a/protocol/group_message.pb.go +++ b/protocol/group_message.pb.go @@ -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, } diff --git a/protocol/group_message.proto b/protocol/group_message.proto index 7c5231e..3a8e13c 100644 --- a/protocol/group_message.proto +++ b/protocol/group_message.proto @@ -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; } diff --git a/protocol/spam/spamguard.go b/protocol/spam/spamguard.go index d83dd3c..f5c0eaf 100644 --- a/protocol/spam/spamguard.go +++ b/protocol/spam/spamguard.go @@ -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 diff --git a/protocol/spam/spamguard_test.go b/protocol/spam/spamguard_test.go index 0470422..1672fe1 100644 --- a/protocol/spam/spamguard_test.go +++ b/protocol/spam/spamguard_test.go @@ -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") +} diff --git a/testing/cwtch_peer_server_intergration_test.go b/testing/cwtch_peer_server_intergration_test.go index 1c35101..697150d 100644 --- a/testing/cwtch_peer_server_intergration_test.go +++ b/testing/cwtch_peer_server_intergration_test.go @@ -1,9 +1,9 @@ package testing import ( + "git.mascherari.press/cwtch/peer" "testing" "time" - "git.mascherari.press/cwtch/peer" ) func TestCwtchPeerIntegration(t *testing.T) {