crypto.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. "fmt"
  23. "time"
  24. "github.com/pkg/errors"
  25. "maunium.net/go/maulogger/v2"
  26. "maunium.net/go/mautrix"
  27. "maunium.net/go/mautrix-whatsapp/database"
  28. "maunium.net/go/mautrix/crypto"
  29. "maunium.net/go/mautrix/event"
  30. "maunium.net/go/mautrix/id"
  31. )
  32. var levelTrace = maulogger.Level{
  33. Name: "Trace",
  34. Severity: -10,
  35. Color: -1,
  36. }
  37. type CryptoHelper struct {
  38. bridge *Bridge
  39. client *mautrix.Client
  40. mach *crypto.OlmMachine
  41. store *database.SQLCryptoStore
  42. log maulogger.Logger
  43. baseLog maulogger.Logger
  44. }
  45. func NewCryptoHelper(bridge *Bridge) Crypto {
  46. if !bridge.Config.Bridge.Encryption.Allow {
  47. bridge.Log.Debugln("Bridge built with end-to-bridge encryption, but disabled in config")
  48. return nil
  49. } else if bridge.Config.Bridge.LoginSharedSecret == "" {
  50. bridge.Log.Warnln("End-to-bridge encryption enabled, but login_shared_secret not set")
  51. return nil
  52. }
  53. baseLog := bridge.Log.Sub("Crypto")
  54. return &CryptoHelper{
  55. bridge: bridge,
  56. log: baseLog.Sub("Helper"),
  57. baseLog: baseLog,
  58. }
  59. }
  60. func (helper *CryptoHelper) Init() error {
  61. helper.log.Debugln("Initializing end-to-bridge encryption...")
  62. helper.store = database.NewSQLCryptoStore(helper.bridge.DB, helper.bridge.AS.BotMXID(),
  63. fmt.Sprintf("@%s:%s", helper.bridge.Config.Bridge.FormatUsername("%"), helper.bridge.AS.HomeserverDomain))
  64. var err error
  65. helper.client, err = helper.loginBot()
  66. if err != nil {
  67. return err
  68. }
  69. helper.log.Debugln("Logged in as bridge bot with device ID", helper.client.DeviceID)
  70. logger := &cryptoLogger{helper.baseLog}
  71. stateStore := &cryptoStateStore{helper.bridge}
  72. helper.mach = crypto.NewOlmMachine(helper.client, logger, helper.store, stateStore)
  73. helper.client.Logger = logger.int.Sub("Bot")
  74. helper.client.Syncer = &cryptoSyncer{helper.mach}
  75. helper.client.Store = &cryptoClientStore{helper.store}
  76. return helper.mach.Load()
  77. }
  78. func (helper *CryptoHelper) loginBot() (*mautrix.Client, error) {
  79. deviceID := helper.store.FindDeviceID()
  80. if len(deviceID) > 0 {
  81. helper.log.Debugln("Found existing device ID for bot in database:", deviceID)
  82. }
  83. mac := hmac.New(sha512.New, []byte(helper.bridge.Config.Bridge.LoginSharedSecret))
  84. mac.Write([]byte(helper.bridge.AS.BotMXID()))
  85. client, err := mautrix.NewClient(helper.bridge.AS.HomeserverURL, "", "")
  86. if err != nil {
  87. return nil, err
  88. }
  89. resp, err := client.Login(&mautrix.ReqLogin{
  90. Type: "m.login.password",
  91. Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: string(helper.bridge.AS.BotMXID())},
  92. Password: hex.EncodeToString(mac.Sum(nil)),
  93. DeviceID: deviceID,
  94. InitialDeviceDisplayName: "WhatsApp Bridge",
  95. StoreCredentials: true,
  96. })
  97. if err != nil {
  98. return nil, err
  99. }
  100. if len(deviceID) == 0 {
  101. helper.store.DeviceID = resp.DeviceID
  102. }
  103. return client, nil
  104. }
  105. func (helper *CryptoHelper) Start() {
  106. helper.log.Debugln("Starting syncer for receiving to-device messages")
  107. err := helper.client.Sync()
  108. if err != nil {
  109. helper.log.Errorln("Fatal error syncing:", err)
  110. }
  111. }
  112. func (helper *CryptoHelper) Stop() {
  113. helper.client.StopSync()
  114. }
  115. func (helper *CryptoHelper) Decrypt(evt *event.Event) (*event.Event, error) {
  116. return helper.mach.DecryptMegolmEvent(evt)
  117. }
  118. func (helper *CryptoHelper) Encrypt(roomID id.RoomID, evtType event.Type, content event.Content) (*event.EncryptedEventContent, error) {
  119. encrypted, err := helper.mach.EncryptMegolmEvent(roomID, evtType, &content)
  120. if err != nil {
  121. if err != crypto.SessionExpired && err != crypto.SessionNotShared && err != crypto.NoGroupSession {
  122. return nil, err
  123. }
  124. helper.log.Debugfln("Got %v while encrypting event for %s, sharing group session and trying again...", err, roomID)
  125. users, err := helper.store.GetRoomMembers(roomID)
  126. if err != nil {
  127. return nil, errors.Wrap(err, "failed to get room member list")
  128. }
  129. err = helper.mach.ShareGroupSession(roomID, users)
  130. if err != nil {
  131. return nil, errors.Wrap(err, "failed to share group session")
  132. }
  133. encrypted, err = helper.mach.EncryptMegolmEvent(roomID, evtType, &content)
  134. if err != nil {
  135. return nil, errors.Wrap(err, "failed to encrypt event after re-sharing group session")
  136. }
  137. }
  138. return encrypted, nil
  139. }
  140. func (helper *CryptoHelper) HandleMemberEvent(evt *event.Event) {
  141. helper.mach.HandleMemberEvent(evt)
  142. }
  143. type cryptoSyncer struct {
  144. *crypto.OlmMachine
  145. }
  146. func (syncer *cryptoSyncer) ProcessResponse(resp *mautrix.RespSync, since string) error {
  147. syncer.ProcessSyncResponse(resp, since)
  148. return nil
  149. }
  150. func (syncer *cryptoSyncer) OnFailedSync(_ *mautrix.RespSync, err error) (time.Duration, error) {
  151. syncer.Log.Error("Error /syncing, waiting 10 seconds: %v", err)
  152. return 10 * time.Second, nil
  153. }
  154. func (syncer *cryptoSyncer) GetFilterJSON(_ id.UserID) *mautrix.Filter {
  155. everything := []event.Type{{Type: "*"}}
  156. return &mautrix.Filter{
  157. Presence: mautrix.FilterPart{NotTypes: everything},
  158. AccountData: mautrix.FilterPart{NotTypes: everything},
  159. Room: mautrix.RoomFilter{
  160. IncludeLeave: false,
  161. Ephemeral: mautrix.FilterPart{NotTypes: everything},
  162. AccountData: mautrix.FilterPart{NotTypes: everything},
  163. State: mautrix.FilterPart{NotTypes: everything},
  164. Timeline: mautrix.FilterPart{NotTypes: everything},
  165. },
  166. }
  167. }
  168. type cryptoLogger struct {
  169. int maulogger.Logger
  170. }
  171. func (c *cryptoLogger) Error(message string, args ...interface{}) {
  172. c.int.Errorfln(message, args...)
  173. }
  174. func (c *cryptoLogger) Warn(message string, args ...interface{}) {
  175. c.int.Warnfln(message, args...)
  176. }
  177. func (c *cryptoLogger) Debug(message string, args ...interface{}) {
  178. c.int.Debugfln(message, args...)
  179. }
  180. func (c *cryptoLogger) Trace(message string, args ...interface{}) {
  181. c.int.Logfln(levelTrace, message, args...)
  182. }
  183. type cryptoClientStore struct {
  184. int *database.SQLCryptoStore
  185. }
  186. func (c cryptoClientStore) SaveFilterID(_ id.UserID, _ string) {}
  187. func (c cryptoClientStore) LoadFilterID(_ id.UserID) string { return "" }
  188. func (c cryptoClientStore) SaveRoom(_ *mautrix.Room) {}
  189. func (c cryptoClientStore) LoadRoom(_ id.RoomID) *mautrix.Room { return nil }
  190. func (c cryptoClientStore) SaveNextBatch(_ id.UserID, nextBatchToken string) {
  191. c.int.PutNextBatch(nextBatchToken)
  192. }
  193. func (c cryptoClientStore) LoadNextBatch(_ id.UserID) string {
  194. return c.int.GetNextBatch()
  195. }
  196. var _ mautrix.Storer = (*cryptoClientStore)(nil)
  197. type cryptoStateStore struct {
  198. bridge *Bridge
  199. }
  200. var _ crypto.StateStore = (*cryptoStateStore)(nil)
  201. func (c *cryptoStateStore) IsEncrypted(id id.RoomID) bool {
  202. portal := c.bridge.GetPortalByMXID(id)
  203. if portal != nil {
  204. return portal.Encrypted
  205. }
  206. return false
  207. }
  208. func (c *cryptoStateStore) FindSharedRooms(id id.UserID) []id.RoomID {
  209. return c.bridge.StateStore.FindSharedRooms(id)
  210. }
  211. func (c *cryptoStateStore) GetEncryptionEvent(id.RoomID) *event.EncryptionEventContent {
  212. // TODO implement
  213. return nil
  214. }