diff --git a/.travis.yml b/.travis.yml index a89bd8e3fca927741f3f77424ea12a7fd1a39eb8..06a95396f81ca367f6f5fef2e907dd8e66f7e281 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,9 +46,12 @@ matrix: - "g++-7" install: + - if [ $TRAVIS_OS_NAME == osx ]; then brew update && brew install boost 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 - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get update -qq; fi - - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install -qq -y cmake; fi + - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install -qq -y cmake libsodium-dev; fi script: - if [[ "${CXX_VERSION}" != "" ]]; then export CXX=${CXX_VERSION}; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index d40308465d656205e5d4b0fe5a4c5b833115a457..692124e59d8259afeaf45e3c8dd3b86c69fbee46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,13 @@ set(MTXCLIENT_DEPS "") find_package(OpenSSL REQUIRED) include_directories(${OPENSSL_INCLUDE_DIR}) +# +# libsodium +# +include(Findsodium) +set(MTXCLIENT_LIBS ${MTXCLIENT_LIBS} ${sodium_LIBRARY_RELEASE}) +include_directories(SYSTEM sodium_INCLUDE_DIR) + # # Boost 1.66 # @@ -84,7 +91,8 @@ endif() include_directories(SYSTEM ${MATRIX_STRUCTS_INCLUDE_DIR}) -set(MTXCLIENT_LIBS matrix_structs +set(MTXCLIENT_LIBS ${MTXCLIENT_LIBS} + matrix_structs ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/cmake/Findsodium.cmake b/cmake/Findsodium.cmake new file mode 100644 index 0000000000000000000000000000000000000000..30e6f5c1d166e392db5e1cdf67848d61598b49d0 --- /dev/null +++ b/cmake/Findsodium.cmake @@ -0,0 +1,288 @@ +# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl> +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# +# http://creativecommons.org/publicdomain/zero/1.0/ +# +######################################################################## +# Tries to find the local libsodium installation. +# +# On Windows the sodium_DIR environment variable is used as a default +# hint which can be overridden by setting the corresponding cmake variable. +# +# Once done the following variables will be defined: +# +# sodium_FOUND +# sodium_INCLUDE_DIR +# sodium_LIBRARY_DEBUG +# sodium_LIBRARY_RELEASE +# +# +# Furthermore an imported "sodium" target is created. +# + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU" + OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(_GCC_COMPATIBLE 1) +endif() + +# static library option +if (NOT DEFINED sodium_USE_STATIC_LIBS) + option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) +endif() +if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) + unset(sodium_LIBRARY CACHE) + unset(sodium_LIBRARY_DEBUG CACHE) + unset(sodium_LIBRARY_RELEASE CACHE) + unset(sodium_DLL_DEBUG CACHE) + unset(sodium_DLL_RELEASE CACHE) + set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") +endif() + + +######################################################################## +# UNIX +if (UNIX) + # import pkg-config + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(sodium_PKG QUIET libsodium) + endif() + + if(sodium_USE_STATIC_LIBS) + foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) + if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a + list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") + endif() + endforeach() + list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) + + # if pkgconfig for libsodium doesn't provide + # static lib info, then override PKG_STATIC here.. + if (sodium_PKG_STATIC_LIBRARIES STREQUAL "") + set(sodium_PKG_STATIC_LIBRARIES libsodium.a) + endif() + + set(XPREFIX sodium_PKG_STATIC) + else() + if (sodium_PKG_LIBRARIES STREQUAL "") + set(sodium_PKG_LIBRARIES sodium) + endif() + + set(XPREFIX sodium_PKG) + endif() + + find_path(sodium_INCLUDE_DIR sodium.h + HINTS ${${XPREFIX}_INCLUDE_DIRS} + ) + find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + + +######################################################################## +# Windows +elseif (WIN32) + set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") + mark_as_advanced(sodium_DIR) + + find_path(sodium_INCLUDE_DIR sodium.h + HINTS ${sodium_DIR} + PATH_SUFFIXES include + ) + + if (MSVC) + # detect target architecture + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[ + #if defined _M_IX86 + #error ARCH_VALUE x86_32 + #elif defined _M_X64 + #error ARCH_VALUE x86_64 + #endif + #error ARCH_VALUE unknown + ]=]) + try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c" + OUTPUT_VARIABLE _COMPILATION_LOG + ) + string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") + + # construct library path + if (_TARGET_ARCH STREQUAL "x86_32") + string(APPEND _PLATFORM_PATH "Win32") + elseif(_TARGET_ARCH STREQUAL "x86_64") + string(APPEND _PLATFORM_PATH "x64") + else() + message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") + endif() + string(APPEND _PLATFORM_PATH "/$$CONFIG$$") + + if (MSVC_VERSION LESS 1900) + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") + else() + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") + endif() + string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") + + if (sodium_USE_STATIC_LIBS) + string(APPEND _PLATFORM_PATH "/static") + else() + string(APPEND _PLATFORM_PATH "/dynamic") + endif() + + string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") + string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") + + find_library(sodium_LIBRARY_DEBUG libsodium.lib + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(sodium_LIBRARY_RELEASE libsodium.lib + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + if (NOT sodium_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") + find_library(sodium_DLL_DEBUG libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(sodium_DLL_RELEASE libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) + endif() + + elseif(_GCC_COMPATIBLE) + if (sodium_USE_STATIC_LIBS) + find_library(sodium_LIBRARY_DEBUG libsodium.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + find_library(sodium_LIBRARY_RELEASE libsodium.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + else() + find_library(sodium_LIBRARY_DEBUG libsodium.dll.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + find_library(sodium_LIBRARY_RELEASE libsodium.dll.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + + file(GLOB _DLL + LIST_DIRECTORIES false + RELATIVE "${sodium_DIR}/bin" + "${sodium_DIR}/bin/libsodium*.dll" + ) + find_library(sodium_DLL_DEBUG ${_DLL} libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES bin + ) + find_library(sodium_DLL_RELEASE ${_DLL} libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES bin + ) + endif() + else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") + endif() + + +######################################################################## +# unsupported +else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") +endif() + + +######################################################################## +# common stuff + +# extract sodium version +if (sodium_INCLUDE_DIR) + set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") + if (EXISTS _VERSION_HEADER) + file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" + sodium_VERSION "${_VERSION_HEADER_CONTENT}") + set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) + endif() +endif() + +# communicate results +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(sodium + REQUIRED_VARS + sodium_LIBRARY_RELEASE + sodium_LIBRARY_DEBUG + sodium_INCLUDE_DIR + VERSION_VAR + sodium_VERSION +) + +# mark file paths as advanced +mark_as_advanced(sodium_INCLUDE_DIR) +mark_as_advanced(sodium_LIBRARY_DEBUG) +mark_as_advanced(sodium_LIBRARY_RELEASE) +if (WIN32) + mark_as_advanced(sodium_DLL_DEBUG) + mark_as_advanced(sodium_DLL_RELEASE) +endif() + +# create imported target +if(sodium_USE_STATIC_LIBS) + set(_LIB_TYPE STATIC) +else() + set(_LIB_TYPE SHARED) +endif() +add_library(sodium ${_LIB_TYPE} IMPORTED) + +set_target_properties(sodium PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" +) + +if (sodium_USE_STATIC_LIBS) + set_target_properties(sodium PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" + IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" + ) +else() + if (UNIX) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" + ) + elseif (WIN32) + set_target_properties(sodium PROPERTIES + IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}" + IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}" + ) + if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" + ) + endif() + if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" + ) + endif() + endif() +endif() diff --git a/src/client.cpp b/src/client.cpp index ce0baeae5cd97165b59b0c155bd3a7fe1da31ce1..c0e69263de897600ddeca9246fd6a97749928712 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -567,3 +567,20 @@ Client::upload_identity_keys( post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>( "/client/r0/keys/upload", req, callback); } + +void +Client::upload_one_time_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(); + + 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); +} diff --git a/src/client.hpp b/src/client.hpp index afe2769d9c03d65b2d2584d2367a585260fab093..39188a89fe8a44baac6bab7b6aed5c8259447aeb 100644 --- a/src/client.hpp +++ b/src/client.hpp @@ -200,12 +200,18 @@ public: // Encryption related endpoints. // - //! Upload identity & one time keys. + //! 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 nlohmann::json &one_time_keys, + std::function<void(const mtx::responses::UploadKeys &res, RequestErr err)> cb); + private: template<class Request, class Response> void post(const std::string &endpoint, diff --git a/src/crypto.cpp b/src/crypto.cpp index b8c39ab811700ecad545cf0e37e2ed1574703bd6..17c8a003c0434de88dd66b24aff975a974f7bcc0 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,15 +1,28 @@ +#include <sodium.h> + #include "crypto.hpp" using json = nlohmann::json; +using namespace mtx::client::crypto; + +std::unique_ptr<uint8_t[]> +mtx::client::crypto::create_buffer(std::size_t nbytes) +{ + auto buf = std::make_unique<uint8_t[]>(nbytes); + randombytes_buf(buf.get(), nbytes); + + return buf; +} std::shared_ptr<olm::Account> mtx::client::crypto::olm_new_account() { - auto olm_account = std::make_shared<olm::Account>(); - const auto buf_size = olm_account->new_account_random_length(); - auto account_buf = std::make_unique<uint8_t[]>(buf_size); + auto olm_account = std::make_shared<olm::Account>(); - int result = olm_account->new_account(account_buf.get(), buf_size); + const auto nbytes = olm_account->new_account_random_length(); + auto buf = create_buffer(nbytes); + + int result = olm_account->new_account(buf.get(), nbytes); if (result == -1) throw olm_exception("olm_new_account", olm_account->last_error); @@ -20,31 +33,41 @@ mtx::client::crypto::olm_new_account() json mtx::client::crypto::identity_keys(std::shared_ptr<olm::Account> account) { - const auto buf_size = account->get_identity_json_length(); - auto json_buf = std::make_unique<uint8_t[]>(buf_size); + const auto nbytes = account->get_identity_json_length(); + auto buf = create_buffer(nbytes); - int result = account->get_identity_json(json_buf.get(), buf_size); + int result = account->get_identity_json(buf.get(), nbytes); if (result == -1) throw olm_exception("identity_keys", account->last_error); - std::string data(json_buf.get(), json_buf.get() + buf_size); + std::string data(buf.get(), buf.get() + nbytes); return json::parse(data); } +std::size_t +mtx::client::crypto::generate_one_time_keys(std::shared_ptr<olm::Account> account, + std::size_t number_of_keys) +{ + const auto nbytes = account->generate_one_time_keys_random_length(number_of_keys); + + auto buf = create_buffer(nbytes); + return account->generate_one_time_keys(number_of_keys, buf.get(), nbytes); +} + json mtx::client::crypto::one_time_keys(std::shared_ptr<olm::Account> account) { - const auto buf_size = account->get_one_time_keys_json_length(); - auto json_buf = std::make_unique<uint8_t[]>(buf_size); + const auto nbytes = account->get_one_time_keys_json_length(); + auto buf = create_buffer(nbytes); - int result = account->get_one_time_keys_json(json_buf.get(), buf_size); + int result = account->get_one_time_keys_json(buf.get(), nbytes); if (result == -1) throw olm_exception("one_time_keys", account->last_error); - std::string data(json_buf.get(), json_buf.get() + buf_size); + std::string data(buf.get(), buf.get() + nbytes); return json::parse(data); } diff --git a/src/crypto.hpp b/src/crypto.hpp index bd6240a95ee3c22d40ebbc835abba65066e64763..be8b8f91be6e32206011780324eae4e490db0b6f 100644 --- a/src/crypto.hpp +++ b/src/crypto.hpp @@ -37,10 +37,17 @@ olm_new_account(); nlohmann::json identity_keys(std::shared_ptr<olm::Account> user); +//! Generate a number of one time keys. +std::size_t +generate_one_time_keys(std::shared_ptr<olm::Account> account, std::size_t number_of_keys); + //! Retrieve the json representation of the one time keys for the given account. nlohmann::json one_time_keys(std::shared_ptr<olm::Account> user); +std::unique_ptr<uint8_t[]> +create_buffer(std::size_t nbytes); + } // namespace crypto } // namespace client } // namespace mtx diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp index 16167fad0edc1ed6bfe89d156bf7f1d9c1b936ab..9af76e64ae61c0916cf07d3ffd5d3e1394b9b89c 100644 --- a/tests/e2ee.cpp +++ b/tests/e2ee.cpp @@ -68,3 +68,29 @@ TEST(Encryption, UploadIdentityKeys) alice->close(); } + +TEST(Encryption, UploadOneTimeKeys) +{ + 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 number_of_keys = mtx::client::crypto::generate_one_time_keys(olm_account, 5); + EXPECT_EQ(number_of_keys, 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); + }); + + alice->close(); +}