package model import ( "crypto/rand" "fmt" "git.mascherari.press/cwtch/protocol" "github.com/golang/protobuf/proto" "github.com/s-rah/go-ricochet/utils" "golang.org/x/crypto/nacl/secretbox" "io" "log" "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 SignedGroupID []byte GroupKey [32]byte GroupServer string Timeline []Message Accepted bool Owner string } // NewGroup initializes a new group associated with a given CwtchServer func NewGroup(server string) *Group { group := new(Group) group.GroupServer = server var groupID [16]byte if _, err := io.ReadFull(rand.Reader, groupID[:]); err != nil { panic(err) } group.GroupID = fmt.Sprintf("%x", groupID) var groupKey [32]byte if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil { 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, SignedGroupId: g.SignedGroupID[:], } log.Printf("INVITEBEFORE %v", gci) 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 } // RemoveMember ... func (g *Group) RemoveMember() { // TODO: Rotate Key } //EncryptMessage takes a message and encrypts the message under the group key. func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) []byte { var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { panic(err) } 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, *protocol.DecryptedGroupMessage) { 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 } } return false, nil }