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"
|
2018-05-06 04:18:00 +00:00
|
|
|
"sync"
|
2018-05-09 19:09:00 +00:00
|
|
|
"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 {
|
2018-09-21 18:02:46 +00:00
|
|
|
GroupID string
|
|
|
|
SignedGroupID []byte
|
|
|
|
GroupKey [32]byte
|
|
|
|
GroupServer string
|
|
|
|
Timeline Timeline
|
|
|
|
Accepted bool
|
|
|
|
Owner string
|
|
|
|
IsCompromised bool
|
|
|
|
InitialMessage []byte
|
2018-11-02 23:43:40 +00:00
|
|
|
Attributes map[string]string
|
2018-09-21 18:02:46 +00:00
|
|
|
lock sync.Mutex
|
2018-10-05 03:18:34 +00:00
|
|
|
NewMessage chan Message `json:"-"`
|
2019-01-29 20:56:59 +00:00
|
|
|
LocalID string
|
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
|
2018-09-27 00:08:54 +00:00
|
|
|
func NewGroup(server string) (*Group, error) {
|
2018-03-09 20:44:13 +00:00
|
|
|
group := new(Group)
|
2019-01-29 20:56:59 +00:00
|
|
|
group.LocalID = generateRandomID()
|
2018-11-21 22:15:43 +00:00
|
|
|
|
|
|
|
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)
|
2018-09-27 00:08:54 +00:00
|
|
|
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)
|
2018-09-27 00:08:54 +00:00
|
|
|
return nil, err
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
copy(group.GroupKey[:], groupKey[:])
|
2018-03-15 20:53:22 +00:00
|
|
|
group.Owner = "self"
|
2018-11-02 23:43:40 +00:00
|
|
|
group.Attributes = make(map[string]string)
|
2018-09-27 00:08:54 +00:00
|
|
|
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.
|
2018-03-15 20:53:22 +00:00
|
|
|
func (g *Group) SignGroup(signature []byte) {
|
|
|
|
g.SignedGroupID = signature
|
2018-06-15 16:21:07 +00:00
|
|
|
copy(g.Timeline.SignedGroupID[:], g.SignedGroupID)
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-09-21 18:02:46 +00:00
|
|
|
// 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
|
2018-09-21 18:02:46 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-09-21 18:02:46 +00:00
|
|
|
g.InitialMessage = initialMessage[:]
|
|
|
|
|
2018-03-15 20:53:22 +00:00
|
|
|
gci := &protocol.GroupChatInvite{
|
|
|
|
GroupName: g.GroupID,
|
|
|
|
GroupSharedKey: g.GroupKey[:],
|
|
|
|
ServerHost: g.GroupServer,
|
2018-03-30 21:16:51 +00:00
|
|
|
SignedGroupId: g.SignedGroupID[:],
|
2018-09-21 18:02:46 +00:00
|
|
|
InitialMessage: initialMessage[:],
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
2018-04-28 17:52:59 +00:00
|
|
|
|
2018-03-15 20:53:22 +00:00
|
|
|
cp := &protocol.CwtchPeerPacket{
|
|
|
|
GroupChatInvite: gci,
|
|
|
|
}
|
|
|
|
invite, err := proto.Marshal(cp)
|
2018-05-16 20:18:47 +00:00
|
|
|
return invite, err
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 20:18:47 +00:00
|
|
|
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
2019-02-03 03:24:42 +00:00
|
|
|
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
|
2018-03-31 19:33:32 +00:00
|
|
|
timelineMessage := &Message{
|
2018-04-02 21:10:29 +00:00
|
|
|
Message: message.GetText(),
|
|
|
|
Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
|
2018-05-09 19:09:00 +00:00
|
|
|
Received: time.Now(),
|
2018-06-22 18:11:23 +00:00
|
|
|
Signature: sig,
|
2018-04-02 21:10:29 +00:00
|
|
|
PeerID: message.GetOnion(),
|
|
|
|
PreviousMessageSig: message.GetPreviousMessageSig(),
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
2019-02-03 03:24:42 +00:00
|
|
|
seen := g.Timeline.Insert(timelineMessage)
|
|
|
|
return timelineMessage, seen
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
|
|
|
|
2018-12-03 19:12:34 +00:00
|
|
|
// GetTimeline provides a safe copy of the timeline
|
|
|
|
func (g *Group) GetTimeline() (timeline []Message) {
|
2018-05-06 04:18:00 +00:00
|
|
|
g.lock.Lock()
|
2018-12-03 19:12:34 +00:00
|
|
|
defer g.lock.Unlock()
|
|
|
|
return g.Timeline.GetMessages()
|
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.
|
2018-09-27 00:08:54 +00:00
|
|
|
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)
|
2018-09-27 00:08:54 +00:00
|
|
|
return nil, err
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
2018-03-30 21:16:51 +00:00
|
|
|
wire, err := proto.Marshal(message)
|
2018-03-15 20:53:22 +00:00
|
|
|
utils.CheckError(err)
|
|
|
|
encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
|
2018-09-27 00:08:54 +00:00
|
|
|
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.
|
2018-03-15 20:53:22 +00:00
|
|
|
func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) {
|
2018-03-09 20:44:13 +00:00
|
|
|
var decryptNonce [24]byte
|
|
|
|
copy(decryptNonce[:], ciphertext[:24])
|
|
|
|
decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
|
|
|
|
if ok {
|
2018-03-15 20:53:22 +00:00
|
|
|
dm := &protocol.DecryptedGroupMessage{}
|
|
|
|
err := proto.Unmarshal(decrypted, dm)
|
|
|
|
if err == nil {
|
|
|
|
return true, dm
|
|
|
|
}
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
2018-03-15 20:53:22 +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
|
|
|
|
}
|