Page MenuHomePhorge

D173.1726848531.diff
No OneTemporary

D173.1726848531.diff

diff --git a/design-docs/undecryptable-event-fallback.md b/design-docs/undecryptable-event-fallback.md
new file mode 100644
--- /dev/null
+++ b/design-docs/undecryptable-event-fallback.md
@@ -0,0 +1,85 @@
+
+# Undecryptable event fallbacks
+
+For events that cannot be decrypted, `event.decrypted()` will be false, `event.type()` will be `m.room.message`, and `event.content().get().at("msgtype")` can have the following values:
+
+## xyz.tusooa.kazv.not.yet.decrypted
+
+This means we haven't even tried to decrypt this event. The most probable case is that we do not have the corresponding session key.
+
+## moe.kazv.mxc.cannot.decrypt
+
+This means that the corresponding session is known, but there are errors in the decryption.
+
+The event content will have the following properties:
+
+- `moe.kazv.mxc.errcode` The error code.
+- `moe.kazv.mxc.error` The error message.
+- `moe.kazv.mxc.raw` The raw data associated with the error.
+
+### Possible error codes
+#### `MOE.KAZV.MXC_DECRYPT_ERROR`
+
+There is an error from libkazvcrypto when decrypting the event.
+
+`moe.kazv.mxc.error` contains the error message.
+
+`moe.kazv.mxc.raw` is always `null`.
+
+#### `M_NOT_JSON`
+
+The decrypted message is not valid json.
+
+`moe.kazv.mxc.error` contains the error message from the json library when parsing the decrypted message.
+
+`moe.kazv.mxc.raw` contains the decrypted message as string.
+
+#### `MOE.KAZV.MXC_DEVICE_KEY_UNKNOWN`
+
+The sender's device key is unknown. Only for olm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_BAD_SENDER`
+
+The sender specified in the decrypted event does not match the sender in the original event. Only for olm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_BAD_RECIPIENT`
+
+The recipient specified in the decrypted event does not match the current user. Only for olm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_BAD_RECIPIENT_KEYS`
+
+The recipient keys specified in the decrypted event do not match the current user. Only for olm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_BAD_SENDER_KEYS`
+
+The sender keys specified in the decrypted event do not match the sender keys we received from the homeserver. Only for olm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_BAD_ROOM_ID`
+
+The room id specified in the decrypted event do not match the one we received from the homeserver. Only for megolm-encrypted events.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `MOE.KAZV.MXC_UNKNOWN_ALGORITHM`
+
+The encryption algorithm is unknown.
+
+`moe.kazv.mxc.raw` is the decrypted json.
+
+#### `M_BAD_JSON`
+
+The decrypted json does not have a valid format.
+
+`moe.kazv.mxc.error` contains the error message from the json library when accessing required properties (e.g. `at()` or `template get()`).
+
+`moe.kazv.mxc.raw` is the decrypted json.
diff --git a/src/client/actions/encryption.cpp b/src/client/actions/encryption.cpp
--- a/src/client/actions/encryption.cpp
+++ b/src/client/actions/encryption.cpp
@@ -159,19 +159,26 @@
return { std::move(m), lager::noop };
}
- static JsonWrap cannotDecryptEvent(std::string reason)
+ static JsonWrap cannotDecryptEvent(
+ const std::string &reason,
+ const std::string &errcode,
+ const json &raw)
{
return json{
{"type", "m.room.message"},
{"content", {
{"msgtype","moe.kazv.mxc.cannot.decrypt"},
- {"body", "**This message cannot be decrypted due to " + reason + ".**"}}}};
+ {"body", "**This message cannot be decrypted due to " + reason + ".**"},
+ {"moe.kazv.mxc.error", reason},
+ {"moe.kazv.mxc.errcode", errcode},
+ {"moe.kazv.mxc.raw", raw},
+ }},
+ };
}
- static bool verifyEvent(ClientModel &m, Event e, const json &plainJson)
+ // returns std::nullopt on success, and an error event on failure
+ static std::optional<JsonWrap> verifyEvent(ClientModel &m, Event e, const json &plainJson)
{
- bool valid = true;
-
try {
std::string algo = e.originalJson().get().at("content").at("algorithm");
if (algo == olmAlgo) {
@@ -183,44 +190,76 @@
if (! deviceInfoOpt) {
kzo.client.dbg() << "Device key " << senderCurve25519Key
<< " unknown, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "device key unknown",
+ "MOE.KAZV.MXC_DEVICE_KEY_UNKNOWN",
+ plainJson
+ );
}
auto deviceInfo = deviceInfoOpt.value();
if (! (plainJson.at("sender") == e.sender())) {
kzo.client.dbg() << "Sender does not match, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "sender does not match",
+ "MOE.KAZV.MXC_BAD_SENDER",
+ plainJson
+ );
}
if (! (plainJson.at("recipient") == m.userId)) {
kzo.client.dbg() << "Recipient does not match, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "recipient does not match",
+ "MOE.KAZV.MXC_BAD_RECIPIENT",
+ plainJson
+ );
}
if (! (plainJson.at("recipient_keys").at(ed25519) == m.constCrypto().ed25519IdentityKey())) {
kzo.client.dbg() << "Recipient key does not match, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "recipient keys do not match",
+ "MOE.KAZV.MXC_BAD_RECIPIENT_KEYS",
+ plainJson
+ );
}
auto thisEd25519Key = plainJson.at("keys").at(ed25519).get<std::string>();
if (thisEd25519Key != deviceInfo.ed25519Key) {
kzo.client.dbg() << "Sender ed25519 key does not match, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "sender keys do not match",
+ "MOE.KAZV.MXC_BAD_SENDER_KEYS",
+ plainJson
+ );
}
} else if (algo == megOlmAlgo) {
if (! (plainJson.at("room_id").get<std::string>() ==
e.originalJson().get().at("room_id").get<std::string>())) {
kzo.client.dbg() << "Room id does not match, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "room id does not match",
+ "MOE.KAZV.MXC_BAD_ROOM_ID",
+ plainJson
+ );
}
} else {
kzo.client.dbg() << "Unknown algorithm, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ "unknown algorithm",
+ "MOE.KAZV.MXC_UNKNOWN_ALGORITHM",
+ plainJson
+ );
}
- } catch (const std::exception &) {
+ } catch (const std::exception &exception) {
kzo.client.dbg() << "json format is not correct, thus invalid" << std::endl;
- valid = false;
+ return cannotDecryptEvent(
+ exception.what(),
+ "M_BAD_JSON",
+ plainJson
+ );
}
- return valid;
+ return std::nullopt;
}
static Event decryptEvent(ClientModel &m, Event e)
@@ -239,16 +278,36 @@
if (! maybePlainText) {
kzo.client.dbg() << "Cannot decrypt: " << maybePlainText.reason() << std::endl;
- return e.setDecryptedJson(cannotDecryptEvent(maybePlainText.reason()), Event::NotDecrypted);
+ return e.setDecryptedJson(
+ cannotDecryptEvent(
+ maybePlainText.reason(),
+ "MOE.KAZV.MXC_DECRYPT_ERROR",
+ json(nullptr)
+ ),
+ Event::NotDecrypted);
} else {
- auto plainJson = json::parse(maybePlainText.value());
- auto valid = verifyEvent(m, e, plainJson);
- if (valid) {
- kzo.client.dbg() << "The decrypted event is valid." << std::endl;
+ try {
+ auto plainJson = json::parse(maybePlainText.value());
+ auto error = verifyEvent(m, e, plainJson);
+ auto valid = !error.has_value();
+ if (valid) {
+ kzo.client.dbg() << "The decrypted event is valid." << std::endl;
+ }
+ return valid
+ ? e.setDecryptedJson(plainJson, Event::Decrypted)
+ : e.setDecryptedJson(
+ error.value(),
+ Event::NotDecrypted);
+ } catch (const std::exception &exception) {
+ return e.setDecryptedJson(
+ cannotDecryptEvent(
+ exception.what(),
+ "M_NOT_JSON",
+ maybePlainText.value()
+ ),
+ Event::NotDecrypted
+ );
}
- return valid
- ? e.setDecryptedJson(plainJson, Event::Decrypted)
- : e.setDecryptedJson(cannotDecryptEvent("invalid event"), Event::NotDecrypted);
}
}
@@ -313,10 +372,9 @@
zug::filter([&](auto eventId) {
auto event = room.messages[eventId];
auto decrypted = decryptFunc(event);
- if (decrypted.decrypted()) {
- room.messages = std::move(room.messages)
- .set(eventId, decrypted);
- }
+ room.messages = std::move(room.messages)
+ .set(eventId, decrypted);
+
return !decrypted.decrypted();
}),
eventIds
diff --git a/src/tests/client/sync-test.cpp b/src/tests/client/sync-test.cpp
--- a/src/tests/client/sync-test.cpp
+++ b/src/tests/client/sync-test.cpp
@@ -18,6 +18,7 @@
#include <sdk-model.hpp>
#include <client/client.hpp>
#include <client/actions/sync.hpp>
+#include <outbound-group-session.hpp>
#include "client-test-util.hpp"
#include "factory.hpp"
@@ -652,3 +653,93 @@
REQUIRE(room.timelineGaps.size() == 0);
REQUIRE(room.messages.count("$1") != 0);
}
+
+TEST_CASE("it does not crash when receiving a malformed encrypted event")
+{
+ auto crypto = makeCrypto();
+ auto ogs = OutboundGroupSession(
+ RandomTag{},
+ genRandomData(crypto.rotateMegOlmSessionRandomSize()),
+ 0);
+ REQUIRE(ogs.valid());
+
+ crypto.createInboundGroupSession(
+ KeyOfGroupSession{"!foo:example.com", ogs.sessionId()},
+ ogs.sessionKey(),
+ crypto.ed25519IdentityKey()
+ );
+
+ ClientModel m = makeClient(
+ withCrypto(std::move(crypto))
+ | withRoom(makeRoom(
+ withRoomId("!foo:example.com")
+ | withRoomEncrypted(true))));
+
+ auto [validEvent, ignore] = m.megOlmEncrypt(Event(json{
+ {"content", {{"body", "foo"}}},
+ {"type", "m.room.message"},
+ {"sender", "@foo:example.com"},
+ {"origin_server_ts", 0},
+ }), "!foo:example.com", 0, genRandomData(EncryptMegOlmEventAction::maxRandomSize()));
+ auto validEventJson = validEvent.originalJson().get();
+ validEventJson["event_id"] = "$0";
+
+ auto [plainText, exceptedErrorCode] = GENERATE(
+ table<std::string, std::string>({
+ {"not-json", "M_NOT_JSON"},
+ {"null", "M_BAD_JSON"},
+ {"{}", "M_BAD_JSON"},
+ {R"_({"room_id": "!other:example.com"})_", "MOE.KAZV.MXC_BAD_ROOM_ID"},
+ }));
+ auto encrypted = ogs.encrypt(plainText);
+
+ auto encryptedEventJson = json{
+ {"content", {
+ {"algorithm", CryptoConstants::megOlmAlgo},
+ {"ciphertext", encrypted},
+ {"session_id", ogs.sessionId()},
+ }},
+ {"event_id", "$1"},
+ {"sender", "@foo:example.com"},
+ {"type", "m.room.encrypted"},
+ {"origin_server_ts", 0},
+ };
+
+ auto body = json{
+ {"device_one_time_keys_count", {
+ {"signed_curve25519", 721},
+ }},
+ {"device_unused_fallback_key_types", nullptr},
+ {"next_batch", "some"},
+ {"rooms", {
+ {"join", {
+ {"!foo:example.com", {
+ {"timeline", {
+ {"events", {
+ validEventJson,
+ encryptedEventJson,
+ }},
+ {"prev_batch", "prev-batch"},
+ }},
+ }},
+ }},
+ }},
+ };
+
+ auto resp = makeResponse(
+ "Sync",
+ withResponseJsonBody(body)
+ | withResponseDataKV("is", "incremental")
+ );
+
+ auto [next, _] = processResponse(m, SyncResponse{resp});
+
+ auto messagesMap = next.roomList.rooms.at("!foo:example.com").messages;
+ REQUIRE(messagesMap.at("$0").decrypted());
+
+ REQUIRE(messagesMap.count("$1"));
+
+ auto event = messagesMap.at("$1");
+ REQUIRE(!event.decrypted());
+ REQUIRE(event.content().get().at("moe.kazv.mxc.errcode") == exceptedErrorCode);
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Sep 20, 9:08 AM (18 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16067
Default Alt Text
D173.1726848531.diff (14 KB)

Event Timeline