diff --git a/src/client.cpp b/src/client.cpp index b370b0fdce37c3e09c0c6ef111a5718f4b65173a..71ebb954c8a443ff9f28261edf0817af9f8fb15e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -562,47 +562,9 @@ Client::flow_response(const std::string &user, void Client::upload_keys( - const nlohmann::json &identity_keys, - const std::map<std::string, nlohmann::json> &one_time_keys, + const mtx::requests::UploadKeys &req, std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> callback) { - mtx::requests::UploadKeys req; - req.one_time_keys = one_time_keys; - - req.device_keys.user_id = user_id().to_string(); - req.device_keys.device_id = device_id(); - req.device_keys.keys.emplace("curve25519:" + device_id(), identity_keys["curve25519"]); - req.device_keys.keys.emplace("ed25519:" + device_id(), identity_keys["ed25519"]); - - post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>( - "/client/r0/keys/upload", req, callback); -} - -void -Client::upload_identity_keys( - const nlohmann::json &identity_keys, - std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> callback) -{ - mtx::requests::UploadKeys req; - req.device_keys.user_id = user_id().to_string(); - req.device_keys.device_id = device_id(); - req.device_keys.keys.emplace("curve25519:" + device_id(), identity_keys["curve25519"]); - req.device_keys.keys.emplace("ed25519:" + device_id(), identity_keys["ed25519"]); - - post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>( - "/client/r0/keys/upload", req, callback); -} - -void -Client::upload_one_time_keys( - const std::map<std::string, nlohmann::json> &one_time_keys, - std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> callback) -{ - mtx::requests::UploadKeys req; - req.device_keys.user_id = user_id().to_string(); - req.device_keys.device_id = device_id(); - req.one_time_keys = one_time_keys; - post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>( "/client/r0/keys/upload", req, callback); } diff --git a/src/client.hpp b/src/client.hpp index 19ea23c353eff60d7f38e12cadd507144ff1d9cb..94509748ef32a2fca6a5f1ed381bf5a763da317e 100644 --- a/src/client.hpp +++ b/src/client.hpp @@ -12,6 +12,7 @@ #include <boost/thread/thread.hpp> #include <json.hpp> +#include "crypto.hpp" #include "errors.hpp" #include "mtx/requests.hpp" #include "mtx/responses.hpp" @@ -201,22 +202,8 @@ public: // //! Upload identity keys & one time keys. - // TODO: Replace json with a proper type. API methods shouldn't throw. void upload_keys( - const nlohmann::json &identity_keys, - const std::map<std::string, nlohmann::json> &one_time_keys, - std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb); - - //! Upload identity keys. - // TODO: Replace json with a proper type. API methods shouldn't throw. - void upload_identity_keys( - const nlohmann::json &identity_keys, - std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb); - - //! Upload one time keys. - // TODO: Replace json with a proper type. API methods shouldn't throw. - void upload_one_time_keys( - const std::map<std::string, nlohmann::json> &one_time_keys, + const mtx::requests::UploadKeys &req, std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb); private: diff --git a/src/crypto.cpp b/src/crypto.cpp index 843d41574d7ce586741b54a33727faa9da39d18c..b9d260cb6fb6e459eacaf8116f1095609eae3c71 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -34,7 +34,7 @@ mtx::client::crypto::olm_new_account() return olm_account; } -json +IdentityKeys mtx::client::crypto::identity_keys(std::shared_ptr<olm::Account> account) { const auto nbytes = account->get_identity_json_length(); @@ -46,8 +46,27 @@ mtx::client::crypto::identity_keys(std::shared_ptr<olm::Account> account) throw olm_exception("identity_keys", account->last_error); std::string data(buf.get(), buf.get() + nbytes); + IdentityKeys keys = json::parse(data); - return json::parse(data); + return keys; +} + +std::string +mtx::client::crypto::sign_identity_keys(std::shared_ptr<olm::Account> account, + const IdentityKeys &keys, + const mtx::identifiers::User &user_id, + const std::string &device_id) +{ + json body{{"algorithms", {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, + {"user_id", user_id.to_string()}, + {"device_id", device_id}, + {"keys", + { + {"curve25519:" + device_id, keys.curve25519}, + {"ed25519:" + device_id, keys.ed25519}, + }}}; + + return encode_base64(sign_message(account, body.dump()).get(), SIGNATURE_SIZE); } std::size_t @@ -88,6 +107,24 @@ mtx::client::crypto::sign_one_time_key(std::shared_ptr<olm::Account> account, return encode_base64(signature_buf.get(), SIGNATURE_SIZE); } +std::map<std::string, json> +mtx::client::crypto::sign_one_time_keys(std::shared_ptr<olm::Account> account, + const mtx::client::crypto::OneTimeKeys &keys, + const mtx::identifiers::User &user_id, + const std::string &device_id) +{ + // 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(account, elem.second); + + signed_one_time_keys["signed_curve25519:" + elem.first] = + signed_one_time_key_json(user_id, device_id, elem.second, sig); + } + + return signed_one_time_keys; +} + std::unique_ptr<uint8_t[]> mtx::client::crypto::sign_message(std::shared_ptr<olm::Account> account, const std::string &msg) { diff --git a/src/crypto.hpp b/src/crypto.hpp index 3c961daeb809efc9900533b011b0a1f4d71be910..ff6dbf0a6270b4104b16ef17df184159c733c225 100644 --- a/src/crypto.hpp +++ b/src/crypto.hpp @@ -8,10 +8,53 @@ #include <olm/account.hh> #include <olm/error.h> +static constexpr const char *ED25519 = "ed25519"; +static constexpr const char *CURVE25519 = "curve25519"; + namespace mtx { namespace client { namespace crypto { +struct IdentityKeys +{ + std::string curve25519; + std::string ed25519; +}; + +inline void +to_json(nlohmann::json &obj, const IdentityKeys &keys) +{ + obj[ED25519] = keys.ed25519; + obj[CURVE25519] = keys.curve25519; +} + +inline void +from_json(const nlohmann::json &obj, IdentityKeys &keys) +{ + keys.ed25519 = obj.at(ED25519).get<std::string>(); + keys.curve25519 = obj.at(CURVE25519).get<std::string>(); +} + +struct OneTimeKeys +{ + using KeyId = std::string; + using EncodedKey = std::string; + + std::map<KeyId, EncodedKey> curve25519; +}; + +inline void +to_json(nlohmann::json &obj, const OneTimeKeys &keys) +{ + obj["curve25519"] = keys.curve25519; +} + +inline void +from_json(const nlohmann::json &obj, OneTimeKeys &keys) +{ + keys.curve25519 = obj.at("curve25519").get<std::map<std::string, std::string>>(); +} + class olm_exception : public std::exception { public: @@ -35,7 +78,7 @@ std::shared_ptr<olm::Account> olm_new_account(); //! Retrieve the json representation of the identity keys for the given account. -nlohmann::json +IdentityKeys identity_keys(std::shared_ptr<olm::Account> user); //! Generate a number of one time keys. @@ -54,6 +97,13 @@ create_buffer(std::size_t nbytes); std::string sign_one_time_key(std::shared_ptr<olm::Account> account, const std::string &key); +//! Sign the identity keys. The result should be used as part of the /keys/upload/ request. +std::string +sign_identity_keys(std::shared_ptr<olm::Account> account, + const IdentityKeys &keys, + const mtx::identifiers::User &user_id, + const std::string &device_id); + //! Sign the given message. std::unique_ptr<uint8_t[]> sign_message(std::shared_ptr<olm::Account> account, const std::string &msg); @@ -65,6 +115,13 @@ signed_one_time_key_json(const mtx::identifiers::User &user_id, const std::string &key, const std::string &signature); +//! Sign one_time_keys and generate the appropriate structure for the /keys/upload request. +std::map<std::string, nlohmann::json> +sign_one_time_keys(std::shared_ptr<olm::Account> account, + const mtx::client::crypto::OneTimeKeys &keys, + const mtx::identifiers::User &user_id, + const std::string &device_id); + std::string encode_base64(const uint8_t *data, std::size_t len); diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp index 10573c8b6131dcd2672e2c716bbb192fe3e7199e..fbe7e8415ec1363d9096743a678760147b53a226 100644 --- a/tests/e2ee.cpp +++ b/tests/e2ee.cpp @@ -24,6 +24,52 @@ using namespace std; using ErrType = std::experimental::optional<errors::ClientError>; +std::map<std::string, json> +sign_one_time_keys(std::shared_ptr<olm::Account> account, + const mtx::client::crypto::OneTimeKeys &keys, + const mtx::identifiers::User &user_id, + const std::string &device_id) +{ + // Sign & append the one time keys. + std::map<std::string, json> signed_one_time_keys; + for (const auto &elem : keys.curve25519) { + auto sig = mtx::client::crypto::sign_one_time_key(account, elem.second); + + signed_one_time_keys["signed_curve25519:" + elem.first] = + mtx::client::crypto::signed_one_time_key_json( + user_id, device_id, elem.second, sig); + } + + return signed_one_time_keys; +} + +mtx::requests::UploadKeys +create_upload_keys_request(std::shared_ptr<olm::Account> account, + const mtx::client::crypto::IdentityKeys &identity_keys, + const mtx::client::crypto::OneTimeKeys &one_time_keys, + const mtx::identifiers::User &user_id, + const string &device_id) +{ + mtx::requests::UploadKeys req; + req.device_keys.user_id = user_id.to_string(); + req.device_keys.device_id = device_id; + + req.device_keys.keys["curve25519:" + device_id] = identity_keys.curve25519; + req.device_keys.keys["ed25519:" + device_id] = identity_keys.ed25519; + + // Generate and add the signature to the request. + auto sig = sign_identity_keys(account, identity_keys, user_id, device_id); + req.device_keys.signatures[user_id.to_string()]["ed25519:" + device_id] = sig; + + if (one_time_keys.curve25519.empty()) + return req; + + // Sign & append the one time keys. + req.one_time_keys = ::sign_one_time_keys(account, one_time_keys, user_id, device_id); + + return req; +} + void check_error(ErrType err) { @@ -52,19 +98,20 @@ TEST(Encryption, UploadIdentityKeys) while (alice->access_token().empty()) std::this_thread::sleep_for(std::chrono::milliseconds(100)); - json identity_keys = mtx::client::crypto::identity_keys(olm_account); + auto identity_keys = mtx::client::crypto::identity_keys(olm_account); - ASSERT_TRUE(identity_keys.find("curve25519") != identity_keys.end()); - ASSERT_TRUE(identity_keys.at("curve25519").get<std::string>().size() > 10); + ASSERT_TRUE(identity_keys.curve25519.size() > 10); + ASSERT_TRUE(identity_keys.curve25519.size() > 10); - ASSERT_TRUE(identity_keys.find("ed25519") != identity_keys.end()); - ASSERT_TRUE(identity_keys.at("ed25519").get<std::string>().size() > 10); + mtx::client::crypto::OneTimeKeys unused; + auto request = ::create_upload_keys_request( + olm_account, identity_keys, unused, alice->user_id(), alice->device_id()); - alice->upload_identity_keys(identity_keys, - [](const mtx::responses::UploadKeys &res, ErrType err) { - check_error(err); - EXPECT_EQ(res.one_time_key_counts.size(), 0); - }); + // Make the request with the signed identity keys. + alice->upload_keys(request, [](const mtx::responses::UploadKeys &res, ErrType err) { + check_error(err); + EXPECT_EQ(res.one_time_key_counts.size(), 0); + }); alice->close(); } @@ -85,14 +132,18 @@ TEST(Encryption, UploadOneTimeKeys) auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account); + mtx::requests::UploadKeys req; + // Create the proper structure for uploading. - std::map<std::string, json> keys; + std::map<std::string, json> unsigned_keys; auto obj = one_time_keys.at("curve25519"); for (auto it = obj.begin(); it != obj.end(); ++it) - keys["curve25519:" + it.key()] = it.value(); + unsigned_keys["curve25519:" + it.key()] = it.value(); - alice->upload_one_time_keys(keys, [](const mtx::responses::UploadKeys &res, ErrType err) { + req.one_time_keys = unsigned_keys; + + alice->upload_keys(req, [](const mtx::responses::UploadKeys &res, ErrType err) { check_error(err); EXPECT_EQ(res.one_time_key_counts.size(), 1); EXPECT_EQ(res.one_time_key_counts.at("curve25519"), 5); @@ -117,24 +168,15 @@ TEST(Encryption, UploadSignedOneTimeKeys) auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account); - // Create the proper structure for uploading. - std::map<std::string, json> signed_keys; - - auto obj = one_time_keys.at("curve25519"); - for (auto it = obj.begin(); it != obj.end(); ++it) { - auto sig = mtx::client::crypto::sign_one_time_key(olm_account, it.value()); - - signed_keys["signed_curve25519:" + it.key()] = - mtx::client::crypto::signed_one_time_key_json( - alice->user_id(), alice->device_id(), it.value(), sig); - } + mtx::requests::UploadKeys req; + req.one_time_keys = + ::sign_one_time_keys(olm_account, one_time_keys, alice->user_id(), alice->device_id()); - alice->upload_one_time_keys( - signed_keys, [nkeys](const mtx::responses::UploadKeys &res, ErrType err) { - check_error(err); - EXPECT_EQ(res.one_time_key_counts.size(), 1); - EXPECT_EQ(res.one_time_key_counts.at("signed_curve25519"), nkeys); - }); + alice->upload_keys(req, [nkeys](const mtx::responses::UploadKeys &res, ErrType err) { + check_error(err); + EXPECT_EQ(res.one_time_key_counts.size(), 1); + EXPECT_EQ(res.one_time_key_counts.at("signed_curve25519"), nkeys); + }); alice->close(); } @@ -155,24 +197,14 @@ TEST(Encryption, UploadKeys) mtx::client::crypto::generate_one_time_keys(olm_account, 1); auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account); - // Create the proper structure for uploading. - std::map<std::string, json> signed_keys; - - auto obj = one_time_keys.at("curve25519"); - for (auto it = obj.begin(); it != obj.end(); ++it) { - auto sig = mtx::client::crypto::sign_one_time_key(olm_account, it.value()); + auto req = ::create_upload_keys_request( + olm_account, identity_keys, one_time_keys, alice->user_id(), alice->device_id()); - signed_keys["signed_curve25519:" + it.key()] = - mtx::client::crypto::signed_one_time_key_json( - alice->user_id(), alice->device_id(), it.value(), sig); - } - - alice->upload_keys( - identity_keys, signed_keys, [](const mtx::responses::UploadKeys &res, ErrType err) { - check_error(err); - EXPECT_EQ(res.one_time_key_counts.size(), 1); - EXPECT_EQ(res.one_time_key_counts.at("signed_curve25519"), 1); - }); + alice->upload_keys(req, [](const mtx::responses::UploadKeys &res, ErrType err) { + check_error(err); + EXPECT_EQ(res.one_time_key_counts.size(), 1); + EXPECT_EQ(res.one_time_key_counts.at("signed_curve25519"), 1); + }); alice->close(); }