From daf690e4dca943b54f1a4fd73f60ea09e07e4fa1 Mon Sep 17 00:00:00 2001 From: Nikos Filippakis Date: Wed, 1 Jul 2020 20:21:46 +0200 Subject: [PATCH] Integration test for receiving and sending encrypted messages Signed-off-by: Nikos Filippakis --- goneb_services_test.go | 204 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 4 deletions(-) diff --git a/goneb_services_test.go b/goneb_services_test.go index 130edd1..b392921 100644 --- a/goneb_services_test.go +++ b/goneb_services_test.go @@ -2,10 +2,18 @@ package main import ( "bytes" + "fmt" + "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" + + "github.com/matrix-org/go-neb/clients" + "github.com/matrix-org/go-neb/database" + "maunium.net/go/mautrix/crypto" + "maunium.net/go/mautrix/crypto/olm" + mevt "maunium.net/go/mautrix/event" ) func TestConfigureClient(t *testing.T) { @@ -80,15 +88,13 @@ func TestRespondToEcho(t *testing.T) { mxTripper.Handle("POST", "/_matrix/client/r0/join/*", func(req *http.Request) (*http.Response, error) { parts := strings.Split(req.URL.String(), "/") joinedRoom = parts[len(parts)-1] - n, _ := req.Body.Read(joinedRoomBody) - joinedRoomBody = joinedRoomBody[:n] + joinedRoomBody, _ = ioutil.ReadAll(req.Body) return newResponse(200, `{}`), nil }) var roomMsgBody []byte mxTripper.Handle("PUT", "/_matrix/client/r0/rooms/!greatdekutree:hyrule/send/m.room.message/*", func(req *http.Request) (*http.Response, error) { - n, _ := req.Body.Read(roomMsgBody) - roomMsgBody = roomMsgBody[:n] + roomMsgBody, _ = ioutil.ReadAll(req.Body) return newResponse(200, `{}`), nil }) @@ -168,3 +174,193 @@ func TestRespondToEcho(t *testing.T) { t.Errorf("Expected echo response to be `%v`, got `%v`", expectedEchoResp, string(roomMsgBody)) } } + +func TestEncryptedRespondToEcho(t *testing.T) { + mux := http.NewServeMux() + mxTripper := newMatrixTripper() + setup(envVars{ + BaseURL: "http://go.neb", + DatabaseType: "sqlite3", + DatabaseURL: ":memory:", + }, mux, &http.Client{ + Transport: mxTripper, + }) + + mxTripper.ClearHandlers() + mockWriter := httptest.NewRecorder() + reqChan := make(chan string) + mxTripper.HandlePOSTFilter("@link:hyrule") + mxTripper.Handle("GET", "/_matrix/client/r0/sync", + func(req *http.Request) (*http.Response, error) { + reqBody := <-reqChan + return newResponse(200, reqBody), nil + }, + ) + + // create the two accounts, inbound and outbound sessions, both the bot and mock ones + accountMock := olm.NewAccount() + accountBot := olm.NewAccount() + signingKeyMock, identityKeyMock := accountMock.IdentityKeys() + signingKeyBot, identityKeyBot := accountBot.IdentityKeys() + ogsBot := crypto.NewOutboundGroupSession("!greatdekutree:hyrule") + ogsBot.Shared = true + igsMock, err := crypto.NewInboundGroupSession(identityKeyBot, signingKeyBot, "!greatdekutree:hyrule", ogsBot.Internal.Key()) + if err != nil { + t.Errorf("Error creating mock IGS: %v", err) + } + ogsMock := crypto.NewOutboundGroupSession("!greatdekutree:hyrule") + ogsMock.Shared = true + igsBot, err := crypto.NewInboundGroupSession(identityKeyMock, signingKeyMock, "!greatdekutree:hyrule", ogsMock.Internal.Key()) + if err != nil { + t.Errorf("Error creating bot IGS: %v", err) + } + + mxTripper.Handle("POST", "/_matrix/client/r0/keys/upload", func(req *http.Request) (*http.Response, error) { + return newResponse(200, `{}`), nil + }) + + var joinedRoom string + var joinedRoomBody []byte + mxTripper.Handle("POST", "/_matrix/client/r0/join/*", func(req *http.Request) (*http.Response, error) { + parts := strings.Split(req.URL.String(), "/") + joinedRoom = parts[len(parts)-1] + joinedRoomBody, _ = ioutil.ReadAll(req.Body) + return newResponse(200, `{}`), nil + }) + + var decryptedMsg string + mxTripper.Handle("PUT", "/_matrix/client/r0/rooms/!greatdekutree:hyrule/send/m.room.encrypted/*", func(req *http.Request) (*http.Response, error) { + encryptedMsg, _ := ioutil.ReadAll(req.Body) + var encryptedContent mevt.EncryptedEventContent + encryptedContent.UnmarshalJSON(encryptedMsg) + decryptedMsgBytes, _, err := igsMock.Internal.Decrypt(encryptedContent.MegolmCiphertext) + if err != nil { + t.Errorf("Error decrypting message sent by bot: %v", err) + } + decryptedMsg = string(decryptedMsgBytes) + return newResponse(200, `{}`), nil + }) + + // configure the client + clientConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureClient", bytes.NewBufferString(` + { + "UserID":"@link:hyrule", + "DeviceID":"mastersword", + "HomeserverURL":"http://hyrule.loz", + "AccessToken":"dangeroustogoalone", + "Sync":true, + "AutoJoinRooms":true + }`)) + mux.ServeHTTP(mockWriter, clientConfigReq) + + // configure the echo service + serviceConfigReq, _ := http.NewRequest("POST", "http://go.neb/admin/configureService", bytes.NewBufferString(` + { + "Type": "echo", + "Id": "test_echo_service", + "UserID": "@link:hyrule", + "Config": {} + }`)) + mux.ServeHTTP(mockWriter, serviceConfigReq) + + // get the initial syncs out of the way + reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}` + reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}` + + // send neb an invite to a room + reqChan <- `{ + "next_batch":"11_22_33_44", + "rooms": { + "invite": { + "!greatdekutree:hyrule": {"invite_state": {"events": [{ + "type": "m.room.member", + "sender": "@navi:hyrule", + "content": {"membership": "invite"}, + "state_key": "@link:hyrule", + "origin_server_ts": 10000, + "unsigned": {"age": 100}, + "event_id": "evt123" + }]}}} + } + }` + + // wait for it to be processed + reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}` + + expectedRoom := "%21greatdekutree:hyrule" + if joinedRoom != expectedRoom { + t.Errorf("Expected join for room %v, got %v", expectedRoom, joinedRoom) + } + if expectedBody := `{"inviter":"@navi:hyrule"}`; string(joinedRoomBody) != expectedBody { + t.Errorf("Expected join message body to be %v, got %v", expectedBody, string(joinedRoomBody)) + } + + // send neb the room state: encrypted with one member + reqChan <- `{ + "next_batch":"11_22_33_44", + "rooms": { + "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{ + "type": "m.room.encryption", + "state_key": "", + "content": { + "algorithm": "m.megolm.v1.aes-sha2" + } + }, { + "type": "m.room.member", + "sender": "@navi:hyrule", + "content": {"membership": "join", "displayname": "Navi"}, + "state_key": "@navi:hyrule", + "origin_server_ts": 100, + "event_id": "evt124" + }]}}} + } + }` + + // wait for it to be processed + reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}` + + // DB is initialized, store the megolm sessions from before for the bot to be able to decrypt and encrypt + sqlDB, dialect := database.GetServiceDB().(*database.ServiceDB).GetSQLDb() + cryptoStore := crypto.NewSQLCryptoStore(sqlDB, dialect, "mastersword", []byte("masterswordpickle"), clients.CryptoMachineLogger{}) + if err := cryptoStore.AddOutboundGroupSession(ogsBot); err != nil { + t.Errorf("Error storing bot OGS: %v", err) + } + + if err := cryptoStore.PutGroupSession("!greatdekutree:hyrule", identityKeyMock, igsBot.ID(), igsBot); err != nil { + t.Errorf("Error storing bot IGS: %v", err) + } + + plaintext := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"body":"!echo save zelda","msgtype":"m.text"}}` + ciphertext, err := ogsMock.Encrypt([]byte(plaintext)) + if err != nil { + t.Errorf("Error encrypting bytes: %v", err) + } + + // send neb an !echo message, encrypted with our mock OGS which it has an IGS for + reqChan <- fmt.Sprintf(`{ + "next_batch":"11_22_33_44", + "rooms": { + "join": {"!greatdekutree:hyrule": {"timeline": {"events": [{ + "type": "m.room.encrypted", + "sender": "@navi:hyrule", + "content": { + "algorithm":"m.megolm.v1.aes-sha2", + "sender_key":"%s", + "ciphertext":"%s", + "session_id":"%s" + }, + "origin_server_ts": 10000, + "unsigned": {"age": 100}, + "event_id": "evt125" + }]}}} + } + }`, identityKeyMock, string(ciphertext), ogsMock.ID()) + + // wait for it to be processed + reqChan <- `{"next_batch":"11_22_33_44", "rooms": {}}` + + expectedDecryptedMsg := `{"room_id":"!greatdekutree:hyrule","type":"m.room.message","content":{"msgtype":"m.notice","body":"save zelda"}}` + if decryptedMsg != expectedDecryptedMsg { + t.Errorf("Expected decrypted message to be `%v`, got `%v`", expectedDecryptedMsg, decryptedMsg) + } +}