forked from cwtch.im/cwtch
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
This commit is contained in:
parent
5f44fb8ef8
commit
f1d0a8e900
49
attacks.md
49
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
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Group struct {
|
||||||
SignedGroupID []byte
|
SignedGroupID []byte
|
||||||
GroupKey [32]byte
|
GroupKey [32]byte
|
||||||
GroupServer string
|
GroupServer string
|
||||||
Timeline []Message
|
Timeline Timeline
|
||||||
Accepted bool
|
Accepted bool
|
||||||
Owner string
|
Owner string
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,14 @@ func (g *Group) Invite() []byte {
|
||||||
|
|
||||||
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified bool) *Message {
|
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified bool) *Message {
|
||||||
timelineMessage := &Message{
|
timelineMessage := &Message{
|
||||||
Message: message.GetText(),
|
Message: message.GetText(),
|
||||||
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
||||||
Signature: message.GetSignature(),
|
Signature: message.GetSignature(),
|
||||||
Verified: verified,
|
Verified: verified,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.GetOnion(),
|
||||||
|
PreviousMessageSig: message.GetPreviousMessageSig(),
|
||||||
}
|
}
|
||||||
g.Timeline = append(g.Timeline, *timelineMessage)
|
g.Timeline.Insert(timelineMessage)
|
||||||
return timelineMessage
|
return timelineMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,73 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Timeline struct {
|
||||||
|
Messages []Message
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
PeerID string
|
PeerID string
|
||||||
Message string
|
Message string
|
||||||
Signature []byte
|
Signature []byte
|
||||||
Verified bool
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"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"
|
// "log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -127,10 +127,9 @@ 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)
|
||||||
p.Groups[group.GroupID] = group
|
p.Groups[group.GroupID] = group
|
||||||
}
|
} else if exists && existingGroup.Owner == group.Owner {
|
||||||
|
|
||||||
if exists && existingGroup.Owner == group.Owner {
|
|
||||||
p.Groups[group.GroupID] = group
|
p.Groups[group.GroupID] = group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +144,7 @@ func (p *Profile) AddGroup(group *Group) {
|
||||||
func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
|
func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
|
||||||
for _, group := range p.Groups {
|
for _, group := range p.Groups {
|
||||||
success, dgm := group.DecryptMessage(ciphertext)
|
success, dgm := group.DecryptMessage(ciphertext)
|
||||||
log.Printf("Decrypt Attempt %v %v", success, dgm)
|
//log.Printf("Decrypt Attempt %v %v", success, dgm)
|
||||||
if success {
|
if success {
|
||||||
verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
|
verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
|
||||||
return true, group.AddMessage(dgm, verified)
|
return true, group.AddMessage(dgm, verified)
|
||||||
|
@ -160,12 +159,19 @@ func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphert
|
||||||
group := p.Groups[groupID]
|
group := p.Groups[groupID]
|
||||||
timestamp := time.Now().Unix()
|
timestamp := time.Now().Unix()
|
||||||
signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
|
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{
|
dm := &protocol.DecryptedGroupMessage{
|
||||||
Onion: proto.String(p.Onion),
|
Onion: proto.String(p.Onion),
|
||||||
Text: proto.String(message),
|
Text: proto.String(message),
|
||||||
SignedGroupId: group.SignedGroupID[:],
|
SignedGroupId: group.SignedGroupID[:],
|
||||||
Timestamp: proto.Int32(int32(timestamp)),
|
Timestamp: proto.Int32(int32(timestamp)),
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
|
PreviousMessageSig: prevSig,
|
||||||
}
|
}
|
||||||
ciphertext = group.EncryptMessage(dm)
|
ciphertext = group.EncryptMessage(dm)
|
||||||
return
|
return
|
||||||
|
|
|
@ -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":{}}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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 @@
|
||||||
{"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":{}}}
|
|
@ -84,12 +84,13 @@ func (m *GroupMessage) GetSpamguard() []byte {
|
||||||
// and is only ever sent when encrypted in the ciphertext parameter of
|
// and is only ever sent when encrypted in the ciphertext parameter of
|
||||||
// GroupMessage
|
// GroupMessage
|
||||||
type DecryptedGroupMessage struct {
|
type DecryptedGroupMessage struct {
|
||||||
Onion *string `protobuf:"bytes,1,req,name=onion" json:"onion,omitempty"`
|
Onion *string `protobuf:"bytes,1,req,name=onion" json:"onion,omitempty"`
|
||||||
Timestamp *int32 `protobuf:"varint,2,req,name=timestamp" json:"timestamp,omitempty"`
|
Timestamp *int32 `protobuf:"varint,2,req,name=timestamp" json:"timestamp,omitempty"`
|
||||||
Text *string `protobuf:"bytes,3,req,name=text" json:"text,omitempty"`
|
Text *string `protobuf:"bytes,3,req,name=text" json:"text,omitempty"`
|
||||||
Signature []byte `protobuf:"bytes,4,req,name=signature" json:"signature,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"`
|
SignedGroupId []byte `protobuf:"bytes,5,req,name=signed_group_id,json=signedGroupId" json:"signed_group_id,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
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{} }
|
func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} }
|
||||||
|
@ -132,6 +133,13 @@ func (m *DecryptedGroupMessage) GetSignedGroupId() []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *DecryptedGroupMessage) GetPreviousMessageSig() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.PreviousMessageSig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var E_ServerNonce = &proto.ExtensionDesc{
|
var E_ServerNonce = &proto.ExtensionDesc{
|
||||||
ExtendedType: (*control.ChannelResult)(nil),
|
ExtendedType: (*control.ChannelResult)(nil),
|
||||||
ExtensionType: ([]byte)(nil),
|
ExtensionType: ([]byte)(nil),
|
||||||
|
@ -152,26 +160,28 @@ func init() {
|
||||||
func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) }
|
func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) }
|
||||||
|
|
||||||
var fileDescriptor2 = []byte{
|
var fileDescriptor2 = []byte{
|
||||||
// 332 bytes of a gzipped FileDescriptorProto
|
// 358 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0xdb, 0x4a, 0xf3, 0x30,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xcd, 0x4a, 0xeb, 0x40,
|
||||||
0x1c, 0xa7, 0x3b, 0xc0, 0xb7, 0xff, 0xba, 0x7d, 0x18, 0xa7, 0x06, 0x11, 0x29, 0xbd, 0x90, 0x5d,
|
0x14, 0x26, 0xfd, 0xb9, 0xdc, 0x9e, 0xa6, 0xbd, 0xdc, 0xb9, 0xbd, 0x3a, 0x88, 0x48, 0xc8, 0x42,
|
||||||
0xed, 0xc2, 0x4b, 0x87, 0x20, 0x4c, 0x14, 0x41, 0x45, 0xea, 0x03, 0x8c, 0x90, 0xfe, 0xd7, 0x16,
|
0xba, 0x2a, 0xe2, 0xd2, 0x22, 0x08, 0x15, 0x45, 0x50, 0x91, 0xf4, 0x01, 0x42, 0x48, 0x4e, 0x93,
|
||||||
0xdb, 0xa4, 0x24, 0xa9, 0x87, 0x37, 0xf0, 0x45, 0x7c, 0x1b, 0x1f, 0x4a, 0x9a, 0xee, 0x90, 0x7a,
|
0xc1, 0x64, 0x66, 0x98, 0x99, 0x54, 0x7d, 0x03, 0x1f, 0xce, 0xbd, 0xaf, 0x23, 0x99, 0x34, 0x6d,
|
||||||
0xe1, 0x55, 0xf8, 0x9d, 0x43, 0x02, 0xfb, 0x89, 0x92, 0x55, 0xb9, 0x2c, 0x50, 0x6b, 0x96, 0xe0,
|
0xea, 0xc2, 0xd5, 0x70, 0xbe, 0xdf, 0xc3, 0x61, 0xe0, 0x5f, 0xaa, 0x44, 0x29, 0xc3, 0x02, 0xb5,
|
||||||
0xac, 0x54, 0xd2, 0x48, 0xf2, 0xcf, 0x1e, 0x5c, 0xe6, 0xc7, 0x93, 0x85, 0x14, 0x46, 0xc9, 0x7c,
|
0x8e, 0x52, 0x9c, 0x49, 0x25, 0x8c, 0x20, 0xbf, 0xed, 0x13, 0x8b, 0xfc, 0x68, 0xb2, 0x10, 0xdc,
|
||||||
0x91, 0x32, 0x21, 0x30, 0x6f, 0xf4, 0xf0, 0xdb, 0x83, 0xbd, 0xc5, 0x9b, 0xe1, 0xe9, 0x33, 0xaa,
|
0x28, 0x91, 0x2f, 0xb2, 0x88, 0x73, 0xcc, 0x6b, 0xde, 0xff, 0x70, 0xe0, 0xef, 0xe2, 0xc5, 0xc4,
|
||||||
0x57, 0x54, 0x4f, 0x8c, 0xbf, 0xa0, 0x21, 0x73, 0x18, 0xb5, 0xca, 0xa8, 0x17, 0x78, 0xd3, 0xe1,
|
0xd9, 0x12, 0xd5, 0x1a, 0xd5, 0x53, 0x14, 0x3f, 0xa3, 0x21, 0x73, 0x18, 0xed, 0x85, 0x51, 0xc7,
|
||||||
0xf9, 0xe1, 0x6c, 0xd3, 0x36, 0xbb, 0xad, 0xe5, 0x87, 0x46, 0x8d, 0xfc, 0xc4, 0x41, 0x75, 0x78,
|
0x73, 0xa6, 0xc3, 0xf3, 0x83, 0x59, 0x93, 0x36, 0xbb, 0xad, 0xe8, 0x87, 0x9a, 0x0d, 0xdc, 0xb4,
|
||||||
0x85, 0x86, 0xa7, 0xdb, 0x70, 0xe7, 0x77, 0xf8, 0xa6, 0x96, 0xb7, 0xe1, 0x95, 0x83, 0xc8, 0x25,
|
0x35, 0x55, 0xe6, 0x15, 0x9a, 0x38, 0xdb, 0x9a, 0x3b, 0xdf, 0xcd, 0x37, 0x15, 0xbd, 0x35, 0xaf,
|
||||||
0x8c, 0x5b, 0xcb, 0x9a, 0x76, 0x83, 0xee, 0x1f, 0xd3, 0x23, 0x77, 0x5a, 0x87, 0x63, 0xf0, 0xdd,
|
0x5a, 0x13, 0xb9, 0x84, 0xf1, 0x5e, 0xb3, 0xa6, 0x5d, 0xaf, 0xfb, 0x43, 0xf5, 0xa8, 0x5d, 0xad,
|
||||||
0xf2, 0xf0, 0x1e, 0x7c, 0xd7, 0x4e, 0x4e, 0x01, 0x78, 0x56, 0xa6, 0xa8, 0x0c, 0xbe, 0x1b, 0xea,
|
0xfd, 0x31, 0xb8, 0xed, 0x70, 0xff, 0x1e, 0xdc, 0xb6, 0x9c, 0x9c, 0x00, 0xc4, 0x4c, 0x66, 0xa8,
|
||||||
0x05, 0x9d, 0xa9, 0x1f, 0x39, 0x0c, 0x39, 0x81, 0x81, 0x2e, 0x59, 0x91, 0x54, 0x4c, 0xc5, 0xb4,
|
0x0c, 0xbe, 0x1a, 0xea, 0x78, 0x9d, 0xa9, 0x1b, 0xb4, 0x10, 0x72, 0x0c, 0x03, 0x2d, 0xa3, 0x22,
|
||||||
0x63, 0xe5, 0x1d, 0x11, 0x7e, 0x79, 0x70, 0x70, 0x8d, 0x5c, 0x7d, 0x94, 0x06, 0xe3, 0x56, 0xef,
|
0x2d, 0x23, 0x95, 0xd0, 0x8e, 0xa5, 0x77, 0x80, 0xff, 0xe9, 0xc0, 0xff, 0x6b, 0x8c, 0xd5, 0x9b,
|
||||||
0x04, 0xfa, 0x52, 0x64, 0x52, 0xd8, 0xca, 0x41, 0xd4, 0x80, 0xba, 0xcd, 0x64, 0x05, 0x6a, 0xc3,
|
0x34, 0x98, 0xec, 0xe5, 0x4e, 0xa0, 0x2f, 0x38, 0x13, 0xdc, 0x46, 0x0e, 0x82, 0x7a, 0xa8, 0xd2,
|
||||||
0x8a, 0xd2, 0xb6, 0xf5, 0xa3, 0x1d, 0x41, 0x08, 0xf4, 0xec, 0x2d, 0xba, 0x36, 0xd2, 0xdb, 0xee,
|
0x0c, 0x2b, 0x50, 0x9b, 0xa8, 0x90, 0x36, 0xad, 0x1f, 0xec, 0x00, 0x42, 0xa0, 0x67, 0xb7, 0xe8,
|
||||||
0x67, 0x89, 0x60, 0xa6, 0x52, 0x48, 0x7b, 0xeb, 0xfd, 0x0d, 0x41, 0xce, 0xe0, 0x7f, 0x0d, 0x30,
|
0x5a, 0x4b, 0x6f, 0xdb, 0xcf, 0x52, 0x1e, 0x99, 0x52, 0x21, 0xed, 0x6d, 0xfa, 0x1b, 0x80, 0x9c,
|
||||||
0x5e, 0x36, 0x6f, 0x94, 0xc5, 0xb4, 0x6f, 0x3d, 0xa3, 0x86, 0xb6, 0x57, 0xba, 0x8b, 0x2f, 0xe6,
|
0xc2, 0x9f, 0x6a, 0xc0, 0x24, 0xac, 0x6f, 0xc4, 0x12, 0xda, 0xb7, 0x9a, 0x51, 0x0d, 0xdb, 0x95,
|
||||||
0xe0, 0x6b, 0xfb, 0x9d, 0x4b, 0x21, 0x05, 0x47, 0x72, 0xb4, 0x7b, 0xbc, 0xf5, 0xef, 0x47, 0xa8,
|
0xee, 0x12, 0x72, 0x06, 0x13, 0xa9, 0x70, 0xcd, 0x44, 0xa9, 0x9b, 0x3b, 0x86, 0x9a, 0xa5, 0xf4,
|
||||||
0xab, 0xdc, 0xd0, 0xcf, 0xab, 0xc0, 0x9b, 0xfa, 0xd1, 0xb0, 0x71, 0x3f, 0xd6, 0xe6, 0x9f, 0x00,
|
0x97, 0x15, 0x93, 0x86, 0xdb, 0x2c, 0xbf, 0x64, 0xe9, 0xc5, 0x1c, 0x5c, 0x6d, 0x3f, 0x40, 0xc8,
|
||||||
0x00, 0x00, 0xff, 0xff, 0xbf, 0x17, 0x09, 0x79, 0x47, 0x02, 0x00, 0x00,
|
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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,5 @@ message DecryptedGroupMessage {
|
||||||
required string text = 3;
|
required string text = 3;
|
||||||
required bytes signature = 4;
|
required bytes signature = 4;
|
||||||
required bytes signed_group_id = 5;
|
required bytes signed_group_id = 5;
|
||||||
|
required bytes previous_message_sig =6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,12 @@ type Guard struct {
|
||||||
nonce [24]byte
|
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
|
//GenerateChallenge returns a channel result packet with a spamguard challenge nonce
|
||||||
func (sg *Guard) GenerateChallenge(channelID int32) []byte {
|
func (sg *Guard) GenerateChallenge(channelID int32) []byte {
|
||||||
|
|
||||||
|
@ -25,9 +31,7 @@ func (sg *Guard) GenerateChallenge(channelID int32) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
var nonce [24]byte
|
var nonce [24]byte
|
||||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
getRandomness(&nonce)
|
||||||
utils.CheckError(err)
|
|
||||||
}
|
|
||||||
sg.nonce = nonce
|
sg.nonce = nonce
|
||||||
err := proto.SetExtension(cr, protocol.E_ServerNonce, sg.nonce[:])
|
err := proto.SetExtension(cr, protocol.E_ServerNonce, sg.nonce[:])
|
||||||
utils.CheckError(err)
|
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))
|
solve := make([]byte, len(challenge)+len(message)+len(spamguard))
|
||||||
for !solved {
|
for !solved {
|
||||||
|
|
||||||
if _, err := io.ReadFull(rand.Reader, spamguard[:]); err != nil {
|
getRandomness(&spamguard)
|
||||||
utils.CheckError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(solve[0:], challenge[:])
|
copy(solve[0:], challenge[:])
|
||||||
copy(solve[len(challenge):], message[:])
|
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[:]
|
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):], message[:])
|
||||||
copy(solve[len(sg.nonce)+len(message):], spamguard[:])
|
copy(solve[len(sg.nonce)+len(message):], spamguard[:])
|
||||||
sum := sha256.Sum256(solve)
|
sum := sha256.Sum256(solve)
|
||||||
//log.Printf("Got answer: %v %x %x\n", len(solve), solve, sum)
|
|
||||||
for i := 0; i < sg.Difficulty; i++ {
|
for i := 0; i < sg.Difficulty; i++ {
|
||||||
if sum[i] != 0x00 {
|
if sum[i] != 0x00 {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -31,3 +31,39 @@ func TestSpamGuard(t *testing.T) {
|
||||||
t.Errorf("Failed SpamGaurd")
|
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,9 +1,9 @@
|
||||||
package testing
|
package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.mascherari.press/cwtch/peer"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"git.mascherari.press/cwtch/peer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCwtchPeerIntegration(t *testing.T) {
|
func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue