Official cwtch.im peer and server implementations. https://cwtch.im
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

group.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package model
  2. import (
  3. "crypto/rand"
  4. "cwtch.im/cwtch/protocol"
  5. "errors"
  6. "fmt"
  7. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  8. "git.openprivacy.ca/openprivacy/libricochet-go/utils"
  9. "github.com/golang/protobuf/proto"
  10. "golang.org/x/crypto/nacl/secretbox"
  11. "io"
  12. "sync"
  13. "time"
  14. )
  15. // Group defines and encapsulates Cwtch's conception of group chat. Which are sessions
  16. // tied to a server under a given group key. Each group has a set of Messages.
  17. type Group struct {
  18. GroupID string
  19. SignedGroupID []byte
  20. GroupKey [32]byte
  21. GroupServer string
  22. Timeline Timeline `json:"-"`
  23. Accepted bool
  24. Owner string
  25. IsCompromised bool
  26. InitialMessage []byte
  27. Attributes map[string]string
  28. lock sync.Mutex
  29. LocalID string
  30. State string `json:"-"`
  31. unacknowledgedMessages []Message
  32. }
  33. // NewGroup initializes a new group associated with a given CwtchServer
  34. func NewGroup(server string) (*Group, error) {
  35. group := new(Group)
  36. group.LocalID = GenerateRandomID()
  37. if utils.IsValidHostname(server) == false {
  38. return nil, errors.New("Server is not a valid v3 onion")
  39. }
  40. group.GroupServer = server
  41. var groupID [16]byte
  42. if _, err := io.ReadFull(rand.Reader, groupID[:]); err != nil {
  43. log.Errorf("Cannot read from random: %v\n", err)
  44. return nil, err
  45. }
  46. group.GroupID = fmt.Sprintf("%x", groupID)
  47. var groupKey [32]byte
  48. if _, err := io.ReadFull(rand.Reader, groupKey[:]); err != nil {
  49. log.Errorf("Error: Cannot read from random: %v\n", err)
  50. return nil, err
  51. }
  52. copy(group.GroupKey[:], groupKey[:])
  53. group.Owner = "self"
  54. group.Attributes = make(map[string]string)
  55. return group, nil
  56. }
  57. // SignGroup adds a signature to the group.
  58. func (g *Group) SignGroup(signature []byte) {
  59. g.SignedGroupID = signature
  60. copy(g.Timeline.SignedGroupID[:], g.SignedGroupID)
  61. }
  62. // Compromised should be called if we detect a a groupkey leak.
  63. func (g *Group) Compromised() {
  64. g.IsCompromised = true
  65. }
  66. // GetInitialMessage returns the first message of the group, if one was sent with the invite.
  67. func (g *Group) GetInitialMessage() []byte {
  68. g.lock.Lock()
  69. defer g.lock.Unlock()
  70. return g.InitialMessage
  71. }
  72. // Invite generates a invitation that can be sent to a cwtch peer
  73. func (g *Group) Invite(initialMessage []byte) ([]byte, error) {
  74. if g.SignedGroupID == nil {
  75. return nil, errors.New("group isn't signed")
  76. }
  77. g.InitialMessage = initialMessage[:]
  78. gci := &protocol.GroupChatInvite{
  79. GroupName: g.GroupID,
  80. GroupSharedKey: g.GroupKey[:],
  81. ServerHost: g.GroupServer,
  82. SignedGroupId: g.SignedGroupID[:],
  83. InitialMessage: initialMessage[:],
  84. }
  85. invite, err := proto.Marshal(gci)
  86. return invite, err
  87. }
  88. // AddSentMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
  89. func (g *Group) AddSentMessage(message *protocol.DecryptedGroupMessage, sig []byte) Message {
  90. g.lock.Lock()
  91. defer g.lock.Unlock()
  92. timelineMessage := Message{
  93. Message: message.GetText(),
  94. Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
  95. Received: time.Unix(0, 0),
  96. Signature: sig,
  97. PeerID: message.GetOnion(),
  98. PreviousMessageSig: message.GetPreviousMessageSig(),
  99. ReceivedByServer: false,
  100. }
  101. g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
  102. return timelineMessage
  103. }
  104. // ErrorSentMessage removes a sent message from the unacknowledged list and sets its error flag if found, otherwise returns false
  105. func (g *Group) ErrorSentMessage(sig []byte, error string) bool {
  106. g.lock.Lock()
  107. defer g.lock.Unlock()
  108. var message *Message
  109. // Delete the message from the unack'd buffer if it exists
  110. for i, unAckedMessage := range g.unacknowledgedMessages {
  111. if compareSignatures(unAckedMessage.Signature, sig) {
  112. message = &unAckedMessage
  113. g.unacknowledgedMessages = append(g.unacknowledgedMessages[:i], g.unacknowledgedMessages[i+1:]...)
  114. message.Error = error
  115. g.Timeline.Insert(message)
  116. return true
  117. }
  118. }
  119. return false
  120. }
  121. // AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
  122. func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
  123. g.lock.Lock()
  124. defer g.lock.Unlock()
  125. // Delete the message from the unack'd buffer if it exists
  126. for i, unAckedMessage := range g.unacknowledgedMessages {
  127. if compareSignatures(unAckedMessage.Signature, sig) {
  128. g.unacknowledgedMessages = append(g.unacknowledgedMessages[:i], g.unacknowledgedMessages[i+1:]...)
  129. break
  130. }
  131. }
  132. timelineMessage := &Message{
  133. Message: message.GetText(),
  134. Timestamp: time.Unix(int64(message.GetTimestamp()), 0),
  135. Received: time.Now(),
  136. Signature: sig,
  137. PeerID: message.GetOnion(),
  138. PreviousMessageSig: message.GetPreviousMessageSig(),
  139. ReceivedByServer: true,
  140. Error: "",
  141. }
  142. seen := g.Timeline.Insert(timelineMessage)
  143. return timelineMessage, seen
  144. }
  145. // GetTimeline provides a safe copy of the timeline
  146. func (g *Group) GetTimeline() (timeline []Message) {
  147. g.lock.Lock()
  148. defer g.lock.Unlock()
  149. return append(g.Timeline.GetMessages(), g.unacknowledgedMessages...)
  150. }
  151. //EncryptMessage takes a message and encrypts the message under the group key.
  152. func (g *Group) EncryptMessage(message *protocol.DecryptedGroupMessage) ([]byte, error) {
  153. var nonce [24]byte
  154. if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
  155. log.Errorf("Cannot read from random: %v\n", err)
  156. return nil, err
  157. }
  158. wire, err := proto.Marshal(message)
  159. if err != nil {
  160. return nil, err
  161. }
  162. encrypted := secretbox.Seal(nonce[:], []byte(wire), &nonce, &g.GroupKey)
  163. return encrypted, nil
  164. }
  165. // DecryptMessage takes a ciphertext and returns true and the decrypted message if the
  166. // cipher text can be successfully decrypted,else false.
  167. func (g *Group) DecryptMessage(ciphertext []byte) (bool, *protocol.DecryptedGroupMessage) {
  168. if len(ciphertext) > 24 {
  169. var decryptNonce [24]byte
  170. copy(decryptNonce[:], ciphertext[:24])
  171. decrypted, ok := secretbox.Open(nil, ciphertext[24:], &decryptNonce, &g.GroupKey)
  172. if ok {
  173. dm := &protocol.DecryptedGroupMessage{}
  174. err := proto.Unmarshal(decrypted, dm)
  175. if err == nil {
  176. return true, dm
  177. }
  178. }
  179. }
  180. return false, nil
  181. }
  182. // SetAttribute allows applications to store arbitrary configuration info at the group level.
  183. func (g *Group) SetAttribute(name string, value string) {
  184. g.lock.Lock()
  185. defer g.lock.Unlock()
  186. g.Attributes[name] = value
  187. }
  188. // GetAttribute returns the value of a value set with SetAttribute. If no such value has been set exists is set to false.
  189. func (g *Group) GetAttribute(name string) (value string, exists bool) {
  190. g.lock.Lock()
  191. defer g.lock.Unlock()
  192. value, exists = g.Attributes[name]
  193. return
  194. }