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.

profile.go 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package model
  2. import (
  3. "crypto/rand"
  4. "crypto/rsa"
  5. "cwtch.im/cwtch/protocol"
  6. "encoding/asn1"
  7. "encoding/json"
  8. "errors"
  9. "github.com/golang/protobuf/proto"
  10. "github.com/s-rah/go-ricochet/utils"
  11. "golang.org/x/crypto/ed25519"
  12. "io"
  13. "io/ioutil"
  14. "strconv"
  15. "sync"
  16. "time"
  17. )
  18. // PublicProfile is a local copy of a CwtchIdentity
  19. type PublicProfile struct {
  20. Name string
  21. Ed25519PublicKey ed25519.PublicKey
  22. Trusted bool
  23. Blocked bool
  24. Onion string
  25. }
  26. // Profile encapsulates all the attributes necessary to be a Cwtch Peer.
  27. type Profile struct {
  28. PublicProfile
  29. Contacts map[string]*PublicProfile
  30. Ed25519PrivateKey ed25519.PrivateKey
  31. OnionPrivateKey *rsa.PrivateKey
  32. Groups map[string]*Group
  33. lock sync.Mutex
  34. }
  35. // GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
  36. func GenerateNewProfile(name string) *Profile {
  37. p := new(Profile)
  38. p.Name = name
  39. pub, priv, _ := ed25519.GenerateKey(rand.Reader)
  40. p.Ed25519PublicKey = pub
  41. p.Ed25519PrivateKey = priv
  42. p.OnionPrivateKey, _ = utils.GeneratePrivateKey()
  43. // DER Encode the Public Key
  44. publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
  45. N: p.OnionPrivateKey.PublicKey.N,
  46. E: p.OnionPrivateKey.PublicKey.E,
  47. })
  48. p.Onion = utils.GetTorHostname(publicKeyBytes)
  49. p.Contacts = make(map[string]*PublicProfile)
  50. p.Contacts[p.Onion] = &p.PublicProfile
  51. p.Groups = make(map[string]*Group)
  52. return p
  53. }
  54. // GetCwtchIdentityPacket returns the wire message for conveying this profiles identity.
  55. func (p *Profile) GetCwtchIdentityPacket() (message []byte) {
  56. ci := &protocol.CwtchIdentity{
  57. Name: p.Name,
  58. Ed25519PublicKey: p.Ed25519PublicKey,
  59. }
  60. cpp := &protocol.CwtchPeerPacket{
  61. CwtchIdentify: ci,
  62. }
  63. message, err := proto.Marshal(cpp)
  64. utils.CheckError(err)
  65. return
  66. }
  67. // AddCwtchIdentity takes a wire message and if it is a CwtchIdentity message adds the identity as a contact
  68. // otherwise returns an error
  69. func (p *Profile) AddCwtchIdentity(onion string, ci *protocol.CwtchIdentity) {
  70. p.AddContact(onion, &PublicProfile{Name: ci.GetName(), Ed25519PublicKey: ci.GetEd25519PublicKey(), Onion: onion})
  71. }
  72. // AddContact allows direct manipulation of cwtch contacts
  73. func (p *Profile) AddContact(onion string, profile *PublicProfile) {
  74. p.lock.Lock()
  75. p.Contacts[onion] = profile
  76. p.lock.Unlock()
  77. }
  78. // RejectInvite rejects and removes a group invite
  79. func (p *Profile) RejectInvite(groupID string) {
  80. p.lock.Lock()
  81. delete(p.Groups, groupID)
  82. p.lock.Unlock()
  83. }
  84. // AcceptInvite accepts a group invite
  85. func (p *Profile) AcceptInvite(groupID string) (err error) {
  86. p.lock.Lock()
  87. defer p.lock.Unlock()
  88. group, ok := p.Groups[groupID]
  89. if ok {
  90. group.Accepted = true
  91. } else {
  92. err = errors.New("group does not exist")
  93. }
  94. return
  95. }
  96. // BlockPeer blocks a contact
  97. func (p *Profile) BlockPeer(onion string) (err error) {
  98. p.lock.Lock()
  99. defer p.lock.Unlock()
  100. contact, ok := p.Contacts[onion]
  101. if ok {
  102. contact.Blocked = true
  103. } else {
  104. err = errors.New("peer does not exist")
  105. }
  106. return
  107. }
  108. // TrustPeer sets a contact to trusted
  109. func (p *Profile) TrustPeer(onion string) (err error) {
  110. p.lock.Lock()
  111. defer p.lock.Unlock()
  112. contact, ok := p.Contacts[onion]
  113. if ok {
  114. contact.Trusted = true
  115. } else {
  116. err = errors.New("peer does not exist")
  117. }
  118. return
  119. }
  120. // IsBlocked returns true if the contact has been blocked, false otherwise
  121. func (p *Profile) IsBlocked(onion string) bool {
  122. contact, ok := p.GetContact(onion)
  123. if ok {
  124. return contact.Blocked
  125. }
  126. return false
  127. }
  128. func (p *Profile) GetContact(onion string) (*PublicProfile, bool) {
  129. p.lock.Lock()
  130. defer p.lock.Unlock()
  131. contact, ok := p.Contacts[onion]
  132. return contact, ok
  133. }
  134. // VerifyGroupMessage confirms the authenticity of a message given an onion, message and signature.
  135. func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, timestamp int32, signature []byte) bool {
  136. if onion == p.Onion {
  137. m := message + groupID + strconv.Itoa(int(timestamp))
  138. return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature)
  139. }
  140. contact, found := p.GetContact(onion)
  141. if found {
  142. m := message + groupID + strconv.Itoa(int(timestamp))
  143. return ed25519.Verify(contact.Ed25519PublicKey, []byte(m), signature)
  144. }
  145. return false
  146. }
  147. // SignMessage takes a given message and returns an Ed21159 signature
  148. func (p *Profile) SignMessage(message string) []byte {
  149. sig := ed25519.Sign(p.Ed25519PrivateKey, []byte(message))
  150. return sig
  151. }
  152. //StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed
  153. // invite which can be sent on the wire.
  154. func (p *Profile) StartGroup(server string) (groupID string, invite []byte, err error) {
  155. group := NewGroup(server)
  156. groupID = group.GroupID
  157. signedGroupID := p.SignMessage(groupID + server)
  158. group.SignGroup(signedGroupID)
  159. invite, err = group.Invite()
  160. p.lock.Lock()
  161. defer p.lock.Unlock()
  162. p.Groups[group.GroupID] = group
  163. return
  164. }
  165. // GetGroupByGroupID a pointer to a Group by the group Id, returns nil if no group found.
  166. func (p *Profile) GetGroupByGroupID(groupID string) (g *Group) {
  167. p.lock.Lock()
  168. defer p.lock.Unlock()
  169. g = p.Groups[groupID]
  170. return
  171. }
  172. // ProcessInvite adds a new group invite to the profile.
  173. func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite, peerHostname string) {
  174. group := new(Group)
  175. group.GroupID = gci.GetGroupName()
  176. group.SignedGroupID = gci.GetSignedGroupId()
  177. copy(group.GroupKey[:], gci.GetGroupSharedKey()[:])
  178. group.GroupServer = gci.GetServerHost()
  179. group.Accepted = false
  180. group.Owner = peerHostname
  181. p.AddGroup(group)
  182. }
  183. // AddGroup is a convenience method for adding a group to a profile.
  184. func (p *Profile) AddGroup(group *Group) {
  185. existingGroup, exists := p.Groups[group.GroupID]
  186. if !exists {
  187. owner, ok := p.GetContact(group.Owner)
  188. if ok {
  189. valid := ed25519.Verify(owner.Ed25519PublicKey, []byte(group.GroupID+group.GroupServer), group.SignedGroupID)
  190. if valid {
  191. p.lock.Lock()
  192. defer p.lock.Unlock()
  193. p.Groups[group.GroupID] = group
  194. }
  195. }
  196. } else if exists && existingGroup.Owner == group.Owner {
  197. p.lock.Lock()
  198. defer p.lock.Unlock()
  199. p.Groups[group.GroupID] = group
  200. }
  201. // If we are sent an invite or group update by someone who is not an owner
  202. // then we reject the group.
  203. }
  204. // AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
  205. func (p *Profile) AttemptDecryption(ciphertext []byte) (bool, *Message) {
  206. for _, group := range p.Groups {
  207. success, dgm := group.DecryptMessage(ciphertext)
  208. if success {
  209. // Assert that we know the owner of the group
  210. owner, ok := p.Contacts[group.Owner]
  211. if ok {
  212. valid := ed25519.Verify(owner.Ed25519PublicKey, []byte(group.GroupID+group.GroupServer), dgm.SignedGroupId)
  213. // If we can decrypt the message, but the group id is wrong that means that
  214. // this message is from someone who was not invited to the group.
  215. // As such this group has been compromised, probably by one of the other members.
  216. // We set the flag to be handled by the UX and reject the message.
  217. if !valid {
  218. group.Compromised()
  219. return false, nil
  220. }
  221. }
  222. verified := p.VerifyGroupMessage(dgm.GetOnion(), group.GroupID, dgm.GetText(), dgm.GetTimestamp(), dgm.GetSignature())
  223. return true, group.AddMessage(dgm, verified)
  224. }
  225. }
  226. return false, nil
  227. }
  228. func getRandomness(arr *[]byte) {
  229. if _, err := io.ReadFull(rand.Reader, (*arr)[:]); err != nil {
  230. utils.CheckError(err)
  231. }
  232. }
  233. // EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
  234. // profile
  235. func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, error) {
  236. group := p.GetGroupByGroupID(groupID)
  237. if group != nil {
  238. timestamp := time.Now().Unix()
  239. signature := p.SignMessage(message + groupID + strconv.Itoa(int(timestamp)))
  240. var prevSig []byte
  241. if len(group.Timeline.Messages) > 0 {
  242. prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature
  243. } else {
  244. prevSig = group.SignedGroupID
  245. }
  246. lenPadding := 1024 - len(message)
  247. padding := make([]byte, lenPadding)
  248. getRandomness(&padding)
  249. dm := &protocol.DecryptedGroupMessage{
  250. Onion: proto.String(p.Onion),
  251. Text: proto.String(message),
  252. SignedGroupId: group.SignedGroupID[:],
  253. Timestamp: proto.Int32(int32(timestamp)),
  254. Signature: signature,
  255. PreviousMessageSig: prevSig,
  256. Padding: padding[:],
  257. }
  258. ciphertext := group.EncryptMessage(dm)
  259. return ciphertext, nil
  260. }
  261. return nil, errors.New("group does not exist")
  262. }
  263. // Save makes a opy of the profile in the given file
  264. func (p *Profile) Save(profilefile string) error {
  265. p.lock.Lock()
  266. defer p.lock.Unlock()
  267. bytes, _ := json.Marshal(p)
  268. return ioutil.WriteFile(profilefile, bytes, 0600)
  269. }
  270. // LoadProfile loads a saved profile from a file.
  271. func LoadProfile(profilefile string) (*Profile, error) {
  272. bytes, _ := ioutil.ReadFile(profilefile)
  273. profile := new(Profile)
  274. err := json.Unmarshal(bytes, &profile)
  275. return profile, err
  276. }