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.

353 lines
12 KiB

  1. // Package cryptotest implements a Service which provides several commands for testing the e2e functionalities of other devices.
  2. package cryptotest
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "strconv"
  7. "time"
  8. "github.com/matrix-org/go-neb/clients"
  9. "github.com/matrix-org/go-neb/types"
  10. log "github.com/sirupsen/logrus"
  11. "maunium.net/go/mautrix"
  12. "maunium.net/go/mautrix/crypto"
  13. mevt "maunium.net/go/mautrix/event"
  14. "maunium.net/go/mautrix/id"
  15. )
  16. // ServiceType of the Cryptotest service
  17. const ServiceType = "cryptotest"
  18. var expectedString map[id.RoomID]string
  19. var helpMsgs = map[string]string{
  20. "crypto_help": ": Displays the help message",
  21. "crypto_challenge": "[prefix] : The bot sets a random challenge for the room and echoes it. " +
  22. "The client tested should respond with \"!crypto_response challenge\"." +
  23. "Alternatively the prefix that the challenge will be echoed with can be set.",
  24. "crypto_response": "<challenge> : Should repeat the crypto_challenge's challenge code.",
  25. "crypto_new_session": ": Asks the bot to invalidate its current outgoing group session and create a new one.",
  26. "sas_verify_me": "<device_id> : Asks the bot to start a decimal SAS verification transaction with the sender's specified device.",
  27. "sas_decimal_code": "<device_id> <sas1> <sas2> <sas3> : Sends the device's generated decimal SAS code for the bot to verify, " +
  28. "after a \"!sas_verify_me\" command.",
  29. "request_my_room_key": "<device_id> <sender_key> <session_id> : Asks the bot to request the room key for the current room " +
  30. "and given sender key and session ID from the sender's given device.",
  31. "forward_me_room_key": "<device_id> <sender_key> <session_id> : Asks the bot to send the room key for the current room " +
  32. "and given sender key and session ID to the sender's given device.",
  33. }
  34. // Service represents the Cryptotest service. It has no Config fields.
  35. type Service struct {
  36. types.DefaultService
  37. Rooms []id.RoomID `json:"rooms"`
  38. }
  39. func randomString() (res string) {
  40. for i := 0; i < 10; i++ {
  41. res += string(rune(rand.Intn('Z'-'A') + 'A'))
  42. }
  43. return
  44. }
  45. func (s *Service) inRoom(roomID id.RoomID) bool {
  46. for _, joinedRoomID := range s.Rooms {
  47. if joinedRoomID == roomID {
  48. return true
  49. }
  50. }
  51. return false
  52. }
  53. func (s *Service) handleEventMessage(source mautrix.EventSource, evt *mevt.Event) {
  54. log.Infof("got a %v", evt.Content.AsMessage().Body)
  55. }
  56. func (s *Service) cmdCryptoHelp(roomID id.RoomID) (interface{}, error) {
  57. if s.inRoom(roomID) {
  58. helpTxt := "Supported crypto test methods:\n\n"
  59. for cmd, helpMsg := range helpMsgs {
  60. helpTxt += fmt.Sprintf("!%v %v\n\n", cmd, helpMsg)
  61. }
  62. return mevt.MessageEventContent{MsgType: mevt.MsgText, Body: helpTxt}, nil
  63. }
  64. return nil, nil
  65. }
  66. func (s *Service) cmdCryptoChallenge(roomID id.RoomID, arguments []string) (interface{}, error) {
  67. if s.inRoom(roomID) {
  68. randStr := randomString()
  69. log.Infof("Setting challenge for room %v: %v", roomID, expectedString)
  70. expectedString[roomID] = randStr
  71. prefix := "!challenge"
  72. if len(arguments) > 0 {
  73. prefix = arguments[0]
  74. }
  75. return mevt.MessageEventContent{MsgType: mevt.MsgText, Body: fmt.Sprintf("%v %v", prefix, randStr)}, nil
  76. }
  77. return nil, nil
  78. }
  79. func (s *Service) cmdCryptoResponse(userID id.UserID, roomID id.RoomID, arguments []string) (interface{}, error) {
  80. if s.inRoom(roomID) {
  81. if len(arguments) != 1 {
  82. return mevt.MessageEventContent{
  83. MsgType: mevt.MsgText,
  84. Body: "!crypto_response " + helpMsgs["crypto_response"],
  85. }, nil
  86. }
  87. if arguments[0] == expectedString[roomID] {
  88. return mevt.MessageEventContent{
  89. MsgType: mevt.MsgText,
  90. Body: fmt.Sprintf("Correct response received from %v", userID.String()),
  91. }, nil
  92. }
  93. return mevt.MessageEventContent{
  94. MsgType: mevt.MsgText,
  95. Body: fmt.Sprintf("Incorrect response received from %v", userID.String()),
  96. }, nil
  97. }
  98. return nil, nil
  99. }
  100. func (s *Service) cmdCryptoNewSession(botClient *clients.BotClient, roomID id.RoomID) (interface{}, error) {
  101. if s.inRoom(roomID) {
  102. sessionID, err := botClient.InvalidateRoomSession(roomID)
  103. if err != nil {
  104. log.WithField("room_id", roomID).Errorf("Error invalidating session ID: %v", err)
  105. return mevt.MessageEventContent{MsgType: mevt.MsgText, Body: fmt.Sprintf("Error invalidating session ID: %v", sessionID)}, nil
  106. }
  107. return mevt.MessageEventContent{
  108. MsgType: mevt.MsgText,
  109. Body: fmt.Sprintf("Invalidated previous session ID (%v)", sessionID),
  110. }, nil
  111. }
  112. return nil, nil
  113. }
  114. func (s *Service) cmdSASVerifyMe(botClient *clients.BotClient, roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  115. if s.inRoom(roomID) {
  116. if len(arguments) != 1 {
  117. return mevt.MessageEventContent{
  118. MsgType: mevt.MsgText,
  119. Body: "sas_verify_me " + helpMsgs["sas_verify_me"],
  120. }, nil
  121. }
  122. deviceID := id.DeviceID(arguments[0])
  123. transaction, err := botClient.StartSASVerification(userID, deviceID)
  124. if err != nil {
  125. log.WithFields(log.Fields{"user_id": userID, "device_id": deviceID}).WithError(err).Error("Error starting SAS verification")
  126. return mevt.MessageEventContent{
  127. MsgType: mevt.MsgText,
  128. Body: fmt.Sprintf("Error starting SAS verification: %v", err),
  129. }, nil
  130. }
  131. return mevt.MessageEventContent{
  132. MsgType: mevt.MsgText,
  133. Body: fmt.Sprintf("Started SAS verification with user %v device %v: transaction %v", userID, deviceID, transaction),
  134. }, nil
  135. }
  136. return nil, nil
  137. }
  138. func (s *Service) cmdSASVerifyDecimalCode(botClient *clients.BotClient, roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  139. if s.inRoom(roomID) {
  140. if len(arguments) != 4 {
  141. return mevt.MessageEventContent{
  142. MsgType: mevt.MsgText,
  143. Body: "sas_decimal_code " + helpMsgs["sas_decimal_code"],
  144. }, nil
  145. }
  146. deviceID := id.DeviceID(arguments[0])
  147. var decimalSAS crypto.DecimalSASData
  148. for i := 0; i < 3; i++ {
  149. sasCode, err := strconv.Atoi(arguments[i+1])
  150. if err != nil {
  151. log.WithFields(log.Fields{"user_id": userID, "device_id": deviceID}).WithError(err).Error("Error reading SAS code")
  152. return mevt.MessageEventContent{
  153. MsgType: mevt.MsgText,
  154. Body: fmt.Sprintf("Error reading SAS code: %v", err),
  155. }, nil
  156. }
  157. decimalSAS[i] = uint(sasCode)
  158. }
  159. botClient.SubmitDecimalSAS(userID, deviceID, decimalSAS)
  160. return mevt.MessageEventContent{
  161. MsgType: mevt.MsgText,
  162. Body: fmt.Sprintf("Read SAS code from user %v device %v: %v", userID, deviceID, decimalSAS),
  163. }, nil
  164. }
  165. return nil, nil
  166. }
  167. func (s *Service) cmdRequestRoomKey(botClient *clients.BotClient, roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  168. if s.inRoom(roomID) {
  169. if len(arguments) != 3 {
  170. return mevt.MessageEventContent{
  171. MsgType: mevt.MsgText,
  172. Body: "request_my_room_key " + helpMsgs["request_my_room_key"],
  173. }, nil
  174. }
  175. deviceID := id.DeviceID(arguments[0])
  176. senderKey := id.SenderKey(arguments[1])
  177. sessionID := id.SessionID(arguments[2])
  178. receivedChan, err := botClient.SendRoomKeyRequest(userID, deviceID, roomID, senderKey, sessionID, time.Minute)
  179. if err != nil {
  180. log.WithFields(log.Fields{
  181. "user_id": userID,
  182. "device_id": deviceID,
  183. "sender_key": senderKey,
  184. "session_id": sessionID,
  185. }).WithError(err).Error("Error requesting room key")
  186. return mevt.MessageEventContent{
  187. MsgType: mevt.MsgText,
  188. Body: fmt.Sprintf("Error requesting room key for session %v: %v", sessionID, err),
  189. }, nil
  190. }
  191. go func() {
  192. var result string
  193. received := <-receivedChan
  194. if received {
  195. result = "Key received successfully!"
  196. } else {
  197. result = "Key was not received in the time limit"
  198. }
  199. content := mevt.MessageEventContent{
  200. MsgType: mevt.MsgText,
  201. Body: fmt.Sprintf("Room key request for session %v result: %v", sessionID, result),
  202. }
  203. if _, err := botClient.SendMessageEvent(roomID, mevt.EventMessage, content); err != nil {
  204. log.WithFields(log.Fields{
  205. "room_id": roomID,
  206. "content": content,
  207. }).WithError(err).Error("Failed to send room key request result to room")
  208. }
  209. }()
  210. return mevt.MessageEventContent{
  211. MsgType: mevt.MsgText,
  212. Body: fmt.Sprintf("Sent room key request for session %v to device %v", sessionID, deviceID),
  213. }, nil
  214. }
  215. return nil, nil
  216. }
  217. func (s *Service) cmdForwardRoomKey(botClient *clients.BotClient, roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  218. if s.inRoom(roomID) {
  219. if len(arguments) != 3 {
  220. return mevt.MessageEventContent{
  221. MsgType: mevt.MsgText,
  222. Body: "forward_me_room_key " + helpMsgs["forward_me_room_key"],
  223. }, nil
  224. }
  225. deviceID := id.DeviceID(arguments[0])
  226. senderKey := id.SenderKey(arguments[1])
  227. sessionID := id.SessionID(arguments[2])
  228. err := botClient.ForwardRoomKeyToDevice(userID, deviceID, roomID, senderKey, sessionID)
  229. if err != nil {
  230. log.WithFields(log.Fields{
  231. "user_id": userID,
  232. "device_id": deviceID,
  233. "sender_key": senderKey,
  234. "session_id": sessionID,
  235. }).WithError(err).Error("Error forwarding room key")
  236. return mevt.MessageEventContent{
  237. MsgType: mevt.MsgText,
  238. Body: fmt.Sprintf("Error forwarding room key for session %v: %v", sessionID, err),
  239. }, nil
  240. }
  241. return mevt.MessageEventContent{
  242. MsgType: mevt.MsgText,
  243. Body: fmt.Sprintf("Forwarded room key for session %v to device %v", sessionID, deviceID),
  244. }, nil
  245. }
  246. return nil, nil
  247. }
  248. // Commands supported:
  249. // !crypto_help Displays a help string
  250. // !crypto_challenge Sets a challenge for a room which clients should reply to with !crypto_response
  251. // !crypto_response Used by the client to repeat the room challenge
  252. // !crypto_new_session Invalidates the bot's current outgoing session
  253. // !sas_verify_me Asks the bot to verify the sender
  254. // !sas_decimal_code Sends the sender's SAS code to the bot for verification
  255. // !request_my_room_key Asks the bot to request a room key from the sender
  256. // !forward_me_room_key Asks the bot to forward a room key to the sender
  257. // This service can be used for testing other clients by writing the commands above in a room where this service is enabled.
  258. func (s *Service) Commands(cli types.MatrixClient) []types.Command {
  259. botClient := cli.(*clients.BotClient)
  260. return []types.Command{
  261. {
  262. Path: []string{"crypto_help"},
  263. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  264. return s.cmdCryptoHelp(roomID)
  265. },
  266. },
  267. {
  268. Path: []string{"crypto_challenge"},
  269. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  270. return s.cmdCryptoChallenge(roomID, arguments)
  271. },
  272. },
  273. {
  274. Path: []string{"crypto_response"},
  275. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  276. return s.cmdCryptoResponse(userID, roomID, arguments)
  277. },
  278. },
  279. {
  280. Path: []string{"crypto_new_session"},
  281. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  282. return s.cmdCryptoNewSession(botClient, roomID)
  283. },
  284. },
  285. {
  286. Path: []string{"sas_verify_me"},
  287. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  288. return s.cmdSASVerifyMe(botClient, roomID, userID, arguments)
  289. },
  290. },
  291. {
  292. Path: []string{"sas_decimal_code"},
  293. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  294. return s.cmdSASVerifyDecimalCode(botClient, roomID, userID, arguments)
  295. },
  296. },
  297. {
  298. Path: []string{"request_my_room_key"},
  299. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  300. return s.cmdRequestRoomKey(botClient, roomID, userID, arguments)
  301. },
  302. },
  303. {
  304. Path: []string{"forward_me_room_key"},
  305. Command: func(roomID id.RoomID, userID id.UserID, arguments []string) (interface{}, error) {
  306. return s.cmdForwardRoomKey(botClient, roomID, userID, arguments)
  307. },
  308. },
  309. }
  310. }
  311. // Register registers
  312. func (s *Service) Register(oldService types.Service, client types.MatrixClient) error {
  313. botClient := client.(*clients.BotClient)
  314. botClient.Syncer.(mautrix.ExtensibleSyncer).OnEventType(mevt.EventMessage, s.handleEventMessage)
  315. for _, roomID := range s.Rooms {
  316. if _, err := client.JoinRoom(roomID.String(), "", nil); err != nil {
  317. log.WithFields(log.Fields{
  318. log.ErrorKey: err,
  319. "room_id": roomID,
  320. }).Error("Failed to join room")
  321. }
  322. }
  323. return nil
  324. }
  325. func init() {
  326. expectedString = make(map[id.RoomID]string)
  327. types.RegisterService(func(serviceID string, serviceUserID id.UserID, webhookEndpointURL string) types.Service {
  328. s := &Service{
  329. DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
  330. }
  331. return s
  332. })
  333. }