cwtch/model/group.go

224 lines
6.7 KiB
Go
Raw Normal View History

2018-03-09 20:44:13 +00:00
package model
import (
"crypto/rand"
2018-05-28 18:05:06 +00:00
"cwtch.im/cwtch/protocol"
2018-05-16 20:20:46 +00:00
"errors"
2018-03-09 20:44:13 +00:00
"fmt"
2018-12-04 02:52:11 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2018-06-23 16:15:36 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
2018-06-29 19:20:07 +00:00
"github.com/golang/protobuf/proto"
2018-03-30 21:16:51 +00:00
"golang.org/x/crypto/nacl/secretbox"
"io"
"sync"
"time"
2018-03-09 20:44:13 +00:00
)
2018-10-05 03:18:34 +00:00
// Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
2019-02-03 01:18:33 +00:00
// tied to a server under a given group key. Each group has a set of Messages.
2018-03-09 20:44:13 +00:00
type Group struct {
GroupID string
SignedGroupID []byte
GroupKey [32]byte
GroupServer string
Timeline Timeline `json:"-"`
Accepted bool
Owner string
IsCompromised bool
InitialMessage []byte
Attributes map[string]string
lock sync.Mutex
LocalID string
State string `json:"-"`
unacknowledgedMessages []Message
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// NewGroup initializes a new group associated with a given CwtchServer
func NewGroup(server string) (*Group, error) {
2018-03-09 20:44:13 +00:00
group := new(Group)
group.LocalID = GenerateRandomID()
if utils.IsValidHostname(server) == false {
return nil, errors.New("Server is not a valid v3 onion")
}
2018-03-09 20:44:13 +00:00
group.GroupServer = server
var groupID [16]byte
if _, err := io.ReadFull(rand.Reader, groupID[:]); err != nil {
2018-12-04 02:52:11 +00:00
log.Errorf("Cannot read from random: %v\n", err)
return nil, err
2018-03-09 20:44:13 +00:00
}
group.GroupID = fmt.Sprintf("%x", groupID)
var groupKey [32]byte
if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
2018-12-04 02:52:11 +00:00
log.Errorf("Error: Cannot read from random: %v\n", err)
return nil, err
2018-03-09 20:44:13 +00:00
}
copy(group.GroupKey[:], groupKey[:])
group.Owner = "self"
2018-11-02 23:43:40 +00:00
group.Attributes = make(map[string]string)
return group, nil
2018-03-09 20:44:13 +00:00
}
2018-05-16 20:18:47 +00:00
// SignGroup adds a signature to the group.
func (g *Group) SignGroup(signature []byte) {
g.SignedGroupID = signature
copy(g.Timeline.SignedGroupID[:], g.SignedGroupID)
}
2018-05-20 18:38:56 +00:00
// Compromised should be called if we detect a a groupkey leak.
func (g *Group) Compromised() {
g.IsCompromised = true
}
// GetInitialMessage returns the first message of the group, if one was sent with the invite.
func (g *Group) GetInitialMessage() []byte {
g.lock.Lock()
defer g.lock.Unlock()
return g.InitialMessage
}
2018-05-16 20:18:47 +00:00
// Invite generates a invitation that can be sent to a cwtch peer
func (g *Group) Invite(initialMessage []byte) ([]byte, error) {
2018-05-16 20:18:47 +00:00
if g.SignedGroupID == nil {
return nil, errors.New("group isn't signed")
}
g.InitialMessage = initialMessage[:]
gci := &protocol.GroupChatInvite{
GroupName: g.GroupID,
GroupSharedKey: g.GroupKey[:],
ServerHost: g.GroupServer,
2018-03-30 21:16:51 +00:00
SignedGroupId: g.SignedGroupID[:],
InitialMessage: initialMessage[:],
}
invite, err := proto.Marshal(gci)
2018-05-16 20:18:47 +00:00
return invite, err
}
// AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
func (g *Group) AddSentMessage(message *protocol.DecryptedGroupMessage, sig []byte) Message {
g.lock.Lock()
defer g.lock.Unlock()
timelineMessage := Message{
Message: message.GetText(),
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
Received: time.Unix(0, 0),
Signature: sig,
PeerID: message.GetOnion(),
PreviousMessageSig: message.GetPreviousMessageSig(),
ReceivedByServer: false,
}
g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
return timelineMessage
}
// ErrorSentMessage removes a sent message from the unacknowledged list and sets its error flag if found, otherwise returns false
func (g *Group) ErrorSentMessage(sig []byte, error string) bool {
g.lock.Lock()
defer g.lock.Unlock()
var message *Message
// Delete the message from the unack'd buffer if it exists
for i, unAckedMessage := range g.unacknowledgedMessages {
if compareSignatures(unAckedMessage.Signature, sig) {
message = &unAckedMessage
g.unacknowledgedMessages = append(g.unacknowledgedMessages[:i], g.unacknowledgedMessages[i+1:]...)
message.Error = error
g.Timeline.Insert(message)
return true
}
}
return false
}
2018-05-16 20:18:47 +00:00
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
g.lock.Lock()
defer g.lock.Unlock()
// Delete the message from the unack'd buffer if it exists
for i, unAckedMessage := range g.unacknowledgedMessages {
if compareSignatures(unAckedMessage.Signature, sig) {
g.unacknowledgedMessages = append(g.unacknowledgedMessages[:i], g.unacknowledgedMessages[i+1:]...)
break
}
}
2018-03-31 19:33:32 +00:00
timelineMessage := &Message{
Message: message.GetText(),
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
Received: time.Now(),
Signature: sig,
PeerID: message.GetOnion(),
PreviousMessageSig: message.GetPreviousMessageSig(),
ReceivedByServer: true,
Error: "",
}
seen := g.Timeline.Insert(timelineMessage)
return timelineMessage, seen
}
2018-12-03 19:12:34 +00:00
// GetTimeline provides a safe copy of the timeline
func (g *Group) GetTimeline() (timeline []Message) {
g.lock.Lock()
2018-12-03 19:12:34 +00:00
defer g.lock.Unlock()
return append(g.Timeline.GetMessages(), g.unacknowledgedMessages...)
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
//EncryptMessage takes a message and encrypts the message under the group key.
func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte, error) {
2018-03-09 20:44:13 +00:00
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
2018-12-04 02:52:11 +00:00
log.Errorf("Cannot read from random: %v\n", err)
return nil, err
2018-03-09 20:44:13 +00:00
}
2018-03-30 21:16:51 +00:00
wire, err := proto.Marshal(message)
2019-11-08 00:39:27 +00:00
if err != nil {
return nil, err
}
encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
return encrypted, nil
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// 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, *protocol.DecryptedGroupMessage) {
2019-11-08 00:39:27 +00:00
if len(ciphertext) > 24 {
var decryptNonce [24]byte
copy(decryptNonce[:], ciphertext[:24])
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
if ok {
dm := &protocol.DecryptedGroupMessage{}
err := proto.Unmarshal(decrypted, dm)
if err == nil {
return true, dm
}
}
2018-03-09 20:44:13 +00:00
}
return false, nil
2018-03-09 20:44:13 +00:00
}
2018-11-02 23:43:40 +00:00
// SetAttribute allows applications to store arbitrary configuration info at the group level.
func (g *Group) SetAttribute(name string, value string) {
g.lock.Lock()
defer g.lock.Unlock()
g.Attributes[name] = value
}
// GetAttribute returns the value of a value set with SetAttribute. If no such value has been set exists is set to false.
func (g *Group) GetAttribute(name string) (value string, exists bool) {
g.lock.Lock()
defer g.lock.Unlock()
value, exists = g.Attributes[name]
return
}