diff --git a/src/client.cpp b/src/client.cpp index e1e359b4aad7c9b92e24ba1676ed8537e16b8dcb..b370b0fdce37c3e09c0c6ef111a5718f4b65173a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -561,11 +561,14 @@ Client::flow_response(const std::string &user, // void -Client::upload_identity_keys( +Client::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)> 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"]); @@ -576,17 +579,29 @@ Client::upload_identity_keys( } void -Client::upload_one_time_keys( +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"]); - auto obj = identity_keys.at("curve25519"); - for (auto it = obj.begin(); it != obj.end(); ++it) - req.one_time_keys.emplace("curve25519:" + it.key(), it.value()); + 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 be4f16c1cd5041173faf7283a50645f79b0f29cc..19ea23c353eff60d7f38e12cadd507144ff1d9cb 100644 --- a/src/client.hpp +++ b/src/client.hpp @@ -200,6 +200,13 @@ public: // Encryption related endpoints. // + //! 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( @@ -209,7 +216,7 @@ public: //! Upload one time keys. // TODO: Replace json with a proper type. API methods shouldn't throw. void upload_one_time_keys( - const nlohmann::json &one_time_keys, + const std::map<std::string, nlohmann::json> &one_time_keys, std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb); private: diff --git a/src/crypto.cpp b/src/crypto.cpp index 17c8a003c0434de88dd66b24aff975a974f7bcc0..c8282c3133a25c39c90dc669ba9e74803ae9489c 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,6 +1,8 @@ +#include <iostream> #include <sodium.h> #include "crypto.hpp" +#include "olm/base64.hh" using json = nlohmann::json; using namespace mtx::client::crypto; @@ -71,3 +73,37 @@ mtx::client::crypto::one_time_keys(std::shared_ptr<olm::Account> account) return json::parse(data); } + +std::string +mtx::client::crypto::sign_one_time_key(std::shared_ptr<olm::Account> account, + const std::string &key) +{ + json j{{"key", key}}; + auto str_json = j.dump(); + + constexpr std::size_t SIGNATURE_SIZE = 64; + + // Message + std::vector<std::uint8_t> tmp(str_json.begin(), str_json.end()); + std::uint8_t *buf = &tmp[0]; + std::size_t nbytes = str_json.size(); + + // Signature + auto signature_buf = create_buffer(SIGNATURE_SIZE); + account->sign(buf, nbytes, signature_buf.get(), SIGNATURE_SIZE); + + auto encoded_buf = create_buffer(SIGNATURE_SIZE); + olm::encode_base64(signature_buf.get(), SIGNATURE_SIZE, encoded_buf.get()); + + return std::string(encoded_buf.get(), encoded_buf.get() + SIGNATURE_SIZE); +} + +json +mtx::client::crypto::signed_one_time_key_json(const mtx::identifiers::User &user_id, + const std::string &device_id, + const std::string &key, + const std::string &signature) +{ + return json{{"key", key}, + {"signatures", {{user_id.to_string(), {{"ed25519:" + device_id, signature}}}}}}; +} diff --git a/src/crypto.hpp b/src/crypto.hpp index 4f033cbef531efa245a3dfca5ba250857899945b..68d930b336d52aea8fa83f6a6268329b70bc787a 100644 --- a/src/crypto.hpp +++ b/src/crypto.hpp @@ -4,6 +4,7 @@ #include <memory> #include <json.hpp> +#include <mtx/identifiers.hpp> #include <olm/account.hh> #include <olm/error.h> @@ -49,6 +50,16 @@ one_time_keys(std::shared_ptr<olm::Account> user); std::unique_ptr<uint8_t[]> create_buffer(std::size_t nbytes); +//! Sign the given one time keys. +std::string +sign_one_time_key(std::shared_ptr<olm::Account> account, const std::string &key); + +//! Generate the json structure for the signed one time key. +nlohmann::json +signed_one_time_key_json(const mtx::identifiers::User &user_id, + const std::string &device_id, + const std::string &key, + const std::string &signature); } // namespace crypto } // namespace client } // namespace mtx diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp index 9af76e64ae61c0916cf07d3ffd5d3e1394b9b89c..10573c8b6131dcd2672e2c716bbb192fe3e7199e 100644 --- a/tests/e2ee.cpp +++ b/tests/e2ee.cpp @@ -80,17 +80,99 @@ TEST(Encryption, UploadOneTimeKeys) while (alice->access_token().empty()) std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto number_of_keys = mtx::client::crypto::generate_one_time_keys(olm_account, 5); - EXPECT_EQ(number_of_keys, 5); + auto nkeys = mtx::client::crypto::generate_one_time_keys(olm_account, 5); + EXPECT_EQ(nkeys, 5); auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account); - alice->upload_one_time_keys(one_time_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("curve25519"), 5); - }); + // Create the proper structure for uploading. + std::map<std::string, json> keys; + + auto obj = one_time_keys.at("curve25519"); + for (auto it = obj.begin(); it != obj.end(); ++it) + keys["curve25519:" + it.key()] = it.value(); + + alice->upload_one_time_keys(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("curve25519"), 5); + }); + + alice->close(); +} + +TEST(Encryption, UploadSignedOneTimeKeys) +{ + auto alice = std::make_shared<Client>("localhost"); + auto olm_account = mtx::client::crypto::olm_new_account(); + + alice->login( + "alice", "secret", [](const mtx::responses::Login &, ErrType err) { check_error(err); }); + + while (alice->access_token().empty()) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + auto nkeys = mtx::client::crypto::generate_one_time_keys(olm_account, 5); + EXPECT_EQ(nkeys, 5); + + 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); + } + + 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->close(); +} + +TEST(Encryption, UploadKeys) +{ + auto alice = std::make_shared<Client>("localhost"); + auto olm_account = mtx::client::crypto::olm_new_account(); + + alice->login( + "alice", "secret", [](const mtx::responses::Login &, ErrType err) { check_error(err); }); + + while (alice->access_token().empty()) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + json identity_keys = mtx::client::crypto::identity_keys(olm_account); + + 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()); + + 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->close(); }