crypto.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2020 Tulir Asokan
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. // +build cgo
  17. package main
  18. import (
  19. "crypto/hmac"
  20. "crypto/sha512"
  21. "encoding/hex"
  22. "time"
  23. "github.com/pkg/errors"
  24. "maunium.net/go/maulogger/v2"
  25. "maunium.net/go/mautrix"
  26. "maunium.net/go/mautrix/crypto"
  27. "maunium.net/go/mautrix/event"
  28. "maunium.net/go/mautrix/id"
  29. )
  30. var levelTrace = maulogger.Level{
  31. Name: "Trace",
  32. Severity: -10,
  33. Color: -1,
  34. }
  35. type CryptoHelper struct {
  36. bridge *Bridge
  37. client *mautrix.Client
  38. mach *crypto.OlmMachine
  39. log maulogger.Logger
  40. }
  41. func (bridge *Bridge) initCrypto() error {
  42. if !bridge.Config.Bridge.Encryption.Allow {
  43. bridge.Log.Debugln("Bridge built with end-to-bridge encryption, but disabled in config")
  44. return nil
  45. } else if bridge.Config.Bridge.LoginSharedSecret == "" {
  46. bridge.Log.Warnln("End-to-bridge encryption enabled, but login_shared_secret not set")
  47. return nil
  48. }
  49. bridge.Log.Debugln("Initializing end-to-bridge encryption...")
  50. client, err := bridge.loginBot()
  51. if err != nil {
  52. return err
  53. }
  54. // TODO put this in the database
  55. cryptoStore, err := crypto.NewGobStore("crypto.gob")
  56. if err != nil {
  57. return err
  58. }
  59. log := bridge.Log.Sub("Crypto")
  60. logger := &cryptoLogger{log}
  61. stateStore := &cryptoStateStore{bridge}
  62. helper := &CryptoHelper{
  63. bridge: bridge,
  64. client: client,
  65. log: log.Sub("Helper"),
  66. mach: crypto.NewOlmMachine(client, logger, cryptoStore, stateStore),
  67. }
  68. client.Logger = logger.int.Sub("Bot")
  69. client.Syncer = &cryptoSyncer{helper.mach}
  70. // TODO put this in the database too
  71. client.Store = mautrix.NewInMemoryStore()
  72. err = helper.mach.Load()
  73. if err != nil {
  74. return err
  75. }
  76. bridge.Crypto = helper
  77. return nil
  78. }
  79. func (helper *CryptoHelper) Start() {
  80. helper.log.Debugln("Starting syncer for receiving to-device messages")
  81. err := helper.client.Sync()
  82. if err != nil {
  83. helper.log.Errorln("Fatal error syncing:", err)
  84. }
  85. }
  86. func (helper *CryptoHelper) Stop() {
  87. helper.client.StopSync()
  88. }
  89. func (bridge *Bridge) loginBot() (*mautrix.Client, error) {
  90. mac := hmac.New(sha512.New, []byte(bridge.Config.Bridge.LoginSharedSecret))
  91. mac.Write([]byte(bridge.AS.BotMXID()))
  92. resp, err := bridge.AS.BotClient().Login(&mautrix.ReqLogin{
  93. Type: "m.login.password",
  94. Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: string(bridge.AS.BotMXID())},
  95. Password: hex.EncodeToString(mac.Sum(nil)),
  96. DeviceID: "WhatsApp Bridge",
  97. InitialDeviceDisplayName: "WhatsApp Bridge",
  98. })
  99. if err != nil {
  100. return nil, err
  101. }
  102. client, err := mautrix.NewClient(bridge.AS.HomeserverURL, bridge.AS.BotMXID(), resp.AccessToken)
  103. if err != nil {
  104. return nil, err
  105. }
  106. client.DeviceID = "WhatsApp Bridge"
  107. return client, nil
  108. }
  109. func (helper *CryptoHelper) Decrypt(evt *event.Event) (*event.Event, error) {
  110. return helper.mach.DecryptMegolmEvent(evt)
  111. }
  112. func (helper *CryptoHelper) Encrypt(roomID id.RoomID, evtType event.Type, content event.Content) (*event.EncryptedEventContent, error) {
  113. encrypted, err := helper.mach.EncryptMegolmEvent(roomID, evtType, content)
  114. if err != nil {
  115. if err != crypto.SessionExpired && err != crypto.SessionNotShared && err != crypto.NoGroupSession {
  116. return nil, err
  117. }
  118. helper.log.Debugfln("Got %v while encrypting event for %s, sharing group session and trying again...", err, roomID)
  119. users, err := helper.bridge.StateStore.GetRoomMemberList(roomID)
  120. if err != nil {
  121. return nil, errors.Wrap(err, "failed to get room member list")
  122. }
  123. err = helper.mach.ShareGroupSession(roomID, users)
  124. if err != nil {
  125. return nil, errors.Wrap(err, "failed to share group session")
  126. }
  127. encrypted, err = helper.mach.EncryptMegolmEvent(roomID, evtType, content)
  128. if err != nil {
  129. return nil, errors.Wrap(err, "failed to encrypt event after re-sharing group session")
  130. }
  131. }
  132. return encrypted, nil
  133. }
  134. func (helper *CryptoHelper) HandleMemberEvent(evt *event.Event) {
  135. helper.mach.HandleMemberEvent(evt)
  136. }
  137. type cryptoSyncer struct {
  138. *crypto.OlmMachine
  139. }
  140. func (syncer *cryptoSyncer) ProcessResponse(resp *mautrix.RespSync, since string) error {
  141. syncer.ProcessSyncResponse(resp, since)
  142. return nil
  143. }
  144. func (syncer *cryptoSyncer) OnFailedSync(_ *mautrix.RespSync, err error) (time.Duration, error) {
  145. syncer.Log.Error("Error /syncing, waiting 10 seconds: %v", err)
  146. return 10 * time.Second, nil
  147. }
  148. func (syncer *cryptoSyncer) GetFilterJSON(_ id.UserID) *mautrix.Filter {
  149. everything := []event.Type{{Type: "*"}}
  150. return &mautrix.Filter{
  151. Presence: mautrix.FilterPart{NotTypes: everything},
  152. AccountData: mautrix.FilterPart{NotTypes: everything},
  153. Room: mautrix.RoomFilter{
  154. IncludeLeave: false,
  155. Ephemeral: mautrix.FilterPart{NotTypes: everything},
  156. AccountData: mautrix.FilterPart{NotTypes: everything},
  157. State: mautrix.FilterPart{NotTypes: everything},
  158. Timeline: mautrix.FilterPart{NotTypes: everything},
  159. },
  160. }
  161. }
  162. type cryptoLogger struct {
  163. int maulogger.Logger
  164. }
  165. func (c *cryptoLogger) Error(message string, args ...interface{}) {
  166. c.int.Errorfln(message, args...)
  167. }
  168. func (c *cryptoLogger) Warn(message string, args ...interface{}) {
  169. c.int.Warnfln(message, args...)
  170. }
  171. func (c *cryptoLogger) Debug(message string, args ...interface{}) {
  172. c.int.Debugfln(message, args...)
  173. }
  174. func (c *cryptoLogger) Trace(message string, args ...interface{}) {
  175. c.int.Logfln(levelTrace, message, args...)
  176. }
  177. type cryptoStateStore struct {
  178. bridge *Bridge
  179. }
  180. func (c *cryptoStateStore) IsEncrypted(id id.RoomID) bool {
  181. portal := c.bridge.GetPortalByMXID(id)
  182. if portal != nil {
  183. return portal.Encrypted
  184. }
  185. return false
  186. }
  187. func (c *cryptoStateStore) FindSharedRooms(id id.UserID) []id.RoomID {
  188. return c.bridge.StateStore.FindSharedRooms(id)
  189. }