diff --git a/native/cpp/CommonCpp/CryptoTools/CMakeLists.txt b/native/cpp/CommonCpp/CryptoTools/CMakeLists.txt new file mode 100644 index 000000000..fb45441b2 --- /dev/null +++ b/native/cpp/CommonCpp/CryptoTools/CMakeLists.txt @@ -0,0 +1,60 @@ +project(comm-cryptotools) +cmake_minimum_required(VERSION 3.4) + +include(GNUInstallDirs) + +set(CMAKE_CXX_STANDARD 14) + +set(CRYPTO_HDRS + "CryptoModule.h" + "Persist.h" + "Session.h" + "Tools.h" +) + +set(CRYPTO_SRCS + "CryptoModule.cpp" + "Session.cpp" + "Tools.cpp" +) + +add_library(comm-cryptotools + ${CRYPTO_HDRS} + ${CRYPTO_SRCS} +) + +find_package(Olm) + +target_link_libraries(comm-cryptotools + Olm::Olm +) + +# reference local directory when building, use installation path when installing +target_include_directories(comm-cryptotools + PUBLIC + $ + $ + $ + $ +) + +install(TARGETS comm-cryptotools EXPORT comm-cryptotools-export + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT comm-cryptotools + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT comm-cryptotools + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT comm-cryptotools +) + +install(FILES ${TOOLS_HDRS} DESTINATION include/Tools) + +set(_builddir_export_path cmake/comm-cryptotools/comm-cryptotools-targets.cmake) +export(TARGETS comm-cryptotools + NAMESPACE comm-cryptotools:: + FILE ${CMAKE_CURRENT_BINARY_DIR}/${_builddir_export_path} +) + +# For installation +install(EXPORT comm-cryptotools-export + FILE comm-cryptotools-targets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/comm-cryptotools + NAMESPACE comm-cryptotools:: +) diff --git a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp index 084bf2d74..9ca734e68 100644 --- a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp +++ b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp @@ -1,304 +1,306 @@ #include "CryptoModule.h" #include "PlatformSpecificTools.h" #include "olm/session.hh" +#include + namespace comm { namespace crypto { CryptoModule::CryptoModule(std::string id) : id{id} { this->createAccount(); } CryptoModule::CryptoModule( std::string id, std::string secretKey, Persist persist) : id{id} { if (persist.isEmpty()) { this->createAccount(); } else { this->restoreFromB64(secretKey, persist); } } void CryptoModule::createAccount() { this->accountBuffer.resize(::olm_account_size()); this->account = ::olm_account(this->accountBuffer.data()); size_t randomSize = ::olm_create_account_random_length(this->account); OlmBuffer randomBuffer; PlatformSpecificTools::generateSecureRandomBytes(randomBuffer, randomSize); if (-1 == ::olm_create_account(this->account, randomBuffer.data(), randomSize)) { throw std::runtime_error{"error createAccount => ::olm_create_account"}; }; } void CryptoModule::exposePublicIdentityKeys() { size_t identityKeysSize = ::olm_account_identity_keys_length(this->account); if (this->keys.identityKeys.size() == identityKeysSize) { return; } this->keys.identityKeys.resize( ::olm_account_identity_keys_length(this->account)); if (-1 == ::olm_account_identity_keys( this->account, this->keys.identityKeys.data(), this->keys.identityKeys.size())) { throw std::runtime_error{ "error generateIdentityKeys => ::olm_account_identity_keys"}; } } void CryptoModule::generateOneTimeKeys(size_t oneTimeKeysAmount) { size_t oneTimeKeysSize = ::olm_account_generate_one_time_keys_random_length( this->account, oneTimeKeysAmount); if (this->keys.oneTimeKeys.size() == oneTimeKeysSize) { return; } OlmBuffer random; PlatformSpecificTools::generateSecureRandomBytes(random, oneTimeKeysSize); if (-1 == ::olm_account_generate_one_time_keys( this->account, oneTimeKeysAmount, random.data(), random.size())) { throw std::runtime_error{ "error generateOneTimeKeys => ::olm_account_generate_one_time_keys"}; } } // returns number of published keys size_t CryptoModule::publishOneTimeKeys() { this->keys.oneTimeKeys.resize( ::olm_account_one_time_keys_length(this->account)); if (-1 == ::olm_account_one_time_keys( this->account, this->keys.oneTimeKeys.data(), this->keys.oneTimeKeys.size())) { throw std::runtime_error{ "error publishOneTimeKeys => ::olm_account_one_time_keys"}; } return ::olm_account_mark_keys_as_published(this->account); } Keys CryptoModule::keysFromStrings( const std::string &identityKeys, const std::string &oneTimeKeys) { return { OlmBuffer(identityKeys.begin(), identityKeys.end()), OlmBuffer(oneTimeKeys.begin(), oneTimeKeys.end())}; } std::string CryptoModule::getIdentityKeys() { this->exposePublicIdentityKeys(); return std::string{ this->keys.identityKeys.begin(), this->keys.identityKeys.end()}; } std::string CryptoModule::getOneTimeKeys(size_t oneTimeKeysAmount) { this->generateOneTimeKeys(oneTimeKeysAmount); size_t publishedOneTimeKeys = this->publishOneTimeKeys(); if (publishedOneTimeKeys != oneTimeKeysAmount) { throw std::runtime_error{ "error generateKeys => invalid amount of one-time keys published. " "Expected " + std::to_string(oneTimeKeysAmount) + ", got " + std::to_string(publishedOneTimeKeys)}; } return std::string{ this->keys.oneTimeKeys.begin(), this->keys.oneTimeKeys.end()}; } void CryptoModule::initializeInboundForReceivingSession( const std::string &targetUserId, const OlmBuffer &encryptedMessage, const OlmBuffer &idKeys, const bool overwrite) { if (this->hasSessionFor(targetUserId)) { if (overwrite) { this->sessions.erase(this->sessions.find(targetUserId)); } else { throw std::runtime_error{ "error initializeInboundForReceivingSession => session already " "initialized"}; } } std::unique_ptr newSession = Session::createSessionAsResponder( this->account, this->keys.identityKeys.data(), encryptedMessage, idKeys); this->sessions.insert(make_pair(targetUserId, std::move(newSession))); } void CryptoModule::initializeOutboundForSendingSession( const std::string &targetUserId, const OlmBuffer &idKeys, const OlmBuffer &oneTimeKeys, size_t keyIndex) { if (this->hasSessionFor(targetUserId)) { throw std::runtime_error{ "error initializeOutboundForSendingSession => session already " "initialized"}; } std::unique_ptr newSession = Session::createSessionAsInitializer( this->account, this->keys.identityKeys.data(), idKeys, oneTimeKeys, keyIndex); this->sessions.insert(make_pair(targetUserId, std::move(newSession))); } bool CryptoModule::hasSessionFor(const std::string &targetUserId) { return (this->sessions.find(targetUserId) != this->sessions.end()); } std::shared_ptr CryptoModule::getSessionByUserId(const std::string &userId) { return this->sessions.at(userId); } bool CryptoModule::matchesInboundSession( const std::string &targetUserId, EncryptedData encryptedData, const OlmBuffer &theirIdentityKey) const { OlmSession *session = this->sessions.at(targetUserId)->getOlmSession(); // Check that the inbound session matches the message it was created from. OlmBuffer tmpEncryptedMessage(encryptedData.message); if (1 != ::olm_matches_inbound_session( session, tmpEncryptedMessage.data(), tmpEncryptedMessage.size())) { return false; } // Check that the inbound session matches the key this message is supposed // to be from. tmpEncryptedMessage = OlmBuffer(encryptedData.message); return 1 == ::olm_matches_inbound_session_from( session, theirIdentityKey.data() + ID_KEYS_PREFIX_OFFSET, KEYSIZE, tmpEncryptedMessage.data(), tmpEncryptedMessage.size()); } Persist CryptoModule::storeAsB64(const std::string &secretKey) { Persist persist; size_t accountPickleLength = ::olm_pickle_account_length(this->account); OlmBuffer accountPickleBuffer(accountPickleLength); if (accountPickleLength != ::olm_pickle_account( this->account, secretKey.data(), secretKey.size(), accountPickleBuffer.data(), accountPickleLength)) { throw std::runtime_error{"error storeAsB64 => ::olm_pickle_account"}; } persist.account = accountPickleBuffer; std::unordered_map>::iterator it; for (it = this->sessions.begin(); it != this->sessions.end(); ++it) { OlmBuffer buffer = it->second->storeAsB64(secretKey); persist.sessions.insert(make_pair(it->first, buffer)); } return persist; } void CryptoModule::restoreFromB64( const std::string &secretKey, Persist persist) { this->accountBuffer.resize(::olm_account_size()); this->account = ::olm_account(this->accountBuffer.data()); if (-1 == ::olm_unpickle_account( this->account, secretKey.data(), secretKey.size(), persist.account.data(), persist.account.size())) { throw std::runtime_error{"error restoreFromB64 => ::olm_unpickle_account"}; } if (persist.account.size() != ::olm_pickle_account_length(this->account)) { throw std::runtime_error{ "error restoreFromB64 => ::olm_pickle_account_length"}; } std::unordered_map::iterator it; for (it = persist.sessions.begin(); it != persist.sessions.end(); ++it) { std::unique_ptr session = session->restoreFromB64( this->account, this->keys.identityKeys.data(), secretKey, it->second); this->sessions.insert(make_pair(it->first, move(session))); } } EncryptedData CryptoModule::encrypt( const std::string &targetUserId, const std::string &content) { if (!this->hasSessionFor(targetUserId)) { throw std::runtime_error{"error encrypt => uninitialized session"}; } OlmSession *session = this->sessions.at(targetUserId)->getOlmSession(); OlmBuffer encryptedMessage( ::olm_encrypt_message_length(session, content.size())); OlmBuffer messageRandom; PlatformSpecificTools::generateSecureRandomBytes( messageRandom, ::olm_encrypt_random_length(session)); size_t messageType = ::olm_encrypt_message_type(session); if (-1 == ::olm_encrypt( session, (uint8_t *)content.data(), content.size(), messageRandom.data(), messageRandom.size(), encryptedMessage.data(), encryptedMessage.size())) { throw std::runtime_error{"error encrypt => ::olm_encrypt"}; } return {encryptedMessage, messageType}; } std::string CryptoModule::decrypt( const std::string &targetUserId, EncryptedData encryptedData, const OlmBuffer &theirIdentityKey) { if (!this->hasSessionFor(targetUserId)) { throw std::runtime_error{"error decrypt => uninitialized session"}; } OlmSession *session = this->sessions.at(targetUserId)->getOlmSession(); OlmBuffer tmpEncryptedMessage(encryptedData.message); if (encryptedData.messageType == (size_t)olm::MessageType::PRE_KEY) { if (!this->matchesInboundSession( targetUserId, encryptedData, theirIdentityKey)) { throw std::runtime_error{"error decrypt => matchesInboundSession"}; } } size_t maxSize = ::olm_decrypt_max_plaintext_length( session, encryptedData.messageType, tmpEncryptedMessage.data(), tmpEncryptedMessage.size()); OlmBuffer decryptedMessage(maxSize); size_t decryptedSize = ::olm_decrypt( session, encryptedData.messageType, encryptedData.message.data(), encryptedData.message.size(), decryptedMessage.data(), decryptedMessage.size()); if (decryptedSize == -1) { throw std::runtime_error{"error ::olm_decrypt"}; } return std::string{(char *)decryptedMessage.data(), decryptedSize}; } } // namespace crypto } // namespace comm diff --git a/native/cpp/CommonCpp/CryptoTools/Session.cpp b/native/cpp/CommonCpp/CryptoTools/Session.cpp index 3463554cc..253d82f68 100644 --- a/native/cpp/CommonCpp/CryptoTools/Session.cpp +++ b/native/cpp/CommonCpp/CryptoTools/Session.cpp @@ -1,111 +1,113 @@ #include "Session.h" #include "PlatformSpecificTools.h" +#include + namespace comm { namespace crypto { std::unique_ptr Session::createSessionAsInitializer( OlmAccount *account, std::uint8_t *ownerIdentityKeys, const OlmBuffer &idKeys, const OlmBuffer &oneTimeKeys, size_t keyIndex) { std::unique_ptr session(new Session(account, ownerIdentityKeys)); session->olmSessionBuffer.resize(::olm_session_size()); session->olmSession = ::olm_session(session->olmSessionBuffer.data()); OlmBuffer randomBuffer; PlatformSpecificTools::generateSecureRandomBytes( randomBuffer, ::olm_create_outbound_session_random_length(session->olmSession)); if (-1 == ::olm_create_outbound_session( session->olmSession, session->ownerUserAccount, idKeys.data() + ID_KEYS_PREFIX_OFFSET, KEYSIZE, oneTimeKeys.data() + ONE_TIME_KEYS_PREFIX_OFFSET + (KEYSIZE + ONE_TIME_KEYS_MIDDLE_OFFSET) * keyIndex, KEYSIZE, randomBuffer.data(), randomBuffer.size())) { throw std::runtime_error( "error createOutbound => ::olm_create_outbound_session"); } return session; } std::unique_ptr Session::createSessionAsResponder( OlmAccount *account, std::uint8_t *ownerIdentityKeys, const OlmBuffer &encryptedMessage, const OlmBuffer &idKeys) { std::unique_ptr session(new Session(account, ownerIdentityKeys)); OlmBuffer tmpEncryptedMessage(encryptedMessage); session->olmSessionBuffer.resize(::olm_session_size()); session->olmSession = ::olm_session(session->olmSessionBuffer.data()); if (-1 == ::olm_create_inbound_session( session->olmSession, session->ownerUserAccount, tmpEncryptedMessage.data(), encryptedMessage.size())) { throw std::runtime_error( "error createInbound => ::olm_create_inbound_session"); } return session; } OlmBuffer Session::storeAsB64(const std::string &secretKey) { size_t pickleLength = ::olm_pickle_session_length(this->olmSession); OlmBuffer pickle(pickleLength); size_t res = ::olm_pickle_session( this->olmSession, secretKey.data(), secretKey.size(), pickle.data(), pickleLength); if (pickleLength != res) { throw std::runtime_error("error pickleSession => ::olm_pickle_session"); } return pickle; } std::unique_ptr Session::restoreFromB64( OlmAccount *account, std::uint8_t *ownerIdentityKeys, const std::string &secretKey, OlmBuffer &b64) { std::unique_ptr session(new Session(account, ownerIdentityKeys)); session->olmSessionBuffer.resize(::olm_session_size()); session->olmSession = ::olm_session(session->olmSessionBuffer.data()); if (-1 == ::olm_unpickle_session( session->olmSession, secretKey.data(), secretKey.size(), b64.data(), b64.size())) { throw std::runtime_error("error pickleSession => ::olm_unpickle_session"); } if (b64.size() != ::olm_pickle_session_length(session->olmSession)) { throw std::runtime_error( "error pickleSession => ::olm_pickle_session_length"); } return session; } OlmSession *Session::getOlmSession() { if (this->olmSession == nullptr) { throw std::runtime_error( "trying to obtain a session pointer of uninitialized session"); } return this->olmSession; } } // namespace crypto } // namespace comm diff --git a/native/cpp/CommonCpp/CryptoTools/Tools.h b/native/cpp/CommonCpp/CryptoTools/Tools.h index ce185c822..2c9dabc0d 100644 --- a/native/cpp/CommonCpp/CryptoTools/Tools.h +++ b/native/cpp/CommonCpp/CryptoTools/Tools.h @@ -1,38 +1,40 @@ #pragma once +#include +#include #include #include "olm/olm.h" #define KEYSIZE 43 #define ID_KEYS_PREFIX_OFFSET 15 #define ONE_TIME_KEYS_PREFIX_OFFSET 25 #define ONE_TIME_KEYS_MIDDLE_OFFSET 12 namespace comm { namespace crypto { typedef std::vector OlmBuffer; struct Keys { OlmBuffer identityKeys; // size = 116 OlmBuffer oneTimeKeys; // size = 43 each }; struct EncryptedData { OlmBuffer message; size_t messageType; }; class Tools { private: static std::string generateRandomString(size_t size, const std::string &availableSigns); public: static std::string generateRandomString(size_t size); static std::string generateRandomHexString(size_t size); }; } // namespace crypto } // namespace comm