From a4531d9579f54ef3953232c2a503e12c5db3a464 Mon Sep 17 00:00:00 2001
From: Daniel Lyubomirov <dennislt@gmail.com>
Date: Wed, 24 Jun 2020 19:00:43 +0300
Subject: [PATCH] [17] crypto storage windows implementation

---
 cpp/src/CMakeLists.txt                    |   8 +-
 cpp/src/csandbox.cc                       |  32 +++-
 cpp/src/vereign/bytes/view.hh             |   8 +-
 cpp/src/vereign/core/lock_guard.hh        |  28 +++
 cpp/src/vereign/core/string.cc            |  84 +++++++++
 cpp/src/vereign/core/string.hh            |  18 ++
 cpp/src/vereign/encoding/hex.cc           |  17 ++
 cpp/src/vereign/encoding/hex.hh           |   2 +
 cpp/src/vereign/kvstore/sqlite_storage.cc |  79 ++++++--
 cpp/src/vereign/kvstore/sqlite_storage.hh |  14 ++
 cpp/src/vereign/kvstore/storage.hh        |   3 +
 cpp/src/vereign/ncrypt/errors.cc          |  45 +++++
 cpp/src/vereign/ncrypt/errors.hh          |  26 +++
 cpp/src/vereign/ncrypt/rsa.cc             | 173 ++++++++++++++++++
 cpp/src/vereign/ncrypt/rsa.hh             |  23 +++
 cpp/src/vereign/ncrypt/unique_ptr.cc      |  82 +++++++++
 cpp/src/vereign/ncrypt/unique_ptr.hh      |  33 ++++
 cpp/src/vereign/sqlite/connection.cc      |  13 ++
 cpp/src/vereign/sqlite/connection.hh      |   4 +-
 cpp/tests/vereign/CMakeLists.txt          |  13 +-
 cpp/tests/vereign/crypto/rsa_test.cc      |  12 +-
 cpp/tests/vereign/ncrypt/rsa_test.cc      | 211 ++++++++++++++++++++++
 22 files changed, 899 insertions(+), 29 deletions(-)
 create mode 100644 cpp/src/vereign/core/lock_guard.hh
 create mode 100644 cpp/src/vereign/core/string.cc
 create mode 100644 cpp/src/vereign/core/string.hh
 create mode 100644 cpp/src/vereign/ncrypt/errors.cc
 create mode 100644 cpp/src/vereign/ncrypt/errors.hh
 create mode 100644 cpp/src/vereign/ncrypt/rsa.cc
 create mode 100644 cpp/src/vereign/ncrypt/rsa.hh
 create mode 100644 cpp/src/vereign/ncrypt/unique_ptr.cc
 create mode 100644 cpp/src/vereign/ncrypt/unique_ptr.hh
 create mode 100644 cpp/tests/vereign/ncrypt/rsa_test.cc

diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index c97a227..c0abb99 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 0f3226d..e8288a6 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 ddf1c75..c64f59a 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 0000000..9e310b2
--- /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 0000000..933e2e2
--- /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 0000000..1db2212
--- /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 6c8dc18..e513512 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 495f090..f2e4064 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 390c2cb..de5b48d 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 ef91c50..012c35f 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 a8adf3d..2bdb268 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 0000000..fedb516
--- /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 0000000..a37056c
--- /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 0000000..d745434
--- /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 0000000..749eb51
--- /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 0000000..06e7d2c
--- /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 0000000..8341e3b
--- /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 d5af4f3..e12fb7c 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 ffa4ef8..86bfd6f 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 31ba5f2..172c403 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 c9356e1..b484d6e 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 0000000..0c3c4aa
--- /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);
+  }
+}
-- 
GitLab