crypto.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. //go:build cgo && !nocrypto
  17. package main
  18. import (
  19. "fmt"
  20. "runtime/debug"
  21. "time"
  22. "github.com/lib/pq"
  23. "maunium.net/go/maulogger/v2"
  24. "maunium.net/go/mautrix"
  25. "maunium.net/go/mautrix/crypto"
  26. "maunium.net/go/mautrix/event"
  27. "maunium.net/go/mautrix/id"
  28. "maunium.net/go/mautrix-whatsapp/database"
  29. )
  30. var NoSessionFound = crypto.NoSessionFound
  31. var levelTrace = maulogger.Level{
  32. Name: "TRACE",
  33. Severity: -10,
  34. Color: -1,
  35. }
  36. type CryptoHelper struct {
  37. bridge *Bridge
  38. client *mautrix.Client
  39. mach *crypto.OlmMachine
  40. store *database.SQLCryptoStore
  41. log maulogger.Logger
  42. baseLog maulogger.Logger
  43. }
  44. func init() {
  45. crypto.PostgresArrayWrapper = pq.Array
  46. }
  47. func NewCryptoHelper(bridge *Bridge) Crypto {
  48. if !bridge.Config.Bridge.Encryption.Allow {
  49. bridge.Log.Debugln("Bridge built with end-to-bridge encryption, but disabled in config")
  50. return nil
  51. }
  52. baseLog := bridge.Log.Sub("Crypto")
  53. return &CryptoHelper{
  54. bridge: bridge,
  55. log: baseLog.Sub("Helper"),
  56. baseLog: baseLog,
  57. }
  58. }
  59. func (helper *CryptoHelper) Init() error {
  60. helper.log.Debugln("Initializing end-to-bridge encryption...")
  61. helper.store = database.NewSQLCryptoStore(helper.bridge.DB, helper.bridge.AS.BotMXID(),
  62. fmt.Sprintf("@%s:%s", helper.bridge.Config.Bridge.FormatUsername("%"), helper.bridge.AS.HomeserverDomain))
  63. var err error
  64. helper.client, err = helper.loginBot()
  65. if err != nil {
  66. return err
  67. }
  68. helper.log.Debugln("Logged in as bridge bot with device ID", helper.client.DeviceID)
  69. logger := &cryptoLogger{helper.baseLog}
  70. stateStore := &cryptoStateStore{helper.bridge}
  71. helper.mach = crypto.NewOlmMachine(helper.client, logger, helper.store, stateStore)
  72. helper.mach.AllowKeyShare = helper.allowKeyShare
  73. helper.client.Syncer = &cryptoSyncer{helper.mach}
  74. helper.client.Store = &cryptoClientStore{helper.store}
  75. return helper.mach.Load()
  76. }
  77. func (helper *CryptoHelper) allowKeyShare(device *crypto.DeviceIdentity, info event.RequestedKeyInfo) *crypto.KeyShareRejection {
  78. cfg := helper.bridge.Config.Bridge.Encryption.KeySharing
  79. if !cfg.Allow {
  80. return &crypto.KeyShareRejectNoResponse
  81. } else if device.Trust == crypto.TrustStateBlacklisted {
  82. return &crypto.KeyShareRejectBlacklisted
  83. } else if device.Trust == crypto.TrustStateVerified || !cfg.RequireVerification {
  84. portal := helper.bridge.GetPortalByMXID(info.RoomID)
  85. if portal == nil {
  86. helper.log.Debugfln("Rejecting key request for %s from %s/%s: room is not a portal", info.SessionID, device.UserID, device.DeviceID)
  87. return &crypto.KeyShareRejection{Code: event.RoomKeyWithheldUnavailable, Reason: "Requested room is not a portal room"}
  88. }
  89. user := helper.bridge.GetUserByMXID(device.UserID)
  90. // FIXME reimplement IsInPortal
  91. if !user.Admin /*&& !user.IsInPortal(portal.Key)*/ {
  92. helper.log.Debugfln("Rejecting key request for %s from %s/%s: user is not in portal", info.SessionID, device.UserID, device.DeviceID)
  93. return &crypto.KeyShareRejection{Code: event.RoomKeyWithheldUnauthorized, Reason: "You're not in that portal"}
  94. }
  95. helper.log.Debugfln("Accepting key request for %s from %s/%s", info.SessionID, device.UserID, device.DeviceID)
  96. return nil
  97. } else {
  98. return &crypto.KeyShareRejectUnverified
  99. }
  100. }
  101. func (helper *CryptoHelper) loginBot() (*mautrix.Client, error) {
  102. deviceID := helper.store.FindDeviceID()
  103. if len(deviceID) > 0 {
  104. helper.log.Debugln("Found existing device ID for bot in database:", deviceID)
  105. }
  106. client, err := mautrix.NewClient(helper.bridge.AS.HomeserverURL, "", "")
  107. if err != nil {
  108. return nil, fmt.Errorf("failed to initialize client: %w", err)
  109. }
  110. client.Logger = helper.baseLog.Sub("Bot")
  111. client.Client = helper.bridge.AS.HTTPClient
  112. client.DefaultHTTPRetries = helper.bridge.AS.DefaultHTTPRetries
  113. flows, err := client.GetLoginFlows()
  114. if err != nil {
  115. return nil, fmt.Errorf("failed to get supported login flows: %w", err)
  116. } else if !flows.HasFlow(mautrix.AuthTypeAppservice) {
  117. return nil, fmt.Errorf("homeserver does not support appservice login")
  118. }
  119. // We set the API token to the AS token here to authenticate the appservice login
  120. // It'll get overridden after the login
  121. client.AccessToken = helper.bridge.AS.Registration.AppToken
  122. resp, err := client.Login(&mautrix.ReqLogin{
  123. Type: mautrix.AuthTypeAppservice,
  124. Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
  125. DeviceID: deviceID,
  126. InitialDeviceDisplayName: "WhatsApp Bridge",
  127. StoreCredentials: true,
  128. })
  129. if err != nil {
  130. return nil, fmt.Errorf("failed to log in as bridge bot: %w", err)
  131. }
  132. helper.store.DeviceID = resp.DeviceID
  133. return client, nil
  134. }
  135. func (helper *CryptoHelper) Start() {
  136. helper.log.Debugln("Starting syncer for receiving to-device messages")
  137. err := helper.client.Sync()
  138. if err != nil {
  139. helper.log.Errorln("Fatal error syncing:", err)
  140. } else {
  141. helper.log.Infoln("Bridge bot to-device syncer stopped without error")
  142. }
  143. }
  144. func (helper *CryptoHelper) Stop() {
  145. helper.log.Debugln("CryptoHelper.Stop() called, stopping bridge bot sync")
  146. helper.client.StopSync()
  147. }
  148. func (helper *CryptoHelper) Decrypt(evt *event.Event) (*event.Event, error) {
  149. return helper.mach.DecryptMegolmEvent(evt)
  150. }
  151. func (helper *CryptoHelper) Encrypt(roomID id.RoomID, evtType event.Type, content event.Content) (*event.EncryptedEventContent, error) {
  152. encrypted, err := helper.mach.EncryptMegolmEvent(roomID, evtType, &content)
  153. if err != nil {
  154. if err != crypto.SessionExpired && err != crypto.SessionNotShared && err != crypto.NoGroupSession {
  155. return nil, err
  156. }
  157. helper.log.Debugfln("Got %v while encrypting event for %s, sharing group session and trying again...", err, roomID)
  158. users, err := helper.store.GetRoomMembers(roomID)
  159. if err != nil {
  160. return nil, fmt.Errorf("failed to get room member list: %w", err)
  161. }
  162. err = helper.mach.ShareGroupSession(roomID, users)
  163. if err != nil {
  164. return nil, fmt.Errorf("failed to share group session: %w", err)
  165. }
  166. encrypted, err = helper.mach.EncryptMegolmEvent(roomID, evtType, &content)
  167. if err != nil {
  168. return nil, fmt.Errorf("failed to encrypt event after re-sharing group session: %w", err)
  169. }
  170. }
  171. return encrypted, nil
  172. }
  173. func (helper *CryptoHelper) WaitForSession(roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, timeout time.Duration) bool {
  174. return helper.mach.WaitForSession(roomID, senderKey, sessionID, timeout)
  175. }
  176. func (helper *CryptoHelper) RequestSession(roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, userID id.UserID, deviceID id.DeviceID) {
  177. err := helper.mach.SendRoomKeyRequest(roomID, senderKey, sessionID, "", map[id.UserID][]id.DeviceID{userID: {deviceID}})
  178. if err != nil {
  179. helper.log.Warnfln("Failed to send key request to %s/%s for %s in %s: %v", userID, deviceID, sessionID, roomID, err)
  180. } else {
  181. helper.log.Debugfln("Sent key request to %s/%s for %s in %s", userID, deviceID, sessionID, roomID)
  182. }
  183. }
  184. func (helper *CryptoHelper) ResetSession(roomID id.RoomID) {
  185. err := helper.mach.CryptoStore.RemoveOutboundGroupSession(roomID)
  186. if err != nil {
  187. helper.log.Debugfln("Error manually removing outbound group session in %s: %v", roomID, err)
  188. }
  189. }
  190. func (helper *CryptoHelper) HandleMemberEvent(evt *event.Event) {
  191. helper.mach.HandleMemberEvent(evt)
  192. }
  193. type cryptoSyncer struct {
  194. *crypto.OlmMachine
  195. }
  196. func (syncer *cryptoSyncer) ProcessResponse(resp *mautrix.RespSync, since string) error {
  197. done := make(chan struct{})
  198. go func() {
  199. defer func() {
  200. if err := recover(); err != nil {
  201. syncer.Log.Error("Processing sync response (%s) panicked: %v\n%s", since, err, debug.Stack())
  202. }
  203. done <- struct{}{}
  204. }()
  205. syncer.Log.Trace("Starting sync response handling (%s)", since)
  206. syncer.ProcessSyncResponse(resp, since)
  207. syncer.Log.Trace("Successfully handled sync response (%s)", since)
  208. }()
  209. select {
  210. case <-done:
  211. case <-time.After(30 * time.Second):
  212. syncer.Log.Warn("Handling sync response (%s) is taking unusually long", since)
  213. }
  214. return nil
  215. }
  216. func (syncer *cryptoSyncer) OnFailedSync(_ *mautrix.RespSync, err error) (time.Duration, error) {
  217. syncer.Log.Error("Error /syncing, waiting 10 seconds: %v", err)
  218. return 10 * time.Second, nil
  219. }
  220. func (syncer *cryptoSyncer) GetFilterJSON(_ id.UserID) *mautrix.Filter {
  221. everything := []event.Type{{Type: "*"}}
  222. return &mautrix.Filter{
  223. Presence: mautrix.FilterPart{NotTypes: everything},
  224. AccountData: mautrix.FilterPart{NotTypes: everything},
  225. Room: mautrix.RoomFilter{
  226. IncludeLeave: false,
  227. Ephemeral: mautrix.FilterPart{NotTypes: everything},
  228. AccountData: mautrix.FilterPart{NotTypes: everything},
  229. State: mautrix.FilterPart{NotTypes: everything},
  230. Timeline: mautrix.FilterPart{NotTypes: everything},
  231. },
  232. }
  233. }
  234. type cryptoLogger struct {
  235. int maulogger.Logger
  236. }
  237. func (c *cryptoLogger) Error(message string, args ...interface{}) {
  238. c.int.Errorfln(message, args...)
  239. }
  240. func (c *cryptoLogger) Warn(message string, args ...interface{}) {
  241. c.int.Warnfln(message, args...)
  242. }
  243. func (c *cryptoLogger) Debug(message string, args ...interface{}) {
  244. c.int.Debugfln(message, args...)
  245. }
  246. func (c *cryptoLogger) Trace(message string, args ...interface{}) {
  247. c.int.Logfln(levelTrace, message, args...)
  248. }
  249. type cryptoClientStore struct {
  250. int *database.SQLCryptoStore
  251. }
  252. func (c cryptoClientStore) SaveFilterID(_ id.UserID, _ string) {}
  253. func (c cryptoClientStore) LoadFilterID(_ id.UserID) string { return "" }
  254. func (c cryptoClientStore) SaveRoom(_ *mautrix.Room) {}
  255. func (c cryptoClientStore) LoadRoom(_ id.RoomID) *mautrix.Room { return nil }
  256. func (c cryptoClientStore) SaveNextBatch(_ id.UserID, nextBatchToken string) {
  257. c.int.PutNextBatch(nextBatchToken)
  258. }
  259. func (c cryptoClientStore) LoadNextBatch(_ id.UserID) string {
  260. return c.int.GetNextBatch()
  261. }
  262. var _ mautrix.Storer = (*cryptoClientStore)(nil)
  263. type cryptoStateStore struct {
  264. bridge *Bridge
  265. }
  266. var _ crypto.StateStore = (*cryptoStateStore)(nil)
  267. func (c *cryptoStateStore) IsEncrypted(id id.RoomID) bool {
  268. portal := c.bridge.GetPortalByMXID(id)
  269. if portal != nil {
  270. return portal.Encrypted
  271. }
  272. return false
  273. }
  274. func (c *cryptoStateStore) FindSharedRooms(id id.UserID) []id.RoomID {
  275. return c.bridge.StateStore.FindSharedRooms(id)
  276. }
  277. func (c *cryptoStateStore) GetEncryptionEvent(id.RoomID) *event.EncryptionEventContent {
  278. // TODO implement
  279. return nil
  280. }