diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index c97a2279b2700354c0e8a94e36dda3887c7769df..c0abb99ca994d4d9ff91947fe00f5dbf0dbeaeee 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -3,7 +3,7 @@ if (fmt_FOUND) endif() if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_definitions(-DNOGDI) + add_definitions(-DNOGDI -DNOMINMAX) endif() add_definitions(-DBOOST_FILESYSTEM_NO_DEPRECATED) @@ -50,6 +50,7 @@ set(VEREIGNLIB_SRC vereign/core/rand.cc vereign/core/temp.cc vereign/core/fs.cc + vereign/core/string.cc vereign/restapi/detail/http_reader.cc vereign/restapi/client.cc @@ -95,6 +96,9 @@ if (LINUX) ) elseif (WIN32) list(APPEND VEREIGNLIB_SRC + vereign/ncrypt/errors.cc + vereign/ncrypt/unique_ptr.cc + vereign/ncrypt/rsa.cc vereign/kvstore/detail/linux_crypto_storage.cc ) endif() @@ -115,6 +119,8 @@ target_link_libraries(vereignlib PUBLIC Boost::filesystem $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> SQLite::SQLite3 + $<$<CXX_COMPILER_ID:MSVC>:ncrypt.lib> + $<$<CXX_COMPILER_ID:MSVC>:cryptui.lib> ) add_library(vereign SHARED diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc index 0f3226d364e1b95441b80b2706f5f0222b347e8a..e8288a68446d589959df01778774b5344b9429fa 100644 --- a/cpp/src/csandbox.cc +++ b/cpp/src/csandbox.cc @@ -1,6 +1,10 @@ #include <boost/core/ignore_unused.hpp> #include <iostream> #include <boost/filesystem.hpp> +#include <vereign/core/string.hh> + +#include <vereign/bytes/view.hh> +#include <vereign/bytes/view_dump.hh> namespace fs = boost::filesystem; @@ -10,11 +14,37 @@ auto main(int argc, char** argv) -> int { // fs::path ; - std::cout << "Temp directory is " << fs::temp_directory_path() << '\n'; + // std::wstring s = L"c:\\проба.txt"; + std::string path = "c:\\ztrash\\проба.txt"; + std::wstring ws = vereign::string::widen(path); + + std::string p2 = vereign::string::narrow(ws); + std::cout << vereign::bytes::dump(vereign::bytes::View(path)) << std::endl; + std::cout << vereign::bytes::dump(vereign::bytes::View(p2)) << std::endl; + if (p2 == path) { + std::cout << "yes" << std::endl; + } + + std::cout << vereign::bytes::dump(vereign::bytes::View(ws)) << std::endl; + + // std::ofstream f{ws}; + // std::wcout << ws; + std::cout << path << std::endl; + std::wcout << ws << std::endl; + // std::cout << "проба" << std::endl; + // wchar_t ru[] = L"Привет"; //Russian language + // std::wcout << ru; + // std::cout << std::endl; + // std::cout << "Temp directory is " << fs::temp_directory_path() << '\n'; // std::cout << "Home directory is " << fs::user << '\n'; // p.wstring(); // std::cout << p.string() << std::endl;; + // wchar_t* in = L"ทดสà¸à¸š"; // thai language + // char* out=(char *)malloc(15); + // WideCharToMultiByte(874, 0, in, 15, out, 15, NULL, NULL); + // printf(out); // result is correctly in Thai although not neat + return 1; } diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh index ddf1c7514b8df61731d6d96c6e38ad3937c18309..c64f59a14003857e8d7b5c3f7efbf0b619ad8f97 100644 --- a/cpp/src/vereign/bytes/view.hh +++ b/cpp/src/vereign/bytes/view.hh @@ -11,7 +11,7 @@ namespace vereign::bytes { class View { public: - View() noexcept : size_{0}, data_{nullptr} {} + View() = default; View(const uint8_t* data, std::size_t size) noexcept : size_{size}, @@ -25,6 +25,12 @@ public: { } + View(std::wstring_view str) noexcept + : size_{str.length() * sizeof(wchar_t)}, + data_{size_ > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr} + { + } + View(const void* ptr, std::size_t size) noexcept : size_{size}, data_{static_cast<const uint8_t*>(ptr)} diff --git a/cpp/src/vereign/core/lock_guard.hh b/cpp/src/vereign/core/lock_guard.hh new file mode 100644 index 0000000000000000000000000000000000000000..9e310b21ca53ce5c53fea581a483ecaa568004f4 --- /dev/null +++ b/cpp/src/vereign/core/lock_guard.hh @@ -0,0 +1,28 @@ +#ifndef __VEREIGN_LOCK_GUARD_HH +#define __VEREIGN_LOCK_GUARD_HH + +namespace vereign::core { + +template <class Lockable> +class LockGuard { +public: + explicit LockGuard(Lockable& lock) + : lock_{lock} + { + lock.Lock(); + } + + ~LockGuard() noexcept { + lock_.Unlock(); + } + + LockGuard(const LockGuard&) = delete; + auto operator=(const LockGuard&) -> LockGuard& = delete; + +private: + Lockable& lock_; +}; + +} // namespace vereign::kvstore + +#endif // __VEREIGN_LOCK_GUARD_HH diff --git a/cpp/src/vereign/core/string.cc b/cpp/src/vereign/core/string.cc new file mode 100644 index 0000000000000000000000000000000000000000..933e2e2fb8412ff2a0e0bf244c90bf7873924594 --- /dev/null +++ b/cpp/src/vereign/core/string.cc @@ -0,0 +1,84 @@ +#include <vereign/core/string.hh> + +#include <iostream> + +#ifdef _WIN32 +# include <windows.h> +# include <stringapiset.h> +#endif + +namespace vereign::string { + +#ifdef _WIN32 + +auto widen(const std::string& utf8_str) -> std::wstring { + if (utf8_str.empty()) { + return L""; + } + + int num_chars = MultiByteToWideChar(CP_UTF8 , 0, utf8_str.data(), utf8_str.length(), nullptr, 0); + if (num_chars == 0) { + return std::wstring{}; + } + + std::wstring result; + result.resize(num_chars); + num_chars = MultiByteToWideChar( + CP_UTF8, + 0, + utf8_str.data(), + utf8_str.length(), + result.data(), + num_chars + ); + + if (num_chars == 0) { + return L""; + } + + return result; +} + +auto narrow(const std::wstring& utf16_str) -> std::string { + if (utf16_str.empty()) { + return ""; + } + + int num_chars = WideCharToMultiByte( + CP_UTF8, + 0, + utf16_str.data(), + utf16_str.length(), + nullptr, + 0, + nullptr, + nullptr + ); + + if (num_chars == 0) { + return ""; + } + + std::string result; + result.resize(num_chars); + num_chars = WideCharToMultiByte( + CP_UTF8, + 0, + utf16_str.data(), + utf16_str.length(), + result.data(), + num_chars, + nullptr, + nullptr + ); + + if (num_chars == 0) { + return ""; + } + + return result; +} + +#endif + +} // vereign::string diff --git a/cpp/src/vereign/core/string.hh b/cpp/src/vereign/core/string.hh new file mode 100644 index 0000000000000000000000000000000000000000..1db221266e4aca68d3d86764dbbbf59008adccd3 --- /dev/null +++ b/cpp/src/vereign/core/string.hh @@ -0,0 +1,18 @@ +#ifndef __VEREIGN_CORE_STRING_HH +#define __VEREIGN_CORE_STRING_HH + +#include <string> +#include <codecvt> + +namespace vereign::string { + +#ifdef _WIN32 + +auto widen(const std::string& utf8_str) -> std::wstring; +auto narrow(const std::wstring& utf16_str) -> std::string; + +#endif + +} // vereign::string + +#endif // __VEREIGN_CORE_STRING_HH diff --git a/cpp/src/vereign/encoding/hex.cc b/cpp/src/vereign/encoding/hex.cc index 6c8dc18cf76bc8d93ae78e82c739ea70744e77a1..e513512bfddecebab9f9fdd51423bbf5ad811f36 100644 --- a/cpp/src/vereign/encoding/hex.cc +++ b/cpp/src/vereign/encoding/hex.cc @@ -51,4 +51,21 @@ void Decode(std::string_view src, bytes::Buffer& decoded) { decoded.IncSize(src.size() / 2); } +void EncodeReverse(bytes::View src, bytes::Buffer& encoded) { + if (src.Size() == 0) { + return; + } + + static const char* nibbles = { "0123456789abcdef" }; + + encoded.Reserve(src.Size() * 2); + + for (std::size_t i = 0; i < src.Size(); ++i) { + encoded[i*2] = nibbles[src[src.Size() - 1 - i] >> 4]; + encoded[i*2 + 1] = nibbles[src[src.Size() - 1 - i] & 0x0F]; + } + + encoded.IncSize(src.Size() * 2); +} + } // vereign::encoding::base64 diff --git a/cpp/src/vereign/encoding/hex.hh b/cpp/src/vereign/encoding/hex.hh index 495f09027acb88f9c7f4066fc33d29a05f476b49..f2e406453bda12dbead8086bcf70099b72d2b176 100644 --- a/cpp/src/vereign/encoding/hex.hh +++ b/cpp/src/vereign/encoding/hex.hh @@ -9,6 +9,8 @@ namespace vereign::encoding::hex { void Encode(bytes::View src, bytes::Buffer& encoded); void Decode(bytes::View src, bytes::Buffer& decoded); +void EncodeReverse(bytes::View src, bytes::Buffer& encoded); + } // vereign::encoding::hex #endif // __VEREIGN_ENCODING_HEX_HH diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc index 390c2cb4fd462068457288aefe93b9d200393bf0..de5b48d28b5f2e68628f4c1b3b155de3f29f4db9 100644 --- a/cpp/src/vereign/kvstore/sqlite_storage.cc +++ b/cpp/src/vereign/kvstore/sqlite_storage.cc @@ -1,12 +1,59 @@ #include <vereign/kvstore/sqlite_storage.hh> + #include <vereign/encoding/binary.hh> +#include <vereign/kvstore/errors.hh> +#include <boost/optional.hpp> +#include <vereign/core/lock_guard.hh> +#include <mutex> #include <array> namespace vereign::kvstore { +namespace detail { + +class SqliteLock { +public: + SqliteLock(sqlite::Connection& db) + : db_{db} + { + } + + ~SqliteLock() { + Unlock(); + } + + void Lock() { + if (!tr_) { + tr_ = db_.BeginExplicitTransaction(); + } + + count_++; + } + + void Unlock() { + if (count_ == 0 || !tr_) { + throw Error{"SqliteStorage unexpected call Unlock with non existent lock"}; + } + + count_--; + if (count_ == 0) { + tr_->Commit(); + tr_.reset(); + } + } + +private: + int count_ = 0; + boost::optional<sqlite::Transaction> tr_; + sqlite::Connection& db_; +}; + +} + SqliteStorage::SqliteStorage(const std::string& db_path) - : db_{db_path} + : db_{db_path}, + lock_{std::make_unique<detail::SqliteLock>(db_)} { db_.Execute(R"( CREATE TABLE IF NOT EXISTS storage ( @@ -16,19 +63,27 @@ CREATE TABLE IF NOT EXISTS storage ( )"); } +SqliteStorage::~SqliteStorage() = default; + +void SqliteStorage::Lock() { + lock_->Lock(); +} + +void SqliteStorage::Unlock() { + lock_->Unlock(); +} + void SqliteStorage::PutBytes(const std::string& key, bytes::View value) { - auto tr = db_.BeginExplicitTransaction(); + core::LockGuard<detail::SqliteLock> l{*lock_}; auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);"); stmt.BindText(1, key); stmt.BindBlob(2, value); stmt.Step(); - - tr.Commit(); } void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) { - auto tr = db_.BeginExplicitTransaction(); + core::LockGuard<detail::SqliteLock> l{*lock_}; auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?"); stmt.BindText(1, key); @@ -37,12 +92,11 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) { if (!end) { value.Write(stmt.GetColumnBlob(0)); } - - tr.Commit(); } void SqliteStorage::PutInt64(const std::string& key, int64_t value) { - auto tr = db_.BeginExplicitTransaction(); + core::LockGuard<detail::SqliteLock> l{*lock_}; + std::array<uint8_t, 8> encoded; encoding::binary::EncodeUint64(encoded.data(), value); @@ -50,12 +104,10 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) { stmt.BindText(1, key); stmt.BindBlob(2, bytes::View(encoded.data(), 8)); stmt.Step(); - - tr.Commit(); } auto SqliteStorage::GetInt64(const std::string& key) -> int64_t { - auto tr = db_.BeginExplicitTransaction(); + core::LockGuard<detail::SqliteLock> l{*lock_}; auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?"); stmt.BindText(1, key); @@ -67,14 +119,11 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t { auto buf = stmt.GetColumnBlob(0); if (buf.Size() != 8) { - // FIXME: probably throw an exception here - return 0; + throw Error("cannot decode bytes size"); } int64_t result = encoding::binary::DecodeUint64(buf); - tr.Commit(); - return result; } diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh index ef91c50e0a40dec5b07c16355a13de1bf01b7d4a..012c35f494faae64d4bfc04b9e23cfb19b6d8555 100644 --- a/cpp/src/vereign/kvstore/sqlite_storage.hh +++ b/cpp/src/vereign/kvstore/sqlite_storage.hh @@ -4,11 +4,24 @@ #include <vereign/kvstore/storage.hh> #include <vereign/sqlite/connection.hh> +#include <memory> + namespace vereign::kvstore { +namespace detail { +class SqliteLock; +} + class SqliteStorage : public Storage { public: SqliteStorage(const std::string& db_path); + ~SqliteStorage() override; + + SqliteStorage(const SqliteStorage&) = delete; + auto operator=(const SqliteStorage&) -> SqliteStorage& = delete; + + void Lock() override; + void Unlock() override; void PutBytes(const std::string& key, bytes::View value) override; void GetBytes(const std::string& key, bytes::Buffer& value) override; @@ -18,6 +31,7 @@ public: private: sqlite::Connection db_; + std::unique_ptr<detail::SqliteLock> lock_; }; } // namespace vereign::kvstore diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh index a8adf3d756d070b949f1ac9c443ec284fb357b07..2bdb268d6395ceb981bed9adf6b442e7b34c0fe1 100644 --- a/cpp/src/vereign/kvstore/storage.hh +++ b/cpp/src/vereign/kvstore/storage.hh @@ -7,6 +7,9 @@ namespace vereign::kvstore { class Storage { public: + virtual void Lock() = 0; + virtual void Unlock() = 0; + virtual void PutBytes(const std::string& key, bytes::View value) = 0; virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0; diff --git a/cpp/src/vereign/ncrypt/errors.cc b/cpp/src/vereign/ncrypt/errors.cc new file mode 100644 index 0000000000000000000000000000000000000000..fedb516591b3709bad2cbc244caabc0f4f9a3e79 --- /dev/null +++ b/cpp/src/vereign/ncrypt/errors.cc @@ -0,0 +1,45 @@ +#include <vereign/ncrypt/errors.hh> +#include <vereign/bytes/buffer.hh> +#include <vereign/bytes/view_dump.hh> +#include <vereign/encoding/hex.hh> +#include <vereign/encoding/binary.hh> + +#include <string> + +namespace vereign::ncrypt { + +auto SecurityStatusToString(SECURITY_STATUS status) -> std::string { + switch (status) { + case NTE_FAIL: + return "NTE_FAIL"; + case NTE_INVALID_PARAMETER: + return "NTE_INVALID_PARAMETER"; + case NTE_BUFFER_TOO_SMALL: + return "NTE_BUFFER_TOO_SMALL"; + case NTE_EXISTS: + return "NTE_EXISTS"; + case NTE_BAD_KEYSET: + return "NTE_BAD_KEYSET"; + case NTE_INVALID_HANDLE: + return "NTE_INVALID_HANDLE"; + case NTE_NOT_SUPPORTED: + return "NTE_NOT_SUPPORTED"; + case NTE_NULL_REFERENCE_POINTER: + return "NTE_NULL_REFERENCE_POINTER"; + default: + bytes::Buffer encoded; + encoding::hex::EncodeReverse(bytes::View(&status, sizeof(status)), encoded); + return std::string(encoded.View().String()); + } +} + +Error::Error(SECURITY_STATUS status, const std::string& msg) + : std::runtime_error{msg + ": " + SecurityStatusToString(status)}, + status_{status} +{} + +auto Error::SecurityStatus() const -> SECURITY_STATUS { + return status_; +} + +} // vereign::ncrypt diff --git a/cpp/src/vereign/ncrypt/errors.hh b/cpp/src/vereign/ncrypt/errors.hh new file mode 100644 index 0000000000000000000000000000000000000000..a37056c02947ccdcd0aa49841aaeb4593dd6c06e --- /dev/null +++ b/cpp/src/vereign/ncrypt/errors.hh @@ -0,0 +1,26 @@ +#ifndef __VEREIGN_NCRYPT_ERRORS_HH +#define __VEREIGN_NCRYPT_ERRORS_HH + +#include <windows.h> +#include <ncrypt.h> +#include <stdexcept> + +namespace vereign::ncrypt { + +constexpr SECURITY_STATUS NTE_NULL_REFERENCE_POINTER = 0x800706f4; + +auto SecurityStatusToString(SECURITY_STATUS status) -> std::string; + +class Error : public std::runtime_error { +public: + Error(SECURITY_STATUS status, const std::string& msg); + + auto SecurityStatus() const -> SECURITY_STATUS; + +private: + SECURITY_STATUS status_; +}; + +} // vereign::ncrypt + +#endif // __VEREIGN_NCRYPT_ERRORS_HH diff --git a/cpp/src/vereign/ncrypt/rsa.cc b/cpp/src/vereign/ncrypt/rsa.cc new file mode 100644 index 0000000000000000000000000000000000000000..d745434813e1c82f256267047f48e4ef0b716a21 --- /dev/null +++ b/cpp/src/vereign/ncrypt/rsa.cc @@ -0,0 +1,173 @@ +#include <vereign/ncrypt/rsa.hh> + +#include <vereign/ncrypt/errors.hh> +#include <vereign/core/string.hh> + +namespace vereign::ncrypt::rsa { + +auto OpenStorageProvider() -> UniquePtr { + UniquePtr provider{}; + auto status = NCryptOpenStorageProvider(provider.Ref(), MS_KEY_STORAGE_PROVIDER, 0); + if (status != ERROR_SUCCESS) { + throw Error{status, "open crypto store failed"}; + } + + return provider; +} + +auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr { + UniquePtr key{}; + auto wkey_name = string::widen(key_name); + auto status = NCryptOpenKey(provider, key.Ref(), wkey_name.data(), 0, 0); + if (status != ERROR_SUCCESS && status != NTE_BAD_KEYSET) { + throw Error{status, "open key failed"}; + } + + return key; +} + +auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr { + UniquePtr key{}; + auto wkey_name = string::widen(key_name); + auto status = NCryptCreatePersistedKey( + provider, + key.Ref(), + BCRYPT_RSA_ALGORITHM, + wkey_name.data(), + 0, + 0 + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "creating rsa key failed"}; + } + + auto key_len = DWORD(bits); + status = NCryptSetProperty( + key.Get(), + NCRYPT_LENGTH_PROPERTY, + (PBYTE)&key_len, + sizeof(key_len), + NCRYPT_PERSIST_FLAG + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "setup rsa key length failed"}; + } + + // FIXME: add the ui policy + // NCRYPT_UI_POLICY ui_policy{}; + // ui_policy.dwVersion = 1; + // ui_policy.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG; + // ui_policy.pszCreationTitle = L"Vereign Client"; + // ui_policy.pszDescription = + // L"Vereign Client will use this key to authenticate with the Vereign Services"; + // ui_policy.pszFriendlyName = L"Vereign Client Identity Key"; + + // status = NCryptSetProperty( + // key.Get(), + // NCRYPT_UI_POLICY_PROPERTY, + // (PBYTE)&ui_policy, + // sizeof(ui_policy), + // NCRYPT_PERSIST_FLAG + // ); + // if (status != ERROR_SUCCESS) { + // throw Error{status, "configure key ui policy failed"}; + // } + + status = NCryptFinalizeKey(key.Get(), 0); + if (status != ERROR_SUCCESS) { + throw Error{status, "finalizing rsa key failed"}; + } + + return key; +} + +void DeleteKey(NCRYPT_KEY_HANDLE key) { + auto status = NCryptDeleteKey(key, 0); + if (status != ERROR_SUCCESS) { + throw Error{status, "deleting key failed"}; + } +} + +void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted) { + BCRYPT_OAEP_PADDING_INFO pad; + int flags = NCRYPT_PAD_OAEP_FLAG; + pad.pszAlgId = BCRYPT_SHA1_ALGORITHM; + pad.pbLabel = nullptr; + pad.cbLabel = 0; + DWORD size; + + auto status = NCryptEncrypt( + key, + (PBYTE)src.Data(), + (DWORD)src.Size(), + &pad, + nullptr, + 0, + &size, + flags + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "encryption failed"}; + } + + encrypted.Reserve(size); + + status = NCryptEncrypt( + key, + (PBYTE)src.Data(), + (DWORD)src.Size(), + &pad, + (PBYTE)encrypted.end(), + size, + &size, + flags + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "encryption failed"}; + } + + encrypted.IncSize(size); +} + +void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted) { + BCRYPT_OAEP_PADDING_INFO pad; + int flags = NCRYPT_PAD_OAEP_FLAG; + pad.pszAlgId = BCRYPT_SHA1_ALGORITHM; + pad.pbLabel = nullptr; + pad.cbLabel = 0; + DWORD size; + + auto status = NCryptDecrypt( + key, + (PBYTE)src.Data(), + (DWORD)src.Size(), + &pad, + nullptr, + 0, + &size, + flags + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "decryption failed"}; + } + + decrypted.Reserve(size); + + status = NCryptDecrypt( + key, + (PBYTE)src.Data(), + (DWORD)src.Size(), + &pad, + (PBYTE)decrypted.end(), + size, + &size, + flags + ); + if (status != ERROR_SUCCESS) { + throw Error{status, "decryption failed"}; + } + + decrypted.IncSize(size); +} + +} // vereign::ncrypt::rsa diff --git a/cpp/src/vereign/ncrypt/rsa.hh b/cpp/src/vereign/ncrypt/rsa.hh new file mode 100644 index 0000000000000000000000000000000000000000..749eb512eca488b08b33df38eb0dbd24f8f2fd57 --- /dev/null +++ b/cpp/src/vereign/ncrypt/rsa.hh @@ -0,0 +1,23 @@ +#ifndef __VEREIGN_NCRYPT_RSA_HH +#define __VEREIGN_NCRYPT_RSA_HH + +#include <vereign/ncrypt/unique_ptr.hh> +#include <vereign/bytes/buffer.hh> + +#include <windows.h> +#include <ncrypt.h> +#include <string> + +namespace vereign::ncrypt::rsa { + +auto OpenStorageProvider() -> UniquePtr; +auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr; +auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr; +void DeleteKey(NCRYPT_KEY_HANDLE key); + +void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted); +void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted); + +} // vereign::ncrypt::rsa + +#endif // __VEREIGN_NCRYPT_RSA_HH diff --git a/cpp/src/vereign/ncrypt/unique_ptr.cc b/cpp/src/vereign/ncrypt/unique_ptr.cc new file mode 100644 index 0000000000000000000000000000000000000000..06e7d2c9239e9b68b4877b7b78ff8621ef897837 --- /dev/null +++ b/cpp/src/vereign/ncrypt/unique_ptr.cc @@ -0,0 +1,82 @@ +#include <vereign/ncrypt/unique_ptr.hh> + +#include <vereign/ncrypt/errors.hh> +#include <utility> +#include <iostream> + +namespace vereign::ncrypt { + +UniquePtr::UniquePtr() noexcept + : ptr_{0} +{ +} + +UniquePtr::UniquePtr(ULONG_PTR ptr) noexcept + : ptr_{ptr} +{ +} + +UniquePtr::UniquePtr(UniquePtr&& other) noexcept + : ptr_{other.ptr_} +{ + other.ptr_ = 0; +} + +auto UniquePtr::operator=(UniquePtr&& other) noexcept -> UniquePtr& { + std::swap(ptr_, other.ptr_); + + return *this; +} + +UniquePtr::~UniquePtr() noexcept { + if (ptr_ == 0) { + return; + } + + NCryptFreeObject(ptr_); +} + +auto UniquePtr::Get() const noexcept -> ULONG_PTR { + return ptr_; +} + +auto UniquePtr::Ref() noexcept -> ULONG_PTR* { + return &ptr_; +} + +void UniquePtr::Reset() { + if (ptr_ == 0) { + return; + } + + auto status = NCryptFreeObject(ptr_); + if (status == NTE_INVALID_HANDLE) { + throw Error(status, "free object failed"); + } + + ptr_ = 0; +} + +auto UniquePtr::Release() -> ULONG_PTR { + if (ptr_ == 0) { + return 0; + } + + auto result = ptr_; + + auto status = NCryptFreeObject(ptr_); + if (status == NTE_INVALID_HANDLE) { + throw Error(status, "free object failed"); + } + + ptr_ = 0; + + return result; +} + + +UniquePtr::operator bool() const noexcept { + return ptr_ != 0; +} + +} // vereign::ncrypt diff --git a/cpp/src/vereign/ncrypt/unique_ptr.hh b/cpp/src/vereign/ncrypt/unique_ptr.hh new file mode 100644 index 0000000000000000000000000000000000000000..8341e3b08bbd08848a06c0931067ac23848e03e1 --- /dev/null +++ b/cpp/src/vereign/ncrypt/unique_ptr.hh @@ -0,0 +1,33 @@ +#ifndef __VEREIGN_NCRYPT_UNIQUE_PTR_HH +#define __VEREIGN_NCRYPT_UNIQUE_PTR_HH + +#include <windows.h> + +namespace vereign::ncrypt { + +class UniquePtr { +public: + UniquePtr() noexcept; + explicit UniquePtr(ULONG_PTR ptr) noexcept; + + UniquePtr(UniquePtr&& other) noexcept; + auto operator=(UniquePtr&& other) noexcept -> UniquePtr&; + + UniquePtr(const UniquePtr&) = delete; + auto operator=(const UniquePtr&) -> UniquePtr& = delete; + + ~UniquePtr() noexcept ; + + auto Get() const noexcept -> ULONG_PTR; + auto Ref() noexcept -> ULONG_PTR*; + void Reset(); + auto Release() -> ULONG_PTR; + operator bool() const noexcept; + +private: + ULONG_PTR ptr_; +}; + +} // vereign::ncrypt + +#endif // __VEREIGN_NCRYPT_UNIQUE_PTR_HH diff --git a/cpp/src/vereign/sqlite/connection.cc b/cpp/src/vereign/sqlite/connection.cc index d5af4f388aff59a068d51de0879176379dead23f..e12fb7c4a85e97df396c12c2d8649290e05b1499 100644 --- a/cpp/src/vereign/sqlite/connection.cc +++ b/cpp/src/vereign/sqlite/connection.cc @@ -1,3 +1,4 @@ +#include <stdexcept> #include <vereign/sqlite/connection.hh> #include <vereign/sqlite/errors.hh> @@ -21,6 +22,18 @@ Transaction::Transaction(Transaction&& other) other.finished_ = true; } +auto Transaction::operator=(Transaction&& other) -> Transaction& { + if (!finished_) { + throw std::runtime_error{"cannot move assign to non finished transaction"}; + } + + db_ = other.db_; + finished_ = other.finished_; + other.finished_ = true; + + return *this; +} + void Transaction::Commit() { char* errMsg = nullptr; auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); }); diff --git a/cpp/src/vereign/sqlite/connection.hh b/cpp/src/vereign/sqlite/connection.hh index ffa4ef85b885675e8bd662c9bd436ec492a1a675..86bfd6f839847984f26d0eae3d8ba05ef1f1fbb7 100644 --- a/cpp/src/vereign/sqlite/connection.hh +++ b/cpp/src/vereign/sqlite/connection.hh @@ -14,14 +14,14 @@ namespace vereign::sqlite { class Transaction { private: friend class Connection; - explicit Transaction(sqlite3* db); - Transaction(Transaction&&); public: void Commit(); void Rollback(); + Transaction(Transaction&&); + auto operator=(Transaction&&) -> Transaction&; ~Transaction(); Transaction(const Transaction&) = delete; diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt index 31ba5f294ee8062cf252096588ebfa17d9f289f9..172c4032825569568b6a6cba042d4cde96a91360 100644 --- a/cpp/tests/vereign/CMakeLists.txt +++ b/cpp/tests/vereign/CMakeLists.txt @@ -1,6 +1,6 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_definitions(-DNOGDI) + add_definitions(-DNOGDI -DNOMINMAX) endif() include_directories( @@ -10,7 +10,7 @@ include_directories( ${Boost_INCLUDE_DIRS} ) -list(APPEND tests_src +list(APPEND TESTS_SRC init_tests.cc ../util/protobuf.cc ../experiment/array.cc @@ -38,7 +38,14 @@ list(APPEND tests_src grpc/server_test.cc grpc/json/encoder_test.cc ) -add_executable(tests ${tests_src}) + +if (WIN32) + list(APPEND TESTS_SRC + ncrypt/rsa_test.cc + ) +endif() + +add_executable(tests ${TESTS_SRC}) target_proto_generate( TARGET tests diff --git a/cpp/tests/vereign/crypto/rsa_test.cc b/cpp/tests/vereign/crypto/rsa_test.cc index c9356e1f456547b6137ca8423709e7c75ba9de55..b484d6eca07021b0838f95ad6ef56f3ffc815aed 100644 --- a/cpp/tests/vereign/crypto/rsa_test.cc +++ b/cpp/tests/vereign/crypto/rsa_test.cc @@ -11,7 +11,7 @@ using namespace vereign; TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa][vereign/crypto]") { SECTION("small input") { - auto key = crypto::rsa::GenerateKey(4096); + auto key = crypto::rsa::GenerateKey(2048); const std::string input{"foo bar"}; bytes::Buffer encrypted; @@ -25,7 +25,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa } SECTION("zero input") { - auto key = crypto::rsa::GenerateKey(4096); + auto key = crypto::rsa::GenerateKey(2048); const std::string input; bytes::Buffer encrypted; @@ -39,9 +39,9 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa } SECTION("max size input") { - auto key = crypto::rsa::GenerateKey(4096); + auto key = crypto::rsa::GenerateKey(2048); - bytes::Buffer input{470}; + bytes::Buffer input{214}; crypto::Rand(input); bytes::Buffer encrypted; @@ -54,9 +54,9 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa } SECTION("invalid big input") { - auto key = crypto::rsa::GenerateKey(4096); + auto key = crypto::rsa::GenerateKey(2048); - bytes::Buffer input{471}; + bytes::Buffer input{215}; crypto::Rand(input); bytes::Buffer encrypted; diff --git a/cpp/tests/vereign/ncrypt/rsa_test.cc b/cpp/tests/vereign/ncrypt/rsa_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..0c3c4aae1143630b32bb4540560d2374b5f0b63b --- /dev/null +++ b/cpp/tests/vereign/ncrypt/rsa_test.cc @@ -0,0 +1,211 @@ +#include <vereign/crypto/rand.hh> +#include <vereign/ncrypt/rsa.hh> + +#include <vereign/core/scope_guard.hh> +#include <vereign/ncrypt/unique_ptr.hh> +#include <vereign/bytes/view_dump.hh> + +#include <catch2/catch.hpp> +#include <iostream> + +using namespace vereign; + +TEST_CASE("ncrypt::rsa::OpenStorageProvider", "[vereign/ncrypt/rsa][vereign/ncrypt]") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + CHECK(provider.Get() != 0); +} + +TEST_CASE("ncrypt::CreateKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") { + const auto test_key = std::string{"vereign_test_key"}; + + SECTION("when the key does not exists, it must create a new key") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + CHECK(new_key.Get() != 0); + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() != 0); + + // cleanup + ncrypt::rsa::DeleteKey(key.Get()); + } + + SECTION("when the key already exists, it must fail with NTE_EXISTS") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + CHECK(new_key.Get() != 0); + + CHECK_THROWS_WITH( + ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key), + "creating rsa key failed: NTE_EXISTS" + ); + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() != 0); + + // cleanup + ncrypt::rsa::DeleteKey(key.Get()); + } +} + +TEST_CASE("ncrypt::rsa::LoadKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") { + const auto test_key = std::string{"vereign_test_key"}; + + SECTION("when the key exists, it must load the key") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + CHECK(new_key.Get() != 0); + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() != 0); + + // cleanup + ncrypt::rsa::DeleteKey(key.Get()); + } + + SECTION("when the key does not exists, it must fail") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() == 0); + } +} + +TEST_CASE("ncrypt::rsa::DeleteKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") { + const auto test_key = std::string{"vereign_test_key"}; + + SECTION("when the key exists, it must delete the key") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + CHECK(new_key.Get() != 0); + + ncrypt::rsa::DeleteKey(new_key.Get()); + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() == 0); + } + + SECTION("when the key does not exists, it must fail") { + auto provider = ncrypt::rsa::OpenStorageProvider(); + + // cleanup + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + CHECK(new_key.Get() != 0); + + key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + CHECK(key.Get() != 0); + + ncrypt::rsa::DeleteKey(new_key.Get()); + + CHECK_THROWS_WITH( + ncrypt::rsa::DeleteKey(key.Get()), + "deleting key failed: NTE_BAD_KEYSET" + ); + } +} + +TEST_CASE("ncrypt::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/ncrypt/rsa][vereign/ncrypt]") { + const auto test_key = std::string{"vereign_test_key"}; + auto provider = ncrypt::rsa::OpenStorageProvider(); + auto key = ncrypt::rsa::LoadKey(provider.Get(), test_key); + if (key) { + ncrypt::rsa::DeleteKey(key.Get()); + } + + key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key); + REQUIRE(key.Get() != 0); + auto delete_key = core::ScopeGuard([&key] { ncrypt::rsa::DeleteKey(key.Get()); }); + + SECTION("small input") { + const std::string input{"foo bar"}; + bytes::Buffer encrypted; + + ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted); + + bytes::Buffer decrypted; + ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted); + + CHECK(decrypted.View() == bytes::View(input)); + } + + SECTION("zero input") { + const std::string input; + bytes::Buffer encrypted; + + CHECK_THROWS_WITH( + ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted), + "encryption failed: NTE_NULL_REFERENCE_POINTER" + ); + + bytes::Buffer decrypted; + CHECK_THROWS_WITH( + ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted), + "decryption failed: NTE_NULL_REFERENCE_POINTER" + ); + } + + SECTION("max size input") { + bytes::Buffer input{214}; + crypto::Rand(input); + bytes::Buffer encrypted; + + ncrypt::rsa::PublicKeyEncrypt(key.Get(), input.View(), encrypted); + + bytes::Buffer decrypted; + ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted); + + CHECK(decrypted.View() == input.View()); + } + + SECTION("invalid big input") { + bytes::Buffer input{215}; + crypto::Rand(input); + bytes::Buffer encrypted; + + CHECK_THROWS_WITH( + ncrypt::rsa::PublicKeyEncrypt(key.Get(), input.View(), encrypted), + "encryption failed: NTE_INVALID_PARAMETER" + ); + + CHECK(encrypted.Size() == 0); + } +}