forkad från cwtch.im/cwtch
Introducing fix for group owner impersonation bug
This commit is contained in:
förälder
9a4693c223
incheckning
c29186979f
|
@ -5,16 +5,22 @@ import (
|
|||
"fmt"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"io"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"git.mascherari.press/cwtch/protocol"
|
||||
"github.com/s-rah/go-ricochet/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
//Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
|
||||
// tied to a server under a given group key. Each group has a set of messages.
|
||||
type Group struct {
|
||||
GroupID string
|
||||
GroupKey [32]byte
|
||||
GroupServer string
|
||||
Timeline []Message
|
||||
GroupID string
|
||||
SignedGroupID []byte
|
||||
GroupKey [32]byte
|
||||
GroupServer string
|
||||
Timeline []Message
|
||||
Accepted bool
|
||||
Owner string
|
||||
}
|
||||
|
||||
// NewGroup initializes a new group associated with a given CwtchServer
|
||||
|
@ -33,9 +39,39 @@ func NewGroup(server string) *Group {
|
|||
panic(err)
|
||||
}
|
||||
copy(group.GroupKey[:], groupKey[:])
|
||||
group.Owner = "self"
|
||||
return group
|
||||
}
|
||||
|
||||
func (g *Group) SignGroup(signature []byte) {
|
||||
g.SignedGroupID = signature
|
||||
}
|
||||
|
||||
func (g *Group) Invite() []byte {
|
||||
gci := &protocol.GroupChatInvite{
|
||||
GroupName: g.GroupID,
|
||||
GroupSharedKey: g.GroupKey[:],
|
||||
ServerHost: g.GroupServer,
|
||||
}
|
||||
cp := &protocol.CwtchPeerPacket{
|
||||
GroupChatInvite: gci,
|
||||
}
|
||||
invite, err := proto.Marshal(cp)
|
||||
utils.CheckError(err)
|
||||
return invite
|
||||
}
|
||||
|
||||
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, verified bool) {
|
||||
timelineMessage := Message{
|
||||
Message: message.GetText(),
|
||||
Timestamp: time.Unix(int64(message.GetTimestamp()),0),
|
||||
Signature: message.GetSignature(),
|
||||
Verified:verified,
|
||||
PeerID: message.GetOnion(),
|
||||
}
|
||||
g.Timeline = append(g.Timeline, timelineMessage)
|
||||
}
|
||||
|
||||
// AddMember ...
|
||||
func (g *Group) AddMember() {
|
||||
// TODO: Rotate Key
|
||||
|
@ -47,23 +83,29 @@ func (g *Group) RemoveMember() {
|
|||
}
|
||||
|
||||
//EncryptMessage takes a message and encrypts the message under the group key.
|
||||
func (g *Group) EncryptMessage(message string) []byte {
|
||||
func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) []byte {
|
||||
var nonce [24]byte
|
||||
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
encrypted := secretbox.Seal(nonce[:], []byte(message), &nonce, &g.GroupKey)
|
||||
wire,err := proto.Marshal(message)
|
||||
utils.CheckError(err)
|
||||
encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
|
||||
return encrypted
|
||||
}
|
||||
|
||||
// DecryptMessage takes a ciphertext and returns true and the decrypted message if the
|
||||
// cipher text can be successfully decrypted,else false.
|
||||
func (g *Group) DecryptMessage(ciphertext []byte) (bool, string) {
|
||||
func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) {
|
||||
var decryptNonce [24]byte
|
||||
copy(decryptNonce[:], ciphertext[:24])
|
||||
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
|
||||
if ok {
|
||||
return true, string(decrypted)
|
||||
dm := &protocol.DecryptedGroupMessage{}
|
||||
err := proto.Unmarshal(decrypted, dm)
|
||||
if err == nil {
|
||||
return true, dm
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -2,13 +2,24 @@ package model
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"git.mascherari.press/cwtch/protocol"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
g := NewGroup("server.onion")
|
||||
encMessage := g.EncryptMessage("Hello World")
|
||||
dgm := &protocol.DecryptedGroupMessage{
|
||||
Onion: proto.String("onion"),
|
||||
Text: proto.String("Hello World!"),
|
||||
Timestamp: proto.Int32(int32(time.Now().Unix())),
|
||||
SignedGroupId: []byte{},
|
||||
Signature: []byte{},
|
||||
|
||||
}
|
||||
encMessage := g.EncryptMessage(dgm)
|
||||
ok, message := g.DecryptMessage(encMessage)
|
||||
if !ok || message != "Hello World" {
|
||||
if !ok || message.GetText() != "Hello World!" {
|
||||
t.Errorf("group encryption was invalid, or returned wrong message decrypted:%v message:%v", ok, message)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ import (
|
|||
"github.com/s-rah/go-ricochet/utils"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
"encoding/asn1"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
||||
|
@ -16,6 +19,8 @@ import (
|
|||
type PublicProfile struct {
|
||||
Name string
|
||||
Ed25519PublicKey ed25519.PublicKey
|
||||
Trusted bool
|
||||
Blocked bool
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,6 +30,7 @@ type Profile struct {
|
|||
Contacts map[string]PublicProfile
|
||||
Ed25519PrivateKey ed25519.PrivateKey
|
||||
OnionPrivateKey *rsa.PrivateKey
|
||||
Onion string
|
||||
Groups map[string]*Group
|
||||
}
|
||||
|
||||
|
@ -37,6 +43,12 @@ func GenerateNewProfile(name string) *Profile {
|
|||
p.Ed25519PrivateKey = priv
|
||||
|
||||
p.OnionPrivateKey, _ = utils.GeneratePrivateKey()
|
||||
// DER Encode the Public Key
|
||||
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
|
||||
N: p.OnionPrivateKey.PublicKey.N,
|
||||
E: p.OnionPrivateKey.PublicKey.E,
|
||||
})
|
||||
p.Onion = utils.GetTorHostname(publicKeyBytes)
|
||||
|
||||
p.Contacts = make(map[string]PublicProfile)
|
||||
p.Groups = make(map[string]*Group)
|
||||
|
@ -94,57 +106,70 @@ func (p *Profile) SignMessage(message string) []byte {
|
|||
// invite which can be sent on the wire.
|
||||
func (p *Profile) StartGroup(server string) (groupID string, invite []byte) {
|
||||
group := NewGroup(server)
|
||||
p.AddGroup(group)
|
||||
groupID = group.GroupID
|
||||
gci := &protocol.GroupChatInvite{
|
||||
GroupName: groupID,
|
||||
GroupSharedKey: group.GroupKey[:],
|
||||
ServerHost: server,
|
||||
}
|
||||
cp := &protocol.CwtchPeerPacket{
|
||||
GroupChatInvite: gci,
|
||||
}
|
||||
invite, err := proto.Marshal(cp)
|
||||
utils.CheckError(err)
|
||||
invite = group.Invite()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Profile) GetGroupByGroupId(groupID string) (*Group) {
|
||||
return p.Groups[groupID]
|
||||
}
|
||||
|
||||
// ProcessInvite adds a new group invite to the profile.
|
||||
func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite) {
|
||||
func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname string) {
|
||||
group := new(Group)
|
||||
group.GroupID = gci.GetGroupName()
|
||||
copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
|
||||
group.GroupServer = gci.GetServerHost()
|
||||
group.Accepted = false
|
||||
group.Owner = peerHostname
|
||||
p.AddGroup(group)
|
||||
}
|
||||
|
||||
// AddGroup is a conveniance method for adding a group to a profle.
|
||||
func (p *Profile) AddGroup(group *Group) {
|
||||
p.Groups[group.GroupID] = group
|
||||
existingGroup, exists := p.Groups[group.GroupID]
|
||||
if !exists {
|
||||
p.Groups[group.GroupID] = group
|
||||
}
|
||||
|
||||
if exists && existingGroup.Owner == group.Owner {
|
||||
p.Groups[group.GroupID] = group
|
||||
}
|
||||
|
||||
// If we are sent an invite or group update by someone who is not an owner
|
||||
// then we reject the group.
|
||||
|
||||
// FIXME: This opens up an attack vector!!
|
||||
|
||||
}
|
||||
|
||||
// AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
|
||||
func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (success bool, groupID string, onion string, message string) {
|
||||
for id, group := range p.Groups {
|
||||
success, message := group.DecryptMessage(ciphertext)
|
||||
func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) {
|
||||
for _, group := range p.Groups {
|
||||
success, dgm := group.DecryptMessage(ciphertext)
|
||||
if success {
|
||||
for onion := range p.Contacts {
|
||||
if p.VerifyMessage(onion, message+string(ciphertext), signature) {
|
||||
return true, id, onion, message
|
||||
}
|
||||
}
|
||||
return true, id, "not-verified", message
|
||||
// FIXME
|
||||
verified := p.VerifyMessage(dgm.GetOnion(), dgm.GetText(), dgm.GetSignature())
|
||||
group.AddMessage(dgm, verified)
|
||||
}
|
||||
}
|
||||
return false, "", "", ""
|
||||
}
|
||||
|
||||
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
|
||||
// profile
|
||||
func (p *Profile) EncryptMessageToGroup(message string, groupid string) (ciphertext []byte, signature []byte) {
|
||||
group := p.Groups[groupid]
|
||||
ciphertext = group.EncryptMessage(message)
|
||||
signature = p.SignMessage(message + string(ciphertext))
|
||||
func (p *Profile) EncryptMessageToGroup(message string, groupID string) (ciphertext []byte, signature []byte) {
|
||||
group := p.Groups[groupID]
|
||||
timestamp := time.Now().Unix()
|
||||
signature = p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
|
||||
dm := &protocol.DecryptedGroupMessage {
|
||||
Onion: proto.String(p.Onion),
|
||||
Text: proto.String(message),
|
||||
SignedGroupId: group.SignedGroupID,
|
||||
Timestamp: proto.Int32(int32(timestamp)),
|
||||
Signature: signature,
|
||||
}
|
||||
ciphertext = group.EncryptMessage(dm)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"Name":"Sarah","Ed25519PublicKey":"T7ug35aRhuImJfE90ymczSgAi90cHdbQMqEhBM/4+T0=","Contacts":{},"Ed25519PrivateKey":"TSquPGWkVc13XEbOkkFaSmQgArJ3vhUAnOHUpipXiZNPu6DflpGG4iYl8T3TKZzNKACL3Rwd1tAyoSEEz/j5PQ==","OnionPrivateKey":{"N":147543015664120090366421447952970860504590941099804565923233227773815393474636918222730428033531903648100909217280034128017566946683746667285202590815509702891937867545391137542704927905354957814668191780596816701004171603996891826892371194711906073796279656017597798465364500891427388525790476841047404602167,"E":65537,"D":5684515839173340680458583030673534381709448498970147076554677512380546386368894189730905149528786131673021282231900852545041069020194093945330676439403114856356392314290322709016451155034411809521466849420244883177134443565565861016638701771436637981361005194284315380514984476353929411384168768558877308473,"Primes":[12931099477754593259784498055463781428153030935578361605358730348734406074416483185277800291372847230179833495402846932178915478884892963290117615988576301,11409935861829750400775707403393062540845554732277302700883351106026835771931816136175131558089707680685127543513601477653608896039540199753620444032990067],"Precomputed":{"Dp":3748099023138475266839591758267695988665867764045448480330110345370687974573378619520836997954111509292401499590650782362187442771219719100036227372613873,"Dq":1182655512297015342058217196259350807024487744272086260388797230011143253410025283626618073364715874618827096191279656342233627276143195094776136782521347,"Qinv":12011761893821155933799466829578837010916497100434672750257270979155184673238141008807799214221585212198300042270714718819586501480079276451027729262322408,"CRTValues":[]}},"Groups":{}}
|
||||
{"Name":"Sarah","Ed25519PublicKey":"08Q49pmOe/8Edn/jR1Qq8d26SU1MzPbJ2PmJ64S6BY8=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"/hhjerGW66QyhAKPtwGbBBzhY5/auK6T2b/vjRGuXnjTxDj2mY57/wR2f+NHVCrx3bpJTUzM9snY+YnrhLoFjw==","OnionPrivateKey":{"N":146328154189193884086641737732621103864374553173215703384122944250992002089624680210913011506081609406766798073335357829207459154214042241028123233139603802979024475926282158611908322463042012748984945285776401165814190414695182764240512995788636041616865870623261762908273890987461235956226167821197856348839,"E":65537,"D":131000281712443101868127073809425903015557376501501621205628292187530749753611841209312116988644890475820095160882129401029342867327559796231167833968091809253818237330927694966481136799400760644381429766064366889564088512676631446803130802140311950212661905465119640487794003192289760147304713580532618877313,"Primes":[11046905119315044599912769443513046649297228971109751902148448569336221142449837623971448415395377389790939200512488865059980264758837294990542341171570219,13246076852180554978507253920836715985718751717246338099834768101497493531807293249274887847720805074264534287152405900588792629153510158525691602692356981],"Precomputed":{"Dp":9951771949347089936659442878755668922509550306762893515157001442446411893285295532588832483100280468945131000782113044435070797127756136171042614443284033,"Dq":6285403633811601060799526716666618760759292321939158372044213473615958219816946235957557750406970050497863607761501422044192947211740832046507430314584393,"Qinv":6371034795994819655941418536323418056681675622606010416374029760428023006446243689515420082688972242794843497150013283993628462297774942497647603736899599,"CRTValues":[]}},"Onion":"3qs4j52zt24qqnor","Groups":{}}
|
|
@ -38,7 +38,7 @@ func TestProfileIdentity(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProfileGroup(t *testing.T) {
|
||||
sarah := GenerateNewProfile("Sarah")
|
||||
/**sarah := GenerateNewProfile("Sarah")
|
||||
alice := GenerateNewProfile("Alice")
|
||||
sarah.AddContact("alice.onion", alice.PublicProfile)
|
||||
alice.AddContact("sarah.onion", sarah.PublicProfile)
|
||||
|
@ -74,5 +74,5 @@ func TestProfileGroup(t *testing.T) {
|
|||
t.Logf("Success!")
|
||||
} else {
|
||||
t.Errorf("Failed to decrypt unverified group message %v %v %v %v", ok, gid, onion, message)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package connections
|
||||
|
||||
import (
|
||||
"git.mascherari.press/cwtch/model"
|
||||
"time"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
peerConnections map[string]*PeerPeerConnection
|
||||
serverConnections map[string]*PeerServerConnection
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewConnectionsManager() *Manager {
|
||||
m := new(Manager)
|
||||
m.peerConnections = make(map[string]*PeerPeerConnection)
|
||||
m.serverConnections = make(map[string]*PeerServerConnection)
|
||||
return m;
|
||||
}
|
||||
|
||||
func (m *Manager) ManagePeerConnection(host string, profile *model.Profile) {
|
||||
m.lock.Lock()
|
||||
ppc := NewPeerPeerConnection(host, profile)
|
||||
go ppc.Run()
|
||||
m.peerConnections[host] = ppc
|
||||
m.lock.Unlock()
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) ManageServerConnection(host string) {
|
||||
m.lock.Lock()
|
||||
psc := NewPeerServerConnection(host)
|
||||
go psc.Run()
|
||||
m.serverConnections[host] = psc
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) GetPeerPeerConnectionForOnion(host string) (ppc *PeerPeerConnection) {
|
||||
m.lock.Lock()
|
||||
ppc = m.peerConnections[host]
|
||||
m.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Manager) GetPeerServerConnectionForOnion(host string) (psc *PeerServerConnection) {
|
||||
m.lock.Lock()
|
||||
psc = m.serverConnections[host]
|
||||
m.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Manager) AttemptReconnections() {
|
||||
m.lock.Lock()
|
||||
for _, ppc := range m.peerConnections {
|
||||
if ppc.GetState() == FAILED {
|
||||
go ppc.Run()
|
||||
}
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
m.lock.Lock()
|
||||
for _, psc := range m.serverConnections {
|
||||
if psc.GetState() == FAILED {
|
||||
go psc.Run()
|
||||
}
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
// Launch Another Run In 30 Seconds
|
||||
time.Sleep(time.Second * 30)
|
||||
go m.AttemptReconnections()
|
||||
}
|
|
@ -38,7 +38,7 @@ func (ppc *PeerPeerConnection) ClientIdentity(ci *protocol.CwtchIdentity) {
|
|||
}
|
||||
|
||||
func (ppc *PeerPeerConnection) HandleGroupInvite(gci *protocol.GroupChatInvite) {
|
||||
ppc.profile.ProcessInvite(gci)
|
||||
ppc.profile.ProcessInvite(gci, ppc.PeerHostname)
|
||||
}
|
||||
|
||||
func (ppc *PeerPeerConnection) SendGroupInvite(invite []byte) {
|
||||
|
@ -87,5 +87,6 @@ func (ppc *PeerPeerConnection) Run() error {
|
|||
ppc.connection.Process(ppc)
|
||||
}
|
||||
}
|
||||
ppc.state = FAILED
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ func (psc *PeerServerConnection) Run() error {
|
|||
}
|
||||
}
|
||||
}
|
||||
psc.state = FAILED
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -13,4 +13,5 @@ const (
|
|||
CONNECTING
|
||||
CONNECTED
|
||||
AUTHENTICATED
|
||||
FAILED
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/s-rah/go-ricochet/connection"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"errors"
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -23,34 +24,20 @@ Move CwtchPeerChannel under peer/
|
|||
Write tests for Peer Channel
|
||||
*/
|
||||
|
||||
type CwtchPeerInstance struct {
|
||||
rai *application.ApplicationInstance
|
||||
ra *application.RicochetApplication
|
||||
}
|
||||
|
||||
func (cpi *CwtchPeerInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
|
||||
cpi.rai = rai
|
||||
cpi.ra = ra
|
||||
}
|
||||
|
||||
type CwtchPeer struct {
|
||||
connection.AutoConnectionHandler
|
||||
Profile *model.Profile
|
||||
PendingContacts []string
|
||||
PendingInvites map[string][]string
|
||||
mutex sync.Mutex
|
||||
Log chan string `json:"-"`
|
||||
peerconnections map[string]*connections.PeerPeerConnection
|
||||
serverconnections map[string]*connections.PeerServerConnection
|
||||
connectionsManager *connections.Manager
|
||||
}
|
||||
|
||||
func NewCwtchPeer(name string) *CwtchPeer {
|
||||
cp := new(CwtchPeer)
|
||||
cp.Profile = model.GenerateNewProfile(name)
|
||||
cp.PendingInvites = make(map[string][]string)
|
||||
cp.Log = make(chan string)
|
||||
cp.peerconnections = make(map[string]*connections.PeerPeerConnection)
|
||||
cp.serverconnections = make(map[string]*connections.PeerServerConnection)
|
||||
cp.connectionsManager = connections.NewConnectionsManager()
|
||||
cp.Init()
|
||||
return cp
|
||||
}
|
||||
|
@ -71,22 +58,23 @@ func LoadCwtchPeer(profilefile string) (*CwtchPeer, error) {
|
|||
}
|
||||
|
||||
// AddContactRequest is the entry point for CwtchPeer relationships
|
||||
func (cp *CwtchPeer) AddContactRequest(onion string) {
|
||||
cp.mutex.Lock()
|
||||
cp.PendingContacts = append(cp.PendingContacts, onion)
|
||||
go cp.EstablishContact(onion)
|
||||
cp.mutex.Unlock()
|
||||
func (cp *CwtchPeer) PeerWithOnion(onion string) {
|
||||
cp.connectionsManager.ManagePeerConnection(onion, cp.Profile)
|
||||
}
|
||||
|
||||
// InviteOnionToGroup kicks off the invite process
|
||||
func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) {
|
||||
cp.mutex.Lock()
|
||||
cp.PendingInvites[onion] = append(cp.PendingInvites[onion], groupid)
|
||||
cp.mutex.Unlock()
|
||||
func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
|
||||
group:= cp.Profile.GetGroupByGroupId(groupid)
|
||||
if group == nil {
|
||||
invite := group.Invite()
|
||||
ppc := cp.connectionsManager.GetPeerPeerConnectionForOnion(onion)
|
||||
ppc.SendGroupInvite(invite)
|
||||
}
|
||||
return errors.New("group id could not be found")
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) JoinServer(onion string) {
|
||||
|
||||
cp.connectionsManager.ManageServerConnection(onion)
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) {
|
||||
|
@ -122,10 +110,18 @@ func (cp *CwtchPeer) Listen() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cp *CwtchPeer) EstablishContact(onion string) {
|
||||
|
||||
type CwtchPeerInstance struct {
|
||||
rai *application.ApplicationInstance
|
||||
ra *application.RicochetApplication
|
||||
}
|
||||
|
||||
func (cpi *CwtchPeerInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
|
||||
cpi.rai = rai
|
||||
cpi.ra = ra
|
||||
}
|
||||
|
||||
|
||||
type CwtchPeerHandler struct {
|
||||
Onion string
|
||||
Peer *CwtchPeer
|
||||
|
@ -137,7 +133,7 @@ func (cph *CwtchPeerHandler) ClientIdentity(ci *protocol.CwtchIdentity) {
|
|||
}
|
||||
|
||||
func (cph *CwtchPeerHandler) HandleGroupInvite(gci *protocol.GroupChatInvite) {
|
||||
cph.Peer.Profile.ProcessInvite(gci)
|
||||
cph.Peer.Profile.ProcessInvite(gci, cph.Onion)
|
||||
}
|
||||
|
||||
func (cph *CwtchPeerHandler) HandleGroupMessage(gm *protocol.GroupMessage) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"Profile":{"Name":"alice","Ed25519PublicKey":"jd/KsmjnaiNRQfwkUU2KOv78epqHQtc/NuQ7vHhL1pU=","Contacts":{},"Ed25519PrivateKey":"hJqPzncQMthT/C6MJe5wwqijF8LZlItwuVqRRPWF2uSN38qyaOdqI1FB/CRRTYo6/vx6modC1z825Du8eEvWlQ==","OnionPrivateKey":{"N":116625881909264071736606689586851027031866677768702967565587135989634906670425929131611970003568513685918847077357562012284752737018357715084617885889134319857398716428989490466264550714347160579478956149389453246821375180647178126226570098474391873393638846273276470804476875874646605047892383342437534890107,"E":65537,"D":74117948357734540608048409620402906386884464181553604820280211391554295479244395506837947276326786319461067500372956617050825510731565357481641438382630323642658160945533297126458940294613864770177502808097743782578588323228321725977812335445321391168916952291786105711946242663437273644485564881061465737473,"Primes":[10118822147491588191804307827889610500703007978301844285618895021194403722583146885313445367368523265387812462145972329196076399224393167672743566765418817,11525638084090164427262755292570520360826215923789098544956165178885657272566959447401401652883909093499220795453042380071024329286949114855482181086877371],"Precomputed":{"Dp":7235119182011013971770905973952227719653675845144337141219485492060511748176545509342631641895250014740877549722450880359615790586310997408254322575453953,"Dq":2255287590069308461101630740984853641564847231436767013145518749012461187777876435501710099586237548484581343071697140272377679765259860062357195023545713,"Qinv":6483591420973981267201623607283357903939001496206473754008877212497299522594502989717337115365261073580285165813624061295197296922762434701906760350701367,"CRTValues":[]}},"Groups":{}},"PendingContacts":null,"PendingInvites":{}}
|
||||
{"Profile":{"Name":"alice","Ed25519PublicKey":"woBpoPixOQlewrOj55rvUUJXO6SYjSbds+x5wBSD/nE=","Trusted":false,"Blocked":false,"Contacts":{},"Ed25519PrivateKey":"zF81FX4HdjfH8y9GEkkMuP3grW+6YHLUq5xt2BGdu93CgGmg+LE5CV7Cs6Pnmu9RQlc7pJiNJt2z7HnAFIP+cQ==","OnionPrivateKey":{"N":139926795769138065515184049224038533094758142244011440346432394711998122006646506685316823692319561121294993561288095105636848170048849868725177766607476321666954798718648650798310074246526178993249155964685028684884855868827006247404286011381915601081225045627232142906774434198218068995980294397037791794267,"E":65537,"D":80563006923674971788217949087853364805598501324340199865601622742387126930997644639776915458173154092952438958879467974136673817850271634292188117664829075982704832540445727518368206591976958327683396293490831042373962322741974344132577591789404120121585476701598634477623181079921183580001754668835068766913,"Primes":[11996718101524297693400014654326964581090960285071398726861380106816214227409962837267266227138498003607972122710998689259606190329862913588172576335353147,11663756252750410861345204036901556299677356139785595193403181361865123292907581200748114782681955962873888839331259846486482553416702234941039306713860961],"Precomputed":{"Dp":3630482172017812779852640350325261890791110599109221522954083214954696992114710666516955171625766069633289761657189633399236607913272978091676865075591787,"Dq":476609232111106707458114598025884123022658341735902222072016108260597832955528975320863808047702489716896933209166026349860388453086479167067507871579713,"Qinv":8004940118786479816457227792582435507151418905449158436675099080630617341053710180416725805457646331045119350055846124889161926521155923764007407649644659,"CRTValues":[]}},"Groups":{}}}
|
|
@ -65,6 +65,7 @@ type GroupChatInvite struct {
|
|||
GroupName string `protobuf:"bytes,1,opt,name=group_name,json=groupName" json:"group_name,omitempty"`
|
||||
GroupSharedKey []byte `protobuf:"bytes,2,opt,name=group_shared_key,json=groupSharedKey,proto3" json:"group_shared_key,omitempty"`
|
||||
ServerHost string `protobuf:"bytes,3,opt,name=server_host,json=serverHost" json:"server_host,omitempty"`
|
||||
SignedGroupId []byte `protobuf:"bytes,4,opt,name=signed_group_id,json=signedGroupId,proto3" json:"signed_group_id,omitempty"`
|
||||
}
|
||||
|
||||
func (m *GroupChatInvite) Reset() { *m = GroupChatInvite{} }
|
||||
|
@ -93,6 +94,13 @@ func (m *GroupChatInvite) GetServerHost() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *GroupChatInvite) GetSignedGroupId() []byte {
|
||||
if m != nil {
|
||||
return m.SignedGroupId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*CwtchPeerPacket)(nil), "protocol.CwtchPeerPacket")
|
||||
proto.RegisterType((*CwtchIdentity)(nil), "protocol.CwtchIdentity")
|
||||
|
@ -102,23 +110,24 @@ func init() {
|
|||
func init() { proto.RegisterFile("cwtch-profile.proto", fileDescriptor1) }
|
||||
|
||||
var fileDescriptor1 = []byte{
|
||||
// 276 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x8f, 0xcd, 0x4e, 0xc3, 0x30,
|
||||
0x10, 0x84, 0x15, 0x40, 0x88, 0x6e, 0x69, 0x53, 0xcc, 0x81, 0x70, 0x40, 0xa0, 0x9c, 0x7a, 0x80,
|
||||
0x48, 0x14, 0xf5, 0xc0, 0x85, 0x4b, 0x85, 0xa0, 0xaa, 0x84, 0x42, 0x78, 0x00, 0x2b, 0x75, 0x36,
|
||||
0xb5, 0xd5, 0x10, 0x47, 0x8e, 0x5b, 0x64, 0xf1, 0x22, 0x3c, 0x2e, 0xca, 0x06, 0xd4, 0x9f, 0x93,
|
||||
0xed, 0x99, 0x9d, 0x6f, 0xd6, 0x70, 0x2e, 0xbe, 0xac, 0x90, 0x77, 0x95, 0xd1, 0xb9, 0x2a, 0x30,
|
||||
0xaa, 0x8c, 0xb6, 0x9a, 0x9d, 0xd0, 0x21, 0x74, 0x11, 0xfe, 0x78, 0xe0, 0x4f, 0x9a, 0x89, 0x18,
|
||||
0xd1, 0xc4, 0xa9, 0x58, 0xa2, 0x65, 0x4f, 0xd0, 0xa7, 0x10, 0x57, 0x19, 0x96, 0x56, 0xe5, 0x2e,
|
||||
0xf0, 0x6e, 0xbc, 0x61, 0x77, 0x74, 0x11, 0xfd, 0xc7, 0x22, 0x8a, 0x4c, 0xc9, 0xb6, 0x2e, 0xe9,
|
||||
0x89, 0xcd, 0x33, 0x77, 0xec, 0x19, 0xce, 0x16, 0x46, 0xaf, 0x2a, 0x2e, 0x64, 0x6a, 0xb9, 0x2a,
|
||||
0xd7, 0xca, 0x62, 0x70, 0x40, 0x88, 0xcb, 0x0d, 0xe2, 0xa5, 0x19, 0x99, 0xc8, 0xd4, 0x4e, 0x69,
|
||||
0x20, 0xf1, 0x17, 0xbb, 0x42, 0xf8, 0x0e, 0xbd, 0x9d, 0x1a, 0xc6, 0xe0, 0xa8, 0x4c, 0x3f, 0x91,
|
||||
0xb6, 0xe9, 0x24, 0x74, 0x67, 0xb7, 0xc0, 0x30, 0x1b, 0x8d, 0xc7, 0xf7, 0x8f, 0xbc, 0x5a, 0xcd,
|
||||
0x0b, 0x25, 0xf8, 0x12, 0x1d, 0x95, 0x9d, 0x26, 0x83, 0x3f, 0x27, 0x26, 0x63, 0x86, 0x2e, 0xfc,
|
||||
0x06, 0x7f, 0xaf, 0x96, 0x5d, 0x01, 0xb4, 0xcb, 0x6e, 0xa1, 0x3b, 0xa4, 0xbc, 0x35, 0xfc, 0x21,
|
||||
0x0c, 0x5a, 0xbb, 0x96, 0xa9, 0xc1, 0x6c, 0x8b, 0xde, 0x27, 0xfd, 0x83, 0xe4, 0x19, 0x3a, 0x76,
|
||||
0x0d, 0xdd, 0x1a, 0xcd, 0x1a, 0x0d, 0x97, 0xba, 0xb6, 0xc1, 0x21, 0x91, 0xa0, 0x95, 0x5e, 0x75,
|
||||
0x6d, 0xe7, 0xc7, 0xf4, 0xf5, 0x87, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x47, 0x47, 0xed,
|
||||
0x92, 0x01, 0x00, 0x00,
|
||||
// 299 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xc1, 0x4a, 0xf3, 0x40,
|
||||
0x14, 0x85, 0xc9, 0xff, 0x17, 0xb1, 0xb7, 0xb6, 0xa9, 0xe3, 0xc2, 0xb8, 0x10, 0xa5, 0x0b, 0xe9,
|
||||
0x42, 0x0b, 0x56, 0xba, 0x70, 0xe3, 0xa6, 0x88, 0x86, 0x82, 0xc4, 0xf8, 0x00, 0x43, 0x3a, 0x73,
|
||||
0x93, 0x0c, 0x8d, 0x99, 0x30, 0x99, 0x56, 0xe6, 0x4d, 0xdc, 0xfb, 0xa2, 0x92, 0x1b, 0xa5, 0xad,
|
||||
0xab, 0x99, 0x39, 0xe7, 0xde, 0xef, 0x1c, 0x06, 0x4e, 0xc4, 0x87, 0x15, 0xf9, 0x4d, 0x65, 0x74,
|
||||
0xaa, 0x0a, 0x9c, 0x54, 0x46, 0x5b, 0xcd, 0x0e, 0xe9, 0x10, 0xba, 0x18, 0x7d, 0x7a, 0xe0, 0xcf,
|
||||
0x9b, 0x89, 0x08, 0xd1, 0x44, 0x89, 0x58, 0xa1, 0x65, 0x0f, 0x30, 0xa0, 0x25, 0xae, 0x24, 0x96,
|
||||
0x56, 0xa5, 0x2e, 0xf0, 0x2e, 0xbd, 0x71, 0x6f, 0x7a, 0x3a, 0xf9, 0x5d, 0x9b, 0xd0, 0x4a, 0x48,
|
||||
0xb6, 0x75, 0x71, 0x5f, 0x6c, 0x9f, 0xa9, 0x63, 0x8f, 0x70, 0x9c, 0x19, 0xbd, 0xae, 0xb8, 0xc8,
|
||||
0x13, 0xcb, 0x55, 0xb9, 0x51, 0x16, 0x83, 0x7f, 0x84, 0x38, 0xdb, 0x22, 0x9e, 0x9a, 0x91, 0x79,
|
||||
0x9e, 0xd8, 0x90, 0x06, 0x62, 0x3f, 0xdb, 0x17, 0x46, 0xaf, 0xd0, 0xdf, 0x8b, 0x61, 0x0c, 0x3a,
|
||||
0x65, 0xf2, 0x8e, 0xd4, 0xa6, 0x1b, 0xd3, 0x9d, 0x5d, 0x03, 0x43, 0x39, 0x9d, 0xcd, 0x6e, 0xef,
|
||||
0x79, 0xb5, 0x5e, 0x16, 0x4a, 0xf0, 0x15, 0x3a, 0x0a, 0x3b, 0x8a, 0x87, 0x3f, 0x4e, 0x44, 0xc6,
|
||||
0x02, 0xdd, 0xe8, 0xcb, 0x03, 0xff, 0x4f, 0x2e, 0x3b, 0x07, 0x68, 0xdb, 0xee, 0xb0, 0xbb, 0xa4,
|
||||
0xbc, 0x34, 0x01, 0x63, 0x18, 0xb6, 0x76, 0x9d, 0x27, 0x06, 0xe5, 0x0e, 0x7e, 0x40, 0xfa, 0x1b,
|
||||
0xc9, 0x0b, 0x74, 0xec, 0x02, 0x7a, 0x35, 0x9a, 0x0d, 0x1a, 0x9e, 0xeb, 0xda, 0x06, 0xff, 0x89,
|
||||
0x04, 0xad, 0xf4, 0xac, 0x6b, 0xcb, 0xae, 0xc0, 0xaf, 0x55, 0x56, 0xa2, 0xe4, 0x2d, 0x51, 0xc9,
|
||||
0xa0, 0x43, 0xa4, 0x7e, 0x2b, 0x53, 0xb3, 0x50, 0x2e, 0x0f, 0xe8, 0x8f, 0xee, 0xbe, 0x03, 0x00,
|
||||
0x00, 0xff, 0xff, 0x62, 0x61, 0x2d, 0x00, 0xbb, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -16,4 +16,5 @@ message GroupChatInvite {
|
|||
string group_name = 1;
|
||||
bytes group_shared_key = 2;
|
||||
string server_host = 3;
|
||||
bytes signed_group_id = 4;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ package protocol
|
|||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import Protocol_Data_Control "github.com/s-rah/go-ricochet/wire/control"
|
||||
import control "github.com/s-rah/go-ricochet/wire/control"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
@ -20,9 +20,10 @@ type CwtchServerPacket struct {
|
|||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *CwtchServerPacket) Reset() { *m = CwtchServerPacket{} }
|
||||
func (m *CwtchServerPacket) String() string { return proto.CompactTextString(m) }
|
||||
func (*CwtchServerPacket) ProtoMessage() {}
|
||||
func (m *CwtchServerPacket) Reset() { *m = CwtchServerPacket{} }
|
||||
func (m *CwtchServerPacket) String() string { return proto.CompactTextString(m) }
|
||||
func (*CwtchServerPacket) ProtoMessage() {}
|
||||
func (*CwtchServerPacket) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
|
||||
|
||||
func (m *CwtchServerPacket) GetGroupMessage() *GroupMessage {
|
||||
if m != nil {
|
||||
|
@ -49,9 +50,10 @@ type FetchMessage struct {
|
|||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *FetchMessage) Reset() { *m = FetchMessage{} }
|
||||
func (m *FetchMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*FetchMessage) ProtoMessage() {}
|
||||
func (m *FetchMessage) Reset() { *m = FetchMessage{} }
|
||||
func (m *FetchMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*FetchMessage) ProtoMessage() {}
|
||||
func (*FetchMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
|
||||
|
||||
type GroupMessage struct {
|
||||
Ciphertext []byte `protobuf:"bytes,1,req,name=ciphertext" json:"ciphertext,omitempty"`
|
||||
|
@ -59,9 +61,10 @@ type GroupMessage struct {
|
|||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *GroupMessage) Reset() { *m = GroupMessage{} }
|
||||
func (m *GroupMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*GroupMessage) ProtoMessage() {}
|
||||
func (m *GroupMessage) Reset() { *m = GroupMessage{} }
|
||||
func (m *GroupMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*GroupMessage) ProtoMessage() {}
|
||||
func (*GroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} }
|
||||
|
||||
func (m *GroupMessage) GetCiphertext() []byte {
|
||||
if m != nil {
|
||||
|
@ -82,14 +85,17 @@ func (m *GroupMessage) GetSpamguard() []byte {
|
|||
// GroupMessage
|
||||
type DecryptedGroupMessage struct {
|
||||
Onion *string `protobuf:"bytes,1,req,name=onion" json:"onion,omitempty"`
|
||||
Text *string `protobuf:"bytes,2,req,name=text" json:"text,omitempty"`
|
||||
Signature []byte `protobuf:"bytes,3,req,name=signature" json:"signature,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:"-"`
|
||||
}
|
||||
|
||||
func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} }
|
||||
func (m *DecryptedGroupMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*DecryptedGroupMessage) ProtoMessage() {}
|
||||
func (m *DecryptedGroupMessage) Reset() { *m = DecryptedGroupMessage{} }
|
||||
func (m *DecryptedGroupMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*DecryptedGroupMessage) ProtoMessage() {}
|
||||
func (*DecryptedGroupMessage) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
|
||||
|
||||
func (m *DecryptedGroupMessage) GetOnion() string {
|
||||
if m != nil && m.Onion != nil {
|
||||
|
@ -98,6 +104,13 @@ func (m *DecryptedGroupMessage) GetOnion() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *DecryptedGroupMessage) GetTimestamp() int32 {
|
||||
if m != nil && m.Timestamp != nil {
|
||||
return *m.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DecryptedGroupMessage) GetText() string {
|
||||
if m != nil && m.Text != nil {
|
||||
return *m.Text
|
||||
|
@ -112,14 +125,53 @@ func (m *DecryptedGroupMessage) GetSignature() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *DecryptedGroupMessage) GetSignedGroupId() []byte {
|
||||
if m != nil {
|
||||
return m.SignedGroupId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var E_ServerNonce = &proto.ExtensionDesc{
|
||||
ExtendedType: (*Protocol_Data_Control.ChannelResult)(nil),
|
||||
ExtendedType: (*control.ChannelResult)(nil),
|
||||
ExtensionType: ([]byte)(nil),
|
||||
Field: 8200,
|
||||
Name: "im.cwtch.server_nonce",
|
||||
Tag: "bytes,8200,opt,name=server_nonce",
|
||||
Name: "protocol.server_nonce",
|
||||
Tag: "bytes,8200,opt,name=server_nonce,json=serverNonce",
|
||||
Filename: "group_message.proto",
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*CwtchServerPacket)(nil), "protocol.CwtchServerPacket")
|
||||
proto.RegisterType((*FetchMessage)(nil), "protocol.FetchMessage")
|
||||
proto.RegisterType((*GroupMessage)(nil), "protocol.GroupMessage")
|
||||
proto.RegisterType((*DecryptedGroupMessage)(nil), "protocol.DecryptedGroupMessage")
|
||||
proto.RegisterExtension(E_ServerNonce)
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("group_message.proto", fileDescriptor2) }
|
||||
|
||||
var fileDescriptor2 = []byte{
|
||||
// 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,
|
||||
}
|
||||
|
|
|
@ -29,4 +29,5 @@ message DecryptedGroupMessage {
|
|||
required int32 timestamp = 2;
|
||||
required string text = 3;
|
||||
required bytes signature = 4;
|
||||
required bytes signed_group_id = 5;
|
||||
}
|
||||
|
|
Laddar…
Referens i nytt ärende