crypto.go 10 KB

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