From 0c0980f9c14e539cc430d5117ca3afda94b91bcb Mon Sep 17 00:00:00 2001
From: Konstantinos Sideris <sideris.konstantin@gmail.com>
Date: Sat, 19 May 2018 21:38:43 +0300
Subject: [PATCH] Fix typo with wrt signature verification

- Add initial version of crypto bot
- Add spdlog as dependency
---
 CMakeLists.txt                      |  14 +-
 README.md                           |   3 +-
 cmake/MatrixStructs.cmake           |   2 +-
 cmake/SpdLog.cmake                  |  23 ++
 examples/crypto_bot.cpp             | 439 ++++++++++++++++++++++++++++
 include/mtxclient/crypto/client.hpp |  20 +-
 include/mtxclient/crypto/types.hpp  |   5 +
 include/mtxclient/http/client.hpp   |   3 +-
 include/mtxclient/utils.hpp         |  29 ++
 lib/crypto/client.cpp               |  55 +++-
 tests/e2ee.cpp                      |  69 ++---
 tests/utils.cpp                     | 118 ++++++--
 12 files changed, 706 insertions(+), 74 deletions(-)
 create mode 100644 cmake/SpdLog.cmake
 create mode 100644 examples/crypto_bot.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index adac1f8a6..7c6330867 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -114,6 +114,14 @@ endif()
 include_directories(${MATRIX_STRUCTS_INCLUDE_DIR})
 set(MTXCLIENT_LIBS ${MTXCLIENT_LIBS} ${MATRIX_STRUCTS_LIBRARY})
 
+#
+# spdlog
+#
+if(NOT SPDLOG_INCLUDE_DIR)
+    include(SpdLog)
+    set(EXTERNAL_PROJECT_DEPS ${EXTERNAL_PROJECT_DEPS} SpdLog)
+endif()
+include_directories(SYSTEM ${SPDLOG_INCLUDE_DIR})
 
 #
 # libolm
@@ -145,9 +153,13 @@ endif()
 
 if (BUILD_LIB_EXAMPLES)
     add_executable(room_feed examples/room_feed.cpp)
-    add_executable(simple_bot examples/simple_bot.cpp)
     target_link_libraries(room_feed matrix_client ${MATRIX_STRUCTS_LIBRARY})
+
+    add_executable(simple_bot examples/simple_bot.cpp)
     target_link_libraries(simple_bot matrix_client ${MATRIX_STRUCTS_LIBRARY})
+
+    add_executable(crypto_bot examples/crypto_bot.cpp)
+    target_link_libraries(crypto_bot matrix_client ${MATRIX_STRUCTS_LIBRARY})
 endif()
 
 if (BUILD_LIB_TESTS)
diff --git a/README.md b/README.md
index 110815e2d..cbfc8cbfb 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@ mtxclient
 [![Build Status](https://travis-ci.org/mujx/mtxclient.svg?branch=master)](https://travis-ci.org/mujx/mtxclient)
 [![Build status](https://ci.appveyor.com/api/projects/status/5hl7a6dc5s60xpxy/branch/master?svg=true)](https://ci.appveyor.com/project/mujx/mtxclient/branch/master)
 [![codecov](https://codecov.io/gh/mujx/mtxclient/branch/master/graph/badge.svg)](https://codecov.io/gh/mujx/mtxclient)
+[![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges)
 
 Client API library for the Matrix protocol, built on top of Boost.Asio.
 
@@ -12,7 +13,7 @@ Client API library for the Matrix protocol, built on top of Boost.Asio.
 
 - Boost 1.66 (includes Boost.Beast)
 - OpenSSL
-- C++ 11 compiler
+- C++ 14 compiler
 - CMake 3.1 or greater
 - Google Test (for testing)
 
diff --git a/cmake/MatrixStructs.cmake b/cmake/MatrixStructs.cmake
index 568540b3a..7a972d46c 100644
--- a/cmake/MatrixStructs.cmake
+++ b/cmake/MatrixStructs.cmake
@@ -21,7 +21,7 @@ ExternalProject_Add(
   MatrixStructs
 
   GIT_REPOSITORY https://github.com/mujx/matrix-structs
-  GIT_TAG a65c0be6082af850f2e2cc7ef0c922e1ce319816
+  GIT_TAG a0e6a10f1e3a659a9353ccd352bac3689da249e2
 
   BUILD_IN_SOURCE 1
   SOURCE_DIR ${MATRIX_STRUCTS_ROOT}
diff --git a/cmake/SpdLog.cmake b/cmake/SpdLog.cmake
new file mode 100644
index 000000000..ce9aaebe4
--- /dev/null
+++ b/cmake/SpdLog.cmake
@@ -0,0 +1,23 @@
+include(ExternalProject)
+
+#
+# Download spdlog.
+#
+
+set(THIRD_PARTY_ROOT ${CMAKE_SOURCE_DIR}/.third-party)
+set(SPDLOG_ROOT ${THIRD_PARTY_ROOT}/spdlog)
+
+set(SPDLOG_INCLUDE_DIR ${SPDLOG_ROOT}/include)
+
+ExternalProject_Add(
+  SpdLog
+
+  GIT_REPOSITORY https://github.com/gabime/spdlog
+  GIT_TAG b1a58cd342b4d0737a6a45e7c9a2abec143f480a
+
+  BUILD_IN_SOURCE 1
+  SOURCE_DIR ${SPDLOG_ROOT}
+  CONFIGURE_COMMAND ""
+  BUILD_COMMAND ""
+  INSTALL_COMMAND ""
+)
diff --git a/examples/crypto_bot.cpp b/examples/crypto_bot.cpp
new file mode 100644
index 000000000..248a4ef1b
--- /dev/null
+++ b/examples/crypto_bot.cpp
@@ -0,0 +1,439 @@
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/beast.hpp>
+
+#include <atomic>
+#include <iostream>
+#include <json.hpp>
+#include <spdlog/spdlog.h>
+#include <unistd.h>
+#include <variant.hpp>
+
+#include <mtx.hpp>
+#include <mtx/identifiers.hpp>
+
+#include "mtxclient/crypto/client.hpp"
+#include "mtxclient/http/client.hpp"
+#include "mtxclient/http/errors.hpp"
+
+#include "mtxclient/utils.hpp"
+#include "test_helpers.hpp"
+
+//
+// Simple example bot that will accept any invite.
+//
+
+using namespace std;
+using namespace mtx::client;
+using namespace mtx::crypto;
+using namespace mtx::http;
+using namespace mtx::events;
+using namespace mtx::identifiers;
+
+using TimelineEvent = mtx::events::collections::TimelineEvents;
+
+template<class Container, class Item>
+bool
+exists(const Container &container, const Item &item)
+{
+        return container.find(item) != container.end();
+}
+
+void
+get_device_keys(const UserId &user);
+
+void
+save_device_keys(const mtx::responses::QueryKeys &res);
+
+void
+mark_encrypted_room(const RoomId &id);
+
+//! Metadata associated with each active megolm session.
+struct GroupSessionMsgData
+{
+        std::string session_id;
+        std::string room_id;
+        std::string event_id;
+
+        uint64_t origin_server_ts;
+        uint64_t message_index;
+};
+
+struct Storage
+{
+        //! Storage for the user_id -> list of devices mapping.
+        std::map<std::string, std::vector<std::string>> devices_;
+        //! Storage for the identity key for a device.
+        std::map<std::string, std::string> device_keys_;
+        //! Flag that indicate if a specific room has encryption enabled.
+        std::map<std::string, bool> encrypted_rooms_;
+};
+
+namespace {
+std::shared_ptr<Client> client        = nullptr;
+std::shared_ptr<OlmClient> olm_client = nullptr;
+Storage storage;
+
+auto console = spdlog::stdout_color_mt("console");
+}
+
+void
+print_errors(RequestErr err)
+{
+        if (err->status_code != boost::beast::http::status::unknown)
+                console->error("status code: {}", static_cast<uint16_t>(err->status_code));
+        if (!err->matrix_error.error.empty())
+                console->error("matrix error: {}", err->matrix_error.error);
+        if (err->error_code)
+                console->error("error code: {}", err->error_code.message());
+}
+
+template<class T>
+bool
+is_room_encryption(const T &event)
+{
+        using namespace mtx::events;
+        using namespace mtx::events::state;
+        return mpark::holds_alternative<StateEvent<Encryption>>(event);
+}
+
+bool
+is_encrypted(const TimelineEvent &event)
+{
+        using namespace mtx::events;
+        return mpark::holds_alternative<EncryptedEvent<msg::Encrypted>>(event);
+}
+
+template<class T>
+bool
+is_member_event(const T &event)
+{
+        using namespace mtx::events;
+        using namespace mtx::events::state;
+        return mpark::holds_alternative<StateEvent<Member>>(event);
+}
+
+// Check if the given event has a textual representation.
+bool
+is_room_message(const TimelineEvent &event)
+{
+        return mpark::holds_alternative<mtx::events::RoomEvent<msg::Audio>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::Emote>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::File>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::Image>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::Notice>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::Text>>(event) ||
+               mpark::holds_alternative<mtx::events::RoomEvent<msg::Video>>(event);
+}
+
+// Retrieves the fallback body value from the event.
+std::string
+get_body(const TimelineEvent &event)
+{
+        if (mpark::holds_alternative<RoomEvent<msg::Audio>>(event))
+                return mpark::get<RoomEvent<msg::Audio>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::Emote>>(event))
+                return mpark::get<RoomEvent<msg::Emote>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::File>>(event))
+                return mpark::get<RoomEvent<msg::File>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::Image>>(event))
+                return mpark::get<RoomEvent<msg::Image>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::Notice>>(event))
+                return mpark::get<RoomEvent<msg::Notice>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::Text>>(event))
+                return mpark::get<RoomEvent<msg::Text>>(event).content.body;
+        else if (mpark::holds_alternative<RoomEvent<msg::Video>>(event))
+                return mpark::get<RoomEvent<msg::Video>>(event).content.body;
+
+        return "";
+}
+
+// Retrieves the sender of the event.
+std::string
+get_sender(const TimelineEvent &event)
+{
+        return mpark::visit([](auto e) { return e.sender; }, event);
+}
+
+template<class T>
+std::string
+get_json(const T &event)
+{
+        return mpark::visit([](auto e) { return json(e).dump(2); }, event);
+}
+
+void
+keys_uploaded_cb(const mtx::responses::UploadKeys &, RequestErr err)
+{
+        if (err) {
+                print_errors(err);
+                return;
+        }
+
+        console->info("keys uploaded");
+}
+
+void
+mark_encrypted_room(const RoomId &id)
+{
+        console->info("encryption is enabled for room: {}", id.get());
+        storage.encrypted_rooms_[id.get()] = true;
+}
+
+void
+parse_messages(const mtx::responses::Sync &res)
+{
+        for (const auto &room : res.rooms.invite) {
+                auto room_id = parse<Room>(room.first);
+
+                console->info("joining room {}", room_id.to_string());
+                client->join_room(room_id, [room_id](const nlohmann::json &, RequestErr e) {
+                        if (e) {
+                                print_errors(e);
+                                console->error("failed to join room {}", room_id.to_string());
+                                return;
+                        }
+                });
+        }
+
+        // Check if we have any new m.room_key messages (i.e starting a new megolm session)
+        for (const auto &id : res.to_device) {
+                cout << id.dump(2) << endl;
+        }
+
+        // Check if the uploaded one time keys are enough
+        for (const auto &device : res.device_one_time_keys_count) {
+                if (device.second < 50) {
+                        olm_client->generate_one_time_keys(50 - device.second);
+                        // TODO: Mark keys as sent
+                        client->upload_keys(olm_client->create_upload_keys_request(),
+                                            &keys_uploaded_cb);
+                }
+        }
+
+        for (const auto &room : res.rooms.join) {
+                const std::string room_id = room.first;
+
+                for (const auto &e : room.second.state.events) {
+                        if (is_room_encryption(e)) {
+                                mark_encrypted_room(RoomId(room_id));
+                                console->debug("{}", get_json(e));
+                        }
+                }
+
+                for (const auto &e : room.second.timeline.events) {
+                        if (is_room_encryption(e)) {
+                                mark_encrypted_room(RoomId(room_id));
+                                console->debug("{}", get_json(e));
+                        } else if (is_encrypted(e)) {
+                                console->info("received an encrypted event: {}", room_id);
+                                console->debug("{}", get_json(e));
+                        }
+                }
+        }
+}
+
+// Callback to executed after a /sync request completes.
+void
+sync_handler(const mtx::responses::Sync &res, RequestErr err)
+{
+        SyncOpts opts;
+
+        if (err) {
+                console->error("error during sync");
+                print_errors(err);
+                opts.since = client->next_batch_token();
+                client->sync(opts, &sync_handler);
+                return;
+        }
+
+        parse_messages(res);
+
+        opts.since = res.next_batch;
+        client->set_next_batch_token(res.next_batch);
+        client->sync(opts, &sync_handler);
+}
+
+// Callback to executed after the first (initial) /sync request completes.
+void
+initial_sync_handler(const mtx::responses::Sync &res, RequestErr err)
+{
+        SyncOpts opts;
+
+        if (err) {
+                console->error("error during initial sync");
+                print_errors(err);
+
+                if (err->status_code != boost::beast::http::status::ok) {
+                        console->error("retrying initial sync ..");
+                        opts.timeout = 0;
+                        client->sync(opts, &initial_sync_handler);
+                }
+
+                return;
+        }
+
+        parse_messages(res);
+
+        for (const auto &room : res.rooms.join) {
+                for (const auto &e : room.second.state.events) {
+                        if (is_member_event(e)) {
+                                auto m =
+                                  mpark::get<mtx::events::StateEvent<mtx::events::state::Member>>(
+                                    e);
+
+                                get_device_keys(UserId(m.state_key));
+                        }
+                }
+        }
+
+        opts.since = res.next_batch;
+        client->set_next_batch_token(res.next_batch);
+        client->sync(opts, &sync_handler);
+}
+
+void
+save_device_keys(const mtx::responses::QueryKeys &res)
+{
+        for (const auto &entry : res.device_keys) {
+                const auto user_id = entry.first;
+
+                if (!exists(storage.devices_, user_id))
+                        console->info("keys for {}", user_id);
+
+                std::vector<std::string> device_list;
+                for (const auto &device : entry.second) {
+                        const auto key_struct = device.second;
+
+                        const std::string device_id = key_struct.device_id;
+                        const std::string index     = "curve25519:" + device_id;
+
+                        if (key_struct.keys.find(index) == key_struct.keys.end())
+                                continue;
+
+                        const auto key = key_struct.keys.at(index);
+
+                        if (!exists(storage.device_keys_, device_id)) {
+                                console->info("{} => {}", device_id, key);
+                                storage.device_keys_[device_id] = key;
+                        }
+
+                        device_list.push_back(device_id);
+                }
+
+                if (!exists(storage.devices_, user_id)) {
+                        storage.devices_[user_id] = device_list;
+                }
+        }
+}
+
+void
+get_device_keys(const UserId &user)
+{
+        // Retrieve all devices keys.
+        mtx::requests::QueryKeys query;
+        query.device_keys[user.get()] = {};
+
+        client->query_keys(query, [](const mtx::responses::QueryKeys &res, RequestErr err) {
+                if (err) {
+                        print_errors(err);
+                        return;
+                }
+
+                for (const auto &key : res.device_keys) {
+                        const auto user_id = key.first;
+                        const auto devices = key.second;
+
+                        for (const auto &device : devices) {
+                                const auto id          = device.first;
+                                const auto data        = device.second;
+                                const auto signing_key = data.keys.at("ed25519:" + id);
+
+                                try {
+                                        auto ok = verify_identity_signature(
+                                          json(data), DeviceId(id), UserId(user_id), signing_key);
+
+                                        if (!ok) {
+                                                console->warn("signature could not be verified");
+                                                console->warn(json(data).dump(2));
+                                        }
+                                } catch (const olm_exception &e) {
+                                        console->warn(e.what());
+                                }
+                        }
+                }
+
+                save_device_keys(std::move(res));
+        });
+}
+
+void
+login_cb(const mtx::responses::Login &, RequestErr err)
+{
+        if (err) {
+                console->error("login error");
+                print_errors(err);
+                return;
+        }
+
+        console->info("User ID: {}", client->user_id().to_string());
+        console->info("Device ID: {}", client->device_id());
+
+        // Upload one time keys.
+        olm_client->set_user_id(client->user_id().to_string());
+        olm_client->set_device_id(client->device_id());
+        olm_client->generate_one_time_keys(50);
+
+        client->upload_keys(olm_client->create_upload_keys_request(),
+                            [](const mtx::responses::UploadKeys &, RequestErr err) {
+                                    if (err) {
+                                            print_errors(err);
+                                            return;
+                                    }
+
+                                    console->info("keys uploaded");
+                                    console->debug("starting initial sync");
+
+                                    SyncOpts opts;
+                                    opts.timeout = 0;
+                                    client->sync(opts, &initial_sync_handler);
+                            });
+}
+
+void
+join_room_cb(const nlohmann::json &obj, RequestErr err)
+{
+        if (err) {
+                print_errors(err);
+                return;
+        }
+
+        (void)obj;
+
+        // Fetch device list for all users.
+}
+
+int
+main()
+{
+        spdlog::set_pattern("[%H:%M:%S] [tid %t] [%^%l%$] %v");
+
+        std::string username, server, password;
+
+        cout << "username: ";
+        std::getline(std::cin, username);
+
+        cout << "server: ";
+        std::getline(std::cin, server);
+
+        password = getpass("password: ");
+
+        client = std::make_shared<Client>(server);
+
+        olm_client = make_shared<OlmClient>();
+        olm_client->create_new_account();
+
+        client->login(username, password, login_cb);
+        client->close();
+
+        return 0;
+}
diff --git a/include/mtxclient/crypto/client.hpp b/include/mtxclient/crypto/client.hpp
index 33e3b2007..b100f1c55 100644
--- a/include/mtxclient/crypto/client.hpp
+++ b/include/mtxclient/crypto/client.hpp
@@ -120,6 +120,13 @@ struct OlmAllocator<OlmInboundGroupSession>
         }
 };
 
+template<class T>
+std::unique_ptr<T, OlmDeleter>
+create_olm_object()
+{
+        return std::unique_ptr<T, OlmDeleter>(OlmAllocator<T>::allocate());
+}
+
 class OlmClient : public std::enable_shared_from_this<OlmClient>
 {
 public:
@@ -138,12 +145,6 @@ public:
         //! Sign the given message.
         Base64String sign_message(const std::string &msg);
 
-        template<class T>
-        std::unique_ptr<T, OlmDeleter> create_olm_object()
-        {
-                return std::unique_ptr<T, OlmDeleter>(OlmAllocator<T>::allocate());
-        }
-
         //! Create a new olm Account. Must be called before any other operation.
         void create_new_account();
         void create_new_utility();
@@ -218,5 +219,12 @@ matches_inbound_session_from(OlmSession *session,
                              const std::string &id_key,
                              const BinaryBuf &one_time_key_message);
 
+//! Verify a signature object as obtained from the response of /keys/query endpoint
+bool
+verify_identity_signature(nlohmann::json obj,
+                          const DeviceId &device_id,
+                          const UserId &user_id,
+                          const std::string &signing_key);
+
 } // namespace crypto
 } // namespace mtx
diff --git a/include/mtxclient/crypto/types.hpp b/include/mtxclient/crypto/types.hpp
index 3c4fff86a..5722c4a4f 100644
--- a/include/mtxclient/crypto/types.hpp
+++ b/include/mtxclient/crypto/types.hpp
@@ -1,7 +1,12 @@
 #pragma once
 
+#include "mtxclient/utils.hpp"
 #include <json.hpp>
 
+STRONG_TYPE(UserId, std::string)
+STRONG_TYPE(DeviceId, std::string)
+STRONG_TYPE(RoomId, std::string)
+
 namespace mtx {
 namespace crypto {
 
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 76decf710..40e6ab94d 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -419,7 +419,8 @@ mtx::http::Client::create_session(HeadersCallback<Response> callback)
                   // If we reach that point we most likely have a valid output from the
                   // homeserver.
                   try {
-                          callback(client::utils::deserialize<Response>(body), header, {});
+                          auto res = client::utils::deserialize<Response>(body);
+                          callback(std::move(res), header, {});
                   } catch (const nlohmann::json::exception &e) {
                           client_error.parse_error = std::string(e.what()) + ": " + body;
                           callback(response_data, header, client_error);
diff --git a/include/mtxclient/utils.hpp b/include/mtxclient/utils.hpp
index 5fd4419d1..0752ddb87 100644
--- a/include/mtxclient/utils.hpp
+++ b/include/mtxclient/utils.hpp
@@ -49,6 +49,35 @@ serialize<std::string>(const std::string &obj)
 {
         return obj;
 }
+
+template<class T, class Name>
+class strong_type
+{
+public:
+        strong_type() = default;
+        explicit strong_type(const T &value)
+          : value_(value)
+        {}
+        explicit strong_type(T &&value)
+          : value_(std::forward<T>(value))
+        {}
+
+        operator T &() noexcept { return value_; }
+        constexpr operator const T &() const noexcept { return value_; }
+
+        T &get() { return value_; }
+        T const &get() const { return value_; }
+
+private:
+        T value_;
+};
+
+// Macro for concisely defining a strong type
+#define STRONG_TYPE(type_name, value_type)                                                         \
+        struct type_name : mtx::client::utils::strong_type<value_type, type_name>                  \
+        {                                                                                          \
+                using strong_type::strong_type;                                                    \
+        };
 }
 }
 }
diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp
index ec7e41f2f..1cb24c6f3 100644
--- a/lib/crypto/client.cpp
+++ b/lib/crypto/client.cpp
@@ -3,6 +3,12 @@
 #include "mtxclient/crypto/client.hpp"
 #include <olm/base64.hh>
 
+#include <spdlog/spdlog.h>
+
+namespace {
+auto logger = spdlog::stdout_color_mt("crypto");
+}
+
 using json = nlohmann::json;
 using namespace mtx::crypto;
 
@@ -117,10 +123,13 @@ OlmClient::sign_one_time_keys(const OneTimeKeys &keys)
         // Sign & append the one time keys.
         std::map<std::string, json> signed_one_time_keys;
         for (const auto &elem : keys.curve25519) {
-                auto sig = sign_one_time_key(elem.second);
+                const auto key_id       = elem.first;
+                const auto one_time_key = elem.second;
+
+                auto sig = sign_one_time_key(one_time_key);
 
-                signed_one_time_keys["signed_curve25519:" + elem.first] =
-                  signed_one_time_key_json(elem.second, sig);
+                signed_one_time_keys["signed_curve25519:" + key_id] =
+                  signed_one_time_key_json(one_time_key, sig);
         }
 
         return signed_one_time_keys;
@@ -332,3 +341,43 @@ mtx::crypto::matches_inbound_session_from(OlmSession *session,
         return olm_matches_inbound_session_from(
           session, id_key.data(), id_key.size(), (void *)tmp.data(), tmp.size());
 }
+
+bool
+mtx::crypto::verify_identity_signature(nlohmann::json obj,
+                                       const DeviceId &device_id,
+                                       const UserId &user_id,
+                                       const std::string &signing_key)
+{
+        using namespace client::utils;
+
+        try {
+                const auto sign_key_id = "ed25519:" + device_id.get();
+                const auto signature =
+                  obj.at("signatures").at(user_id.get()).at(sign_key_id).get<std::string>();
+
+                if (signature.empty())
+                        return false;
+
+                obj.erase("unsigned");
+                obj.erase("signatures");
+
+                const auto msg = obj.dump();
+
+                auto utility = create_olm_object<OlmUtility>();
+                auto ret     = olm_ed25519_verify(utility.get(),
+                                              signing_key.data(),
+                                              signing_key.size(),
+                                              msg.data(),
+                                              msg.size(),
+                                              (void *)signature.data(),
+                                              signature.size());
+
+                if (ret != 0)
+                        throw olm_exception("verify_identity_signature", utility.get());
+
+                return true;
+        } catch (const nlohmann::json::exception &e) {
+                logger->warn("verify_identity_signature: {}", e.what());
+                return false;
+        }
+}
diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp
index 5656cf5cd..8fd0b2514 100644
--- a/tests/e2ee.cpp
+++ b/tests/e2ee.cpp
@@ -304,8 +304,8 @@ TEST(Encryption, ClaimKeys)
         atomic_bool uploaded(false);
 
         // Bob uploads his keys.
-        auto bob_req = ::generate_keys(bob_olm);
-        bob->upload_keys(bob_req,
+        bob_olm->generate_one_time_keys(1);
+        bob->upload_keys(bob_olm->create_upload_keys_request(),
                          [&uploaded](const mtx::responses::UploadKeys &res, RequestErr err) {
                                  check_error(err);
                                  EXPECT_EQ(res.one_time_key_counts.size(), 1);
@@ -321,8 +321,7 @@ TEST(Encryption, ClaimKeys)
         mtx::requests::QueryKeys alice_rk;
         alice_rk.device_keys[bob->user_id().to_string()] = {};
         alice->query_keys(
-          alice_rk,
-          [alice_olm, alice, bob, bob_req](const mtx::responses::QueryKeys &res, RequestErr err) {
+          alice_rk, [alice_olm, alice, bob](const mtx::responses::QueryKeys &res, RequestErr err) {
                   check_error(err);
 
                   auto bob_devices = res.device_keys.at(bob->user_id().to_string());
@@ -334,51 +333,35 @@ TEST(Encryption, ClaimKeys)
                   auto bob_ed25519 =
                     bob_devices.at(bob->device_id()).keys.at("ed25519:" + bob->device_id());
 
-                  alice->claim_keys(
-                    bob->user_id(),
-                    devices,
-                    [alice_olm, bob, bob_req, bob_ed25519](const mtx::responses::ClaimKeys &res,
-                                                           RequestErr err) {
-                            check_error(err);
-
-                            const auto user_id   = bob->user_id().to_string();
-                            const auto device_id = bob->device_id();
+                  const auto current_device = bob_devices.at(bob->device_id());
 
-                            // The device exists.
-                            EXPECT_EQ(res.one_time_keys.size(), 1);
-                            EXPECT_EQ(res.one_time_keys.at(user_id).size(), 1);
+                  // Verify signature.
+                  ASSERT_TRUE(verify_identity_signature(json(current_device),
+                                                        DeviceId(bob->device_id()),
+                                                        UserId(bob->user_id().to_string()),
+                                                        bob_ed25519));
 
-                            // The key is the one bob sent.
-                            auto one_time_key = res.one_time_keys.at(user_id).at(device_id);
-                            ASSERT_TRUE(one_time_key.is_object());
+                  alice->claim_keys(bob->user_id(),
+                                    devices,
+                                    [alice_olm, bob, bob_ed25519](
+                                      const mtx::responses::ClaimKeys &res, RequestErr err) {
+                                            check_error(err);
 
-                            auto algo     = one_time_key.begin().key();
-                            auto contents = one_time_key.begin().value();
+                                            const auto user_id   = bob->user_id().to_string();
+                                            const auto device_id = bob->device_id();
 
-                            EXPECT_EQ(bob_req.one_time_keys.at(algo), contents);
+                                            // The device exists.
+                                            EXPECT_EQ(res.one_time_keys.size(), 1);
+                                            EXPECT_EQ(res.one_time_keys.at(user_id).size(), 1);
 
-                            alice_olm->create_new_utility();
+                                            // The key is the one bob sent.
+                                            auto one_time_key =
+                                              res.one_time_keys.at(user_id).at(device_id);
+                                            ASSERT_TRUE(one_time_key.is_object());
 
-                            auto msg = json{{"key", contents.at("key").get<std::string>()}}.dump();
-                            auto signature = contents.at("signatures")
-                                               .at(user_id)
-                                               .at("ed25519:" + device_id)
-                                               .get<std::string>();
-
-                            // Verify signature.
-                            auto ret = olm_ed25519_verify(
-                              alice_olm->utility(),
-                              reinterpret_cast<const uint8_t *>(bob_ed25519.data()),
-                              bob_ed25519.size(),
-                              msg.data(),
-                              msg.size(),
-                              reinterpret_cast<uint8_t *>(&signature[0]),
-                              signature.size());
-
-                            EXPECT_EQ(std::string(olm_utility_last_error(alice_olm->utility())),
-                                      "SUCCESS");
-                            EXPECT_EQ(ret, 0);
-                    });
+                                            auto algo     = one_time_key.begin().key();
+                                            auto contents = one_time_key.begin().value();
+                                    });
           });
 
         alice->close();
diff --git a/tests/utils.cpp b/tests/utils.cpp
index e0d487c0a..6ff6d407d 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -55,8 +55,6 @@ TEST(Utilities, VerifySignedOneTimeKey)
         alice->create_new_account();
         alice->create_new_utility();
 
-        alice->identity_keys();
-
         alice->generate_one_time_keys(1);
         auto keys = alice->one_time_keys();
 
@@ -65,19 +63,54 @@ TEST(Utilities, VerifySignedOneTimeKey)
 
         auto sig = alice->sign_message(msg);
 
-        auto res = olm_ed25519_verify(
-          alice->utility(),
-          reinterpret_cast<const uint8_t *>(alice->identity_keys().ed25519.data()),
-          alice->identity_keys().ed25519.size(),
-          reinterpret_cast<const uint8_t *>(msg.data()),
-          msg.size(),
-          reinterpret_cast<uint8_t *>(&sig[0]),
-          sig.size());
+        auto res = olm_ed25519_verify(alice->utility(),
+                                      alice->identity_keys().ed25519.data(),
+                                      alice->identity_keys().ed25519.size(),
+                                      msg.data(),
+                                      msg.size(),
+                                      (void *)sig.data(),
+                                      sig.size());
 
         EXPECT_EQ(std::string(olm_utility_last_error(alice->utility())), "SUCCESS");
         EXPECT_EQ(res, 0);
 }
 
+TEST(Utilities, ValidUploadKeysRequest)
+{
+        const std::string user_id   = "@alice:matrix.org";
+        const std::string device_id = "FKALSOCCC";
+
+        auto alice = make_shared<OlmClient>();
+        alice->create_new_account();
+        alice->set_device_id(device_id);
+        alice->set_user_id(user_id);
+        alice->generate_one_time_keys(1);
+
+        auto id_sig = alice->sign_identity_keys();
+
+        json body{{"algorithms", {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}},
+                  {"user_id", user_id},
+                  {"device_id", device_id},
+                  {"keys",
+                   {
+                     {"curve25519:" + device_id, alice->identity_keys().curve25519},
+                     {"ed25519:" + device_id, alice->identity_keys().ed25519},
+                   }}};
+
+        body["signatures"][user_id]["ed25519:" + device_id] = id_sig;
+
+        json obj         = alice->create_upload_keys_request();
+        json device_keys = obj.at("device_keys");
+
+        ASSERT_TRUE(device_keys.dump() == body.dump());
+
+        ASSERT_TRUE(verify_identity_signature(
+          body, DeviceId(device_id), UserId(user_id), alice->identity_keys().ed25519));
+
+        ASSERT_TRUE(verify_identity_signature(
+          device_keys, DeviceId(device_id), UserId(user_id), alice->identity_keys().ed25519));
+}
+
 TEST(Utilities, VerifySignedIdentityKeys)
 {
         auto alice = make_shared<OlmClient>();
@@ -96,15 +129,64 @@ TEST(Utilities, VerifySignedIdentityKeys)
 
         auto sig = alice->sign_message(msg);
 
-        auto res = olm_ed25519_verify(
-          alice->utility(),
-          reinterpret_cast<const uint8_t *>(alice->identity_keys().ed25519.data()),
-          alice->identity_keys().ed25519.size(),
-          reinterpret_cast<const uint8_t *>(msg.data()),
-          msg.size(),
-          reinterpret_cast<uint8_t *>(&sig[0]),
-          sig.size());
+        auto res = olm_ed25519_verify(alice->utility(),
+                                      alice->identity_keys().ed25519.data(),
+                                      alice->identity_keys().ed25519.size(),
+                                      msg.data(),
+                                      msg.size(),
+                                      (void *)sig.data(),
+                                      sig.size());
 
         EXPECT_EQ(std::string(olm_utility_last_error(alice->utility())), "SUCCESS");
         EXPECT_EQ(res, 0);
 }
+
+TEST(Utilities, VerifyIdentityKeyJson)
+{
+        //! JSON extracted from an account created through Riot.
+        json data = R"({
+	"algorithms": [
+	  "m.olm.v1.curve25519-aes-sha2",
+          "m.megolm.v1.aes-sha2"
+        ],
+        "device_id": "VVLXGGTJGN",
+        "keys": {
+          "curve25519:VVLXGGTJGN": "TEdjuBVstvGMy0NYJxpeD7Zo97bLEgT2ukefWDPbe0w",
+          "ed25519:VVLXGGTJGN": "L5IUXmjZGzZO9IwB/j61lTjuD79TCMRDM4bBHvGstT4"
+        },
+        "signatures": {
+          "@nheko_test:matrix.org": {
+            "ed25519:VVLXGGTJGN": "tVWnGmZ5cMHiLJiaMhkZjNThQXlvFBsal3dclgPyiqkm/dG7F65U8xHpRb3QWFWALo9iy+L7W+fwv0yGhJFxBQ"
+          }
+        },
+        "unsigned": {
+          "device_display_name": "https://riot.im/develop/ via Firefox on Linux"
+        },
+        "user_id": "@nheko_test:matrix.org"
+        })"_json;
+
+        const auto signing_key = data.at("keys").at("ed25519:VVLXGGTJGN").get<std::string>();
+        const auto signature   = data.at("signatures")
+                                 .at("@nheko_test:matrix.org")
+                                 .at("ed25519:VVLXGGTJGN")
+                                 .get<std::string>();
+
+        auto tmp = data;
+        tmp.erase("unsigned");
+        tmp.erase("signatures");
+
+        auto msg = tmp.dump();
+
+        auto utility = create_olm_object<OlmUtility>();
+        EXPECT_EQ(olm_ed25519_verify(utility.get(),
+                                     signing_key.data(),
+                                     signing_key.size(),
+                                     msg.data(),
+                                     msg.size(),
+                                     (void *)signature.data(),
+                                     signature.size()),
+                  0);
+
+        ASSERT_TRUE(verify_identity_signature(
+          data, DeviceId("VVLXGGTJGN"), UserId("@nheko_test:matrix.org"), signing_key));
+}
-- 
GitLab