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 [](https://travis-ci.org/mujx/mtxclient) [](https://ci.appveyor.com/project/mujx/mtxclient/branch/master) [](https://codecov.io/gh/mujx/mtxclient) +[](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