diff --git a/.travis.yml b/.travis.yml index 06a95396f81ca367f6f5fef2e907dd8e66f7e281..39a3ed54b7d846d4cc0967e1e24770d79edfe7c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ matrix: - "g++-7" install: - - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install boost libsodium; fi + - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew upgrade boost && brew install libsodium; fi - if [ $TRAVIS_OS_NAME == linux ]; then sudo add-apt-repository -y ppa:george-edison55/cmake-3.x; fi - if [ $TRAVIS_OS_NAME == linux ]; then sudo add-apt-repository -y ppa:chris-lea/libsodium; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 411bd2074bba494d2bb1abaf28a30946850f9702..84079bdbb7d36cd25a224d142a55e2f492bde773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,9 @@ if (BUILD_LIB_TESTS) add_executable(e2ee tests/e2ee.cpp) target_link_libraries(e2ee matrix_client ${GTEST_BOTH_LIBRARIES}) + add_executable(utils tests/utils.cpp) + target_link_libraries(utils matrix_client ${GTEST_BOTH_LIBRARIES}) + add_executable(connection tests/connection.cpp) target_link_libraries(connection matrix_client ${GTEST_BOTH_LIBRARIES}) @@ -163,10 +166,12 @@ if (BUILD_LIB_TESTS) add_dependencies(connection GTest) add_dependencies(media_api GTest) add_dependencies(e2ee GTest) + add_dependencies(utils GTest) endif() add_test(BasicConnectivity connection) add_test(ClientAPI client_api) add_test(MediaAPI media_api) add_test(Encryption e2ee) + add_test(Utilities utils) endif() diff --git a/src/crypto.cpp b/src/crypto.cpp index c8282c3133a25c39c90dc669ba9e74803ae9489c..843d41574d7ce586741b54a33727faa9da39d18c 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -2,11 +2,13 @@ #include <sodium.h> #include "crypto.hpp" -#include "olm/base64.hh" +#include <olm/base64.hh> using json = nlohmann::json; using namespace mtx::client::crypto; +constexpr std::size_t SIGNATURE_SIZE = 64; + std::unique_ptr<uint8_t[]> mtx::client::crypto::create_buffer(std::size_t nbytes) { @@ -81,21 +83,23 @@ mtx::client::crypto::sign_one_time_key(std::shared_ptr<olm::Account> account, json j{{"key", key}}; auto str_json = j.dump(); - constexpr std::size_t SIGNATURE_SIZE = 64; + auto signature_buf = sign_message(account, j.dump()); - // 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(); + return encode_base64(signature_buf.get(), SIGNATURE_SIZE); +} - // Signature - auto signature_buf = create_buffer(SIGNATURE_SIZE); - account->sign(buf, nbytes, signature_buf.get(), SIGNATURE_SIZE); +std::unique_ptr<uint8_t[]> +mtx::client::crypto::sign_message(std::shared_ptr<olm::Account> account, const std::string &msg) +{ + // Message buffer + auto buf = str_to_buffer(msg); + std::size_t nbytes = msg.size(); - auto encoded_buf = create_buffer(SIGNATURE_SIZE); - olm::encode_base64(signature_buf.get(), SIGNATURE_SIZE, encoded_buf.get()); + // Signature buffer + auto signature_buf = create_buffer(SIGNATURE_SIZE); + account->sign(buf.get(), nbytes, signature_buf.get(), SIGNATURE_SIZE); - return std::string(encoded_buf.get(), encoded_buf.get() + SIGNATURE_SIZE); + return signature_buf; } json @@ -107,3 +111,52 @@ mtx::client::crypto::signed_one_time_key_json(const mtx::identifiers::User &user return json{{"key", key}, {"signatures", {{user_id.to_string(), {{"ed25519:" + device_id, signature}}}}}}; } + +std::unique_ptr<uint8_t[]> +mtx::client::crypto::str_to_buffer(const std::string &data) +{ + auto str_pointer = reinterpret_cast<const uint8_t *>(&data[0]); + const auto nbytes = data.size(); + + auto buf = create_buffer(nbytes); + memcpy(buf.get(), str_pointer, nbytes); + + return buf; +} + +std::unique_ptr<uint8_t[]> +mtx::client::crypto::decode_base64(const std::string &data) +{ + const auto nbytes = data.size(); + const int output_nbytes = olm::decode_base64_length(nbytes); + + if (output_nbytes == -1) + throw std::runtime_error("invalid base64 input length"); + + auto output_buf = create_buffer(output_nbytes); + auto input_buf = str_to_buffer(data); + + olm::decode_base64(input_buf.get(), nbytes, output_buf.get()); + + return output_buf; +} + +std::string +mtx::client::crypto::encode_base64(const uint8_t *data, std::size_t len) +{ + const int output_nbytes = olm::encode_base64_length(len); + + if (output_nbytes == -1) + throw std::runtime_error("invalid base64 input length"); + + auto output_buf = create_buffer(output_nbytes); + olm::encode_base64(data, output_nbytes, output_buf.get()); + + return std::string(output_buf.get(), output_buf.get() + output_nbytes); +} + +std::unique_ptr<uint8_t[]> +mtx::client::crypto::json_to_buffer(const nlohmann::json &obj) +{ + return str_to_buffer(obj.dump()); +} diff --git a/src/crypto.hpp b/src/crypto.hpp index 68d930b336d52aea8fa83f6a6268329b70bc787a..3c961daeb809efc9900533b011b0a1f4d71be910 100644 --- a/src/crypto.hpp +++ b/src/crypto.hpp @@ -50,16 +50,36 @@ 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. +//! Sign the given one time keys and encode it to base64. std::string sign_one_time_key(std::shared_ptr<olm::Account> account, const std::string &key); +//! Sign the given message. +std::unique_ptr<uint8_t[]> +sign_message(std::shared_ptr<olm::Account> account, const std::string &msg); + //! 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); + +std::string +encode_base64(const uint8_t *data, std::size_t len); + +//! Decode the given base64 string +std::unique_ptr<uint8_t[]> +decode_base64(const std::string &data); + +//! Convert the given string to an uint8_t buffer. +std::unique_ptr<uint8_t[]> +str_to_buffer(const std::string &data); + +//! Convert the given json struct to an uint8_t buffer. +std::unique_ptr<uint8_t[]> +json_to_buffer(const nlohmann::json &obj); + } // namespace crypto } // namespace client } // namespace mtx diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5e19bae70a4033f0f30e210d1fb53d35c68ad370 --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,77 @@ +#include <gtest/gtest.h> + +#include "crypto.hpp" +#include "json.hpp" + +#include "olm/utility.hh" + +using json = nlohmann::json; +using namespace mtx::client::crypto; + +constexpr int SIGNATURE_SIZE = 64; + +TEST(Utilities, JsonToBuffer) +{ + auto msg = json({{"key", "text"}}); + auto buf = json_to_buffer(msg); + + auto strjson = msg.dump(); + auto len = msg.dump().size(); + + for (uint8_t i = 0; i < len; i++) { + if (strjson[i] != buf[i]) + FAIL(); + } +} + +TEST(Utilities, VerifySignedOneTimeKey) +{ + auto alice = olm_new_account(); + + generate_one_time_keys(alice, 1); + auto keys = one_time_keys(alice); + + auto first_key = keys["curve25519"].begin()->get<std::string>(); + auto msg = json({{"key", first_key}}).dump(); + + auto sig_buf = sign_message(alice, msg); + + olm::Utility utillity; + + auto res = utillity.ed25519_verify(alice->identity_keys.ed25519_key.public_key, + str_to_buffer(msg).get(), + msg.size(), + sig_buf.get(), + SIGNATURE_SIZE); + + EXPECT_EQ(utillity.last_error, 0); + EXPECT_EQ(res, 0); +} + +TEST(Utilities, VerifySignedIdentityKeys) +{ + auto alice = olm_new_account(); + + json keys = identity_keys(alice); + + auto msg = json({{"algorithms", {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, + {"device_id", "some_device"}, + {"user_id", "@alice:localhost"}, + {"keys", + {{"curve25519:some_device", keys["curve25519"]}, + {"ed25519:some_device", keys["ed25519"]}}}}) + .dump(); + + auto sig_buf = sign_message(alice, msg); + + olm::Utility utillity; + + auto res = utillity.ed25519_verify(alice->identity_keys.ed25519_key.public_key, + str_to_buffer(msg).get(), + msg.size(), + sig_buf.get(), + SIGNATURE_SIZE); + + EXPECT_EQ(utillity.last_error, 0); + EXPECT_EQ(res, 0); +}