diff --git a/.gitignore b/.gitignore
index 02bce2e92c459988286bf0fede31144a407d64a2..683bcea58a3a309d3b90ee42ed144f6b3af3fd54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,11 +6,3 @@ Gopkg.lock
 temp/
 yarn-error.log
 /.project
-
-/cpp/cmake-build*
-/cpp/cmake-install*
-/cpp/compile_commands.json
-/cpp/.clangd
-/cpp/tags
-
-/cpp/docs/doxy
diff --git a/cpp/.gitignore b/cpp/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..48d08e65e0c7b04f833e2cb69913b72a86acc93c
--- /dev/null
+++ b/cpp/.gitignore
@@ -0,0 +1,6 @@
+/cmake-*
+/compile_commands.json
+/.clangd
+/tags
+
+/docs/doxy
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index abf9b756454dc84725418d3d5426339b5354583f..35a77a1a47dd339cb229a2fc1ab63a90f3605997 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -9,6 +9,10 @@ option(VEREIGN_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF)
 option(VEREIGN_USE_TIME_TRACE "Use compilation profiler" OFF)
 option(VEREIGN_ENABLE_BENCHMARKING "Enable tests benchmarks" OFF)
 
+if (UNIX AND NOT APPLE)
+  set(LINUX TRUE)
+endif()
+
 if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
   if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.0.24215.1")
     message(FATAL_ERROR "Microsoft Visual C++ version MSVC 19.0.24215.1 required")
@@ -91,6 +95,7 @@ set(_cmake_prefix_paths
   ${VENDOR_INSTALL_DIR}
   ${VENDOR_INSTALL_DIR}/grpc
   ${VENDOR_INSTALL_DIR}/nlohmann
+  ${VENDOR_INSTALL_DIR}/sqlite3
 )
 set(CMAKE_PREFIX_PATH ${_cmake_prefix_paths} CACHE STRING "")
 
@@ -115,7 +120,7 @@ find_package(
   1.72.0
   EXACT
   REQUIRED
-  COMPONENTS regex thread system date_time
+  COMPONENTS regex thread system date_time filesystem
 )
 
 find_package(Protobuf CONFIG REQUIRED)
@@ -137,6 +142,7 @@ else()
 endif()
 
 find_package(nlohmann_json 3.7.3 REQUIRED)
+find_package(SQLite3 REQUIRED)
 
 add_subdirectory("src")
 add_subdirectory("tests")
@@ -176,6 +182,7 @@ message(STATUS "summary of build options:
     Boost libs      ${Boost_LIBRARIES}
     gRPC            ${gRPC_FOUND} [${gRPC_VERSION}] (LIBS='${_grpc_libs}')
     nlohmann        ${nlohmann_json_FOUND} [${nlohmann_json_VERSION}] (HEADERS='${nlohmann_json_DIR}')
+    sqlite3         ${SQLite3_FOUND} [${SQLite3_VERSION}] (LIBS='${SQLite3_LIBRARIES}')
   Options:
     VEREIGN_USE_LLD ${VEREIGN_USE_LLD}
     VEREIGN_USE_PRECOMPILED_HEADERS ${VEREIGN_USE_PRECOMPILED_HEADERS}
diff --git a/cpp/include/vereign/vereign.h b/cpp/include/vereign/vereign.h
index c450bd819efba614e91f8c0cac23dfbe4c5503a3..f77824c16926b03dacd6b6cb9366d53a538474c6 100644
--- a/cpp/include/vereign/vereign.h
+++ b/cpp/include/vereign/vereign.h
@@ -1,5 +1,5 @@
-#ifndef VEREIGN_VEREIGN_H_
-#define VEREIGN_VEREIGN_H_
+#ifndef __VEREIGN_VEREIGN_H
+#define __VEREIGN_VEREIGN_H
 
 #ifdef _WIN32
   #ifdef WIN_EXPORT
@@ -84,8 +84,12 @@ typedef struct vereign_service vereign_service;
  * **NOTE: On failure the `err` object must be freed with vereign_error_free method.**
  *
  * @param listen_address gRPC listen address, for example "localhost:".
- * @param vereignHost Vereign restapi host.
- * @param vereignPort Vereign restapi port - https, 443...
+ * @param vereign_host Vereign restapi host.
+ * @param vereign_port Vereign restapi port - https, 443...
+ * @param storage_path Full path to directory where the storage files will stay.
+ *    If the `storage_path` is `nullptr`, a default will be used. Under linux this default is
+ *    `$HOME/vereign`, and under windows it is `C:\Users\<user>\AppData\Local\vereign`.
+ *
  * @param err On failure err is initialized with the reason of the failure,
  *    otherwise err is set to nullptr.
  * @returns vereign_service object if the gRPC is up and running, otherwise returns nullptr.
@@ -94,8 +98,7 @@ PUBLIC_API vereign_service* vereign_service_start(
   const char* listen_address,
   const char* vereign_host,
   const char* vereign_port,
-  // FIXME: public_key must come from a storage internally.
-  const char* public_key,
+  const char* storage_path,
   vereign_error** err
 );
 
@@ -122,4 +125,4 @@ PUBLIC_API void vereign_service_shutdown(vereign_service* service);
 };
 #endif
 
-#endif // VEREIGN_VEREIGN_H_
+#endif // __VEREIGN_VEREIGN_H
diff --git a/cpp/proto b/cpp/proto
index e861100984116aacf6d84cb8a09dc0ef81041509..3e82fc7d60056e08b041577e332f1ec5029633ee 160000
--- a/cpp/proto
+++ b/cpp/proto
@@ -1 +1 @@
-Subproject commit e861100984116aacf6d84cb8a09dc0ef81041509
+Subproject commit 3e82fc7d60056e08b041577e332f1ec5029633ee
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index 555515506f7c1d62b01e54cf2eeaeeb5401e783b..520ad243c7ff982e01c06d0d7539274c96135545 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -3,9 +3,11 @@ 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)
+
 include_directories(
   ${CMAKE_CURRENT_BINARY_DIR}
   ${CMAKE_SOURCE_DIR}/src
@@ -45,15 +47,61 @@ if (VEREIGN_USE_PRECOMPILED_HEADERS)
 endif()
 
 set(VEREIGNLIB_SRC
+  vereign/core/rand.cc
+  vereign/core/string.cc
+  vereign/fs/util.cc
+  vereign/fs/operations.cc
+  vereign/fs/path.cc
+
   vereign/restapi/detail/http_reader.cc
   vereign/restapi/client.cc
-  vereign/service/passport_service.cc
+
   vereign/grpc/gen/gen.cc
   vereign/grpc/json/encoder.cc
   vereign/grpc/service_registry.cc
   vereign/grpc/server.cc
+
+  vereign/sqlite/statement.cc
+  vereign/sqlite/connection.cc
+
+  vereign/bytes/view_dump.cc
+  vereign/bytes/buffer.cc
+
+  vereign/encoding/binary.cc
+  vereign/encoding/base64.cc
+  vereign/encoding/hex.cc
+
+  vereign/crypto/rand.cc
+  vereign/crypto/aes.cc
+  vereign/crypto/rsa.cc
+  vereign/crypto/bio.cc
+  vereign/crypto/digest.cc
+
+  vereign/kvstore/lock.cc
+  vereign/kvstore/detail/base_crypto_storage.cc
+  vereign/kvstore/sqlite_storage.cc
+  vereign/kvstore/crypto_storage.cc
+  vereign/kvstore/detail/value_encoder.cc
+
+  vereign/identity/provider.cc
+
+  vereign/service/identity_service.cc
 )
 
+
+if (LINUX)
+  list(APPEND VEREIGNLIB_SRC
+    vereign/kvstore/detail/linux_crypto_storage.cc
+  )
+elseif (WIN32)
+  list(APPEND VEREIGNLIB_SRC
+    vereign/ncrypt/errors.cc
+    vereign/ncrypt/unique_ptr.cc
+    vereign/ncrypt/rsa.cc
+    vereign/kvstore/detail/win_crypto_storage.cc
+  )
+endif()
+
 file(GLOB GENERATED_SERVICES_SRC vereign/service/gen/*.cc)
 list(APPEND VEREIGNLIB_SRC ${GENERATED_SERVICES_SRC})
 
@@ -67,7 +115,11 @@ target_link_libraries(vereignlib PUBLIC
   fmt::fmt
   gRPC::grpc++_reflection
   gRPC::grpc++
+  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
@@ -96,6 +148,8 @@ add_executable(csandbox ${csandbox_sources})
 target_link_libraries(csandbox
   PRIVATE vereignlib
   $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
+  Boost::filesystem
+  # Boost::file
   # Boost::thread
   # vereign
   # fmt::fmt
diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc
index 561cf2123f21bd6b5ad1a9b6858d2fb5b2e83a7d..70382932622480f68ad621fb1f2582e0999d247d 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -1,4 +1,17 @@
+#include "vereign/crypto/rand.hh"
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/buffer.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/fs/util.hh>
+#include <iostream>
+
+auto main(int argc, char** argv) -> int {
+  argc = 0;
+  argv = nullptr;
+  auto path = vereign::fs::TempFilePath(vereign::fs::path::Join("tmp", "foo"), "test_db_");
+
+  std::cout << path << std::endl;
 
-auto main(int argc, char* argv[]) -> int {
   return 0;
 }
diff --git a/cpp/src/vereign/bytes/buffer.cc b/cpp/src/vereign/bytes/buffer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..546a4228f372ea30a4dc914b5c66f054ce1c1527
--- /dev/null
+++ b/cpp/src/vereign/bytes/buffer.cc
@@ -0,0 +1,180 @@
+#include <vereign/bytes/buffer.hh>
+
+#include <vereign/bytes/errors.hh>
+
+#include <memory>
+
+namespace vereign::bytes {
+
+Buffer::Buffer() noexcept
+  : cap_{0},
+    size_{0},
+    data_{nullptr}
+{
+}
+
+Buffer::Buffer(std::size_t cap)
+  : cap_{cap},
+    size_{0},
+    data_{nullptr}
+{
+  if (cap == 0) {
+    return;
+  }
+
+  data_ = reinterpret_cast<uint8_t*>(std::malloc(cap));
+  if (data_ == nullptr) {
+    throw std::bad_alloc{};
+  }
+}
+
+Buffer::Buffer(bytes::View src)
+  : cap_{src.Size()},
+    size_{src.Size()},
+    data_{nullptr}
+{
+  if (size_ == 0) {
+    return;
+  }
+
+  data_ = reinterpret_cast<uint8_t*>(std::malloc(cap_));
+  if (data_ == nullptr) {
+    throw std::bad_alloc{};
+  }
+
+  std::memcpy(data_, src.Data(), src.Size());
+}
+
+Buffer::Buffer(Buffer&& other) noexcept
+  : cap_{other.cap_},
+    size_{other.size_},
+    data_{other.data_}
+{
+  other.cap_ = 0;
+  other.size_ = 0;
+  other.data_ = nullptr;
+}
+
+auto Buffer::operator=(Buffer&& other) noexcept -> Buffer& {
+  std::swap(cap_, other.cap_);
+  std::swap(size_, other.size_);
+  std::swap(data_, other.data_);
+
+  return *this;
+}
+
+
+Buffer::~Buffer() {
+  std::free(data_);
+}
+
+
+auto Buffer::Size() const noexcept -> std::size_t {
+  return size_;
+}
+
+auto Buffer::FreeCap() const noexcept -> std::size_t {
+  return cap_ - size_;
+}
+
+auto Buffer::Cap() const noexcept -> std::size_t {
+  return cap_;
+}
+
+auto Buffer::begin() noexcept -> uint8_t* {
+  return data_;
+}
+
+auto Buffer::begin() const noexcept -> const uint8_t* {
+  return data_;
+}
+
+auto Buffer::end() noexcept -> uint8_t* {
+  return data_ + size_;
+}
+
+auto Buffer::end() const noexcept -> const uint8_t* {
+  return data_ + size_;
+}
+
+void Buffer::Reserve(std::size_t size) {
+  if (cap_ == 0) {
+    cap_ = size;
+    data_ = reinterpret_cast<uint8_t*>(std::malloc(cap_));
+    if (data_ == nullptr) {
+      throw std::bad_alloc{};
+    }
+
+    return;
+  }
+
+  if (size <= cap_ - size_) {
+    return;
+  }
+
+  auto cap = cap_ * 2;
+  if (size > cap - size_) {
+    cap = size_ + size;
+  }
+
+  auto newData = reinterpret_cast<uint8_t*>(std::realloc(data_, cap));
+  if (newData == nullptr) {
+    throw std::bad_alloc{};
+  }
+
+  data_ = newData;
+  cap_ = cap;
+}
+
+void Buffer::Reset() {
+  size_ = 0;
+}
+
+void Buffer::IncSize(std::size_t val) {
+  if (size_ + val > cap_) {
+    throw IncrementOutOfBounds{};
+  }
+
+  size_ += val;
+}
+
+auto Buffer::WriteWithinCap(bytes::View src) noexcept -> std::size_t {
+  auto size = std::min(cap_ - size_, src.Size());
+
+  std::memcpy(data_ + size_, src.Data(), size);
+  size_ += size;
+
+  return size;
+}
+
+auto Buffer::Write(bytes::View src) -> std::size_t {
+  Reserve(src.Size());
+
+  return WriteWithinCap(src);
+}
+
+auto Buffer::View() const noexcept -> bytes::View {
+  return bytes::View{data_, size_};
+}
+
+auto Buffer::View(std::size_t start) const noexcept -> bytes::View {
+  return bytes::View{data_, size_}.Slice(start);
+}
+
+auto Buffer::operator[](std::size_t index) -> uint8_t& {
+  if (index >= cap_) {
+    throw IndexOutOfBounds{};
+  }
+
+  return data_[index];
+}
+
+auto Buffer::operator[](std::size_t index) const -> const uint8_t& {
+  if (index >= cap_) {
+    throw IndexOutOfBounds{};
+  }
+
+  return data_[index];
+}
+
+} // namespace vereign::bytes
diff --git a/cpp/src/vereign/bytes/buffer.hh b/cpp/src/vereign/bytes/buffer.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6d0e5c7a8bddee458283afc810b3b79765e76968
--- /dev/null
+++ b/cpp/src/vereign/bytes/buffer.hh
@@ -0,0 +1,275 @@
+#ifndef __VEREIGN_BYTES_BUFFER_HH
+#define __VEREIGN_BYTES_BUFFER_HH
+
+#include <vereign/bytes/view.hh>
+#include <cstring>
+
+namespace vereign::bytes {
+
+/**
+ * Dynamically expandable memory buffer.
+ *
+ * The buffer is a 3-tuple - pointer, size and capacity.
+ * Typically used in functions for output parameters and return values.
+ * Provides API that is easy to use with C APIs.
+ *
+ * The buffer is move only.
+ */
+class Buffer {
+public:
+  /**
+   * Creates empty buffer.
+   */
+  Buffer() noexcept;
+
+  /**
+   * Creates a buffer with reserved memory capacity.
+   *
+   * The size of the buffer is zero.
+   *
+   * @param cap The capacity of the buffer.
+   *
+   * @throws std::bad_alloc when memory reservation fails.
+   */
+  Buffer(std::size_t cap);
+
+  /**
+   * Creates a buffer by copying from source bytes view.
+   *
+   * @param src The source that will be copied from.
+   *
+   * @throws std::bad_alloc when memory reservation fails.
+   */
+  Buffer(View src);
+
+  /**
+   * The buffer is movable.
+   */
+  Buffer(Buffer&& other) noexcept;
+  auto operator=(Buffer&& other) noexcept -> Buffer&;
+
+  // disable copying
+  Buffer(const Buffer&) = delete;
+  auto operator=(const Buffer&) -> Buffer& = delete;
+
+  /**
+   * Frees the buffer memory.
+   */
+  ~Buffer();
+
+  /**
+   * Returns a pointer to the first byte of the buffer.
+   *
+   * Buffer::begin(), Buffer::end() pair is useful for range loops.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo")};
+   * for (const auto& byte : buf) {
+   *   byte = 'x';
+   * }
+   *
+   * assert(buf.View().String() == "xxx");
+   * @endcode
+   */
+  auto begin() noexcept -> uint8_t*;
+
+  /**
+   * Returns a pointer to the first byte of the buffer.
+   *
+   * Buffer::begin(), Buffer::end() pair is useful for range loops.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   * std::string s;
+   *
+   * for (const auto& byte : buf) {
+   *   s += byte;
+   * }
+   *
+   * assert(s == "foo bar");
+   * @endcode
+   */
+  auto begin() const noexcept -> const uint8_t*;
+
+  /**
+   * Returns a pointer to the byte following the last byte in the buffer.
+   *
+   * Note that this is the last byte in the range [0, size).
+   * It is often used when calling C APIs.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   *
+   * buf.Reserve(4);
+   * std::strncpy((char*)buf.end(), " baz", 4);
+   * buf.IncSize(4);
+   *
+   * assert(buf.View().String() == "foo bar baz");
+   * @endcode
+   */
+  auto end() noexcept -> uint8_t*;
+
+  /**
+   * Returns a read only pointer to the byte following the last byte in the buffer.
+   *
+   * Note that this is the last byte in the range [0, size).
+   */
+  auto end() const noexcept -> const uint8_t*;
+
+  /**
+   * Access a byte in the range of [0, cap).
+   *
+   * @param index The index of the byte to access.
+   *
+   * @throws std::runtime_error when the passed index is out of bounds.
+   */
+  auto operator[](std::size_t index) -> uint8_t&;
+
+  /**
+   * Read only access a byte in the range of [0, cap).
+   *
+   * @param index The index of the byte to access.
+   *
+   * @throws std::runtime_error when the passed index is out of bounds.
+   */
+  auto operator[](std::size_t index) const -> const uint8_t&;
+
+  /**
+   * Retrieve buffer size.
+   *
+   * @returns the buffer size.
+   */
+  auto Size() const noexcept -> std::size_t;
+
+  /**
+   * Retrieve buffer capacity.
+   *
+   * @returns the buffer capacity.
+   */
+  auto Cap() const noexcept -> std::size_t;
+
+  /**
+   * Reserve memory, so that there is at least `size` free capacity.
+   *
+   * If there is already enough free capacity, no memory allocation is done.
+   * The allocated memory may be bigger than what is needed.
+   *
+   * If the call is successful then it is guaranteed that `this->FreeCap() >= size`.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   *
+   * buf.Reserve(4); // ensure there will be a free capacity for 4 bytes
+   * std::strncpy((char*)buf.end(), " baz", 4); // copy the bytes
+   * buf.IncSize(4); // update the buffer size with the newly written bytes
+   *
+   * assert(buf.View().String() == "foo bar baz");
+   * @endcode
+   *
+   * @param size The desired free capacity.
+   *
+   * @throws std::bad_alloc when memory allocation fails.
+   */
+  void Reserve(std::size_t size);
+
+  /**
+   * Sets the buffer size to zero.
+   *
+   * This does not free any memory, so the capacity stays the intact, and the buffer can be reused.
+   */
+  void Reset();
+
+  /**
+   * Increments the size of the buffer.
+   *
+   * It is typically used after some function has written bytes to the end of the buffer.
+   *
+   * @param val The value that will be added to the current size.
+   */
+  void IncSize(std::size_t val);
+
+  /**
+   * Retrieve the buffer free capacity.
+   *
+   * This is equal to `this->Cap() - this->Size()`.
+   */
+  auto FreeCap() const noexcept -> std::size_t;
+
+  /**
+   * Adds bytes up to the currently available buffer capacity.
+   *
+   * After the operation succeeds, the buffer size will be incremented with the number of bytes
+   * that have been copied.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{3};
+   * buf.WriteWithinCap(bytes::View("foo bar"));
+   *
+   * // only 3 bytes are written
+   * assert(buf.View.String() == "foo");
+   * @endcode
+   *
+   * @param src The source that will be appended to the buffer.
+   * @returns The amount of bytes that were actually copied into the buffer.
+   */
+  auto WriteWithinCap(bytes::View src) noexcept -> std::size_t;
+
+  /**
+   * Adds a source view of bytes to the buffer.
+   *
+   * If the buffer does not have enough capacity, it will be expanded.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{3};
+   * buf.WriteWithinCap(bytes::View("foo bar"));
+   *
+   * // all bytes are written
+   * assert(buf.View.String() == "foo bar");
+   * @endcode
+   *
+   * @param The source that will be appended to the buffer.
+   * @returns The amount of bytes that were copied into the buffer. That is equal to src.Size().
+   */
+  auto Write(bytes::View src) -> std::size_t;
+
+  /**
+   * Retrieve a read only view of the buffer.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("123")};
+   * assert(buf.View().String() == "123");
+   * @endcode
+   *
+   * @returns a read only view range [0, this->Size()).
+   */
+  auto View() const noexcept -> bytes::View;
+
+  /**
+   * Retrieve a read only view of the buffer staring from a given offset.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("123")};
+   * assert(buf.View(1).String() == "23");
+   * @endcode
+   *
+   * @returns a read only view range [start, this->Size()).
+   */
+  auto View(std::size_t start) const noexcept -> bytes::View;
+
+private:
+  std::size_t cap_;
+  std::size_t size_;
+  uint8_t* data_;
+};
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_BUFFER_HH
diff --git a/cpp/src/vereign/bytes/bytes.hh b/cpp/src/vereign/bytes/bytes.hh
new file mode 100644
index 0000000000000000000000000000000000000000..437a692f7d9b5cd753836a6bfc9d1c591fa5844a
--- /dev/null
+++ b/cpp/src/vereign/bytes/bytes.hh
@@ -0,0 +1,45 @@
+#ifndef __VEREIGN_BYTES_BYTES_HH
+#define __VEREIGN_BYTES_BYTES_HH
+
+#include <vereign/bytes/view.hh>
+#include <cstring>
+
+namespace vereign::bytes {
+
+class Bytes {
+public:
+  Bytes(uint8_t* data, std::size_t size)
+    : size_{size},
+      data_{data}
+  {}
+
+  Bytes(const Bytes&) = default;
+  auto operator=(const Bytes&) -> Bytes& = default;
+
+  auto Data() const -> uint8_t* {
+    return data_;
+  }
+
+  auto CharData() const -> unsigned char* {
+    return data_;
+  }
+
+  auto Size() const -> std::size_t {
+    return size_;
+  }
+
+private:
+  std::size_t size_;
+  uint8_t* data_;
+};
+
+inline auto Copy(Bytes dst, View src) noexcept -> std::size_t {
+  auto size = std::min(src.Size(), dst.Size());
+  std::memcpy(dst.Data(), src.Data(), size);
+
+  return size;
+}
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_BYTES_HH
diff --git a/cpp/src/vereign/bytes/errors.hh b/cpp/src/vereign/bytes/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..ac7e9e66a800a30f340e6b3f0de9c9cbf959cabd
--- /dev/null
+++ b/cpp/src/vereign/bytes/errors.hh
@@ -0,0 +1,35 @@
+#ifndef __VEREIGN_BYTES_ERRORS_HH
+#define __VEREIGN_BYTES_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::bytes {
+
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error{what}
+  {
+
+  }
+};
+
+class IndexOutOfBounds : public Error {
+public:
+  IndexOutOfBounds()
+    : Error{"index out of bounds"}
+  {
+  }
+};
+
+class IncrementOutOfBounds : public Error {
+public:
+  IncrementOutOfBounds()
+    : Error{"cannot increment size pass the capacity"}
+  {
+  }
+};
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_ERRORS_HH
diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4520934c927170ed79682ab95d9f9ed5e430dee8
--- /dev/null
+++ b/cpp/src/vereign/bytes/view.hh
@@ -0,0 +1,211 @@
+#ifndef __VEREIGN_BYTES_VIEW_HH
+#define __VEREIGN_BYTES_VIEW_HH
+
+#include <vereign/bytes/errors.hh>
+
+#include <cstring>
+#include <string>
+#include <string_view>
+#include <stdexcept>
+#include <algorithm>
+
+namespace vereign::bytes {
+
+/**
+ * Bytes view represents a read only access to a range of bytes.
+ *
+ * The View is a 2-tuple with pointer and size.
+ * Typically used in functions for input parameters.
+ *
+ * **NOTE: The View does not own the memory that it references.**
+ */
+class View {
+public:
+  /**
+   * Creates empty view.
+   */
+  View() = default;
+
+  /**
+   * Creates a view from raw pointer and a size.
+   *
+   * @param data Pointer to the memory.
+   * @param size The size of the memory.
+   */
+  View(const uint8_t* data, std::size_t size) noexcept
+    : size_{size},
+      data_{data}
+  {
+  }
+
+  /**
+   * Create a view from a string view.
+   *
+   * @param str The input string view.
+   */
+  View(std::string_view str) noexcept
+    : size_{str.length()},
+      data_{str.length() > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
+  {
+  }
+
+  /**
+   * Creates a view from wide string view.
+   *
+   * @param str The input string.
+   */
+  View(std::wstring_view str) noexcept
+    : size_{str.length() * sizeof(wchar_t)},
+      data_{size_ > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
+  {
+  }
+
+  /**
+   * Creates a view from void pointer and a size.
+   *
+   * @param data Pointer to the memory.
+   * @param size The size of the memory.
+   */
+  View(const void* ptr, std::size_t size) noexcept
+    : size_{size},
+      data_{static_cast<const uint8_t*>(ptr)}
+  {
+  }
+
+  // default copyable.
+  View(const View&) = default;
+  auto operator=(const View&) -> View& = default;
+
+  /**
+   * Slice returns a new view in the interval [start, size).
+   *
+   * If the start is bigger than the size of the slice it returns empty view.
+   *
+   * @param start The beginning of the new View.
+   * @returns a new view in the interval [start, size).
+   */
+  auto Slice(std::size_t start) const -> View {
+    if (start >= size_) {
+      return View(data_, 0);
+    }
+
+    return View(data_ + start, size_ - start);
+  }
+
+  /**
+   * Slice returns a new view in the interval [start, end).
+   *
+   * If the start is bigger than the size of the slice it returns empty view.
+   * If the end is bigger than the size of the slice it returns [start, size).
+   *
+   * @param start The beginning of the new View.
+   * @returns a new view in the interval [start, size).
+   */
+  auto Slice(std::size_t start, std::size_t end) const -> View {
+    if (start >= size_) {
+      return View(data_, 0);
+    }
+
+    return View(data_ + start, std::min(size_, end) - start);
+  }
+
+  /**
+   * Retrieve a pointer to the data.
+   *
+   * @returns a pointer to the data.
+   */
+  auto Data() const noexcept -> const uint8_t* {
+    return data_;
+  }
+
+  /**
+   * Retrieve a char pointer to the data.
+   *
+   * @returns a char pointer to the data.
+   */
+  auto CharData() const noexcept -> const char* {
+    return reinterpret_cast<const char*>(data_);
+  }
+
+  /**
+   * Retrieve a wide char pointer to the data.
+   *
+   * @returns a wide char pointer to the data.
+   */
+  auto WideCharData() const noexcept -> const wchar_t* {
+    return reinterpret_cast<const wchar_t*>(data_);
+  }
+
+  /**
+   * Retrieve view size.
+   *
+   * @returns view size.
+   */
+  auto Size() const noexcept -> std::size_t {
+    return size_;
+  }
+
+  /**
+   * Retrieve a string view of the data.
+   *
+   * @returns a string view of the data.
+   */
+  auto String() const noexcept -> std::string_view {
+    return std::string_view{CharData(), size_};
+  }
+
+  /**
+   * Retrieve a wide string view of the data.
+   *
+   * @returns a wide string view of the data.
+   */
+  auto WideString() const noexcept -> std::wstring_view {
+    return std::wstring_view{WideCharData(), size_/sizeof(wchar_t)};
+  }
+
+  /**
+   * Binary compare the contents of two views.
+   *
+   * @returns true if the views are of the same size and if all the bytes in the two views are equal.
+   */
+  auto operator==(View other) const noexcept -> bool {
+    if (size_ != other.size_) {
+      return false;
+    }
+
+    return std::memcmp(data_, other.data_, size_) == 0;
+  }
+
+  /**
+   * Binary compare the contents of two views.
+   *
+   * @returns true if the views are of different size or if the bytes in the two views are not equal.
+   */
+  auto operator!=(View other) const noexcept -> bool {
+    return !(*this == other);
+  }
+
+  /**
+   * Access a single byte in the view.
+   *
+   * @param index The index of the byte that will be returned.
+   * @returns the byte at the specified index.
+   *
+   * @throws bytes::IndexOutOfBounds when the index is out of bounds.
+   */
+  auto operator[](std::size_t index) const -> const uint8_t& {
+    if (index >= size_ ) {
+      throw IndexOutOfBounds{};
+    }
+
+    return data_[index];
+  }
+
+private:
+  std::size_t size_ = 0;
+  const uint8_t* data_ = nullptr;
+};
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_VIEW_HH
diff --git a/cpp/src/vereign/bytes/view_dump.cc b/cpp/src/vereign/bytes/view_dump.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7d892bb9e350710109b6b34d3472e7334fd3690e
--- /dev/null
+++ b/cpp/src/vereign/bytes/view_dump.cc
@@ -0,0 +1,64 @@
+#include <vereign/bytes/view_dump.hh>
+
+#include <iomanip>
+
+namespace vereign::bytes {
+
+namespace detail {
+
+auto operator<<(std::ostream& os, const ViewDump& vd) -> std::ostream& {
+  if (vd.buf_.Size() == 0) {
+    os << "empty buffer\n";
+    return os;
+  }
+
+  core::IosFlagsLock os_flags(os);
+
+  os << std::hex;
+
+  for (std::size_t i = 0; i < vd.buf_.Size(); ++i) {
+    if (i % 16 == 0) {
+      os << "0x" << std::setfill('0') << std::setw(4) << i << ": ";
+    }
+
+    if (i % 2 == 0) {
+      os << " ";
+    }
+
+    os << std::setfill('0') << std::setw(2) << (unsigned int) vd.buf_.Data()[i];
+
+    if ((i != 0 && (i + 1) % 16 == 0) || i == vd.buf_.Size() - 1) {
+      int bytes = (i + 1) % 16 == 0 ? 0 : (16 - (i + 1) % 16);
+      os << "  " << std::setfill(' ') << std::setw(2 * bytes + bytes / 2) << "";
+      vd.printChars(os, i - i % 16);
+      os << "\n";
+    }
+  }
+
+  return os;
+}
+
+void ViewDump::printChars(std::ostream& os, std::size_t offset) const {
+  auto seq = buf_.Slice(offset, offset + 16);
+  for (int i = 0; i < (int) seq.Size(); ++i) {
+    auto ch = seq.Data()[i];
+    if (std::isprint(ch) != 0) {
+      os << ch;
+    } else {
+      os << ".";
+    }
+  }
+}
+
+} // namespace detail
+
+
+auto dump(View buf) -> detail::ViewDump {
+  return detail::ViewDump{buf};
+}
+
+auto dump(const Buffer& buf) -> detail::ViewDump {
+  return detail::ViewDump{buf.View()};
+}
+
+} // namespace vereign::bytes
diff --git a/cpp/src/vereign/bytes/view_dump.hh b/cpp/src/vereign/bytes/view_dump.hh
new file mode 100644
index 0000000000000000000000000000000000000000..47625f49016bee9fd8181dbc66746d54b7b3131f
--- /dev/null
+++ b/cpp/src/vereign/bytes/view_dump.hh
@@ -0,0 +1,92 @@
+#ifndef __VEREIGN_BYTES_VIEW_DUMP_HH
+#define __VEREIGN_BYTES_VIEW_DUMP_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/buffer.hh>
+#include <vereign/core/io_flags_lock.hh>
+#include <iostream>
+
+namespace vereign::bytes {
+
+namespace detail {
+class ViewDump;
+}
+
+/**
+ * Returns a view dump object that can write the view dump into std::ostream.
+ *
+ * The dump is the good old hexadecimal format.
+ *
+ * Example:
+ * @code
+ * std::string input{"foo bar"};
+ * std::cout << bytes::dump(bytes::View(input)) << std::endl;
+ *
+ * // Output:
+ * // 0x0000:  666f 6f20 6261 72                        foo bar
+ *
+ * auto buf = crypto::Rand(32);
+ * std::cout << bytes::dump(buf.View()) << std::endl;
+ *
+ * // Output:
+ * // 0x0000:  1666 855a 650a 7549 ed0f ec01 4d87 09bf  .f.Ze.uI....M...
+ * // 0x0010:  e644 dbc8 7943 37b4 185c dbab 4977 ff3f  .D..yC7..\..Iw.?
+ *
+ * @endcode
+ */
+auto dump(View buf) -> detail::ViewDump;
+
+/**
+ * Returns a dump object that can write the buffer dump into std::ostream.
+ *
+ * The dump is the good old hexadecimal format.
+ *
+ * Example:
+ * @code
+  auto buf = bytes::Buffer{bytes::View("foo bar")};
+  std::cout << bytes::dump(buf) << std::endl;
+ *
+ * // Output:
+ * // 0x0000:  666f 6f20 6261 72                        foo bar
+ *
+ * auto buf = crypto::Rand(32);
+ * std::cout << bytes::dump(buf) << std::endl;
+ *
+ * // Output:
+ * // 0x0000:  1666 855a 650a 7549 ed0f ec01 4d87 09bf  .f.Ze.uI....M...
+ * // 0x0010:  e644 dbc8 7943 37b4 185c dbab 4977 ff3f  .D..yC7..\..Iw.?
+ *
+ * @endcode
+ */
+auto dump(const Buffer& buf) -> detail::ViewDump;
+
+namespace detail {
+
+/**
+ * Helper for dumping a view to a std::ostream.
+ *
+ * Users typically will use the bytes::dump functions.
+ */
+class ViewDump {
+public:
+  auto operator=(const ViewDump&) -> ViewDump& = delete;
+
+  explicit ViewDump(View buf)
+    : buf_(buf)
+  {
+  }
+
+  friend auto operator<<(std::ostream& os, const ViewDump& vd) -> std::ostream&;
+
+private:
+  void printChars(std::ostream& os, std::size_t offset) const;
+
+private:
+  View buf_;
+};
+
+} // namespace detail
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_VIEW_DUMP_HH
diff --git a/cpp/src/vereign/core/hex.hh b/cpp/src/vereign/core/hex.hh
deleted file mode 100644
index a370a6e98f39594c98578e1c455a8e390881b69e..0000000000000000000000000000000000000000
--- a/cpp/src/vereign/core/hex.hh
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef __VEREIGN_CORE_HEX_HH
-#define __VEREIGN_CORE_HEX_HH
-
-#include <string>
-
-namespace vereign {
-namespace core {
-namespace detail {
-
-inline auto charToInt(char ch) -> int {
-  if (ch >= '0' && ch <= '9') {
-    return ch - '0';
-  }
-
-  if (ch >= 'A' && ch <= 'F') {
-    return ch - 'A' + 10;
-  }
-
-  if (ch >= 'a' && ch <= 'f') {
-    return ch - 'a' + 10;
-  }
-
-  return 0;
-}
-
-} // namespace detail
-
-inline auto BinToHex(const unsigned char* src, std::size_t size) -> std::string {
-  static const char* nibbles = { "0123456789abcdef" };
-
-  std::string result;
-  result.reserve(size * 2);
-
-  for (std::size_t i = 0; i < size; ++i) {
-    result.push_back(nibbles[src[i] >> 4]);
-    result.push_back(nibbles[src[i] & 0x0F]);
-  }
-
-  return result;
-}
-
-inline auto BinToHex(const std::string& src) -> std::string {
-  return BinToHex((unsigned char*) src.c_str(), src.size());
-}
-
-inline void hex_to_bin(const std::string& src, unsigned char* dst) {
-  for (int i = 0, len = (int) src.size() - 1; i < len; i += 2) {
-    dst[i/2] = detail::charToInt(src[i]) * 16 + detail::charToInt(src[i + 1]);
-  }
-}
-
-} // namespace core
-} // namespace vereign
-
-#endif // __VEREIGN_CORE_HEX_HH
diff --git a/cpp/src/vereign/core/io_flags_lock.hh b/cpp/src/vereign/core/io_flags_lock.hh
new file mode 100644
index 0000000000000000000000000000000000000000..8a6feda70736c794162f434650c8cbbb34815691
--- /dev/null
+++ b/cpp/src/vereign/core/io_flags_lock.hh
@@ -0,0 +1,37 @@
+#ifndef __VEREIGN_CORE_IOS_FLAGS_LOCK_HH
+#define __VEREIGN_CORE_IOS_FLAGS_LOCK_HH
+
+#include <ios>
+
+namespace vereign::core {
+
+/**
+ * Restore the ios flags of a stream when is it self destroyed.
+ */
+class IosFlagsLock {
+public:
+  IosFlagsLock(const IosFlagsLock&) = delete;
+  auto operator=(const IosFlagsLock&) -> IosFlagsLock& = delete;
+
+  IosFlagsLock(std::ios_base& str)
+    : str_(str),
+    flags_(str.flags())
+  {
+  }
+
+  ~IosFlagsLock() {
+    restore();
+  }
+
+  void restore() {
+    str_.flags(flags_);
+  }
+
+private:
+  std::ios_base& str_;
+  std::ios_base::fmtflags flags_;
+};
+
+} // namespace vereign::core
+
+#endif // __TURBO_CORE_IOS_FLAGS_LOCK_HH
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/rand.cc b/cpp/src/vereign/core/rand.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7e65a25ae0c2e45a79e34328edd05d54e53c9df1
--- /dev/null
+++ b/cpp/src/vereign/core/rand.cc
@@ -0,0 +1,30 @@
+#include <vereign/core/rand.hh>
+
+#include <random>
+
+namespace vereign::core {
+
+auto RandNumber(int n) -> int {
+  static std::random_device dev;
+  static std::mt19937 rng(dev());
+  using Dist = std::uniform_int_distribution<std::mt19937::result_type>;
+  static Dist dist{};
+
+  return dist(dev, Dist::param_type{0, static_cast<unsigned long>(n) - 1} );
+}
+
+auto RandLowerAlpha(int len) -> std::string {
+  using namespace std::string_view_literals;
+  static constexpr std::string_view chars = "0123456789abcdefghijklmnopqrstuvwxyz"sv;
+
+  std::string result;
+  result.resize(len);
+
+  for (int i = 0; i < len; i++) {
+    result[i] = chars[RandNumber(chars.size())];
+  }
+
+  return result;
+}
+
+} // namespace vereign::core
diff --git a/cpp/src/vereign/core/rand.hh b/cpp/src/vereign/core/rand.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a39eae5e551a259259717e33bc6ceba2395e8a8d
--- /dev/null
+++ b/cpp/src/vereign/core/rand.hh
@@ -0,0 +1,26 @@
+#ifndef __VEREIGN_CORE_RAND_HH
+#define __VEREIGN_CORE_RAND_HH
+
+#include <random>
+
+namespace vereign::core {
+
+/**
+ * Generated random number in the half closed interval [0, n).
+ *
+ * @param n Upper bound of the generated random number.
+ * @returns a random number in the half closed interval [0, n).
+ */
+auto RandNumber(int n) -> int;
+
+/**
+ * Generates random string with alpha lower case characters.
+ *
+ * @param len The length of the desired random string.
+ * @returns a random string of size len consisting of numbers 0-9 and lower case latin chars a-z.
+ */
+auto RandLowerAlpha(int len) -> std::string;
+
+} // namespace vereign::core
+
+#endif // __VEREIGN_CORE_RAND_HH
diff --git a/cpp/src/vereign/core/string.cc b/cpp/src/vereign/core/string.cc
new file mode 100644
index 0000000000000000000000000000000000000000..bf2340fcb9ebafd0b6c8ec5ccdec511ee24a5ecd
--- /dev/null
+++ b/cpp/src/vereign/core/string.cc
@@ -0,0 +1,94 @@
+#include <vereign/core/string.hh>
+
+#include <iostream>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <stringapiset.h>
+#endif
+
+namespace vereign::string {
+
+#ifdef _WIN32
+
+auto widen(std::string_view 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(std::wstring_view 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);
+  int new_size = WideCharToMultiByte(
+    CP_UTF8,
+    0,
+    utf16_str.data(),
+    utf16_str.length(),
+    result.data(),
+    num_chars,
+    nullptr,
+    nullptr
+  );
+
+  if (new_size == 0) {
+    return "";
+  }
+
+  result.resize(new_size);
+
+  return result;
+}
+
+#endif
+
+auto tail(const std::string& src, std::size_t length) -> std::string {
+  if (length >= src.size()) {
+    return src;
+  }
+
+  return src.substr(src.size() - length);
+}
+
+} // vereign::string
diff --git a/cpp/src/vereign/core/string.hh b/cpp/src/vereign/core/string.hh
new file mode 100644
index 0000000000000000000000000000000000000000..62429b28a0d77feaf77575edd8b630372b571f87
--- /dev/null
+++ b/cpp/src/vereign/core/string.hh
@@ -0,0 +1,32 @@
+#ifndef __VEREIGN_CORE_STRING_HH
+#define __VEREIGN_CORE_STRING_HH
+
+#include <string>
+#include <codecvt>
+
+namespace vereign::string {
+
+#ifdef _WIN32
+
+auto widen(std::string_view utf8_str) -> std::wstring;
+auto narrow(std::wstring_view utf16_str) -> std::string;
+
+#endif
+
+/**
+ * Retrieve string tail.
+ *
+ * @code
+ * std::string str = "foobar";
+ * std::string tail = vereign::string::tail(str, 3);
+ * assert(tail == "bar");
+ * @endcode
+ *
+ * @param src Source string.
+ * @param length How many characters from the string tail to return.
+ */
+auto tail(const std::string& src, std::size_t length) -> std::string;
+
+} // vereign::string
+
+#endif // __VEREIGN_CORE_STRING_HH
diff --git a/cpp/src/vereign/crypto/aes.cc b/cpp/src/vereign/crypto/aes.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f9bcefab4365b17f31e960ffb802d662692695d2
--- /dev/null
+++ b/cpp/src/vereign/crypto/aes.cc
@@ -0,0 +1,128 @@
+#include <openssl/err.h>
+#include <vereign/crypto/aes.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/errors.hh>
+
+#include <openssl/base.h>
+#include <openssl/evp.h>
+
+namespace {
+  constexpr int gcmIVSizeBytes = 12;
+  constexpr int gcmTagSizeBytes = 16;
+  constexpr int aes256BlockSizeBytes = 32;
+}
+
+namespace vereign::crypto::aes {
+
+void GCM256Encrypt(
+  bytes::View src,
+  bytes::View key,
+  bytes::Buffer& iv,
+  bytes::Buffer& tag,
+  bytes::Buffer& encrypted
+) {
+  iv.Reserve(gcmIVSizeBytes);
+  crypto::Rand(iv, gcmIVSizeBytes);
+
+  encrypted.Reserve(src.Size() + aes256BlockSizeBytes);
+
+  bssl::UniquePtr<EVP_CIPHER_CTX> ctx{EVP_CIPHER_CTX_new()};
+  if (!ctx) {
+    throw OpenSSLError("evp cipher context cannot be created");
+  }
+
+  auto r = EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
+  if (r != 1) {
+    throw OpenSSLError("AES GCM encrypt init failed");
+  }
+
+  r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.Size(), nullptr);
+  if (r != 1) {
+    throw OpenSSLError("iv size init failed");
+  }
+
+  r = EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key.Data(), iv.View().Data());
+  if (r != 1) {
+    throw OpenSSLError("key and iv init failed");
+  }
+
+  int bytes_written;
+  r = EVP_EncryptUpdate(ctx.get(), encrypted.end(), &bytes_written, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("encrypt failed");
+  }
+  encrypted.IncSize(bytes_written);
+
+  r = EVP_EncryptFinal_ex(ctx.get(), encrypted.end(), &bytes_written);
+  if (r != 1) {
+    throw OpenSSLError("finalize encryption failed");
+  }
+  encrypted.IncSize(bytes_written);
+
+  tag.Reserve(gcmTagSizeBytes);
+  r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, gcmTagSizeBytes, tag.end());
+  if (r != 1) {
+    throw OpenSSLError("getting GCM tag failed");
+  }
+  tag.IncSize(gcmTagSizeBytes);
+
+  r = EVP_CIPHER_CTX_cleanup(ctx.get());
+  if (r != 1) {
+    ERR_clear_error();
+  }
+}
+
+void GCM256Decrypt(
+  bytes::View src,
+  bytes::View key,
+  bytes::View iv,
+  bytes::View tag,
+  bytes::Buffer& decrypted
+) {
+  bssl::UniquePtr<EVP_CIPHER_CTX> ctx{EVP_CIPHER_CTX_new()};
+  if (!ctx) {
+    throw OpenSSLError("evp cipher context cannot be created");
+  }
+
+  auto r = EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
+  if (r != 1) {
+    throw OpenSSLError("AES GCM decrypt init failed");
+  }
+
+  r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.Size(), nullptr);
+  if (r != 1) {
+    throw OpenSSLError("iv size init failed");
+  }
+
+  r = EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.Data(), iv.Data());
+  if (r != 1) {
+    throw OpenSSLError("key and iv init failed");
+  }
+
+  decrypted.Reserve(src.Size());
+
+  int bytes_written;
+  r = EVP_DecryptUpdate(ctx.get(), decrypted.end(), &bytes_written, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("decrypt failed");
+  }
+  decrypted.IncSize(bytes_written);
+
+  r = EVP_CIPHER_CTX_ctrl(
+    ctx.get(),
+    EVP_CTRL_AEAD_SET_TAG,
+    tag.Size(),
+    const_cast<uint8_t*>(tag.Data())
+  );
+  if (r != 1) {
+    throw OpenSSLError("setting GCM tag failed");
+  }
+
+  r = EVP_DecryptFinal_ex(ctx.get(), decrypted.end(), &bytes_written);
+  if (r != 1) {
+    throw OpenSSLError("verification failed");
+  }
+}
+
+} // vereign::crypto::aes
diff --git a/cpp/src/vereign/crypto/aes.hh b/cpp/src/vereign/crypto/aes.hh
new file mode 100644
index 0000000000000000000000000000000000000000..09be668b964bddd8f31aecc93e874ae35834eb3b
--- /dev/null
+++ b/cpp/src/vereign/crypto/aes.hh
@@ -0,0 +1,80 @@
+#ifndef __VEREIGN_CRYPTO_AES_HH
+#define __VEREIGN_CRYPTO_AES_HH
+
+#include <vereign/bytes/buffer.hh>
+
+/**
+ * Provides utilities for AES encryption/decryption.
+ */
+namespace vereign::crypto::aes {
+
+/**
+ * Encrypt given bytes with AES256-GCM.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::Rand(32); // 256 bits
+ *
+ * bytes::Buffer iv;
+ * bytes::Buffer tag;
+ * bytes::Buffer encrypted;
+ *
+ * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted);
+ * @endcode
+ *
+ * @param src The bytes that will be encrypted.
+ * @param key The AES 256 bit key.
+ * @param iv The initialization vector that was used during the encryption.
+ * @param tag The authentication tag that was produced during the encryption.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+void GCM256Encrypt(
+  bytes::View src,
+  bytes::View key,
+  bytes::Buffer& iv,
+  bytes::Buffer& tag,
+  bytes::Buffer& encrypted
+);
+
+/**
+ * Decrypts given bytes with AES256-GCM.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::Rand(32); // 256 bits
+ *
+ * bytes::Buffer iv;
+ * bytes::Buffer tag;
+ * bytes::Buffer encrypted;
+ *
+ * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted);
+ *
+ * assert(input == decrypted.View().String());
+ * @endcode
+ *
+ * @param src The bytes that will be decrypted.
+ * @param key The AES 256 bit key.
+ * @param iv The initialization vector that was used during the encryption.
+ * @param tag The authentication tag that was produced during the encryption.
+ * @param encrypted The result of the decryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+void GCM256Decrypt(
+  bytes::View src,
+  bytes::View key,
+  bytes::View iv,
+  bytes::View tag,
+  bytes::Buffer& decrypted
+);
+
+} // vereign::crypto::aes
+
+#endif // __VEREIGN_CRYPTO_AES_HH
diff --git a/cpp/src/vereign/crypto/bio.cc b/cpp/src/vereign/crypto/bio.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5a630f6eedcd17a51dddb79c826a992827485e2e
--- /dev/null
+++ b/cpp/src/vereign/crypto/bio.cc
@@ -0,0 +1,18 @@
+#include <vereign/crypto/bio.hh>
+
+#include <vereign/crypto/errors.hh>
+
+namespace vereign::crypto::bio {
+
+auto View(const BIO* bio) -> bytes::View {
+  const uint8_t* data;
+  std::size_t size;
+  auto r = BIO_mem_contents(bio, &data, &size);
+  if (r != 1) {
+    throw OpenSSLError("fetching bio mem contents failed");
+  }
+
+  return bytes::View(data, size);
+}
+
+} // vereign::crypto::bio
diff --git a/cpp/src/vereign/crypto/bio.hh b/cpp/src/vereign/crypto/bio.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2bd5cedacee0b6c94c81a0f2af50e448982b6d3f
--- /dev/null
+++ b/cpp/src/vereign/crypto/bio.hh
@@ -0,0 +1,19 @@
+#ifndef __VEREIGN_CRYPTO_BIO_HH
+#define __VEREIGN_CRYPTO_BIO_HH
+
+#include <vereign/bytes/view.hh>
+#include <openssl/bio.h>
+
+namespace vereign::crypto::bio {
+
+/**
+ * Creates a bytes::View for given OpenSSL BIO.
+ *
+ * @param bio The input BIO.
+ * @returns a bytes view of OpenSSL BIO.
+ */
+auto View(const BIO* bio) -> bytes::View;
+
+} // vereign::crypto::bio
+
+#endif // __VEREIGN_CRYPTO_BIO_HH
diff --git a/cpp/src/vereign/crypto/digest.cc b/cpp/src/vereign/crypto/digest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ab9ed2567189b0a1cb68ab092c5afd2725408901
--- /dev/null
+++ b/cpp/src/vereign/crypto/digest.cc
@@ -0,0 +1,33 @@
+#include <vereign/crypto/digest.hh>
+
+#include <vereign/crypto/errors.hh>
+
+#include <openssl/base.h>
+#include <openssl/evp.h>
+
+namespace {
+  constexpr int sha1SizeBytes = 20;
+}
+
+namespace vereign::crypto::digest {
+
+void sha1(bytes::View src, bytes::Buffer& result) {
+  bssl::UniquePtr<EVP_MD_CTX> ctx(EVP_MD_CTX_new());
+  if (!ctx) {
+    throw OpenSSLError("evp digest context cannot be created");
+  }
+
+  auto r = EVP_DigestInit_ex(ctx.get(), EVP_sha1(), nullptr);
+  if (r != 1) {
+    throw OpenSSLError("sha1 init failed");
+  }
+
+  result.Reserve(sha1SizeBytes);
+
+  EVP_DigestUpdate(ctx.get(), src.Data(), src.Size());
+  EVP_DigestFinal_ex(ctx.get(), result.end(), nullptr);
+
+  result.IncSize(sha1SizeBytes);
+}
+
+} // vereign::crypto::digest
diff --git a/cpp/src/vereign/crypto/digest.hh b/cpp/src/vereign/crypto/digest.hh
new file mode 100644
index 0000000000000000000000000000000000000000..365d45e9af3fa5fd3be617d539aaa823ac210af0
--- /dev/null
+++ b/cpp/src/vereign/crypto/digest.hh
@@ -0,0 +1,28 @@
+#ifndef __VEREIGN_CRYPTO_DIGEST_HH
+#define __VEREIGN_CRYPTO_DIGEST_HH
+
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::crypto::digest {
+
+/**
+ * Creates a SHA1 hash of given bytes.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ *
+ * bytes::Buffer hash;
+ * crypto::digest::sha1(bytes::View(data), hash);
+ * @endcode
+ *
+ * @param src The input bytes.
+ * @param result The SHA1 hash of the input.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+void sha1(bytes::View src, bytes::Buffer& result);
+
+} // vereign::crypto::digest
+
+#endif // __VEREIGN_CRYPTO_DIGEST_HH
diff --git a/cpp/src/vereign/crypto/errors.hh b/cpp/src/vereign/crypto/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..949eb248cd8a0caf3014701a8e962c47f866f1ad
--- /dev/null
+++ b/cpp/src/vereign/crypto/errors.hh
@@ -0,0 +1,33 @@
+#ifndef __VEREIGN_CRYPTO_ERRORS_HH
+#define __VEREIGN_CRYPTO_ERRORS_HH
+
+#include <stdexcept>
+#include <openssl/err.h>
+
+namespace vereign::crypto {
+
+/**
+ * The base error type for the namespace vereign::crypto.
+ */
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+/**
+ * An error thrown by the crypto functions.
+ */
+class OpenSSLError : public Error {
+public:
+  OpenSSLError(const std::string& what)
+    : Error(what + ": " + ERR_reason_error_string(ERR_get_error()))
+  {
+  }
+};
+
+} // vereign::crypto
+
+#endif // __VEREIGN_CRYPTO_ERRORS_HH
diff --git a/cpp/src/vereign/crypto/rand.cc b/cpp/src/vereign/crypto/rand.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2acc168ed6d9df6905c3ad25e7053c041e29a715
--- /dev/null
+++ b/cpp/src/vereign/crypto/rand.cc
@@ -0,0 +1,26 @@
+#include <vereign/crypto/rand.hh>
+
+#include <openssl/rand.h>
+#include <vereign/crypto/errors.hh>
+
+namespace vereign::crypto {
+
+void Rand(bytes::Buffer& buf, std::size_t size) {
+  buf.Reserve(size);
+  int result = RAND_bytes(buf.end(), size);
+  if (result == 0) {
+    ERR_clear_error();
+    throw Error("crypto rand failed");
+  }
+
+  buf.IncSize(size);
+}
+
+auto Rand(std::size_t size) -> bytes::Buffer {
+  bytes::Buffer buf{size};
+  Rand(buf, size);
+
+  return buf;
+}
+
+} // vereign::crypto
diff --git a/cpp/src/vereign/crypto/rand.hh b/cpp/src/vereign/crypto/rand.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4c3979900f1464d2e54c9957f689f503af3e7e2f
--- /dev/null
+++ b/cpp/src/vereign/crypto/rand.hh
@@ -0,0 +1,48 @@
+#ifndef __VEREIGN_CRYPTO_RAND_HH
+#define __VEREIGN_CRYPTO_RAND_HH
+
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::crypto {
+
+/**
+ * Appends a random bytes to a buffer.
+ *
+ * After the operation is finished, the passed `buf` will be with incremented size of the newly
+ * added random bytes.
+ *
+ * Example:
+ * @code
+ * bytes::Bytes buf;
+ * crypto::Rand(buf, 16);
+ *
+ * assert(buf.Size() == 16);
+ * @endcode
+ *
+ * @param buf The buffer that will be filled with random bytes.
+ * @param size The number of the random bytes.
+ *
+ * @throws crypto::Error on failure.
+ */
+void Rand(bytes::Buffer& buf, std::size_t size);
+
+/**
+ * Generate random bytes.
+ *
+ * Example:
+ * @code
+ * auto buf = crypto::Rand(16);
+ *
+ * assert(buf.Size() == 16);
+ * @endcode
+ *
+ * @param size The number of the random bytes.
+ * @returns buffer with the generated random bytes.
+ *
+ * @throws crypto::Error on failure.
+ */
+auto Rand(std::size_t size) -> bytes::Buffer;
+
+} // vereign::crypto
+
+#endif // __VEREIGN_CRYPTO_RAND_HH
diff --git a/cpp/src/vereign/crypto/rsa.cc b/cpp/src/vereign/crypto/rsa.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e9a519f6b5887486f37819759e65d33108264602
--- /dev/null
+++ b/cpp/src/vereign/crypto/rsa.cc
@@ -0,0 +1,167 @@
+#include <vereign/crypto/rsa.hh>
+
+#include <vereign/bytes/view.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/bytes/buffer.hh>
+
+#include <openssl/base.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/mem.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+
+namespace vereign::crypto::rsa {
+
+auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY> {
+  bssl::UniquePtr<BIGNUM> bn{BN_new()};
+  if (!bn) {
+    throw OpenSSLError("rsa key generation failed");
+  }
+
+  auto r = BN_set_word(bn.get(), RSA_F4);
+  if (r != 1) {
+    throw OpenSSLError("rsa key generation failed");
+  }
+
+  bssl::UniquePtr<RSA> rsa{RSA_new()};
+  r = RSA_generate_key_ex(rsa.get(), bits, bn.get(), nullptr);
+  if (r != 1) {
+    throw OpenSSLError("rsa key generation failed");
+  }
+
+  bssl::UniquePtr<EVP_PKEY> key(EVP_PKEY_new());
+  if (key == nullptr) {
+    throw OpenSSLError("creating key failed");
+  }
+
+  r = EVP_PKEY_assign_RSA(key.get(), rsa.release());
+  if (r != 1) {
+    throw OpenSSLError("rsa key assign to evp failed");
+  }
+
+  return key;
+}
+
+void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted) {
+  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
+  if (!ctx) {
+    throw OpenSSLError("creating evp ctx failed");
+  }
+
+  auto r = EVP_PKEY_encrypt_init(ctx.get());
+  if (r != 1) {
+    throw OpenSSLError("encrypt init failed");
+  }
+
+  r = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING);
+  if (r != 1) {
+    throw OpenSSLError("pkey padding init failed");
+  }
+
+  std::size_t outlen = 0;
+  r = EVP_PKEY_encrypt(ctx.get(), nullptr, &outlen, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("determining ciphertext size failed");
+  }
+
+  encrypted.Reserve(outlen);
+
+  r = EVP_PKEY_encrypt(ctx.get(), encrypted.end(), &outlen, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("encrypting failed");
+  }
+
+  encrypted.IncSize(outlen);
+}
+
+void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted) {
+  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
+  if (!ctx) {
+    throw OpenSSLError("creating evp ctx failed");
+  }
+
+  auto r = EVP_PKEY_decrypt_init(ctx.get());
+  if (r != 1) {
+    throw OpenSSLError("decrypt init failed");
+  }
+
+  r = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING);
+  if (r != 1) {
+    throw OpenSSLError("init pkey padding failed");
+  }
+
+  std::size_t outlen = 0;
+  r = EVP_PKEY_decrypt(ctx.get(), nullptr, &outlen, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("determining decrypted buffer size failed");
+  }
+
+  decrypted.Reserve(outlen);
+
+  r = EVP_PKEY_decrypt(ctx.get(), decrypted.end(), &outlen, src.Data(), src.Size());
+  if (r != 1) {
+    throw OpenSSLError("decrypting failed");
+  }
+
+  decrypted.IncSize(outlen);
+}
+
+auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> {
+  bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
+  if (!mem) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  auto r = PEM_write_bio_PUBKEY(mem.get(), key);
+  if (r != 1) {
+    throw OpenSSLError("exporting public key to PEM failed");
+  }
+
+  return mem;
+}
+
+auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
+  bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
+  if (mem == nullptr) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  bssl::UniquePtr<EVP_PKEY> key(PEM_read_bio_PUBKEY(mem.get(), nullptr, nullptr, nullptr));
+  if (key == nullptr) {
+    throw OpenSSLError("importing public key from PEM failed");
+  }
+
+  return key;
+}
+
+auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> {
+  bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
+  if (!mem) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  auto r = PEM_write_bio_PrivateKey(mem.get(), key, nullptr, nullptr, 0, nullptr, nullptr);
+  if (r != 1) {
+    throw OpenSSLError("exporting private key to PEM failed");
+  }
+
+  return mem;
+}
+
+auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
+  bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
+  if (mem == nullptr) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  auto key_ptr = PEM_read_bio_PrivateKey(mem.get(), nullptr, nullptr, nullptr);
+  if (key_ptr == nullptr) {
+    throw OpenSSLError("importing public key from PEM failed");
+  }
+
+  return bssl::UniquePtr<EVP_PKEY>(key_ptr);
+}
+
+} // vereign::crypto::rsa
diff --git a/cpp/src/vereign/crypto/rsa.hh b/cpp/src/vereign/crypto/rsa.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4d8e18fc38701d7a921dc2ea42a2781030ee32a6
--- /dev/null
+++ b/cpp/src/vereign/crypto/rsa.hh
@@ -0,0 +1,147 @@
+#ifndef __VEREIGN_CRYPTO_RSA_HH
+#define __VEREIGN_CRYPTO_RSA_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/buffer.hh>
+
+#include <openssl/base.h>
+#include <openssl/evp.h>
+
+namespace vereign::crypto::rsa {
+
+/**
+ * Generates new RSA key.
+ *
+ * Example:
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * @endcode
+ *
+ * @param bits The length of the key.
+ * @returns the newly generated key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY>;
+
+/**
+ * Encrypts given bytes with RSA public key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * bytes::Buffer encrypted;
+ *
+ * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+ *
+ * assert(decrypted.View() == bytes.View(input));
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be encrypted.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted);
+
+/**
+ * Decrypts given bytes with RSA private key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * bytes::Buffer encrypted;
+ *
+ * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+ *
+ * assert(decrypted.View() == bytes.View(input));
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be decrypted.
+ * @param decrypted The result of the decryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted);
+
+/**
+ * Exports a public key part to PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get());
+ * std::cout << crypto::bio::View(bio.get()).String() << std::endl;
+ * @endcode
+ *
+ * @param key The key to export.
+ * @returns a memory BIO with the exported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+
+/**
+ * Import public key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get());
+ * auto imported_key = crypto::rsa::ImportPublicKeyFromPEM(crypto::bio::View(bio.get()));
+ * @endcode
+ *
+ * @param pem PEM encoded key.
+ * @returns imported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
+
+/**
+ * Export private key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+ * std::cout << crypto::bio::View(bio.get()).String() << std::endl;
+ * @endcode
+ *
+ * @param key The key to export.
+ * @returns a memory BIO with the exported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+
+/**
+ * Import private key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+ * auto imported_key = crypto::rsa::ImportPrivateKeyFromPEM(crypto::bio::View(bio.get()));
+ * @endcode
+ *
+ * @param pem PEM encoded key.
+ * @returns imported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
+
+} // vereign::crypto::rsa
+
+#endif // __VEREIGN_CRYPTO_RSA_HH
diff --git a/cpp/src/vereign/encoding/base64.cc b/cpp/src/vereign/encoding/base64.cc
new file mode 100644
index 0000000000000000000000000000000000000000..47ef85a53f69d204c474b6ad862a1cc0942ebbfa
--- /dev/null
+++ b/cpp/src/vereign/encoding/base64.cc
@@ -0,0 +1,27 @@
+#include <vereign/encoding/base64.hh>
+
+#include <boost/beast/core/detail/base64.hpp>
+
+namespace vereign::encoding::base64 {
+
+void Encode(bytes::View src, bytes::Buffer& encoded) {
+  if (src.Size() == 0) {
+    return;
+  }
+
+  encoded.Reserve(boost::beast::detail::base64::encoded_size(src.Size()));
+  auto written = boost::beast::detail::base64::encode(encoded.end(), src.Data(), src.Size());
+  encoded.IncSize(written);
+}
+
+void Decode(bytes::View src, bytes::Buffer& decoded) {
+  if (src.Size() == 0) {
+    return ;
+  }
+
+  decoded.Reserve(boost::beast::detail::base64::decoded_size(src.Size()));
+  auto written = boost::beast::detail::base64::decode(decoded.end(), src.CharData(), src.Size());
+  decoded.IncSize(written.first);
+}
+
+} // vereign::encoding::base64
diff --git a/cpp/src/vereign/encoding/base64.hh b/cpp/src/vereign/encoding/base64.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a69a559403f9126045cdb69e89bbf084a519fd08
--- /dev/null
+++ b/cpp/src/vereign/encoding/base64.hh
@@ -0,0 +1,47 @@
+#ifndef __VEREIGN_ENCODING_BASE64_HH
+#define __VEREIGN_ENCODING_BASE64_HH
+
+#include <vereign/bytes/buffer.hh>
+#include <string>
+
+namespace vereign::encoding::base64 {
+
+/**
+ * Encodes source bytes into base64 encoding.
+ *
+ * No new lines are inserted.
+ *
+ * Example:
+ * @code
+ * std::string s{"foob"};
+ * bytes::Buffer encoded;
+ * encoding::base64::Encode(bytes::View(s), encoded);
+ *
+ * assert(encoded.View().String() == "Zm9vYg==")
+ * @endcode
+ *
+ * @param src The source bytes that will be encoded.
+ * @param encoded The encoded bytes.
+ */
+void Encode(bytes::View src, bytes::Buffer& encoded);
+
+/**
+ * Decodes base64 encoded bytes.
+ *
+ * Example:
+ * @code
+ * std::string s{"Zm9vYg=="};
+ * bytes::Buffer decoded;
+ * encoding::base64::Decode(bytes::View(s), decoded);
+ *
+ * assert(decoded.View().String() == "foob");
+ * @endcode
+ *
+ * @param src The base64 encoded bytes that will be decoded.
+ * @param decoded The decoded bytes.
+ */
+void Decode(bytes::View src, bytes::Buffer& decoded);
+
+} // vereign::encoding::base64
+
+#endif // __VEREIGN_ENCODING_BASE64_HH
diff --git a/cpp/src/vereign/encoding/binary.cc b/cpp/src/vereign/encoding/binary.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3c25446ccf77e123d6c19ec71578a38ab895c34e
--- /dev/null
+++ b/cpp/src/vereign/encoding/binary.cc
@@ -0,0 +1,73 @@
+#include <vereign/encoding/binary.hh>
+
+#include <vereign/encoding/errors.hh>
+
+namespace vereign::encoding::binary {
+
+void EncodeUint8(bytes::Buffer& out, uint8_t v) {
+  out.Reserve(1);
+  out.end()[0] = v;
+  out.IncSize(1);
+}
+
+auto DecodeUint8(bytes::View& b) -> uint8_t {
+  if (b.Size() < 1) {
+    throw encoding::Error("decoding failed the input size is less than 1 bytes");
+  }
+
+  return b.Data()[0];
+}
+
+void EncodeUint64(uint8_t* out, uint64_t v) {
+  out[0] = uint8_t(v);
+  out[1] = uint8_t(v >> 8);
+  out[2] = uint8_t(v >> 16);
+  out[3] = uint8_t(v >> 24);
+  out[4] = uint8_t(v >> 32);
+  out[5] = uint8_t(v >> 40);
+  out[6] = uint8_t(v >> 48);
+  out[7] = uint8_t(v >> 56);
+}
+
+void EncodeUint64(bytes::Buffer& out, uint64_t v) {
+  out.Reserve(8);
+  EncodeUint64(out.end(), v);
+  out.IncSize(8);
+}
+
+auto DecodeUint64(const uint8_t* b) -> uint64_t {
+  return uint64_t(b[0])
+    | uint64_t(b[1]) << 8
+    | uint64_t(b[2]) << 16
+    | uint64_t(b[3]) << 24
+    | uint64_t(b[4]) << 32
+    | uint64_t(b[5]) << 40
+    | uint64_t(b[6]) << 48
+    | uint64_t(b[7]) << 56;
+}
+
+auto DecodeUint64(bytes::View b) -> uint64_t {
+  if (b.Size() < 8) {
+    throw encoding::Error("decoding failed the input size is less than 8 bytes");
+  }
+
+  return DecodeUint64(b.Data());
+}
+
+void EncodeBytes(bytes::Buffer& out, bytes::View bytes) {
+  EncodeUint64(out, uint64_t(bytes.Size()));
+  out.Write(bytes);
+}
+
+auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t {
+  auto size = DecodeUint64(b);
+  if (size < 0 || size + 8 > b.Size()) {
+    throw encoding::Error("decode bytes failed: invalid size");
+  }
+
+  out = b.Slice(8, size + 8);
+
+  return 8 + size;
+}
+
+} // namespace vereign::encoding::binary
diff --git a/cpp/src/vereign/encoding/binary.hh b/cpp/src/vereign/encoding/binary.hh
new file mode 100644
index 0000000000000000000000000000000000000000..8be71c31f3a76f1c4c36c7e631f8731253edd64d
--- /dev/null
+++ b/cpp/src/vereign/encoding/binary.hh
@@ -0,0 +1,101 @@
+#ifndef __VEREIGN_ENCODING_BINARY_HH
+#define __VEREIGN_ENCODING_BINARY_HH
+
+#include <stdexcept>
+#include <vereign/bytes/buffer.hh>
+
+/**
+ * Binary encoding of integers, bytes.
+ *
+ * The integers are encoded in little endian.
+ */
+namespace vereign::encoding::binary {
+
+/**
+ * Encodes a byte.
+ *
+ * On success the out buffer size will be incremented with 1.
+ *
+ * @param out buffer where the byte will be written.
+ * @param v the byte that will be encoded.
+ */
+void EncodeUint8(bytes::Buffer& out, uint8_t v);
+
+/**
+ * Decodes a byte.
+ *
+ * @param b the source bytes from where the single byte will be decoded.
+ */
+auto DecodeUint8(bytes::View& b) -> uint8_t;
+
+/**
+ * Encodes a uint64.
+ *
+ * The number is encoded in little endian.
+ *
+ * @param out where the value will be written.
+ * @param v the number that will be encoded.
+ */
+void EncodeUint64(uint8_t* out, uint64_t v);
+
+/**
+ * Encodes a uint64.
+ *
+ * On success the out buffer size will be incremented with 8.
+ * The number is encoded in little endian.
+ *
+ * @param out where the value will be written.
+ * @param v the number that will be encoded.
+ */
+void EncodeUint64(bytes::Buffer& out, uint64_t v);
+
+/**
+ * Decodes a uint64.
+ *
+ * The number is expected to be encoded in little endian.
+ *
+ * @param b the source number.
+ * @returns the decoded number.
+ */
+auto DecodeUint64(const uint8_t* b) -> uint64_t;
+
+/**
+ * Decodes a uint64.
+ *
+ * The number is expected to be encoded in little endian.
+ *
+ * @param b the source bytes where the encoded number will be read from.
+ * @returns the decoded number.
+ *
+ * @throws encoding::Error when the source bytes size is less than 8.
+ */
+auto DecodeUint64(bytes::View b) -> uint64_t;
+
+/**
+ * Encodes bytes.
+ *
+ * The result is 8 bytes for the size, followed by the bytes them self.
+ * The out buffer size is incremented with out.Size() + 8.
+ *
+ * @param out the where the encoded value will be written.
+ * @param bytes the source bytes that will be encoded.
+ */
+void EncodeBytes(bytes::Buffer& out, bytes::View bytes);
+
+/**
+ * Decode bytes.
+ *
+ * Note that the decoded bytes reference to the source buffer.
+ * In other words it is only a view, there is no copying involved.
+ *
+ * @param b the source with the encoded bytes.
+ * @param out the decoded bytes.
+ * @returns the bytes read during the decoding (8 + out.Size()).
+ *
+ * @throws encoding::Error when the decoding fails.
+ */
+auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t;
+
+} // namespace vereign::encoding::binary
+
+#endif // __VEREIGN_ENCODING_BINARY_HH
diff --git a/cpp/src/vereign/encoding/errors.hh b/cpp/src/vereign/encoding/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d9d012210165fb645f600dc60a989dfb5ffcb392
--- /dev/null
+++ b/cpp/src/vereign/encoding/errors.hh
@@ -0,0 +1,21 @@
+#ifndef __VEREIGN_ENCODING_ERRORS_HH
+#define __VEREIGN_ENCODING_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::encoding {
+
+/**
+ * Base error for namespace vereign::encoding.
+ */
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+} // namespace vereign::encoding::binary
+
+#endif // __VEREIGN_ENCODING_ERRORS_HH
diff --git a/cpp/src/vereign/encoding/hex.cc b/cpp/src/vereign/encoding/hex.cc
new file mode 100644
index 0000000000000000000000000000000000000000..46ed21564dbd5e8cc363e43e04123a9c6a5bddaf
--- /dev/null
+++ b/cpp/src/vereign/encoding/hex.cc
@@ -0,0 +1,71 @@
+#include <vereign/encoding/hex.hh>
+
+namespace vereign::encoding::hex {
+
+namespace detail {
+
+auto charToInt(char ch) -> int {
+  if (ch >= '0' && ch <= '9') {
+    return ch - '0';
+  }
+
+  if (ch >= 'A' && ch <= 'F') {
+    return ch - 'A' + 10;
+  }
+
+  if (ch >= 'a' && ch <= 'f') {
+    return ch - 'a' + 10;
+  }
+
+  return 0;
+}
+
+} // namespace detail
+
+void Encode(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[i] >> 4];
+    encoded[i*2 + 1] = nibbles[src[i] & 0x0F];
+  }
+
+  encoded.IncSize(src.Size() * 2);
+}
+
+void Decode(bytes::View src, bytes::Buffer& decoded) {
+  if (src.Size() == 0) {
+    return ;
+  }
+
+  decoded.Reserve(src.Size() / 2);
+  for (int i = 0, len = (int) src.Size() - 1; i < len; i += 2) {
+    decoded[i/2] = detail::charToInt(src[i]) * 16 + detail::charToInt(src[i + 1]);
+  }
+  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
new file mode 100644
index 0000000000000000000000000000000000000000..6dec0f13270bae1d163d7f9114e56276f082515d
--- /dev/null
+++ b/cpp/src/vereign/encoding/hex.hh
@@ -0,0 +1,59 @@
+#ifndef __VEREIGN_ENCODING_HEX_HH
+#define __VEREIGN_ENCODING_HEX_HH
+
+#include <vereign/bytes/buffer.hh>
+#include <string>
+
+namespace vereign::encoding::hex {
+
+/**
+ * Encodes bytes in hexadecimal.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer encoded;
+ * encoding::hex::Decode(bytes::View("foobar"), encoded);
+ *
+ * assert(encoded.View().String() == "666f6f626172");
+ * @endcode
+ *
+ * @param src The bytes that will be encoded.
+ * @param encoded The buffer where the encoded bytes will be written.
+ */
+void Encode(bytes::View src, bytes::Buffer& encoded);
+
+/**
+ * Decodes hexadecimal encoded bytes.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer decoded;
+ * encoding::hex::Decode(bytes::View("666f6f626172"), decoded);
+ *
+ * assert(decoded.View().String() == "foobar");
+ * @endcode
+ *
+ * @param src The bytes that will be decoded.
+ * @param encoded The buffer where the decoded bytes will be written.
+ */
+void Decode(bytes::View src, bytes::Buffer& decoded);
+
+/**
+ * Encodes bytes in hexadecimal in reverse order.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer encoded;
+ * encoding::hex::Decode(bytes::View("foobar"), encoded);
+ *
+ * assert(encoded.View().String() == "7261626f6f66");
+ * @endcode
+ *
+ * @param src The bytes that will be encoded.
+ * @param encoded The buffer where the encoded bytes will be written.
+ */
+void EncodeReverse(bytes::View src, bytes::Buffer& encoded);
+
+} // vereign::encoding::hex
+
+#endif // __VEREIGN_ENCODING_HEX_HH
diff --git a/cpp/src/vereign/fs/errors.hh b/cpp/src/vereign/fs/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..035dc67edc34f39c03bbd90bbc649896af48cc3b
--- /dev/null
+++ b/cpp/src/vereign/fs/errors.hh
@@ -0,0 +1,28 @@
+#ifndef __VEREIGN_FS_ERRORS_HH
+#define __VEREIGN_FS_ERRORS_HH
+
+#include <exception>
+
+namespace vereign::fs {
+
+class Error : public std::exception {
+  auto what() const noexcept -> const char* override {
+    return "filesystem error";
+  }
+};
+
+class HomeNotFoundError : public std::exception {
+  auto what() const noexcept -> const char* override {
+    return "cannot find user's home dir";
+  }
+};
+
+class TempDirNotFoundError : public std::exception {
+  auto what() const noexcept -> const char* override {
+    return "cannot find user's temp dir";
+  }
+};
+
+} // namespace vereign::fs
+
+#endif // __VEREIGN_FS_ERRORS_HH
diff --git a/cpp/src/vereign/fs/operations.cc b/cpp/src/vereign/fs/operations.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9f33ed2d31d3322c2748ee32ba91d4b9c477f53f
--- /dev/null
+++ b/cpp/src/vereign/fs/operations.cc
@@ -0,0 +1,29 @@
+#include <vereign/fs/operations.hh>
+
+#include <vereign/fs/util.hh>
+#include <vereign/core/string.hh>
+
+#include <boost/filesystem/directory.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <fstream>
+
+namespace vereign::fs {
+
+auto Exists(const std::string& path) -> bool {
+#ifdef _WIN32
+  std::ifstream f{string::widen(path)};
+#else
+  std::ifstream f{path};
+#endif
+  return f.good();
+}
+
+auto IsDir(const std::string& path) -> bool {
+  return boost::filesystem::is_directory(fs::detail::StringToPath(path));
+}
+
+auto CreateDir(const std::string& path) -> bool {
+  return boost::filesystem::create_directory(fs::detail::StringToPath(path));
+}
+
+} // namespace vereign::fs
diff --git a/cpp/src/vereign/fs/operations.hh b/cpp/src/vereign/fs/operations.hh
new file mode 100644
index 0000000000000000000000000000000000000000..e5f60c773a1eb9de85d76b32100e0c11d033c5bf
--- /dev/null
+++ b/cpp/src/vereign/fs/operations.hh
@@ -0,0 +1,29 @@
+#ifndef __VEREIGN_FS_OPERATIONS_HH
+#define __VEREIGN_FS_OPERATIONS_HH
+
+#include <string>
+
+namespace vereign::fs {
+
+/**
+ * Check if file exists and is readable.
+ */
+auto Exists(const std::string& path) -> bool;
+
+/**
+ * Check if given path is a directory.
+ *
+ * @param path Path to check.
+ */
+auto IsDir(const std::string& path) -> bool;
+
+/**
+ * Creates the last directory of the provided path.
+ *
+ * @param path Path of the directory that will be created.
+ */
+auto CreateDir(const std::string& path) -> bool;
+
+} // namespace vereign::fs
+
+#endif // __VEREIGN_FS_OPERATIONS_HH
diff --git a/cpp/src/vereign/fs/path.cc b/cpp/src/vereign/fs/path.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fc92ce61945f17a34df5f3575b4605303733a978
--- /dev/null
+++ b/cpp/src/vereign/fs/path.cc
@@ -0,0 +1,37 @@
+#include <vereign/fs/path.hh>
+
+namespace vereign::fs::path {
+
+/**
+ * Convert relative path from host OS to unix format.
+ *
+ * It replaces the host path separator with the unix path separator.
+ * For example "foo\\bar" on windows becomes "foo/bar".
+ *
+ * @param path Source path.
+ */
+auto to_unix(std::string path) -> std::string {
+  if (sep[0] != unix_sep[0]) {
+    std::replace(path.begin(), path.end(), sep[0], unix_sep[0]);
+  }
+
+  return path;
+}
+
+/**
+ * Convert relative path from unix format to native OS format.
+ *
+ * It replaces the unix path separator with the native path separator.
+ * For example "foo/bar" on windows becomes "foo\\bar".
+ *
+ * @param path Source path.
+ */
+auto to_native(std::string path) -> std::string {
+  if (sep[0] != unix_sep[0]) {
+    std::replace(path.begin(), path.end(), unix_sep[0], sep[0]);
+  }
+
+  return path;
+}
+
+} // namespace vereign::fs::path
diff --git a/cpp/src/vereign/fs/path.hh b/cpp/src/vereign/fs/path.hh
new file mode 100644
index 0000000000000000000000000000000000000000..87d58adc00845b30d16ed155010a1ea23ca4b244
--- /dev/null
+++ b/cpp/src/vereign/fs/path.hh
@@ -0,0 +1,60 @@
+#ifndef __VEREIGN_FS_PATH_HH
+#define __VEREIGN_FS_PATH_HH
+
+#include <boost/core/ignore_unused.hpp>
+#include <algorithm>
+#include <string>
+
+namespace vereign::fs::path {
+
+#if defined(_WIN32)
+  constexpr inline const char* unix_sep = "/";
+  constexpr inline const char* sep = "\\";
+  constexpr inline const char* prefix = "\\\\?\\";
+  constexpr inline const char* drive_sep = ":\\";
+  constexpr inline const char invalid_escape_char = '_';
+#else
+  constexpr inline const char* unix_sep = "/";
+  constexpr inline const char* sep = "/";
+  constexpr inline const char* prefix = "/";
+  constexpr inline const char* drive_sep = "";
+  constexpr inline const char invalid_escape_char = '_';
+#endif
+
+
+/**
+ * Join path fragments with the file system path separator.
+ */
+template <typename... Args>
+auto Join(const Args&... args) -> std::string {
+  std::string path;
+  int unpack[] { 0, (path = path + sep + args, 0)... };
+  boost::ignore_unused(unpack);
+  path.erase(0, 1);
+
+  return path;
+}
+
+/**
+ * Convert relative path from host OS to unix format.
+ *
+ * It replaces the host path separator with the unix path separator.
+ * For example "foo\\bar" on windows becomes "foo/bar".
+ *
+ * @param path Source path.
+ */
+auto to_unix(std::string path) -> std::string;
+
+/**
+ * Convert relative path from unix format to native OS format.
+ *
+ * It replaces the unix path separator with the native path separator.
+ * For example "foo/bar" on windows becomes "foo\\bar".
+ *
+ * @param path Source path.
+ */
+auto to_native(std::string path) -> std::string;
+
+} // namespace vereign::fs::path
+
+#endif // __VEREIGN_FS_PATH_HH
diff --git a/cpp/src/vereign/fs/util.cc b/cpp/src/vereign/fs/util.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2e39a022f9ecd8d38388467b85beaedb955856d6
--- /dev/null
+++ b/cpp/src/vereign/fs/util.cc
@@ -0,0 +1,113 @@
+#include <vereign/fs/util.hh>
+
+#include <vereign/fs/operations.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/fs/errors.hh>
+
+#include <vereign/core/rand.hh>
+#include <vereign/core/string.hh>
+#include <boost/filesystem/operations.hpp>
+
+#ifdef _WIN32
+# include <shlobj_core.h>
+#endif
+
+namespace vereign::fs {
+
+namespace detail {
+
+auto PathToString(const boost::filesystem::path& path) -> std::string {
+#ifdef _WIN32
+  return string::narrow(path.wstring());
+#else
+  return path.string();
+#endif
+}
+
+auto StringToPath(std::string_view path) -> boost::filesystem::path {
+#ifdef _WIN32
+  return boost::filesystem::path{string::widen(path)};
+#else
+  return boost::filesystem::path{std::string{path}};
+#endif
+}
+
+}
+
+RemoveFileGuard::RemoveFileGuard(std::string path)
+  : path_{std::move(path)}
+{
+}
+
+RemoveFileGuard::~RemoveFileGuard() {
+  boost::filesystem::remove(detail::StringToPath(path_));
+}
+
+RemoveAllGuard::RemoveAllGuard(std::string path)
+  : path_{std::move(path)}
+{
+}
+
+RemoveAllGuard::~RemoveAllGuard() {
+  boost::filesystem::remove_all(detail::StringToPath(path_));
+}
+
+auto TempFilePath(std::string_view dir, std::string_view prefix) -> std::string {
+  return path::Join(std::string(dir), std::string(prefix) + core::RandLowerAlpha(10));
+}
+
+auto TempFilePath(std::string_view prefix) -> std::string {
+  return TempFilePath(detail::PathToString(boost::filesystem::temp_directory_path()), prefix);
+}
+
+auto TempDir(std::string_view prefix) -> std::string {
+  auto dir = TempFilePath(prefix);
+
+  CreateDir(dir);
+
+  return dir;
+}
+
+auto HomePath() -> std::string {
+#ifdef _WIN32
+  PWSTR home;
+  SHGetKnownFolderPath(
+    FOLDERID_LocalAppData,
+    0,
+    nullptr,
+    &home
+  );
+
+  std::wstring wpath;
+  try {
+    wpath = home;
+    CoTaskMemFree(home);
+  } catch (...) {
+    CoTaskMemFree(home);
+    throw;
+  }
+
+  return "";
+
+  auto path = boost::filesystem::path(wpath);
+  if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) {
+    return detail::PathToString(path);
+  }
+
+  throw HomeNotFoundError{};
+#else
+  auto home = std::getenv("HOME");
+  if (home == nullptr) {
+    throw HomeNotFoundError{};
+  }
+
+  auto path = boost::filesystem::path{home};
+  if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) {
+    return detail::PathToString(path);
+  }
+
+  throw HomeNotFoundError{};
+#endif
+}
+
+} // namespace vereign::fs
diff --git a/cpp/src/vereign/fs/util.hh b/cpp/src/vereign/fs/util.hh
new file mode 100644
index 0000000000000000000000000000000000000000..c4e9d3dbb03b08f32a63de99fd6a1da389cc8f35
--- /dev/null
+++ b/cpp/src/vereign/fs/util.hh
@@ -0,0 +1,113 @@
+#ifndef __VEREIGN_FS_UTIL_HH
+#define __VEREIGN_FS_UTIL_HH
+
+#include <boost/filesystem/path.hpp>
+#include <string>
+#include <string_view>
+
+namespace vereign::fs {
+
+namespace detail {
+
+auto PathToString(const boost::filesystem::path& path) -> std::string;
+auto StringToPath(std::string_view path) -> boost::filesystem::path;
+
+} // namespace detail
+
+/**
+ * A RAII guard that deletes a file upon destruction.
+ */
+class RemoveFileGuard {
+public:
+  RemoveFileGuard(std::string path);
+  ~RemoveFileGuard();
+
+  RemoveFileGuard(const RemoveFileGuard&) = delete;
+  auto operator=(const RemoveFileGuard&) -> RemoveFileGuard& = delete;
+private:
+  std::string path_;
+};
+
+/**
+ * A RAII guard that deletes all files and directories recursively.
+ */
+class RemoveAllGuard {
+public:
+  RemoveAllGuard(std::string path);
+  ~RemoveAllGuard();
+
+  RemoveAllGuard(const RemoveAllGuard&) = delete;
+  auto operator=(const RemoveAllGuard&) -> RemoveAllGuard& = delete;
+private:
+  std::string path_;
+};
+
+/**
+ * Generates a file path usable as temporary file.
+ *
+ * For a temp file path under the system tmp dir use the other TempFilePath overload.
+ *
+ * @code
+ * auto path = fs::TempFilePath("/tmp/foo", "test_db_");
+ * std::cout << path << std::endl;
+ *
+ * // Output:
+ * // /tmp/foo/test_db_bh0vcr0jbz
+ * @endcode
+ *
+ * @param dir The directory of the temp file path.
+ * @param prefix A prefix to prepend to the temp file name.
+ * @returns a file path usable as temporary file.
+ */
+auto TempFilePath(std::string_view dir, std::string_view prefix) -> std::string;
+
+/**
+ * Generates a file path usable as temporary file under the system temporary directory.
+ *
+ * It tries to detect the correct temp dir under different operation systems.
+ * For Linux this is `/tmp` and for Windows this is `C:\Users\<username>\AppData\Local\Temp`.
+ *
+ * @code
+ * auto path = fs::TempFilePath("test_db_");
+ * std::cout << path << std::endl;
+ *
+ * // Output:
+ * // /tmp/test_db_bh0vcr0jbz
+ * @endcode
+ *
+ * @param prefix A prefix to prepend to the temp file name.
+ * @returns a file path usable as temporary file.
+ */
+auto TempFilePath(std::string_view prefix) -> std::string;
+
+/**
+ * Creates a temporary sub directory under the system temporary directory.
+ *
+ * It tries to detect the correct temp dir under different operation systems.
+ * For Linux this is `/tmp` and for Windows this is `C:\Users\<username>\AppData\Local\Temp`.
+ *
+ * @code
+ * auto path = fs::TempDir("test_db_");
+ * std::cout << path << std::endl;
+ *
+ * // Output:
+ * // /tmp/test_db_bh0vcr0jbz
+ * @endcode
+ *
+ * @param prefix A prefix to prepend to the temp dir name.
+ * @returns a the path of the created temporary directory.
+ */
+auto TempDir(std::string_view prefix) -> std::string;
+
+/**
+ * Returns current user home directory.
+ *
+ * On Windows this is `C:\Users\<username>\AppData\Local`.
+ *
+ * @returns the user's home directory full path.
+ */
+auto HomePath() -> std::string;
+
+} // namespace vereign::fs
+
+#endif // __VEREIGN_FS_UTIL_HH
diff --git a/cpp/src/vereign/grpc/error_code.hh b/cpp/src/vereign/grpc/error_code.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2c5c26e9d6ac060ac6351405aab90f0b4b0b5010
--- /dev/null
+++ b/cpp/src/vereign/grpc/error_code.hh
@@ -0,0 +1,38 @@
+#ifndef __VEREIGN_GRPC_ERROR_CODES_HH
+#define __VEREIGN_GRPC_ERROR_CODES_HH
+
+#include <cstdint>
+#include <string>
+
+namespace vereign::grpc {
+
+static constexpr const char* ClientErrorStatus = "Vereign Client Library Error";
+
+/**
+ * Error codes returned into the gRPC API response `code` field on various failures.
+ *
+ * These are errors that happen inside the Vereign Client Library.
+ * The errors that happen inside the Vereign Restful API are in the standard HTTP status code range
+ * below 600.
+ */
+enum class ErrorCode : uint64_t {
+  ClientError                 = 1000,
+  UnexpectedError             = 1001,
+  DeviceNotRegistered         = 1002,
+  InvalidPinCode              = 1003,
+  InvalidIdentity             = 1004
+};
+
+/**
+ * Convert the error code to string.
+ *
+ * @param ec The error code.
+ * @returns the error code integer as string.
+ */
+inline auto ErrorCodeAsString(ErrorCode ec) -> std::string {
+  return std::to_string(uint64_t(ec));
+}
+
+}
+
+#endif // __VEREIGN_GRPC_ERROR_CODES_HH
diff --git a/cpp/src/vereign/grpc/identity_api.hh b/cpp/src/vereign/grpc/identity_api.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6083548eb488823dafedf3991c9c704af929b91f
--- /dev/null
+++ b/cpp/src/vereign/grpc/identity_api.hh
@@ -0,0 +1,150 @@
+#ifndef __VEREIGN_GRPC_IDENTITY_API_HH
+#define __VEREIGN_GRPC_IDENTITY_API_HH
+
+#include <vereign/grpc/gen/identity_api.hh>
+
+#include <vereign/grpc/error_code.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/identity_types.pb.h>
+#include <boost/core/ignore_unused.hpp>
+
+namespace vereign::grpc {
+
+/**
+ * Implementation of the gRPC `vereign::client_library::IdentityAPI::Service` service.
+ *
+ * Inherits all the API implementations from the generated gen::IdentityAPI and adds some
+ * additional implementations.
+ *
+ * IdentityAPI is a thin layer on top of the service::IdentityService.
+ */
+template <class VereignService>
+class IdentityAPI final : public gen::IdentityAPI<VereignService> {
+public:
+  // API service name.
+  static constexpr const char* Name = gen::IdentityAPI<VereignService>::Name;
+
+  using VereignServiceType = VereignService;
+  using VereignServicePtr = std::unique_ptr<VereignService>;
+
+  /**
+   * Constructs IdentityAPI instance.
+   *
+   * @param service The client library Identity service.
+   */
+  IdentityAPI(VereignServicePtr&& service)
+    : gen::IdentityAPI<VereignService>{std::move(service)}
+  {}
+
+  // disable copying
+  IdentityAPI(const IdentityAPI&) = delete;
+  auto operator=(const IdentityAPI&) -> IdentityAPI& = delete;
+
+  /**
+   * Registers a new device.
+   *
+   * req.pin is required only under Linux.
+   *
+   * Under windows the system cypto storage is used.
+   * When the device is registered a master key is created and the user will be asked for his
+   * consent by showing a dialog window.
+   *
+   * Unexpected error codes:
+   * - ErrorCode::ClientError Error that happen inside the Vereign Client Library
+   * - ErrorCode::UnexpectedError Should never happen.
+   *
+   * Error codes of interest:
+   * - ErrorCode::InvalidPinCode The pin code is invalid, currently during the registration an empty
+   *    pin code is considered invalid.
+   */
+  auto LoginWithNewDevice(
+    ::grpc::ServerContext* ctx,
+    const client_library::LoginFormNewDevice* req,
+    client_library::LoginFormNewDeviceResponse* resp
+  ) -> ::grpc::Status override {
+    boost::ignore_unused(ctx);
+
+    try {
+      this->service_->LoginWithNewDevice(req, resp);
+
+    } catch (const kvstore::InvalidPinCodeError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const std::exception& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (...) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(ClientErrorStatus);
+    }
+
+    return ::grpc::Status::OK;
+  }
+
+  /**
+   * Login with already registered device.
+   *
+   * req.pin is required only under Linux.
+   *
+   * Under windows the system cypto storage is used.
+   * When the device is registered a master key is created and the user will be asked for his
+   * consent by showing a dialog window.
+   *
+   * Unexpected error codes:
+   * - ErrorCode::ClientError Error that happen inside the Vereign Client Library
+   * - ErrorCode::UnexpectedError Should never happen.
+   *
+   * Error codes of interest:
+   * - ErrorCode::DeviceNotRegistered The device is not registered.
+   * - ErrorCode::InvalidPinCode The pin code is invalid and the crypto storage cannot be unlocked.
+   * - ErrorCode::InvalidIdentity Under windows if for some reason the RSA master key has been changed.
+   */
+  auto LoginWithPreviouslyAddedDevice(
+    ::grpc::ServerContext* ctx,
+    const client_library::LoginFormPreviousAddedDevice* req,
+    client_library::EmptyResponse* resp
+  ) -> ::grpc::Status override {
+    boost::ignore_unused(ctx);
+
+    try {
+      this->service_->LoginWithPreviouslyAddedDevice(req, resp);
+
+    } catch (const kvstore::StorageNotInitializedError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::DeviceNotRegistered));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const kvstore::InvalidPinCodeError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const kvstore::InvalidIdentityError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidIdentity));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const std::exception& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (...) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(ClientErrorStatus);
+    }
+
+    return ::grpc::Status::OK;
+  }
+};
+
+} // namespace vereign::grpc
+
+#endif // __VEREIGN_GRPC_IDENTITY_API_HH
diff --git a/cpp/src/vereign/grpc/passport_api.hh b/cpp/src/vereign/grpc/passport_api.hh
deleted file mode 100644
index 02eb2b36c07dfe12e4d7f46c9bf67d5a5ffac6bf..0000000000000000000000000000000000000000
--- a/cpp/src/vereign/grpc/passport_api.hh
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef __VEREIGN_GRPC_PASSPORT_API_HH
-#define __VEREIGN_GRPC_PASSPORT_API_HH
-
-#include <vereign/grpc/gen/passport_api.hh>
-
-namespace vereign {
-namespace grpc {
-
-template <class VereignService>
-class PassportAPI final : public gen::PassportAPI<VereignService> {
-public:
-  static constexpr const char* Name = gen::PassportAPI<VereignService>::Name;
-
-  using VereignServiceType = VereignService;
-  using VereignServicePtr = std::unique_ptr<VereignService>;
-
-  PassportAPI(VereignServicePtr&& service)
-    : gen::PassportAPI<VereignService>{std::move(service)}
-  {}
-
-  PassportAPI(const PassportAPI&) = delete;
-  PassportAPI& operator=(const PassportAPI&) = delete;
-
-  ::grpc::Status ListPassportsManually(
-    ::grpc::ServerContext*,
-    const client_library::ListPassportsForm* request,
-    client_library::ListPassportsFormResponse* response
-  ) override {
-    auto result_future = this->service_->ListPassports(request, response);
-
-    try {
-      result_future.get();
-    } catch (const std::exception& e) {
-      response->set_code("500");
-      response->set_status("Internal Service Error");
-      response->set_error(e.what());
-    } catch (...) {
-      response->set_code("500");
-      response->set_status("Internal Service Error");
-      response->set_error("Internal Service Error");
-    }
-
-    return ::grpc::Status::OK;
-  }
-};
-
-} // namespace grpc
-} // namespace vereign
-
-#endif // __VEREIGN_GRPC_PASSPORT_API_HH
diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc
index fd3c911aae22ff35bb513772a27b7d8d3555b985..f0fae25a176de979bd9274693687a1d1ef6b272f 100644
--- a/cpp/src/vereign/grpc/server.cc
+++ b/cpp/src/vereign/grpc/server.cc
@@ -1,13 +1,22 @@
 #include <vereign/grpc/server.hh>
 
+#include <vereign/fs/path.hh>
+#include <vereign/fs/operations.hh>
+#include <vereign/fs/util.hh>
+
+#include <vereign/kvstore/crypto_storage.hh>
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/kvstore/storage.hh>
+
 #include <vereign/restapi/client.hh>
 #include <vereign/restapi/client_session.hh>
 #include <vereign/service/gen/gen.hh>
 #include <vereign/grpc/gen/gen.hh>
 #include <vereign/grpc/service_registry.hh>
 
-#include <vereign/service/passport_service.hh>
-#include <vereign/grpc/passport_api.hh>
+// manually written api
+#include <vereign/service/identity_service.hh>
+#include <vereign/grpc/identity_api.hh>
 
 #include <grpcpp/server.h>
 #include <boost/asio/io_context.hpp>
@@ -19,42 +28,54 @@
 
 #include <boost/asio/executor_work_guard.hpp>
 
-namespace vereign {
-namespace grpc {
+namespace vereign::grpc {
 
 namespace asio = boost::asio;
 
 class Server::Impl {
 public:
   Impl(
-    const std::string& listenAddress,
-    const std::string& vereignHost,
-    const std::string& vereignPort,
-    // FIXME: the public key must come from a storage
-    const std::string& publicKey
+    const std::string& listen_address,
+    const std::string& vereign_host,
+    const std::string& vereign_port,
+    std::string storage_path
   ) : selected_port_{0},
     work_guard_{asio::make_work_guard(ioc_)},
     ssl_context_{asio::ssl::context::tlsv12_client},
     client_{std::make_unique<restapi::Client>(
-      ioc_, ssl_context_, vereignHost, vereignPort
-    )},
-    client_session_{std::make_unique<restapi::ClientSession>(
-      *client_, publicKey
+      ioc_, ssl_context_, vereign_host, vereign_port
     )},
+    client_session_{std::make_unique<restapi::ClientSession>(*client_)},
+    kvstorage_{nullptr},
+    crypto_storage_{nullptr},
+    identity_provider_{nullptr},
     server_{nullptr}
   {
+    if (storage_path == "") {
+      storage_path = fs::path::Join(fs::HomePath(), "vereign");
+      fs::CreateDir(storage_path);
+    }
+
+    storage_path = fs::path::Join(storage_path, "db");
+
+    kvstorage_ = std::make_unique<kvstore::SqliteStorage>(storage_path);
+    crypto_storage_ = std::make_unique<kvstore::CryptoStorage>(*kvstorage_);
+    identity_provider_ = std::make_unique<identity::Provider>(*crypto_storage_);
 
     // FIXME: Verify the remote server's certificate
     // ssl_context.set_verify_mode(ssl::verify_peer);
     ::grpc::ServerBuilder builder;
     builder.AddListeningPort(
-      listenAddress,
+      listen_address,
       ::grpc::InsecureServerCredentials(),
       &selected_port_
     );
 
     // register manually written services
-    services_registry_.RegisterIfNotExist<PassportAPI<service::PassportService>>(*client_session_);
+    services_registry_.RegisterIfNotExist<IdentityAPI<service::IdentityService>>(
+      *client_session_,
+      *identity_provider_
+    );
 
     // register all generated services
     grpc::gen::RegisterAll(*client_session_, services_registry_);
@@ -101,6 +122,9 @@ private:
   std::unique_ptr<vereign::restapi::Client> client_;
   std::unique_ptr<vereign::restapi::ClientSession> client_session_;
   ServiceRegistry services_registry_;
+  std::unique_ptr<kvstore::Storage> kvstorage_;
+  std::unique_ptr<kvstore::CryptoStorage> crypto_storage_;
+  std::unique_ptr<identity::Provider> identity_provider_;
   std::unique_ptr<::grpc::Server> server_;
   std::thread server_thread_;
   std::thread service_thread_;
@@ -110,10 +134,9 @@ Server::Server(
   const std::string& listenAddress,
   const std::string& vereignHost,
   const std::string& vereignPort,
-  // FIXME: the public key must come from a storage
-  const std::string& publicKey
+  const std::string& storage_path
 )
-  : impl_{std::make_unique<Impl>(listenAddress, vereignHost, vereignPort, publicKey)}
+  : impl_{std::make_unique<Impl>(listenAddress, vereignHost, vereignPort, storage_path)}
 {
 }
 
@@ -129,5 +152,4 @@ auto Server::SelectedPort() const -> int {
   return impl_->SelectedPort();
 }
 
-} // namespace grpc
 } // namespace vereign
diff --git a/cpp/src/vereign/grpc/server.hh b/cpp/src/vereign/grpc/server.hh
index bcadb25458827207c43b6a0050c92c5dda9cd0fa..ef3e8903c76490ded00529f1cfc9af0e5bd09ac5 100644
--- a/cpp/src/vereign/grpc/server.hh
+++ b/cpp/src/vereign/grpc/server.hh
@@ -4,13 +4,12 @@
 #include <string>
 #include <memory>
 
-namespace vereign {
-namespace grpc {
+namespace vereign::grpc {
 
 /**
  * BindError is thrown when the Server::Server could not start listening.
  */
-class BindError: public virtual std::exception {
+class BindError: public std::exception {
 public:
   auto what() const noexcept -> const char* override {
     return "gRPC listen failed";
@@ -34,14 +33,17 @@ public:
    * @param listenAddress gRPC listen address, for example "localhost:".
    * @param vereignHost Vereign restapi host.
    * @param vereignPort Vereign restapi port - https, 443...
+   * @param storage_path Path to directory that will be used for the crypto storage.
+   *    If storage_path is empty string, a default is used. Under linux this default is
+   *    `$HOME/vereign`, and under windows it is `C:\Users\<user>\AppData\Local\vereign`.
+   *
    * @throws BindError when the gRPC server could not start listening.
    */
   explicit Server(
     const std::string& listenAddress,
     const std::string& vereignHost,
     const std::string& vereignPort,
-    // FIXME: the public key must come from a storage
-    const std::string& publicKey
+    const std::string& storage_path = ""
   );
 
   /**
@@ -77,7 +79,6 @@ private:
   std::unique_ptr<Impl> impl_;
 };
 
-} // namespace grpc
 } // namespace vereign
 
 #endif
diff --git a/cpp/src/vereign/grpc/service_registry.hh b/cpp/src/vereign/grpc/service_registry.hh
index cddc32a89d1a323761bcaa6fa54bb4262a613a89..94c0fd355fc8a4c1e7bfae838a07b43f41a603ac 100644
--- a/cpp/src/vereign/grpc/service_registry.hh
+++ b/cpp/src/vereign/grpc/service_registry.hh
@@ -3,18 +3,18 @@
 
 #include <grpcpp/server_builder.h>
 #include <vereign/restapi/client_session.hh>
+#include <vereign/identity/provider.hh>
 
 #include <unordered_map>
 
-namespace vereign {
-namespace grpc {
+namespace vereign::grpc {
 
 class ServiceRegistry {
 public:
   void RegisterIntoBuilder(::grpc::ServerBuilder& builder);
 
   template <class API>
-  bool RegisterIfNotExist(restapi::ClientSession& client_session) {
+  auto RegisterIfNotExist(restapi::ClientSession& client_session) -> bool {
     auto it = services_.find(API::Name);
     if (it != services_.end()) {
       return false;
@@ -26,11 +26,28 @@ public:
     return true;
   }
 
+  template <class API>
+  auto RegisterIfNotExist(
+    restapi::ClientSession& client_session,
+    identity::Provider& identity_provider
+  ) -> bool {
+    auto it = services_.find(API::Name);
+    if (it != services_.end()) {
+      return false;
+    }
+
+    using Service = typename API::VereignServiceType;
+    services_[API::Name] = std::make_unique<API>(
+      std::make_unique<Service>(client_session, identity_provider)
+    );
+
+    return true;
+  }
+
 private:
   std::unordered_map<std::string, std::unique_ptr<::grpc::Service>> services_;
 };
 
-} // namespace grpc
-} // namespace vereign
+} // namespace vereign::grpc
 
 #endif // __VEREIGN_GRPC_SERVICE_REGISTRY_HH
diff --git a/cpp/src/vereign/identity/errors.hh b/cpp/src/vereign/identity/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..7140afec338eeef55150bc988beb260de2b901ac
--- /dev/null
+++ b/cpp/src/vereign/identity/errors.hh
@@ -0,0 +1,18 @@
+#ifndef __VEREIGN_IDENTITY_ERRORS_HH
+#define __VEREIGN_IDENTITY_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::identity {
+
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+} // namespace vereign::identity
+
+#endif // __VEREIGN_IDENTITY_ERRORS_HH
diff --git a/cpp/src/vereign/identity/provider.cc b/cpp/src/vereign/identity/provider.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e69494ad66845c3fdd56216b20559ffffb887942
--- /dev/null
+++ b/cpp/src/vereign/identity/provider.cc
@@ -0,0 +1,80 @@
+#include <vereign/identity/provider.hh>
+
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/bio.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/encoding/base64.hh>
+
+namespace {
+  constexpr int rsaKeySizeBits = 2048;
+}
+
+namespace vereign::identity {
+
+Provider::Provider(kvstore::CryptoStorage& storage)
+  : storage_{storage}
+{}
+
+Provider::~Provider() = default;
+
+auto Provider::RecreateIdentity(const std::string& pin) -> std::string {
+  std::lock_guard<std::mutex> l{mu_};
+
+  storage_.Recreate(pin);
+
+  auto rsa = crypto::rsa::GenerateKey(rsaKeySizeBits);
+
+  auto private_key = crypto::rsa::ExportPrivateKeyToPEM(rsa.get());
+  storage_.PutBytes("identity_private_key", crypto::bio::View(private_key.get()));
+
+  auto public_key = crypto::rsa::ExportPublicKeyToPEM(rsa.get());
+  storage_.PutBytes("identity_public_key", crypto::bio::View(public_key.get()));
+
+  bytes::Buffer encoded;
+  encoding::base64::Encode(crypto::bio::View(public_key.get()), encoded);
+
+  return std::string{encoded.View().String()};
+}
+
+auto Provider::LoadIdentity(const std::string& pin) -> std::string {
+  std::lock_guard<std::mutex> l{mu_};
+
+  storage_.Open(pin);
+
+  bytes::Buffer public_key;
+  storage_.GetBytes("identity_public_key", public_key);
+
+  bytes::Buffer encoded;
+  encoding::base64::Encode(public_key.View(), encoded);
+
+  return std::string(encoded.View().String());
+}
+
+auto Provider::GetIdentityPublicKeyBase64() -> std::string {
+  std::lock_guard<std::mutex> l{mu_};
+
+  bytes::Buffer public_key;
+  storage_.GetBytes("identity_public_key", public_key);
+
+  bytes::Buffer encoded;
+  encoding::base64::Encode(public_key.View(), encoded);
+
+  return std::string(encoded.View().String());
+}
+
+auto Provider::GetDeviceHash() -> std::string {
+  std::lock_guard<std::mutex> l{mu_};
+
+  bytes::Buffer public_key;
+  storage_.GetBytes("identity_public_key", public_key);
+
+  bytes::Buffer hash;
+  crypto::digest::sha1(public_key.View(), hash);
+
+  bytes::Buffer encoded;
+  encoding::base64::Encode(hash.View(), encoded);
+
+  return std::string(encoded.View().String());
+}
+
+} // namespace vereign::identity
diff --git a/cpp/src/vereign/identity/provider.hh b/cpp/src/vereign/identity/provider.hh
new file mode 100644
index 0000000000000000000000000000000000000000..c526e41fa0e2353ca8dc2258609e9076a6a3f17a
--- /dev/null
+++ b/cpp/src/vereign/identity/provider.hh
@@ -0,0 +1,75 @@
+#ifndef __VEREIGN_IDENTITY_PROVIDER_HH
+#define __VEREIGN_IDENTITY_PROVIDER_HH
+
+#include <vereign/kvstore/crypto_storage.hh>
+
+#include <mutex>
+
+namespace vereign::identity {
+
+/**
+ * Identity provider that manages the locally stored user identity.
+ *
+ * All public methods are thread safe.
+ */
+class Provider {
+public:
+  /**
+   * Creates Provider instance.
+   *
+   * @param storage The crypto storage used for read/write identity properties.
+   */
+  Provider(kvstore::CryptoStorage& storage);
+
+  /**
+   * Default constructor.
+   *
+   * Does nothing.
+   */
+  ~Provider();
+
+  // disable copying
+  Provider(const kvstore::Storage&) = delete;
+  auto operator=(const kvstore::Storage&) -> Provider& = delete;
+
+  /**
+   * Recreates the current identity.
+   *
+   * @param pin Required only under Linux. The pin code used for derivation of the crypto storage
+   *    master key.
+   *
+   * @returns The base64 encoded PEM encoded identity public key.
+   */
+  auto RecreateIdentity(const std::string& pin) -> std::string;
+
+  /**
+   * Loads the local identity.
+   *
+   * @param pin Required only under Linux. The pin code used for derivation of the crypto storage
+   *    master key.
+   *
+   * @returns The base64 encoded PEM encoded identity public key.
+   */
+  auto LoadIdentity(const std::string& pin) -> std::string;
+
+  /**
+   * Retrieve identity public key.
+   *
+   * @returns The base64 encoded PEM encoded identity public key.
+   */
+  auto GetIdentityPublicKeyBase64() -> std::string;
+
+  /**
+   * @returns base64 encoded SHA1 hash of the identity public key.
+   */
+  auto GetDeviceHash() -> std::string;
+
+private:
+  std::mutex mu_;
+
+  kvstore::CryptoStorage& storage_;
+};
+
+} // namespace vereign::identity
+
+#endif // __VEREIGN_IDENTITY_PROVIDER_HH
diff --git a/cpp/src/vereign/kvstore/README.md b/cpp/src/vereign/kvstore/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..62a67dc5f3d413eb11c04a66e68c26bb62cca1ce
--- /dev/null
+++ b/cpp/src/vereign/kvstore/README.md
@@ -0,0 +1,71 @@
+# Vereign C++ Client Library Storage Spec {#vcl_storage_spec}
+
+## Overview
+
+The Vereign C++ Library Storage is used for securely store locally user's identity and profile
+certificates.
+
+The storage is a key/value storage where the values are encrypted with `master key` using
+`AES256-GCM` cypher.
+
+The current backend of the storage is sqlite3, where the database file is normally located at 
+`${HOME}/vereign/db`.
+
+The `master key` key is provided in different way depending on the operating system.
+
+## windows master key
+
+Under windows [CNG](https://docs.microsoft.com/en-us/windows/win32/seccng/about-cng) is used for 
+generating a persistent RSA key pair 2048 bits long. The name of the key in the windows storage by 
+default is `vereign_key`. This key is used to encrypt a randomly generated AES256 key, and the 
+encrypted key is saved in the storage under a special key `__master_key`.
+
+## linux master key
+
+Under linux the user must provide a password (pin code).
+This password along with randomly generated salt is used to derive a master key using
+`PKCS5_PBKDF2_HMAC` with `SHA256` where the salt is 16 bytes and the iterations are 2^18.
+The salt and the iterations are stored in the storage under special keys `__master_key_salt` and
+`master_key_iterations`. Note that these are stored in plain form without encryption.
+
+## locking and storage tag
+
+Every time the storage is going to be modified the whole storage must be locked, which with the 
+sqlite3 backend is achieved with so called exclusive transaction.
+This is needed in order to have consistency when more then a single application use the same storage.
+
+But locking is not enough to achieve consistency. If an application A already had opened the storage,
+and application B reset the identity (changing the master key), and after some time A writes to the
+storage it will overwrite values written by B leaving the storage in a state where different values
+are encrypted with different keys.
+
+In order to prevent this, when the master key is created for the first time a 16 byte long random
+tag is generated and stored encrypted with the master key into a special key `__tag`.
+Then whenever a value must be written after the storage is locked additionally the `__tag` is read
+and decrypted. If the tag retrieval or decryption fails, and error is triggered and the write is
+cancelled.
+
+## encrypted values encoding
+
+As already mentioned the values are encrypted with `AES256-GCM`.
+
+The encrypted values are encoded according to the BNF grammars described below.
+
+The primitives used are:
+* `uint8` - a single byte.
+* `uint64` - unsigned 64 bit integer in Little Endian.
+* `bytes` - a sequence of bytes.
+
+### encoding version 1
+
+```
+Bytes = Size Data
+  Size = uint64
+  Data = bytes
+
+EncryptedValue = Version IV Tag EncryptedData
+  Version = uint8
+  IV = bytes
+  Tag = bytes
+  EncryptedData = bytes
+```
diff --git a/cpp/src/vereign/kvstore/crypto_storage.cc b/cpp/src/vereign/kvstore/crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7db16463cd4b0cc9947ff11dc2e9e56c54fb5a6c
--- /dev/null
+++ b/cpp/src/vereign/kvstore/crypto_storage.cc
@@ -0,0 +1,33 @@
+#include <vereign/kvstore/crypto_storage.hh>
+
+#if defined(_WIN32)
+# include <vereign/kvstore/detail/win_crypto_storage.hh>
+#else
+# include <vereign/kvstore/detail/linux_crypto_storage.hh>
+#endif
+
+namespace vereign::kvstore {
+
+CryptoStorage::CryptoStorage(Storage& storage, bool disable_key_protection)
+  : impl_{std::make_unique<detail::CryptoStorageImpl>(storage, disable_key_protection)}
+{}
+
+CryptoStorage::~CryptoStorage() = default;
+
+void CryptoStorage::Recreate(const std::string& pin) {
+  impl_->Recreate(pin);
+}
+
+void CryptoStorage::Open(const std::string& pin) {
+  impl_->Open(pin);
+}
+
+void CryptoStorage::PutBytes(const std::string& key, bytes::View value) {
+  impl_->PutBytes(key, value);
+}
+
+void CryptoStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
+  impl_->GetBytes(key, value);
+}
+
+} // namespace vereign::identity
diff --git a/cpp/src/vereign/kvstore/crypto_storage.hh b/cpp/src/vereign/kvstore/crypto_storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..f0c03648c9d2afe06e7fde140157c691f66d954a
--- /dev/null
+++ b/cpp/src/vereign/kvstore/crypto_storage.hh
@@ -0,0 +1,112 @@
+#ifndef __VEREIGN_KVSTORE_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <memory>
+
+namespace vereign::kvstore {
+
+namespace detail {
+
+class CryptoStorageImpl;
+
+}
+
+/**
+ * Crypto storage is used for securely store sensitive user's data.
+ *
+ * This includes user's device private/public key, profile certificates etc.
+ *
+ * The CryptoStorage has different implementations per operating system.
+ * Check detail/linux_crypto_storage.cc and detail/win_crypto_storage.cc.
+ *
+ * For more information about the crypto storage design check the [storage spec](@ref vcl_storage_spec)
+ * in the README.md.
+ *
+ */
+class CryptoStorage {
+public:
+  /**
+   * Creates CryptoStorage.
+   *
+   * @param storage The underlying key/value storage.
+   * @param disable_key_protection Used only under Windows. If false when the master RSA key
+   *    is created and loaded, the user will be asked for his consent by showing a dialog window
+   *    with text information specified in the UI policy.
+   */
+  CryptoStorage(Storage& storage, bool disable_key_protection = false);
+
+  /**
+   * Default destructor - does nothing.
+   */
+  ~CryptoStorage();
+
+  // disable copying
+  CryptoStorage(const CryptoStorage&) = delete;
+  auto operator=(const CryptoStorage&) -> CryptoStorage& = delete;
+
+  /**
+   * Reinitializes the storage, by effectively initializing a new empty storage.
+   *
+   * Although the resulting storage is empty it will include some internal data needed for the
+   * master key management.
+   *
+   * @param pin Used only under Linux. A pin code that will be used for derivation of the master key.
+   *    Under windows ncrypt is used for generating a RSA key which is used for encryption of the
+   *    master key, and thus the pin is not used.
+   *
+   * @throws Error on failure.
+   */
+  void Recreate(const std::string& pin);
+
+  /**
+   * Open the storage.
+   *
+   * @param pin Used only under Linux. A pin code that will be used for derivation of the master key.
+   *    Under windows ncrypt is used for generating a RSA key which is used for encryption of the
+   *    master key, and thus the pin is not used.
+   *
+   * @throws StorageNotInitializedError when storage is not initialized yet. One must use
+   *    CryptoStorage::Reset to initialize the storage prior calling CryptoStorage::Open.
+   *
+   * @throws InvalidPinCodeError (only under Linux) when the pin code is empty or does not match
+   *    the pin code used with CryptoStorage::Reset.
+   *
+   * @throws Error when the key derivation under Linux fails.
+   *
+   * @throws InvalidIdentityError when the master key could not be decrypted under Windows.
+   *    This possibly means that the RSA key was changed.
+   */
+  void Open(const std::string& pin);
+
+  /**
+   * Encrypt and store bytes value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The bytes that will be encrypted and stored.
+   *
+   * @throws StorageNotInitializedError when the storage is not initialized.
+   * @throws InvalidIdentityError when another application had reset the storage.
+   */
+  void PutBytes(const std::string& key, bytes::View value);
+
+  /**
+   * Retrieve and decrypt bytes value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws StorageNotInitializedError when the storage is not initialized.
+   * @throws ValueNotFoundError when there is no value under the provided key.
+   * @throws encoding::Error when the encrypted value cannot be decoded.
+   * @throws crypto::OpenSSLError when the value cannot be decrypted.
+   */
+  void GetBytes(const std::string& key, bytes::Buffer& value);
+
+private:
+  std::unique_ptr<detail::CryptoStorageImpl> impl_;
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_CRYPTO_STORAGE_HH
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..83bb4c7b1930b76829af0192b6357b674a42f7ae
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
@@ -0,0 +1,98 @@
+#include <vereign/kvstore/detail/base_crypto_storage.hh>
+
+#include <vereign/kvstore/detail/value_encoder.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/bio.hh>
+
+#include <boost/core/ignore_unused.hpp>
+#include <chrono>
+
+namespace {
+
+  // FIXME: should these be injected and provided by the integrator
+  constexpr int tagSizeBytes = 16;
+  constexpr int lockRetryCount = 10;
+  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+}
+
+namespace vereign::kvstore::detail {
+
+BaseCryptoStorageImpl::BaseCryptoStorageImpl(kvstore::Storage& storage)
+  : storage_{storage}
+{
+}
+
+void BaseCryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) {
+  if (key_.Size() == 0) {
+    throw StorageNotInitializedError{"key not initialized"};
+  }
+
+  bytes::Buffer iv;
+  bytes::Buffer tag;
+  bytes::Buffer encrypted;
+
+  crypto::aes::GCM256Encrypt(value, key_.View(), iv, tag, encrypted);
+
+  bytes::Buffer encoded_value;
+  EncodeEncryptedValue(encoded_value, iv.View(), tag.View(), encrypted.View());
+
+  storage_.PutBytes(key, encoded_value.View());
+}
+
+void BaseCryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  if (!isTagValid()) {
+    throw InvalidIdentityError{};
+  }
+
+  encryptBytes(key, value);
+}
+
+void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) const {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  if (key_.Size() == 0) {
+    throw StorageNotInitializedError{"key not initialized"};
+  }
+
+  bytes::Buffer encoded;
+  storage_.GetBytes(key, encoded);
+
+  bytes::View iv;
+  bytes::View tag;
+  bytes::View encrypted;
+  DecodeEncryptedValue(encoded.View(), iv, tag, encrypted);
+
+  crypto::aes::GCM256Decrypt(encrypted, key_.View(), iv, tag, value);
+}
+
+void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) {
+  key_ = std::move(key);
+}
+
+auto BaseCryptoStorageImpl::isTagValid() const -> bool {
+  bytes::Buffer tag;
+  try {
+    GetBytes("__tag", tag);
+
+    return true;
+  } catch (const crypto::Error&) {
+    return false;
+  }
+}
+
+void BaseCryptoStorageImpl::updateTag() {
+  auto tag = crypto::Rand(tagSizeBytes);
+
+  encryptBytes("__tag", tag.View());
+}
+
+} // namespace vereign::kvstore::detail
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..49278c3f6847d4555dfd3ba58fc52a38a7d05e07
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
@@ -0,0 +1,37 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_BASE_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_DETAIL_BASE_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+
+namespace vereign::kvstore::detail {
+
+class BaseCryptoStorageImpl {
+public:
+  BaseCryptoStorageImpl(kvstore::Storage& storage);
+
+  // disable copying
+  BaseCryptoStorageImpl(const BaseCryptoStorageImpl&) = delete;
+  auto operator=(const BaseCryptoStorageImpl&) -> BaseCryptoStorageImpl& = delete;
+
+  void PutBytes(const std::string& key, bytes::View value);
+  void GetBytes(const std::string& key, bytes::Buffer& value) const;
+
+protected:
+  void initKey(bytes::Buffer&& key);
+  auto isTagValid() const -> bool;
+  void updateTag();
+
+private:
+  void encryptBytes(const std::string& key, bytes::View value);
+
+protected:
+  kvstore::Storage& storage_;
+
+private:
+  bytes::Buffer key_;
+};
+
+} // namespace vereign::kvstore::detail
+
+
+#endif // __VEREIGN_KVSTORE_DETAIL_BASE_CRYPTO_STORAGE_HH
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1d59144d03098d016c2d7d1c29942fb7e95bac07
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -0,0 +1,118 @@
+#include <vereign/kvstore/detail/linux_crypto_storage.hh>
+
+#include <vereign/kvstore/detail/value_encoder.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/bio.hh>
+
+#include <boost/core/ignore_unused.hpp>
+#include <chrono>
+
+namespace {
+  // FIXME: should these be injected and provided by the integrator
+  constexpr int iterations = 1 << 18;
+  constexpr int saltSizeBytes = 16;
+  constexpr int aesKeySizeBytes = 32;
+
+  constexpr int lockRetryCount = 10;
+  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+}
+
+namespace vereign::kvstore::detail {
+
+CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection)
+  : BaseCryptoStorageImpl{storage}
+{
+  boost::ignore_unused(disable_key_protection);
+}
+
+void CryptoStorageImpl::Open(const std::string& pin) {
+  if (pin.empty()) {
+    throw InvalidPinCodeError{};
+  }
+
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  bytes::Buffer salt;
+
+  int64_t iterations = 0;
+  try {
+    iterations = storage_.GetInt64("__master_key_iterations");
+    storage_.GetBytes("__master_key_salt", salt);
+  } catch (const std::exception& e) {
+    throw StorageNotInitializedError{e.what()};
+  }
+
+  if (iterations == 0) {
+    throw StorageNotInitializedError{"iterations cannot be zero"};
+  }
+
+  bytes::Buffer key{aesKeySizeBytes};
+
+  int result = PKCS5_PBKDF2_HMAC(
+    pin.data(),
+    pin.length(),
+    salt.View().Data(),
+    salt.View().Size(),
+    iterations,
+    EVP_sha256(),
+    aesKeySizeBytes,
+    key.end()
+  );
+  if (result == 0) {
+    throw Error("key derivation failed");
+  }
+
+  key.IncSize(aesKeySizeBytes);
+
+  initKey(std::move(key));
+  if (!isTagValid()) {
+    throw InvalidPinCodeError{};
+  }
+}
+
+void CryptoStorageImpl::Recreate(const std::string& pin) {
+  if (pin.empty()) {
+    throw InvalidPinCodeError{};
+  }
+
+  auto salt = crypto::Rand(saltSizeBytes);
+
+  bytes::Buffer key{aesKeySizeBytes};
+
+  int result = PKCS5_PBKDF2_HMAC(
+    pin.data(),
+    pin.length(),
+    salt.View().Data(),
+    salt.View().Size(),
+    iterations,
+    EVP_sha256(),
+    aesKeySizeBytes,
+    key.end()
+  );
+  if (result == 0) {
+    throw Error("key derivation failed");
+  }
+
+  key.IncSize(aesKeySizeBytes);
+  initKey(std::move(key));
+
+  {
+    kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+    storage_.DeleteAll();
+
+    updateTag();
+
+    storage_.PutInt64("__master_key_iterations", iterations);
+    storage_.PutBytes("__master_key_salt", salt.View());
+  }
+}
+
+} // namespace vereign::kvstore::detail
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..fcc3e1fc0b67664ede137622c8ac79a7c765a6f6
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
@@ -0,0 +1,24 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <vereign/kvstore/detail/base_crypto_storage.hh>
+
+namespace vereign::kvstore::detail {
+
+class CryptoStorageImpl : public BaseCryptoStorageImpl {
+public:
+  CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection);
+
+  // disable copying
+  CryptoStorageImpl(const CryptoStorageImpl&) = delete;
+  auto operator=(const CryptoStorageImpl&) -> CryptoStorageImpl& = delete;
+
+  void Recreate(const std::string& pin);
+  void Open(const std::string& pin);
+};
+
+} // namespace vereign::kvstore::detail
+
+
+#endif // __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.cc b/cpp/src/vereign/kvstore/detail/value_encoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6144bebbdffdee83f9797cf063d9729b34d96c35
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/value_encoder.cc
@@ -0,0 +1,42 @@
+#include <vereign/kvstore/detail/value_encoder.hh>
+
+#include <vereign/kvstore/errors.hh>
+#include <vereign/encoding/binary.hh>
+
+namespace vereign::kvstore::detail {
+
+void EncodeEncryptedValue(
+  bytes::Buffer& out,
+  bytes::View iv,
+  bytes::View tag,
+  bytes::View encrypted
+) {
+  // 25 = buffers size numbers (3 int64) plus 1 byte for the version
+  out.Reserve(iv.Size() + tag.Size() + encrypted.Size() + 25);
+  // encode version
+  encoding::binary::EncodeUint8(out, 1);
+
+  // encode data
+  encoding::binary::EncodeBytes(out, iv);
+  encoding::binary::EncodeBytes(out, tag);
+  encoding::binary::EncodeBytes(out, encrypted);
+}
+
+void DecodeEncryptedValue(
+  bytes::View encoded,
+  bytes::View& iv,
+  bytes::View& tag,
+  bytes::View& encrypted
+) {
+  auto version = encoding::binary::DecodeUint8(encoded);
+  if (version != 1) {
+    throw Error("decoding encrypted value failed: invalid version");
+  }
+
+  auto offset = 1;
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), iv);
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), tag);
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), encrypted);
+}
+
+} // namespace vereign::kvstore::detail
diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.hh b/cpp/src/vereign/kvstore/detail/value_encoder.hh
new file mode 100644
index 0000000000000000000000000000000000000000..3c9e532853a853febaadf44b39c7941c1f0f28e1
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/value_encoder.hh
@@ -0,0 +1,46 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
+#define __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::kvstore::detail {
+
+/**
+ * Encodes value encrypted with AES in GCM mode.
+ *
+ * @param out The encoded result.
+ * @param iv The IV of the encrypted value.
+ * @param tag The tag of the encrypted value.
+ * @param encrypted The encrypted bytes.
+ */
+void EncodeEncryptedValue(
+  bytes::Buffer& out,
+  bytes::View iv,
+  bytes::View tag,
+  bytes::View encrypted
+);
+
+/**
+ * Decode value encrypted with AES in GCM mode.
+ *
+ * Note that iv, tag and encrypted are decoded in-place, meaning that they point to the same memory
+ * of the `encoded` input.
+ *
+ * @param out The encoded value.
+ * @param iv The decoded IV of the encrypted value.
+ * @param tag The decoded tag of the encrypted value.
+ * @param encrypted The decoded encrypted bytes.
+ *
+ * @throws kvstore::Error on failure.
+ */
+void DecodeEncryptedValue(
+  bytes::View encoded,
+  bytes::View& iv,
+  bytes::View& tag,
+  bytes::View& encrypted
+);
+
+} // namespace vereign::kvstore::detail
+
+#endif // __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6d8651f38261282aa1de180d208e1a3506df1b94
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
@@ -0,0 +1,108 @@
+#include <vereign/kvstore/detail/win_crypto_storage.hh>
+
+#include <vereign/kvstore/detail/value_encoder.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/bio.hh>
+
+#include <boost/core/ignore_unused.hpp>
+#include <vereign/ncrypt/rsa.hh>
+#include <vereign/ncrypt/errors.hh>
+
+#include <chrono>
+
+namespace {
+  // FIXME: should these be injected and provided by the integrator
+  constexpr int keySizeBits = 2048;
+  constexpr int aesKeySizeBytes = 32;
+
+  constexpr int lockRetryCount = 10;
+  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+
+  // FIXME: ask business for these values
+  constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"};
+  constexpr const auto vereignKeyDescription = std::string_view{
+    "Vereign Client will use this key to authenticate with the Vereign Services"
+  };
+  constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"};
+}
+
+namespace vereign::kvstore::detail {
+
+CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection)
+  : BaseCryptoStorageImpl{storage},
+    disable_key_protection_{disable_key_protection}
+{}
+
+void CryptoStorageImpl::Open(const std::string& pin) {
+  boost::ignore_unused(pin);
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName));
+  if (!rsa_key) {
+    throw StorageNotInitializedError{"key not initialized"};
+  }
+
+  bytes::Buffer key;
+  try {
+    bytes::Buffer encrypted_key;
+    storage_.GetBytes("__master_key", encrypted_key);
+
+    ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key);
+  } catch(const std::exception&) {
+    throw InvalidIdentityError{};
+  }
+
+  initKey(std::move(key));
+  // FIXME: write tests for tampering and regular identity change
+  if (!isTagValid()) {
+    throw InvalidIdentityError{};
+  }
+}
+
+void CryptoStorageImpl::Recreate(const std::string& pin) {
+  boost::ignore_unused(pin);
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName));
+  if (old_key) {
+    ncrypt::rsa::DeleteKey(old_key.Get());
+  }
+
+  std::optional<ncrypt::rsa::KeyUIPolicy> ui_policy;
+  if (!disable_key_protection_) {
+    ui_policy = ncrypt::rsa::KeyUIPolicy{
+      vereignKeyCreationTitle,
+      vereignKeyDescription,
+      vereignKeyFriendlyName
+    };
+  }
+
+  auto rsa_key = ncrypt::rsa::CreateKey(
+    provider.Get(),
+    keySizeBits,
+    std::string(VereignKeyName),
+    ui_policy
+  );
+
+  storage_.DeleteAll();
+
+  auto key = crypto::Rand(aesKeySizeBytes);
+
+  bytes::Buffer encrypted_key;
+  ncrypt::rsa::PublicKeyEncrypt(rsa_key.Get(), key.View(), encrypted_key);
+  storage_.PutBytes("__master_key", encrypted_key.View());
+
+  initKey(std::move(key));
+  updateTag();
+}
+
+}
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..48150ac4ff6068fbe79b3abffd257f508b89e907
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh
@@ -0,0 +1,27 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <vereign/kvstore/detail/base_crypto_storage.hh>
+
+namespace vereign::kvstore::detail {
+
+class CryptoStorageImpl : public BaseCryptoStorageImpl {
+public:
+  CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection);
+
+  // disable copying
+  CryptoStorageImpl(const CryptoStorageImpl&) = delete;
+  auto operator=(const CryptoStorageImpl&) -> CryptoStorageImpl& = delete;
+
+  void Recreate(const std::string& pin);
+  void Open(const std::string& pin);
+
+private:
+  bool disable_key_protection_ = false;
+};
+
+} // namespace vereign::kvstore::detail
+
+
+#endif // __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..c119c510273cce58d67071e56da2ad16f43983da
--- /dev/null
+++ b/cpp/src/vereign/kvstore/errors.hh
@@ -0,0 +1,58 @@
+#ifndef __VEREIGN_KVSTORE_ERRORS_HH
+#define __VEREIGN_KVSTORE_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::kvstore {
+
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+class ValueNotFoundError : public Error {
+public:
+  ValueNotFoundError()
+    : Error{"value not found"}
+  {
+  }
+};
+
+class StorageNotInitializedError : public Error {
+public:
+  StorageNotInitializedError(const std::string& reason)
+    : Error{"storage is not initialized: " + reason}
+  {
+  }
+};
+
+class LockError : public Error {
+public:
+  LockError()
+    : Error{"cannot acquire storage lock"}
+  {
+  }
+};
+
+class InvalidPinCodeError : public Error {
+public:
+  InvalidPinCodeError()
+    : Error{"invalid pin code"}
+  {
+  }
+};
+
+class InvalidIdentityError : public Error {
+public:
+  InvalidIdentityError()
+    : Error{"invalid identity"}
+  {
+  }
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_ERRORS_HH
diff --git a/cpp/src/vereign/kvstore/lock.cc b/cpp/src/vereign/kvstore/lock.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5a0863e967ea6de6243652a7bbb1752c8a286480
--- /dev/null
+++ b/cpp/src/vereign/kvstore/lock.cc
@@ -0,0 +1,39 @@
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/kvstore/errors.hh>
+
+#include <thread>
+#include <sqlite3.h>
+
+namespace vereign::kvstore {
+
+Lock::Lock(Storage& storage)
+  : storage_{storage}
+{
+  storage.Lock();
+}
+
+Lock::Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval)
+  : storage_{storage}
+{
+  for (int i = 0; i < retry_count; i++) {
+    try {
+      storage.Lock();
+
+      return;
+    } catch (const LockError&) {
+      std::this_thread::sleep_for(sleep_interval);
+
+      continue;
+    }
+  }
+
+  throw LockError{};
+}
+
+Lock::~Lock() noexcept {
+  storage_.Unlock();
+}
+
+
+} // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/lock.hh b/cpp/src/vereign/kvstore/lock.hh
new file mode 100644
index 0000000000000000000000000000000000000000..1989b1dd0376353417224edfd29c249c1fae4108
--- /dev/null
+++ b/cpp/src/vereign/kvstore/lock.hh
@@ -0,0 +1,53 @@
+#ifndef __VEREIGN_KVSTORE_LOCK_HH
+#define __VEREIGN_KVSTORE_LOCK_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <chrono>
+
+namespace vereign::kvstore {
+
+/**
+ * Lock guard used for lock/unlock Storage within a scope.
+ *
+ * When the Lock is constructed it locks the Storage, and when it is destroyed it unlock it.
+ */
+class Lock {
+public:
+  /**
+   * Creates a Lock and locks the Storage.
+   *
+   * @param storage The storage to lock.
+   * @throws LockError when the lock is held by another process.
+   */
+  explicit Lock(Storage& storage);
+
+  /**
+   * Creates a Lock and locks the Storage.
+   *
+   * If the lock is not possible it retries `retry_count` and sleeps between retries `sleep_interval`.
+   *
+   * @param storage The storage to lock.
+   * @param retry_count How many times to retry if the lock is held by another process.
+   * @param sleep_interval How many time to sleep between retries.
+   *
+   * @throws LockError If the lock could not be held after `retry_count` retries.
+   */
+  Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval);
+
+  /**
+   * Unlocks the storage.
+   */
+  ~Lock() noexcept;
+
+  // copying is disabled.
+  Lock(const Lock&) = delete;
+  auto operator=(const Lock&) -> Lock&  = delete;
+
+private:
+  Storage& storage_;
+};
+
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_LOCK_HH
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4789346fef43cde99a8701c620d2d93eea7f0fb7
--- /dev/null
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -0,0 +1,133 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+
+#include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/encoding/binary.hh>
+#include <boost/optional.hpp>
+#include <vereign/core/lock_guard.hh>
+#include <vereign/sqlite/errors.hh>
+#include <sqlite3.h>
+
+#include <array>
+
+namespace {
+constexpr int createTableRetryCount = 10;
+constexpr auto createTableRetrySleep = std::chrono::milliseconds{1000};
+}
+
+namespace vereign::kvstore {
+
+SqliteStorage::SqliteStorage(const std::string& db_path)
+  : db_{db_path}
+{
+  kvstore::Lock l{*this, createTableRetryCount, createTableRetrySleep};
+
+  db_.Execute(R"(
+CREATE TABLE IF NOT EXISTS storage (
+  key TEXT PRIMARY KEY NOT NULL,
+  value BLOB
+);
+  )");
+}
+
+SqliteStorage::~SqliteStorage() {
+  if (lock_count_ != 0) {
+    db_.Commit();
+  }
+}
+
+void SqliteStorage::Lock() {
+  if (lock_count_ != 0) {
+    lock_count_++;
+
+    return;
+  }
+
+  try {
+    db_.BeginExplicitTransaction();
+    lock_count_++;
+  } catch (const sqlite::Error& err) {
+    if (err.code() == SQLITE_BUSY) {
+      throw LockError{};
+    }
+
+    throw;
+  }
+}
+
+void SqliteStorage::Unlock() {
+  if (lock_count_ == 0) {
+    throw Error{"unexpected call Unlock with non existent lock"};
+  }
+
+  lock_count_--;
+  if (lock_count_ == 0) {
+    db_.Commit();
+  }
+}
+
+
+void SqliteStorage::DeleteAll() {
+  kvstore::Lock l{*this};
+
+  db_.Execute("DELETE FROM storage;");
+}
+
+void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
+  kvstore::Lock l{*this};
+
+  auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
+  stmt.BindText(1, key);
+  stmt.BindBlob(2, value);
+  stmt.Step();
+}
+
+void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
+  kvstore::Lock l{*this};
+
+  auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
+  stmt.BindText(1, key);
+  auto end = stmt.Step();
+
+  if (end) {
+    throw ValueNotFoundError{};
+  }
+
+  value.Write(stmt.GetColumnBlob(0));
+}
+
+void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
+  kvstore::Lock l{*this};
+
+  std::array<uint8_t, 8> encoded;
+  encoding::binary::EncodeUint64(encoded.data(), value);
+
+  auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
+  stmt.BindText(1, key);
+  stmt.BindBlob(2, bytes::View(encoded.data(), 8));
+  stmt.Step();
+}
+
+auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
+  kvstore::Lock l{*this};
+
+  auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
+  stmt.BindText(1, key);
+  auto end = stmt.Step();
+
+  if (end) {
+    throw ValueNotFoundError{};
+  }
+
+  auto buf = stmt.GetColumnBlob(0);
+  if (buf.Size() != 8) {
+    throw Error("cannot decode bytes size");
+  }
+
+  int64_t result = encoding::binary::DecodeUint64(buf);
+
+  return result;
+}
+
+} // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6f7d13992cb82dd6c1507e1f66183da9ca4a5012
--- /dev/null
+++ b/cpp/src/vereign/kvstore/sqlite_storage.hh
@@ -0,0 +1,108 @@
+#ifndef __VEREIGN_KVSTORE_SQLITE_STORAGE_HH
+#define __VEREIGN_KVSTORE_SQLITE_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <vereign/sqlite/connection.hh>
+
+namespace vereign::kvstore {
+
+/**
+ * Sqlite implementation of the kvstore::Storage interface.
+ */
+class SqliteStorage : public Storage {
+public:
+  /**
+   * Creates SqliteStorage instance.
+   *
+   * @param db_path Full path to the sqlite database file.
+   */
+  SqliteStorage(const std::string& db_path);
+
+  /**
+   * Closes the connection with the database.
+   */
+  ~SqliteStorage() override;
+
+  // disable copying
+  SqliteStorage(const SqliteStorage&) = delete;
+  auto operator=(const SqliteStorage&) -> SqliteStorage& = delete;
+
+  /**
+   * Locks the storage.
+   *
+   * The lock must be recursive, meaning that may be called multiple times.
+   * Storage::Unlock must be called the same number of times the Storage::Lock was called.
+   *
+   * Use kvstore::Lock for lock guard and lock with retrials.
+   *
+   * @throws LockError when the lock is held by another process.
+   */
+  void Lock() override;
+
+  /**
+   * Locks the storage.
+   *
+   * The lock must be recursive, meaning that may be called multiple times.
+   * Storage::Unlock must be called the same number of times the Storage::Lock was called.
+   *
+   * Use kvstore::Lock for lock guard and lock with retrials.
+   */
+  void Unlock() override;
+
+  /**
+   * Deletes all the values in the storage.
+   *
+   * @throws sqlite::Error on failure.
+   */
+  void DeleteAll() override;
+
+  /**
+   * Store bytes value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The bytes that will be stored.
+   *
+   * @throws sqlite::Error on failure.
+   */
+  void PutBytes(const std::string& key, bytes::View value) override;
+
+  /**
+   * Retrieve bytes value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws ValueNotFoundError when the key does not exist.
+   * @throws sqlite::Error on failure.
+   */
+  void GetBytes(const std::string& key, bytes::Buffer& value) override;
+
+  /**
+   * Store int64_t value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The value that will be stored.
+   *
+   * @throws sqlite::Error on failure.
+   */
+  void PutInt64(const std::string& key, int64_t value) override;
+
+  /**
+   * Retrieve int64_t value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws ValueNotFoundError when the key does not exist.
+   * @throws sqlite::Error on failure.
+   */
+  auto GetInt64(const std::string& key) -> int64_t override;
+
+private:
+  sqlite::Connection db_;
+  int lock_count_ = 0;
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_SQLITE_STORAGE_HH
diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2e8c55344f0c8481e924fb0c4673e263faa05585
--- /dev/null
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -0,0 +1,87 @@
+#ifndef __VEREIGN_KVSTORE_STORAGE_HH
+#define __VEREIGN_KVSTORE_STORAGE_HH
+
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::kvstore {
+
+// The vereign RSA master key name.
+constexpr const auto VereignKeyName = std::string_view{"vereign_key"};
+
+/**
+ * Key/value storage interface.
+ */
+class Storage {
+public:
+  /**
+   * Locks the storage.
+   *
+   * The lock must be recursive, meaning that may be called multiple times.
+   * Storage::Unlock must be called the same number of times the Storage::Lock was called.
+   *
+   * Use kvstore::Lock for lock guard and lock with retrials.
+   *
+   * @throws LockError when the lock is held by another process.
+   */
+  virtual void Lock() = 0;
+
+  /**
+   * Locks the storage.
+   *
+   * The lock must be recursive, meaning that may be called multiple times.
+   * Storage::Unlock must be called the same number of times the Storage::Lock was called.
+   *
+   * Use kvstore::Lock for lock guard and lock with retrials.
+   */
+  virtual void Unlock() = 0;
+
+  /**
+   * Deletes all the values in the storage.
+   */
+  virtual void DeleteAll() = 0;
+
+  /**
+   * Store bytes value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The bytes that will be stored.
+   */
+  virtual void PutBytes(const std::string& key, bytes::View value) = 0;
+
+  /**
+   * Retrieve bytes value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws ValueNotFoundError when the key does not exist.
+   */
+  virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
+
+  /**
+   * Store int64_t value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The value that will be stored.
+   */
+  virtual void PutInt64(const std::string& key, int64_t value) = 0;
+
+  /**
+   * Retrieve int64_t value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws ValueNotFoundError when the key does not exist.
+   */
+  virtual auto GetInt64(const std::string& key) -> int64_t = 0;
+
+  /**
+   * Destroy and cleanup.
+   */
+  virtual ~Storage() = default;
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_STORAGE_HH
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..fe6920132b3e3cc1f1596c68af9671c3cfa2764f
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/errors.hh
@@ -0,0 +1,49 @@
+#ifndef __VEREIGN_NCRYPT_ERRORS_HH
+#define __VEREIGN_NCRYPT_ERRORS_HH
+
+#include <windows.h>
+#include <ncrypt.h>
+#include <stdexcept>
+
+namespace vereign::ncrypt {
+
+// windows returns this error when there is unexpected NULL input
+constexpr SECURITY_STATUS NTE_NULL_REFERENCE_POINTER = 0x800706f4;
+
+/**
+ * Returns a string representation of the SECURITY_STATUS errors.
+ *
+ * @param status The SECURITY_STATUS error.
+ * @returns a string representation of the SECURITY_STATUS error.
+ */
+auto SecurityStatusToString(SECURITY_STATUS status) -> std::string;
+
+/**
+ * The base error type for the namespace vereign::ncrypto.
+ */
+class Error : public std::runtime_error {
+public:
+  /**
+   * Creates Error with status and additional message.
+   *
+   * Example:
+   * @code
+   * auto err = Error{NTE_FAIL, "operation failed"};
+   * assert(err.what() == "operation failed: NTE_FAIL");
+   * assert(err.SecurityStatus() == NTE_FAIL);
+   * @endcode
+   */
+  Error(SECURITY_STATUS status, const std::string& msg);
+
+  /**
+   * Returns the SECURITY_STATUS associated with this error.
+   */
+  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..a9e351d4a3e7f15f513167b05d154716a5d2d188
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/rsa.cc
@@ -0,0 +1,182 @@
+#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,
+  std::optional<KeyUIPolicy> ui_policy
+) -> 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"};
+  }
+
+  if (ui_policy) {
+    auto creation_title = vereign::string::widen(ui_policy->CreationTitle);
+    auto description = vereign::string::widen(ui_policy->Description);
+    auto friendly_name = vereign::string::widen(ui_policy->FriendlyName);
+
+    NCRYPT_UI_POLICY ui_policy_prop{};
+    ui_policy_prop.dwVersion = 1;
+    ui_policy_prop.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG;
+    ui_policy_prop.pszCreationTitle = creation_title.data();
+    ui_policy_prop.pszDescription = description.data();
+    ui_policy_prop.pszFriendlyName = friendly_name.data();
+
+    status = NCryptSetProperty(
+      key.Get(),
+      NCRYPT_UI_POLICY_PROPERTY,
+      (PBYTE)&ui_policy_prop,
+      sizeof(ui_policy_prop),
+      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..06d3f05d2cb1892b6a43956c6e6940f6d6b3d0a0
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/rsa.hh
@@ -0,0 +1,150 @@
+#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>
+#include <optional>
+
+namespace vereign::ncrypt::rsa {
+
+/**
+ * KeyUIPolicy is used to specify some UI captions shown to the user in a dialog when a key is
+ * created or opened for the first time in a process.
+ */
+struct KeyUIPolicy {
+  std::string_view CreationTitle;
+  std::string_view Description;
+  std::string_view FriendlyName;
+};
+
+/**
+ * Opens the default ncrypt storage provider.
+ *
+ * @returns the storage provider handle.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+auto OpenStorageProvider() -> UniquePtr;
+
+/**
+ * Loads a key from a ncrypt storage provider.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key");
+ * assert(key.Get() != 0);
+ * @endcode
+ *
+ * @param provider The ncrypt storage provider.
+ * @param key_name The name of the key that will be loaded.
+ * @returns the loaded key if the key exists, or null ncrypt::UniquePtr if the key does not exists.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr;
+
+/**
+ * Creates persistent RSA key into a given storage provider.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * // create key without UI policy
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ * assert(key.Get() != 0);
+ * @endcode
+ *
+ * @param provider The ncrypt storage provider.
+ * @param bits The length of the key in bits.
+ * @param key_name The new key name.
+ * @param ui_policy The UI policy. If the UI policy is not provided, the key will not have any
+ *    protection. If the UI policy is provided when the key is created and loaded the user will be
+ *    asked for his consent by showing a dialog window with text information specified in the UI policy.
+ *
+ * @returns the created key on success.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+auto CreateKey(
+  NCRYPT_PROV_HANDLE provider,
+  int bits,
+  const std::string& key_name,
+  std::optional<KeyUIPolicy> ui_policy
+) -> UniquePtr;
+
+/**
+ * Deletes a key.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key");
+ * if (key) {
+ *   ncryp::rsa::DeleteKey(key.Get());
+ * }
+ * @endcode
+ *
+ * @param key The key to delete.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+void DeleteKey(NCRYPT_KEY_HANDLE key);
+
+/**
+ * Encrypts given bytes with RSA public key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ *
+ * bytes::Buffer encrypted;
+ * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+ * assert(decrypted.View() == encrypted.View());
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be encrypted.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted);
+
+/**
+ * Decrypts given bytes with RSA private key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ *
+ * bytes::Buffer encrypted;
+ * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+ * assert(decrypted.View() == encrypted.View());
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be decrypted.
+ * @param decrypted The result of the decryption.
+ *
+ * @throws ncrypt::Error on failure.
+ */
+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..a177839f4ec6c2a12780a5e204db45e92965ecd1
--- /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 handle) noexcept
+  : ptr_{handle}
+{
+}
+
+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..ae4475300c7399f136810082988e4c19d3495666
--- /dev/null
+++ b/cpp/src/vereign/ncrypt/unique_ptr.hh
@@ -0,0 +1,98 @@
+#ifndef __VEREIGN_NCRYPT_UNIQUE_PTR_HH
+#define __VEREIGN_NCRYPT_UNIQUE_PTR_HH
+
+#include <windows.h>
+
+namespace vereign::ncrypt {
+
+/**
+ * Holds ownership of ncrypt handles like NCRYPT_PROV_HANDLE, NCRYPT_PROV_HANDLE.
+ *
+ * The UniquePtr should be used any time a ncrypt API returns a handle in order to ensure exception
+ * safety and resource cleanup.
+ *
+ * The owned handler is typically cleaned up in the UniquePtr::~UniquePtr() by using
+ * NCryptFreeObject function.
+ */
+class UniquePtr {
+public:
+  /**
+   * Constructs a default empty object.
+   */
+  UniquePtr() noexcept;
+
+  /**
+   * Constructs a UniquePtr that becomes an owner of the passed handle.
+   *
+   * @param handle The handle that becomes owned by the crated UniquePtr.
+   */
+  explicit UniquePtr(ULONG_PTR handle) noexcept;
+
+  /**
+   * Move constructor.
+   */
+  UniquePtr(UniquePtr&& other) noexcept;
+
+  /**
+   * Move assignment operator.
+   */
+  auto operator=(UniquePtr&& other) noexcept -> UniquePtr&;
+
+  // Disable copying
+  UniquePtr(const UniquePtr&) = delete;
+  auto operator=(const UniquePtr&) -> UniquePtr&  = delete;
+
+  /**
+   * Upon destruction the owned handle if any is freed with NCryptFreeObject function.
+   */
+  ~UniquePtr() noexcept;
+
+  /**
+   * Retrieve the owned handle.
+   */
+  auto Get() const noexcept -> ULONG_PTR;
+
+  /**
+   * Retrieve a pointer to the owned handle.
+   *
+   * Example:
+   * @code
+   * UniquePtr key{};
+   * NCryptOpenKey(provider, key.Ref(), L"test_key", 0, 0);
+   * assert(bool(key) == true);
+   * @endcode
+   *
+   * This makes it possible for a ncrypt API to inject its output handle directly into the
+   * UniquePtr object.
+   */
+  auto Ref() noexcept -> ULONG_PTR*;
+
+  /**
+   * Free the owned handler.
+   *
+   * If the owned handler is not null, it frees the handle by calling NCryptFreeObject, and sets
+   * the owned handler to null.
+   */
+  void Reset();
+
+  /**
+   * Releases the ownership of the owned handler.
+   *
+   * @returns the owned handler, before releasing the ownership.
+   */
+  auto Release() -> ULONG_PTR;
+
+  /**
+   * Checks if *this owns a non-null handler.
+   *
+   * @returns true if *this owns a handler, false otherwise.
+   */
+  operator bool() const noexcept;
+
+private:
+  ULONG_PTR ptr_;
+};
+
+} // vereign::ncrypt
+
+#endif // __VEREIGN_NCRYPT_UNIQUE_PTR_HH
diff --git a/cpp/src/vereign/restapi/client.cc b/cpp/src/vereign/restapi/client.cc
index 1dd1b8cc9e874ce972ff6725af6ce3768759a4c5..e42834bb8dc48363d3b2a67bb38ca520b8c1ef2d 100644
--- a/cpp/src/vereign/restapi/client.cc
+++ b/cpp/src/vereign/restapi/client.cc
@@ -1,5 +1,3 @@
-#include "vereign/restapi/detail/post_task.hh"
-#include <chrono>
 #include <vereign/restapi/client.hh>
 
 #include <vereign/restapi/detail/http_reader.hh>
@@ -10,6 +8,10 @@
 #include <boost/beast/version.hpp>
 #include <boost/asio/dispatch.hpp>
 
+namespace {
+  constexpr std::string_view httpUserAgent = "Vereign Client Library";
+}
+
 namespace vereign {
 namespace restapi {
 
@@ -21,7 +23,8 @@ Client::Client(
   const std::string& host,
   const std::string& port
 )
-  : executor_{asio::make_strand(ioc)},
+  : user_agent_{httpUserAgent},
+    executor_{asio::make_strand(ioc)},
     ssl_ctx_{ssl_ctx},
     resolver_{ioc},
     reader_{nullptr},
@@ -42,7 +45,8 @@ Client::Client(
   const std::string& port,
   std::chrono::nanoseconds expiry_time
 )
-  : executor_{asio::make_strand(ioc)},
+  : user_agent_{httpUserAgent},
+    executor_{asio::make_strand(ioc)},
     ssl_ctx_{ssl_ctx},
     resolver_{ioc},
     reader_{nullptr},
@@ -60,6 +64,10 @@ Client::~Client() noexcept {
   Close();
 }
 
+auto Client::UserAgent() const -> const std::string& {
+  return user_agent_;
+}
+
 void Client::Close() {
   asio::post(
     executor_,
@@ -213,7 +221,7 @@ void Client::doPost() {
   req_->version(11);
   req_->target(task->Path());
   req_->set(beast::http::field::host, host_);
-  req_->set(beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
+  req_->set(beast::http::field::user_agent, user_agent_);
   req_->set(beast::http::field::content_type, "application/json");
 
   auto err = task->EncodeRequest(*req_);
diff --git a/cpp/src/vereign/restapi/client.hh b/cpp/src/vereign/restapi/client.hh
index 1bb1821f37dacbffdac972e6a5d3e75fe3415be0..6fddf6037d58c5ce4f8c562ba7b00b273510ec75 100644
--- a/cpp/src/vereign/restapi/client.hh
+++ b/cpp/src/vereign/restapi/client.hh
@@ -1,8 +1,6 @@
 #ifndef __VEREIGN_RESTAPI_CLIENT_HH
 #define __VEREIGN_RESTAPI_CLIENT_HH
 
-#include <chrono>
-#include <type_traits>
 #include <vereign/restapi/detail/post_task.hh>
 #include <vereign/restapi/http_header.hh>
 
@@ -16,6 +14,8 @@
 #include <string>
 #include <future>
 #include <deque>
+#include <chrono>
+#include <type_traits>
 
 namespace vereign {
 namespace restapi {
@@ -102,6 +102,15 @@ public:
   Client(const Client&) = delete;
   Client& operator=(const Client&) = delete;
 
+  /**
+   * Retrieve client http user agent string.
+   *
+   * This is what the client puts in the requests as `User-Agent` header.
+   *
+   * @returns the client http user agent string.
+   */
+  auto UserAgent() const -> const std::string&;
+
   /**
    * Close closes the connection.
    *
@@ -310,6 +319,9 @@ private:
   void cancelAllTasks(const detail::PostError& err);
 
 private:
+  // http user agent.
+  std::string user_agent_;
+
   // a strand that calls all completion handlers in single thread.
   asio::executor executor_;
 
diff --git a/cpp/src/vereign/restapi/client_session.hh b/cpp/src/vereign/restapi/client_session.hh
index 28e848a7e270c52f09e083132a862c51e08caded..a94fdce3cb020f7ea9c1a11eec61044c4ba7c83d 100644
--- a/cpp/src/vereign/restapi/client_session.hh
+++ b/cpp/src/vereign/restapi/client_session.hh
@@ -3,15 +3,12 @@
 
 #include <vereign/restapi/client.hh>
 #include <vereign/restapi/http_header.hh>
-
+#include <vereign/client_library/common_types.pb.h>
 #include <vereign/client_library/identity_types.pb.h>
-#include <boost/none.hpp>
 
-#include <vector>
-#include <future>
+#include <boost/none.hpp>
 
-namespace vereign {
-namespace restapi {
+namespace vereign::restapi {
 
 namespace detail {
 struct AuthErrorType {
@@ -29,9 +26,9 @@ struct AuthErrorType {
 
 using AuthError = boost::optional<AuthErrorType>;
 
-inline AuthError MakeAuthError(
+inline auto MakeAuthError(
   const client_library::LoginFormPreviousAddedDeviceResponse& resp
-) {
+) -> AuthError {
   if (resp.code() == "200") {
     return boost::none;
   }
@@ -43,18 +40,24 @@ inline AuthError MakeAuthError(
   };
 }
 
-inline AuthError NoAuthError() {
+inline auto NoAuthError() -> AuthError {
   return boost::none;
 }
 
+struct Session {
+  std::string Token;
+  std::string UUID;
+};
+
 } // namespace detail
 
 /**
  * ClientSession is a Client decorator, that maintains authenticated session.
+ *
+ * All public methods are thread safe.
  */
 class ClientSession {
 public:
-  // FIXME: the key should come from a storage
   /**
    * Constructs ClientSession instance with default API base path.
    *
@@ -63,13 +66,12 @@ public:
    * @param client Restapi client.
    * @returns ClientSession instance.
    */
-  ClientSession(Client& client, const std::string& pub_key)
+  ClientSession(Client& client)
     : client_{client},
-      pub_key_{pub_key},
-      base_path_{"/api"}
+      base_path_{"/api"},
+      pub_key_{""}
   {}
 
-  // FIXME: the key should come from a storage
   /**
    * Constructs ClientSession instance.
    *
@@ -77,18 +79,14 @@ public:
    * @param base_path API base path, for example `/api`
    * @returns ClientSession instance.
    */
-  ClientSession(
-    Client& client,
-    const std::string& base_path,
-    const std::string& pub_key
-  ) : client_{client},
-      pub_key_{pub_key},
-      base_path_{base_path}
+  ClientSession(Client& client, std::string base_path) : client_{client},
+      base_path_{std::move(base_path)},
+      pub_key_{""}
   {}
 
   // Disable copying.
   ClientSession(const ClientSession&) = delete;
-  ClientSession& operator=(const ClientSession&) = delete;
+  auto operator=(const ClientSession&) -> ClientSession& = delete;
 
   /**
    * Closes the connection.
@@ -97,6 +95,37 @@ public:
     Close();
   }
 
+  /**
+   * Set public key used for authentication.
+   *
+   * This effectively resets the current session (if any).
+   *
+   * @param key The public key that will be used for authentication.
+   */
+  void SetPubKey(std::string key) {
+    std::lock_guard<std::mutex> l{mu_};
+
+    pub_key_ = std::move(key);
+    session_token_ = "";
+    uuid_ = "";
+  }
+
+  /**
+   * Retrieve the current public key used for authentication.
+   */
+  auto GetPubKey() -> std::string {
+    std::lock_guard<std::mutex> l{mu_};
+
+    return pub_key_;
+  }
+
+  /**
+   * Retrieve the underlying http client.
+   */
+  auto GetClient() -> Client& {
+    return client_;
+  }
+
   /**
    * Close closes the connection.
    *
@@ -196,7 +225,72 @@ public:
     );
   }
 
+  /**
+   * Initiate authentication.
+   *
+   * This method is blocking.
+   *
+   * @param pub_key The public key to authenticate with.
+   * @param resp Authentication response.
+   */
+  auto Authenticate(const std::string& pub_key, client_library::EmptyResponse* resp) {
+    std::promise<void> promise;
+    auto future = promise.get_future();
+
+    {
+      std::lock_guard<std::mutex> l{mu_};
+      pub_key_ = pub_key;
+      session_token_ = "";
+      uuid_ = "";
+    }
+
+    withAuthentication(
+      [resp, promise = std::move(promise)](const detail::AuthError& err) mutable {
+        if (err) {
+          err->AssignTo(*resp);
+        } else {
+          resp->set_code("200");
+          resp->set_status("OK");
+        }
+
+        promise.set_value();
+      }
+    );
+
+    future.wait();
+  }
+
+  /**
+   * Retrieve the restful-api base path.
+   *
+   * @returns the base path used for http requests, for example: `api/`.
+   */
+  auto BasePath() const -> const std::string& {
+    return base_path_;
+  }
+
 private:
+  auto hasSession() -> bool {
+    std::lock_guard<std::mutex> l{mu_};
+
+    return session_token_.size() != 0;
+  }
+
+  void updateSession(const std::string& token, const std::string& uuid) {
+    std::lock_guard<std::mutex> l{mu_};
+
+    session_token_ = token;
+    uuid_ = uuid;
+  }
+
+  auto getSession() -> detail::Session {
+    std::lock_guard<std::mutex> l{mu_};
+
+    return detail::Session{
+      session_token_,
+      uuid_
+    };
+  }
 
   template <class RequestPtr, class ResponsePtr, class CompletionFunc>
   void authPostAsync(
@@ -216,15 +310,16 @@ private:
     }
 
     using namespace std::placeholders;
+    auto session = getSession();
 
     client_.PostAsync(
       path,
       std::move(req),
       std::move(resp),
       std::vector<vereign::restapi::HttpHeader>{
-        {"publicKey", pub_key_},
-        {"token", session_token_},
-        {"uuid", uuid_}
+        {"publicKey", GetPubKey()},
+        {"token", session.Token},
+        {"uuid", session.UUID}
       },
       std::bind(
         &ClientSession::handlePostResult<RequestPtr, ResponsePtr, CompletionFunc>,
@@ -254,15 +349,16 @@ private:
     }
 
     using namespace std::placeholders;
+    auto session = getSession();
 
     client_.PostAsync(
       path,
       std::move(req),
       std::move(resp),
       std::vector<vereign::restapi::HttpHeader>{
-        {"publicKey", pub_key_},
-        {"token", session_token_},
-        {"uuid", uuid_}
+        {"publicKey", GetPubKey()},
+        {"token", session.Token},
+        {"uuid", session.UUID}
       },
       std::move(cf)
     );
@@ -300,7 +396,7 @@ private:
     asio::post(
       client_.GetExecutor(),
       [this, cf = std::move(cf)]() mutable {
-        if (session_token_.size() != 0) {
+        if (hasSession()) {
           cf(detail::NoAuthError());
           return;
         }
@@ -314,7 +410,7 @@ private:
           std::make_unique<client_library::EmptyRequest>(),
           std::make_unique<client_library::LoginFormPreviousAddedDeviceResponse>(),
           std::vector<vereign::restapi::HttpHeader>{
-            {"publicKey", pub_key_}
+            {"publicKey", GetPubKey()}
           },
           [this, cf = std::move(cf)] (ResultType&& result) mutable {
             if (result.Response->code() != "200") {
@@ -335,8 +431,7 @@ private:
               return;
             }
 
-            session_token_ = result.Response->data().session();
-            uuid_ = result.Response->data().uuid();
+            updateSession(result.Response->data().session(), result.Response->data().uuid());
 
             cf(detail::MakeAuthError(*result.Response));
           }
@@ -346,23 +441,26 @@ private:
   }
 
 private:
-  // http client.
+  // http client
   Client& client_;
 
-  // public key used for creating the authenticated sessions.
-  std::string pub_key_;
-
   // http base path of the api calls, for example `/api`
   std::string base_path_;
 
-  // session uuid.
+  // protects the data that follows
+  std::mutex mu_;
+
+  // session uuid
   std::string uuid_;
 
-  // session token.
+  // session token
   std::string session_token_;
+
+
+  // public key used for creating the authenticated sessions
+  std::string pub_key_;
 };
 
-} // namespace restapi
 } // namespace vereign
 
 #endif // __VEREIGN_RESTAPI_CLIENT_SESSION_HH
diff --git a/cpp/src/vereign/service/identity_service.cc b/cpp/src/vereign/service/identity_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fe39c2be506f67faa36dff971006e9780f35a641
--- /dev/null
+++ b/cpp/src/vereign/service/identity_service.cc
@@ -0,0 +1,66 @@
+#include <vereign/service/identity_service.hh>
+
+#include <vereign/service/gen/identity_service.hh>
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/restapi/http_header.hh>
+#include <vereign/restapi/client_session.hh>
+#include <vereign/encoding/base64.hh>
+
+#include <memory>
+
+namespace {
+  const std::string loginWithPreviouslyAddedDevicePath = "loginWithPreviouslyAddedDevice";
+  const std::string loginWithNewDevicePath = "loginWithNewDevice";
+}
+
+namespace vereign::service {
+
+IdentityService::IdentityService(
+  restapi::ClientSession& client_session,
+  identity::Provider& identity_provider
+)
+  : gen::IdentityService{client_session},
+    client_session_{client_session},
+    identity_provider_{identity_provider}
+{}
+
+void IdentityService::LoginWithExistingPubKey(
+  const client_library::LoginWithExistingPubKeyForm* req,
+  client_library::EmptyResponse* resp
+) {
+  client_session_.Authenticate(req->pubkey(), resp);
+}
+
+void IdentityService::LoginWithNewDevice(
+  const client_library::LoginFormNewDevice* req,
+  client_library::LoginFormNewDeviceResponse* resp
+) {
+  auto public_key = identity_provider_.RecreateIdentity(req->pin());
+
+  auto result = client_session_.GetClient().Post(
+    client_session_.BasePath() + gen::IdentityService::ServicePath + loginWithNewDevicePath,
+    std::make_unique<client_library::EmptyRequest>(),
+    resp,
+    std::vector<vereign::restapi::HttpHeader>{
+      {"publicKey", public_key},
+      {"deviceHash", identity_provider_.GetDeviceHash()}
+    }
+  );
+
+  result.wait();
+
+  if (resp->code() == "200") {
+    client_session_.SetPubKey(public_key);
+  }
+}
+
+
+void IdentityService::LoginWithPreviouslyAddedDevice(
+  const client_library::LoginFormPreviousAddedDevice* req,
+  client_library::EmptyResponse* resp
+) {
+  auto public_key = identity_provider_.LoadIdentity(req->pin());
+  client_session_.Authenticate(public_key, resp);
+}
+
+} // namespace vereign
diff --git a/cpp/src/vereign/service/identity_service.hh b/cpp/src/vereign/service/identity_service.hh
new file mode 100644
index 0000000000000000000000000000000000000000..11de3e0f5318535d3d7307d126aa19d590141ea6
--- /dev/null
+++ b/cpp/src/vereign/service/identity_service.hh
@@ -0,0 +1,107 @@
+#ifndef __VEREIGN_SERVICE_IDENITY_SERVICE_HH
+#define __VEREIGN_SERVICE_IDENITY_SERVICE_HH
+
+#include <vereign/identity/provider.hh>
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/identity_types.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/restapi/post_result.hh>
+#include <vereign/service/gen/identity_service.hh>
+
+#include <future>
+
+namespace vereign {
+
+namespace restapi {
+  class ClientSession;
+}
+
+namespace service {
+
+template <class Request, class Response>
+using Result = restapi::PostResult<Request, Response>;
+
+class IdentityService : public gen::IdentityService {
+public:
+  /**
+   * Creates IdentityService instance.
+   *
+   * @param client_session HTTP client used for communicating with the Vereign Restful API.
+   * @param identity_provider Local identity provider (manager).
+   */
+  IdentityService(
+    restapi::ClientSession& client_session,
+    identity::Provider& identity_provider
+  );
+
+  // disable copying
+  IdentityService(const IdentityService&) = delete;
+  auto operator=(const IdentityService&) -> IdentityService& = delete;
+
+  /**
+   * Login with existing identity public key.
+   *
+   * This API is for test purposes only. It is not exposed under the gRPC API, and thus is not
+   * accessible by the integrators.
+   *
+   * **WARN: do not use this in production code**
+   */
+  void LoginWithExistingPubKey(
+    const client_library::LoginWithExistingPubKeyForm* req,
+    client_library::EmptyResponse* resp
+  );
+
+  /**
+   * Registers a new device.
+   *
+   * req.pin is required only under Linux.
+   *
+   * Under windows the system cypto storage is used.
+   * When the device is registered a master key is created and the user will be asked for his
+   * consent by showing a dialog window.
+   *
+   * @param req Login request.
+   * @param resp Operation response.
+   *
+   * @throws kvstore::InvalidPinCodeError Only under Linux. Thrown when the provided pin is invalid,
+   *    currently that is when the pin is empty.
+   */
+  void LoginWithNewDevice(
+    const client_library::LoginFormNewDevice* req,
+    client_library::LoginFormNewDeviceResponse* resp
+  );
+
+  /**
+   * Login with already registered device.
+   *
+   * req.pin is required only under Linux.
+   *
+   * Under windows the system cypto storage is used.
+   * When the device is registered a master key is created and the user will be asked for his
+   * consent by showing a dialog window.
+   *
+   * @param req Login request.
+   * @param resp Operation response.
+   *
+   * @throws kvstore::StorageNotInitializedError when the crypto storage is empty, meaning that
+   *    the device is not registered.
+   * @throws kvstore::InvalidPinCodeError under Linux, when the provided pin is invalid, meaning
+   *    that the pin does not match the pin used during the registration.
+   * @throws kvstore::InvalidIdentityError under windows, when for some reason the RSA master key
+   *    has been changed.
+   */
+  void LoginWithPreviouslyAddedDevice(
+    const client_library::LoginFormPreviousAddedDevice* req,
+    client_library::EmptyResponse* resp
+  );
+
+private:
+  restapi::ClientSession& client_session_;
+  identity::Provider& identity_provider_;
+};
+
+} // namespace service
+} // namespace vereign
+
+
+#endif // __VEREIGN_SERVICE_IDENITY_SERVICE_HH
diff --git a/cpp/src/vereign/service/passport_service.cc b/cpp/src/vereign/service/passport_service.cc
deleted file mode 100644
index e6cf88372cecd5c47c002b893fa035dfb5a8da8f..0000000000000000000000000000000000000000
--- a/cpp/src/vereign/service/passport_service.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "vereign/service/gen/passport_service.hh"
-#include <vereign/service/passport_service.hh>
-#include <vereign/restapi/client_session.hh>
-
-namespace {
-  const std::string listPassportsPath = "listPassports";
-}
-
-namespace vereign {
-namespace service {
-
-PassportService::PassportService(restapi::ClientSession& client_session)
-  : gen::PassportService{client_session},
-    client_session_{client_session}
-{}
-
-
-std::future<PassportService::ListPassportsResult>
-PassportService::ListPassportsManually(
-    const client_library::ListPassportsForm* req,
-    client_library::ListPassportsFormResponse* resp
-) {
-  return client_session_.Post(
-    gen::PassportService::ServicePath + listPassportsPath,
-    req,
-    resp
-  );
-}
-
-
-} // namespace service
-} // namespace vereign
diff --git a/cpp/src/vereign/service/passport_service.hh b/cpp/src/vereign/service/passport_service.hh
deleted file mode 100644
index f59b4b27290676e1c3ae9c5cd2b1a7714cce31b0..0000000000000000000000000000000000000000
--- a/cpp/src/vereign/service/passport_service.hh
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef __VEREIGN_SERVICE_PASSPORT_SERVICE_HH
-#define __VEREIGN_SERVICE_PASSPORT_SERVICE_HH
-
-#include <vereign/restapi/client_session.hh>
-#include <vereign/restapi/post_result.hh>
-#include <vereign/client_library/types.gen.pb.h>
-#include <vereign/service/gen/passport_service.hh>
-
-#include <future>
-
-namespace vereign {
-
-namespace restapi {
-  class ClientSession;
-}
-
-namespace service {
-
-template <class Request, class Response>
-using Result = restapi::PostResult<Request, Response>;
-
-class PassportService : public gen::PassportService {
-public:
-  explicit PassportService(restapi::ClientSession& client_session);
-
-  PassportService(const PassportService&) = delete;
-  PassportService& operator=(const PassportService&) = delete;
-
-  using ListPassportsResult = Result<
-    const client_library::ListPassportsForm*,
-    client_library::ListPassportsFormResponse*>;
-
-  std::future<ListPassportsResult> ListPassportsManually(
-    const client_library::ListPassportsForm* req,
-    client_library::ListPassportsFormResponse* resp
-  );
-
-private:
-  restapi::ClientSession& client_session_;
-};
-
-} // namespace service
-} // namespace vereign
-
-#endif // __VEREIGN_SERVICE_PASSPORT_SERVICE_HH
diff --git a/cpp/src/vereign/sqlite/connection.cc b/cpp/src/vereign/sqlite/connection.cc
new file mode 100644
index 0000000000000000000000000000000000000000..eefe6a7de2ff004c777f88deb81eb3cee64f9271
--- /dev/null
+++ b/cpp/src/vereign/sqlite/connection.cc
@@ -0,0 +1,75 @@
+#include <vereign/sqlite/connection.hh>
+
+#include <vereign/sqlite/errors.hh>
+#include <vereign/core/scope_guard.hh>
+
+#include <fmt/format.h>
+#include <sqlite3.h>
+#include <iostream>
+
+namespace vereign::sqlite {
+
+Connection::Connection(const std::string& path)
+  : db_{nullptr}
+{
+  auto rc = sqlite3_open(path.data(), &db_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+Connection::~Connection() noexcept {
+  sqlite3_close(db_);
+}
+
+void Connection::BeginExplicitTransaction() {
+  char* errMsg = nullptr;
+  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
+  auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
+  if (rc != SQLITE_OK) {
+    throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
+  }
+}
+
+void Connection::Commit() {
+  char* errMsg = nullptr;
+  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
+
+  auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
+  if (rc != SQLITE_OK) {
+    throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
+  }
+}
+
+void Connection::Rollback() {
+  char* errMsg = nullptr;
+  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
+
+  auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
+  if (rc != SQLITE_OK) {
+    throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
+  }
+}
+
+void Connection::Execute(const std::string& sql) {
+  char* errMsg = nullptr;
+  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
+
+  auto rc = sqlite3_exec(db_, sql.data(), nullptr, nullptr, &errMsg);
+  if (rc != SQLITE_OK) {
+    throw Error(rc, fmt::format("query execution failed, err: {}", errMsg));
+  }
+}
+
+auto Connection::Prepare(const std::string& sql) -> Statement {
+  sqlite3_stmt *stmt = nullptr;
+
+  auto rc = sqlite3_prepare_v2(db_, sql.data(), -1, &stmt, nullptr);
+  if (rc != SQLITE_OK) {
+    throw Error(rc, fmt::format("preparing statement failed, err: {}", sqlite3_errmsg(db_)));
+  }
+
+  return Statement{db_, stmt};
+}
+
+} // namespace vereign::sqlite
diff --git a/cpp/src/vereign/sqlite/connection.hh b/cpp/src/vereign/sqlite/connection.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d1134045dc4c2434ff20ad5b2841e58a51c5c253
--- /dev/null
+++ b/cpp/src/vereign/sqlite/connection.hh
@@ -0,0 +1,32 @@
+#ifndef __VEREIGN_SQLITE_CONNECTION_HH
+#define __VEREIGN_SQLITE_CONNECTION_HH
+
+#include <vereign/sqlite/statement.hh>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+namespace vereign::sqlite {
+
+class Connection {
+public:
+  Connection(const std::string& path);
+  ~Connection() noexcept;
+
+  Connection(const Connection&) = delete;
+  auto operator=(const Connection&) -> Connection& = delete;
+
+  void BeginExplicitTransaction();
+  void Commit();
+  void Rollback();
+
+  void Execute(const std::string& sql);
+  auto Prepare(const std::string& sql) -> Statement;
+
+private:
+  sqlite3* db_;
+};
+
+} // namespace vereign::sqlite
+
+#endif // __VEREIGN_SQLITE_CONNECTION_HH
diff --git a/cpp/src/vereign/sqlite/errors.hh b/cpp/src/vereign/sqlite/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..00622311b42921a41500d7d7b3cc1f5b0e54f9a2
--- /dev/null
+++ b/cpp/src/vereign/sqlite/errors.hh
@@ -0,0 +1,26 @@
+#ifndef __VEREIGN_SQLITE_ERROR_HH
+#define __VEREIGN_SQLITE_ERROR_HH
+
+#include <stdexcept>
+
+namespace vereign::sqlite {
+
+class Error : public std::runtime_error {
+public:
+  Error(int code, const std::string& msg)
+    : std::runtime_error(msg),
+      code_{code}
+  {
+  }
+
+  auto code() const noexcept -> int {
+    return code_;
+  }
+
+private:
+  int code_;
+};
+
+} // namespace vereign::sqlite
+
+#endif // __VEREIGN_SQLITE_CONNECTION_HH
diff --git a/cpp/src/vereign/sqlite/statement.cc b/cpp/src/vereign/sqlite/statement.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0a3d00795383e4498580c1a56265b1ce55d87918
--- /dev/null
+++ b/cpp/src/vereign/sqlite/statement.cc
@@ -0,0 +1,78 @@
+#include <vereign/sqlite/statement.hh>
+#include <vereign/sqlite/errors.hh>
+
+#include <fmt/format.h>
+#include <sqlite3.h>
+
+namespace vereign::sqlite {
+
+Statement::Statement(sqlite3* db, sqlite3_stmt* stmt)
+  : db_{db},
+    stmt_{stmt}
+{}
+
+void Statement::Finalize() {
+  sqlite3_finalize(stmt_);
+}
+
+Statement::~Statement() {
+  sqlite3_finalize(stmt_);
+}
+
+void Statement::BindBlob(int index, bytes::View blob) {
+  auto rc = sqlite3_bind_blob64(stmt_, index, blob.CharData(), blob.Size(), SQLITE_STATIC);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("bind blob parameter failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+void Statement::BindText(int index, const std::string& text) {
+  auto rc = sqlite3_bind_text(stmt_, index, text.data(), text.size(), SQLITE_STATIC);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("bind text parameter failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+auto Statement::Step() -> bool {
+  auto rc = sqlite3_step(stmt_);
+  switch (rc) {
+  case SQLITE_DONE:
+    return true;
+  case SQLITE_ROW:
+    return false;
+  default:
+    throw Error{rc, fmt::format("executing statement failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+auto Statement::GetColumnBlob(int index) -> bytes::View {
+  auto size = sqlite3_column_bytes(stmt_, index);
+  auto blob = sqlite3_column_blob(stmt_, index);
+
+  return bytes::View{blob, static_cast<size_t>(size)};
+}
+
+auto Statement::GetColumnText(int index) -> std::string_view {
+  std::size_t size = sqlite3_column_bytes(stmt_, index);
+  auto blob = sqlite3_column_text(stmt_, index);
+
+  return std::string_view{reinterpret_cast<const char*>(blob), size};
+}
+
+void Statement::ResetAndClearBindings() {
+  auto rc = sqlite3_clear_bindings(stmt_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("statement reset bindings failed, err: {}", sqlite3_errmsg(db_))};
+  }
+
+  Reset();
+}
+
+void Statement::Reset() {
+  auto rc = sqlite3_reset(stmt_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("statement reset failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+} // namespace vereign::sqlite
diff --git a/cpp/src/vereign/sqlite/statement.hh b/cpp/src/vereign/sqlite/statement.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2b8c336d735a5ec33eb673dffa8df0ef87cce676
--- /dev/null
+++ b/cpp/src/vereign/sqlite/statement.hh
@@ -0,0 +1,42 @@
+#ifndef __VEREIGN_SQLITE_STATEMENT_HH
+#define __VEREIGN_SQLITE_STATEMENT_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/bytes.hh>
+
+#include <string>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+namespace vereign::sqlite {
+
+class Statement {
+private:
+  friend class Connection;
+
+  explicit Statement(sqlite3* db, sqlite3_stmt* stmt);
+
+public:
+  ~Statement();
+
+  void BindBlob(int index, bytes::View blob);
+  void BindText(int index, const std::string& text);
+
+  auto Step() -> bool;
+
+  auto GetColumnBlob(int index) -> bytes::View;
+  auto GetColumnText(int index) -> std::string_view;
+
+  void Reset();
+  void ResetAndClearBindings();
+  void Finalize();
+
+private:
+  sqlite3* db_;
+  sqlite3_stmt* stmt_;
+};
+
+} // namespace vereign::sqlite
+
+#endif // __VEREIGN_SQLITE_STATEMENT_HH
diff --git a/cpp/src/vereign/vereign.cc b/cpp/src/vereign/vereign.cc
index 5bbc7e934fe7b9ff917e4b4b75a4a236c8b671f7..6095a55db57e77a6c11feeae3ba8f08217d223f8 100644
--- a/cpp/src/vereign/vereign.cc
+++ b/cpp/src/vereign/vereign.cc
@@ -27,8 +27,7 @@ auto vereign_service_start(
   const char* listen_address,
   const char* vereign_host,
   const char* vereign_port,
-  // FIXME: public_key must come from a storage internally.
-  const char* public_key,
+  const char* storage_path,
   vereign_error** err
 ) -> vereign_service* {
   if (err != nullptr) {
@@ -42,7 +41,7 @@ auto vereign_service_start(
       listen_address,
       vereign_host,
       vereign_port,
-      public_key
+      storage_path != nullptr ? storage_path : ""
     );
 
     return new vereign_service{std::move(serviceImpl)};
@@ -59,7 +58,6 @@ auto vereign_service_start(
   return nullptr;
 }
 
-
 auto vereign_service_selected_port(vereign_service* service) -> int {
   return service->impl->SelectedPort();
 }
@@ -72,4 +70,3 @@ void vereign_service_shutdown(vereign_service* service) {
   service->impl->Shutdown();
   delete service;
 }
-
diff --git a/cpp/tests/integration/CMakeLists.txt b/cpp/tests/integration/CMakeLists.txt
index 9efb1023b1885e94c729c2d0259cd1963e188273..885f35651888dbf67bbe1dfbd30a00ef915eaabc 100644
--- a/cpp/tests/integration/CMakeLists.txt
+++ b/cpp/tests/integration/CMakeLists.txt
@@ -1,7 +1,6 @@
 include_directories(
   ${CMAKE_SOURCE_DIR}/src
   ${CMAKE_SOURCE_DIR}/include
-  # ${VENDOR_INSTALL_DIR}/include
   ${CMAKE_SOURCE_DIR}/proto/cpp
 )
 
@@ -24,3 +23,18 @@ add_test(
   NAME integration_test
   COMMAND integration_test
 )
+
+list(APPEND INIT_INTEGRATION_TEST_STORAGE_SRC
+  ../vereign/test/device.cc
+  ../vereign/test/service_context.cc
+
+  init_integration_test_storage.cc
+)
+
+add_executable(init_integration_test_storage ${INIT_INTEGRATION_TEST_STORAGE_SRC})
+
+target_link_libraries(init_integration_test_storage
+  vereignlib
+  Threads::Threads
+)
+
diff --git a/cpp/tests/integration/init_integration_test_storage.cc b/cpp/tests/integration/init_integration_test_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ff234034cd50f576d43b70263a48f724013b4292
--- /dev/null
+++ b/cpp/tests/integration/init_integration_test_storage.cc
@@ -0,0 +1,34 @@
+#include <vereign/fs/util.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/fs/operations.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+#include <util/env.hh>
+
+using namespace vereign;
+
+/**
+ * The init_integration_test_storage tool is used for creating a new device with
+ * initialized storage at ${HOME}/vereign_integration_test dir.
+ *
+ * FIXME: create a command line utility for this and other commands like starting a gRPC
+ *        server for integration tests.
+ */
+auto main(int argc, char** argv) -> int {
+  boost::ignore_unused(argc);
+  boost::ignore_unused(argv);
+
+  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto storage_path = test::GetEnv("TEST_VEREIGN_INTEGRATION_STORAGE", "");
+  auto pin = test::GetEnv("TEST_VEREIGN_PIN", "foo");
+
+  if (storage_path == "") {
+    storage_path = fs::path::Join(fs::HomePath(), "vereign_integration_test");
+  }
+
+  fs::CreateDir(storage_path);
+
+  test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+}
diff --git a/cpp/tests/integration/integration_test.cc b/cpp/tests/integration/integration_test.cc
index ef42c7201cd946c0b613616ab45813340c351dd8..e3db4498e0af99e6d0e70d425f56ee9a55933a91 100644
--- a/cpp/tests/integration/integration_test.cc
+++ b/cpp/tests/integration/integration_test.cc
@@ -1,16 +1,20 @@
 #include <vereign/vereign.h>
-#include <util/env.hh>
+
 #include <vereign/client_library/types.gen.pb.h>
 #include <vereign/client_library/passport_api.gen.grpc.pb.h>
+#include <vereign/client_library/identity_api.gen.grpc.pb.h>
 #include <vereign/core/scope_guard.hh>
-#include <grpcpp/create_channel.h>
+#include <util/env.hh>
 
+#include <grpcpp/create_channel.h>
 #include <catch2/catch.hpp>
 
 TEST_CASE("C API integration", "[.integration]") {
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  // NOTE: use the init_integration_storage tool to create a new test storage
+  auto storage_path = vereign::test::RequireEnv("TEST_VEREIGN_INTEGRATION_STORAGE");
   auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
   auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto pin = vereign::test::GetEnv("TEST_VEREIGN_PIN", "foo");
 
   // start the service
   vereign_error* err = nullptr;
@@ -18,7 +22,7 @@ TEST_CASE("C API integration", "[.integration]") {
     "localhost:",
     host.data(),
     port.data(),
-    publicKey.data(),
+    storage_path.data(),
     &err
   );
   CHECK(service != nullptr);
@@ -35,28 +39,24 @@ TEST_CASE("C API integration", "[.integration]") {
     ::grpc::InsecureChannelCredentials()
   );
 
+  auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+  auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+  login_req.set_pin(pin);
+  auto login_resp = vereign::client_library::EmptyResponse{};
+  ::grpc::ClientContext login_ctx;
+  auto status = identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+  REQUIRE(status.error_message() == "");
+  REQUIRE(status.ok() == true);
+  REQUIRE(login_resp.error() == "");
+  CHECK(login_resp.status() == "OK");
+  CHECK(login_resp.code() == "200");
+
   auto client = vereign::client_library::PassportAPI::NewStub(channel);
 
   vereign::client_library::ListPassportsForm req;
   vereign::client_library::ListPassportsFormResponse resp;
   ::grpc::ClientContext ctx;
-  auto status = client->ListPassports(&ctx, req, &resp);
-
-  // std::cout << vereign::test::ProtobufToJson(resp) << std::endl;
-
-  REQUIRE(status.error_message() == "");
-  REQUIRE(resp.error() == "");
-  CHECK(resp.status() == "OK");
-  CHECK(resp.code() == "200");
-  CHECK(resp.data().size() > 0);
-  for (auto& passport : resp.data()) {
-    CHECK(passport.uuid().size() == 36);
-  }
-
-  req.Clear();
-  resp.Clear();
-  ::grpc::ClientContext manually_ctx;
-  status = client->ListPassportsManually(&manually_ctx, req, &resp);
+  status = client->ListPassports(&ctx, req, &resp);
 
   // std::cout << vereign::test::ProtobufToJson(resp) << std::endl;
 
diff --git a/cpp/tests/util/env.hh b/cpp/tests/util/env.hh
index cb01a8c1de742a59e95935f5342de7e68fbf900c..d32fd97f8d66d0f25e76758425e426ae8b449079 100644
--- a/cpp/tests/util/env.hh
+++ b/cpp/tests/util/env.hh
@@ -2,24 +2,20 @@
 #define __VEREIGN_TEST_UTIL_ENV_HH
 
 #include <string>
-
-#include <catch2/catch.hpp>
 #include <fmt/core.h>
 
-namespace vereign {
-namespace test {
+namespace vereign::test {
 
-inline std::string RequireEnv(const std::string& name) {
+inline auto RequireEnv(const std::string& name) -> std::string {
   auto var = std::getenv(name.c_str());
   if (var == nullptr) {
-    FAIL(fmt::format("{} env variable is required", name));
-    return "";
+    throw std::runtime_error{fmt::format("{} env variable is required", name)};
   }
 
   return var;
 }
 
-inline std::string GetEnv(const std::string& name, const std::string& default_) {
+inline auto GetEnv(const std::string& name, const std::string& default_) -> std::string {
   auto var = std::getenv(name.c_str());
   if (var == nullptr) {
     return default_;
@@ -28,7 +24,6 @@ inline std::string GetEnv(const std::string& name, const std::string& default_)
   return var;
 }
 
-} // namespace vereign
 } // namespace test
 
 #endif // __VEREIGN_TEST_UTIL_ENV_HH
diff --git a/cpp/tests/util/error.hh b/cpp/tests/util/error.hh
new file mode 100644
index 0000000000000000000000000000000000000000..85d7951d333c60aa7c222cab272ff6737798fd95
--- /dev/null
+++ b/cpp/tests/util/error.hh
@@ -0,0 +1,22 @@
+#ifndef __VEREIGN_TEST_UTIL_ERROR_HH
+#define __VEREIGN_TEST_UTIL_ERROR_HH
+
+#include <optional>
+
+namespace vereign::test {
+
+template <class Error, class Fun>
+auto CatchError(Fun fn) -> std::optional<Error> {
+  try {
+    fn();
+
+    return {};
+  } catch (const Error& err) {
+
+    return err;
+  }
+}
+
+} // namespace test
+
+#endif // __VEREIGN_TEST_UTIL_ERROR_HH
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 2c2b6fce316e7721ed05a80e53330200081a4486..0bcfbc6e919333d7cae2525229b4c53b0cf21247 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,19 +10,49 @@ include_directories(
   ${Boost_INCLUDE_DIRS}
 )
 
-list(APPEND tests_src
+list(APPEND TESTS_SRC
   init_tests.cc
   ../util/protobuf.cc
   ../experiment/array.cc
+  test/device.cc
+  test/service_context.cc
 
   sync/channel_test.cc
+
+  encoding/base64_test.cc
+  encoding/hex_test.cc
+
+  bytes/buffer_test.cc
+  bytes/view_test.cc
+
+  crypto/aes_test.cc
+  crypto/rsa_test.cc
+  crypto/digest_test.cc
+
   restapi/client_test.cc
   restapi/client_session_test.cc
+
+  kvstore/lock_test.cc
+  kvstore/sqlite_storage_test.cc
+  kvstore/crypto_storage_test.cc
+
+  identity/provider_test.cc
+
   service/gen/passport_service_test.cc
+  service/identity_service_test.cc
+
   grpc/server_test.cc
   grpc/json/encoder_test.cc
+  grpc/identity_api_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/bytes/buffer_test.cc b/cpp/tests/vereign/bytes/buffer_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..710c3c184d2111aae21dc02c9b29a9939850842f
--- /dev/null
+++ b/cpp/tests/vereign/bytes/buffer_test.cc
@@ -0,0 +1,491 @@
+#include <vereign/bytes/buffer.hh>
+
+#include <vereign/bytes/errors.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("bytes::Buffer::Buffer", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 0);
+    CHECK(buf.FreeCap() == 0);
+  }
+
+  SECTION("buffer with predefined capacity") {
+    auto buf = bytes::Buffer{16};
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 16);
+    CHECK(buf.FreeCap() == 16);
+  }
+
+  SECTION("buffer from a view") {
+    auto buf = bytes::Buffer{bytes::View{"foo bar"}};
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 0);
+    CHECK(buf.View().String() == "foo bar");
+  }
+
+  SECTION("move construct") {
+    auto old_buf = bytes::Buffer{bytes::View{"foo bar"}};
+    auto buf = std::move(old_buf);
+
+    CHECK(old_buf.Size() == 0);
+    CHECK(old_buf.Cap() == 0);
+    CHECK(old_buf.FreeCap() == 0);
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 0);
+    CHECK(buf.View().String() == "foo bar");
+
+    auto empty = bytes::Buffer{};
+    auto new_empty = std::move(empty);
+
+    CHECK(empty.Size() == 0);
+    CHECK(empty.Cap() == 0);
+    CHECK(empty.FreeCap() == 0);
+
+    CHECK(new_empty.Size() == 0);
+    CHECK(new_empty.Cap() == 0);
+    CHECK(new_empty.FreeCap() == 0);
+  }
+
+  SECTION("move assign") {
+    auto foo = bytes::Buffer{bytes::View{"foo"}};
+    auto bar = bytes::Buffer{bytes::View{"bar"}};
+
+    foo = std::move(bar);
+
+    CHECK(foo.Size() == 3);
+    CHECK(foo.Cap() == 3);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "bar");
+
+    CHECK(bar.Size() == 3);
+    CHECK(bar.Cap() == 3);
+    CHECK(bar.FreeCap() == 0);
+    CHECK(bar.View().String() == "foo");
+
+    auto empty = bytes::Buffer{};
+    foo = std::move(empty);
+
+    CHECK(foo.Size() == 0);
+    CHECK(foo.Cap() == 0);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "");
+
+    CHECK(empty.Size() == 3);
+    CHECK(empty.Cap() == 3);
+    CHECK(empty.FreeCap() == 0);
+    CHECK(empty.View().String() == "bar");
+
+    empty = std::move(foo);
+
+    CHECK(empty.Size() == 0);
+    CHECK(empty.Cap() == 0);
+    CHECK(empty.FreeCap() == 0);
+    CHECK(empty.View().String() == "");
+
+    CHECK(foo.Size() == 3);
+    CHECK(foo.Cap() == 3);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::begin", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.begin() == nullptr);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    CHECK(buf.begin() != nullptr);
+
+    std::string s;
+    for (auto& it : buf) {
+      s += it;
+    }
+
+    CHECK(s == "foo bar");
+
+    for (auto& it : buf) {
+      it = 'x';
+    }
+
+    CHECK(buf.View().String() == "xxxxxxx");
+  }
+}
+
+TEST_CASE("bytes::Buffer::end", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.end() == nullptr);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    CHECK(buf.end() != nullptr);
+
+    std::string s;
+    for (const auto& it : buf) {
+      s += it;
+    }
+
+    CHECK(s == "foo bar");
+
+    for (auto& it : buf) {
+      it = 'x';
+    }
+
+    CHECK(buf.View().String() == "xxxxxxx");
+  }
+
+  SECTION("append to the end") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    buf.Reserve(4);
+    std::strncpy((char*)buf.end(), " baz", 4);
+    buf.IncSize(4);
+
+    CHECK(buf.Size() == 11);
+    CHECK(buf.View().String() == "foo bar baz");
+  }
+}
+
+TEST_CASE("bytes::Buffer::operator[]", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK_THROWS_AS(
+      buf[0],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+    buf.Reserve(16);
+
+    CHECK(buf.Cap() == 19);
+    CHECK(buf[0] == 'f');
+    CHECK(buf[1] == 'o');
+    CHECK(buf[2] == 'o');
+
+    CHECK_THROWS_AS(
+      buf[19],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("modify buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    buf[0] = 'b';
+    buf[1] = 'a';
+    buf[2] = 'r';
+
+    CHECK_THROWS_AS(
+      buf[3] = 'x',
+      bytes::IndexOutOfBounds
+    );
+
+    CHECK(buf.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::Size", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Size() == 3);
+  }
+
+  SECTION("append to buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+    buf.Write(bytes::View(" bar"));
+
+    CHECK(buf.Size() == 7);
+  }
+}
+
+TEST_CASE("bytes::Buffer::Cap", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Cap() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("append to buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    // capacity is doubled
+    buf.Write(bytes::View("b"));
+
+    CHECK(buf.Cap() == 6);
+  }
+}
+
+TEST_CASE("bytes::Buffer::FreeCap", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.FreeCap() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.FreeCap() == 0);
+
+    buf.Reserve(1);
+
+    CHECK(buf.Cap() - buf.Size() == buf.FreeCap());
+  }
+}
+
+TEST_CASE("bytes::Buffer::Reserve", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    buf.Reserve(8);
+    CHECK(buf.Cap() == 8);
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("when the required reservation is less than the old capacity*2, it reserves 2*capacity") {
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(1);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(2);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(3);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+  }
+
+  SECTION("when the required reservation is greater than the old capacity*2, "
+          "it reserves capacity+required size") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.Size() == 3);
+
+    buf.Reserve(4);
+
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 4);
+    CHECK(buf.Size() == 3);
+  }
+}
+
+TEST_CASE("bytes::Buffer::IncSize", "[vereign/bytes]") {
+  SECTION("when IncSize is called on empty buffer, it throws bytes::IncrementOutOfBounds") {
+    auto buf = bytes::Buffer{};
+
+    CHECK_THROWS_AS(
+      buf.IncSize(10),
+      bytes::IncrementOutOfBounds
+    );
+  }
+
+  SECTION("when IncSize is called with value bigger than the free cap, "
+          "it throws bytes::IncrementOutOfBounds") {
+
+    auto buf = bytes::Buffer{3};
+
+    std::strncpy((char*)buf.end(), "bar", 3);
+
+    CHECK_THROWS_AS(
+      buf.IncSize(4),
+      bytes::IncrementOutOfBounds
+    );
+  }
+
+  SECTION("when IncSize is called with zero, it does nothing") {
+    bytes::Buffer buf;
+    buf.IncSize(0);
+
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("success") {
+    auto buf = bytes::Buffer{3};
+
+    std::strncpy((char*)buf.end(), "bar", 3);
+
+    buf.IncSize(3);
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::WriteWithinCap", "[vereign/bytes]") {
+  SECTION("when WriteWithinCap is called on empty buffer, it does nothing") {
+    auto buf = bytes::Buffer{};
+    buf.WriteWithinCap(bytes::View("foo"));
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 0);
+  }
+
+  SECTION("when WriteWithinCap is called with empty source, it does nothing") {
+    auto buf = bytes::Buffer{3};
+    buf.WriteWithinCap(bytes::View());
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("when WriteWithinCap is called with source bigger than the free cap, "
+          "it copies only free cap amount of bytes") {
+
+    auto buf = bytes::Buffer{3};
+    buf.WriteWithinCap(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.View().String() == "foo");
+  }
+
+  SECTION("when WriteWithinCap is called with source smaller than the free cap, "
+          "it copies the full source of bytes") {
+
+    auto buf = bytes::Buffer{7};
+    buf.WriteWithinCap(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::Write", "[vereign/bytes]") {
+  SECTION("when Write is called on empty buffer, "
+          "it expands the buffer and writes all the source bytes") {
+    auto buf = bytes::Buffer{};
+    buf.Write(bytes::View("foo"));
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.View().String() == "foo");
+  }
+
+  SECTION("when Write is called with empty source, it does nothing") {
+    auto buf = bytes::Buffer{3};
+    buf.Write(bytes::View());
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("when Write is called with source bigger than the free cap, "
+          "it expands the buffer and writes all the source bytes") {
+
+    auto buf = bytes::Buffer{3};
+    buf.Write(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+
+  SECTION("when Write is called with source smaller than the free cap, "
+          "it copies the full source of bytes") {
+
+    auto buf = bytes::Buffer{7};
+    buf.Write(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::View", "[vereign/bytes]") {
+  SECTION("when the buffer is empty, it returns empty view") {
+    auto buf = bytes::Buffer{};
+
+    auto v = buf.View();
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the buffer is not empty, it returns a view of the buffer") {
+    auto buf = bytes::Buffer{bytes::View("123")};
+
+    auto v = buf.View();
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "123");
+
+    v = buf.View(1);
+    CHECK(v.Size() == 2);
+    CHECK(v.String() == "23");
+
+    v = buf.View(2);
+    CHECK(v.Size() == 1);
+    CHECK(v.String() == "3");
+
+    v = buf.View(3);
+    CHECK(v.Size() == 0);
+    CHECK(v.String() == "");
+
+    v = buf.View(4);
+    CHECK(v.Size() == 0);
+    CHECK(v.String() == "");
+  }
+}
diff --git a/cpp/tests/vereign/bytes/view_test.cc b/cpp/tests/vereign/bytes/view_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1f29ab454d9d40225c45c89adddf1dce7f06036d
--- /dev/null
+++ b/cpp/tests/vereign/bytes/view_test.cc
@@ -0,0 +1,236 @@
+#include <vereign/bytes/view.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/errors.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("bytes::View::View", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("view from a pointer and a size") {
+    const uint8_t* data = (uint8_t*)"foo";
+    auto v = bytes::View(data, 3);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a void pointer and a size") {
+    const void* data = (void*)"foo";
+    auto v = bytes::View(data, 3);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a string") {
+    std::string data = "foo";
+    auto v = bytes::View(data);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a wide string") {
+    std::wstring data = L"foo";
+    auto v = bytes::View(data);
+
+    CHECK(v.Size() == sizeof(wchar_t)*3);
+    CHECK(v.WideString() == L"foo");
+  }
+}
+
+TEST_CASE("bytes::View::Slice", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Slice(1).Size() == 0);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Slice(0).Size() == 3);
+    CHECK(v.Slice(0).String() == "123");
+
+    CHECK(v.Slice(1).Size() == 2);
+    CHECK(v.Slice(1).String() == "23");
+
+    CHECK(v.Slice(1, 2).Size() == 1);
+    CHECK(v.Slice(1, 2).String() == "2");
+
+    CHECK(v.Slice(1, 3).Size() == 2);
+    CHECK(v.Slice(1, 3).String() == "23");
+
+    CHECK(v.Slice(1, 4).Size() == 2);
+    CHECK(v.Slice(1, 4).String() == "23");
+  }
+
+  SECTION("start/end out of bounds") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Slice(0).Size() == 3);
+    CHECK(v.Slice(0).String() == "123");
+
+    CHECK(v.Slice(3).Size() == 0);
+    CHECK(v.Slice(3).String() == "");
+
+    CHECK(v.Slice(4).Size() == 0);
+    CHECK(v.Slice(4).String() == "");
+
+    CHECK(v.Slice(1, 4).Size() == 2);
+    CHECK(v.Slice(1, 4).String() == "23");
+  }
+}
+
+TEST_CASE("bytes::View::Data", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Data() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Data()[0] == '1');
+    CHECK(v.Data()[1] == '2');
+  }
+}
+
+TEST_CASE("bytes::View::CharData", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.CharData() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    const char* data = v.CharData();
+
+    CHECK(data[0] == '1');
+    CHECK(data[1] == '2');
+  }
+}
+
+TEST_CASE("bytes::View::WideCharData", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.WideCharData() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::wstring s = L"123";
+    auto v = bytes::View(s);
+
+    const wchar_t* data = v.WideCharData();
+
+    CHECK(data[0] == wchar_t('1'));
+    CHECK(data[1] == wchar_t('2'));
+  }
+}
+
+TEST_CASE("bytes::View::String", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.String() == "");
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.String() == "123");
+  }
+}
+
+TEST_CASE("bytes::View::WideString", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.WideString() == L"");
+  }
+
+  SECTION("non-empty view") {
+    std::wstring s = L"123";
+    auto v = bytes::View(s);
+
+    CHECK(v.WideString() == L"123");
+  }
+}
+
+TEST_CASE("bytes::View::operator==", "[vereign/bytes]") {
+  auto s1 = std::string{"foo"};
+  auto s2 = std::string{"foo"};
+
+  CHECK(bytes::View(s1) == bytes::View(s2));
+
+  s2 = "bar";
+  CHECK((bytes::View(s1) == bytes::View(s2)) == false);
+
+  s2 = "";
+  CHECK((bytes::View(s1) == bytes::View(s2)) == false);
+
+  auto b1 = crypto::Rand(16);
+  auto b2 = bytes::Buffer{b1.View()};
+
+  CHECK(bytes::View(b1.begin(), 16) == bytes::View(b2.begin(), 16));
+}
+
+TEST_CASE("bytes::View::operator!=", "[vereign/bytes]") {
+  auto s1 = std::string{"foo"};
+  auto s2 = std::string{"foo"};
+
+  CHECK((bytes::View(s1) != bytes::View(s2)) == false);
+
+  s2 = "bar";
+  CHECK(bytes::View(s1) != bytes::View(s2));
+
+  s2 = "";
+  CHECK(bytes::View(s1) != bytes::View(s2));
+
+  auto b1 = crypto::Rand(16);
+  auto b2 = bytes::Buffer{b1.View()};
+
+  CHECK((bytes::View(b1.begin(), 16) != bytes::View(b2.begin(), 16)) == false);
+}
+
+TEST_CASE("bytes::View::operator[]", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK_THROWS_AS(
+      v[0],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("non-empty view") {
+    auto s = std::string{"123"};
+    auto v = bytes::View(s);
+
+    CHECK(v[0] == '1');
+    CHECK(v[1] == '2');
+    CHECK(v[2] == '3');
+
+    CHECK_THROWS_AS(
+      v[3],
+      bytes::IndexOutOfBounds
+    );
+  }
+}
diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cfbb694c4e2eaddfd922670ad528a6984ebb0850
--- /dev/null
+++ b/cpp/tests/vereign/crypto/aes_test.cc
@@ -0,0 +1,25 @@
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") {
+  const std::string data{"foo bar"};
+  auto key = crypto::Rand(32);
+
+  bytes::Buffer iv;
+  bytes::Buffer tag;
+  bytes::Buffer encrypted;
+
+  crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, tag, encrypted);
+
+  bytes::Buffer decrypted;
+  crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted);
+
+  CHECK(decrypted.View().String() == data);
+}
diff --git a/cpp/tests/vereign/crypto/digest_test.cc b/cpp/tests/vereign/crypto/digest_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0bf6018f3bcc7c1120dc699e8d80024f2c4f3cbb
--- /dev/null
+++ b/cpp/tests/vereign/crypto/digest_test.cc
@@ -0,0 +1,22 @@
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/encoding/hex.hh>
+
+#include <catch2/catch.hpp>
+
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("digest::sha1", "[vereign/crypto/digest][vereign/crypto]") {
+  const std::string data{"foo bar"};
+
+  bytes::Buffer hash;
+  crypto::digest::sha1(bytes::View(data), hash);
+
+  bytes::Buffer encoded;
+  encoding::hex::Encode(hash.View(), encoded);
+
+  CHECK(encoded.View().String() == "3773dea65156909838fa6c22825cafe090ff8030");
+}
diff --git a/cpp/tests/vereign/crypto/rsa_test.cc b/cpp/tests/vereign/crypto/rsa_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f9f27acb9b3a20cf0621dd886144a6a7f2b7306b
--- /dev/null
+++ b/cpp/tests/vereign/crypto/rsa_test.cc
@@ -0,0 +1,100 @@
+#include <vereign/crypto/rsa.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/bio.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/bytes/buffer.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa][vereign/crypto]") {
+  SECTION("small input") {
+    auto key = crypto::rsa::GenerateKey(2048);
+
+    const std::string input{"foo bar"};
+    bytes::Buffer encrypted;
+
+    crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+
+    bytes::Buffer decrypted;
+    crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+
+    CHECK(decrypted.View() == bytes::View(input));
+  }
+
+  SECTION("zero input") {
+    auto key = crypto::rsa::GenerateKey(2048);
+
+    const std::string input;
+    bytes::Buffer encrypted;
+
+    crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+
+    bytes::Buffer decrypted;
+    crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+
+    CHECK(decrypted.View() == bytes::View(input));
+  }
+
+  SECTION("max size input") {
+    auto key = crypto::rsa::GenerateKey(2048);
+
+    auto input = crypto::Rand(214);
+    bytes::Buffer encrypted;
+
+    crypto::rsa::PublicKeyEncrypt(key.get(), input.View(), encrypted);
+
+    bytes::Buffer decrypted;
+    crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+
+    CHECK(decrypted.View() == input.View());
+  }
+
+  SECTION("invalid big input") {
+    auto key = crypto::rsa::GenerateKey(2048);
+
+    auto input = crypto::Rand(215);
+    bytes::Buffer encrypted;
+
+    CHECK_THROWS_WITH(
+      crypto::rsa::PublicKeyEncrypt(key.get(), input.View(), encrypted),
+      "encrypting failed: DATA_TOO_LARGE_FOR_KEY_SIZE"
+    );
+
+    CHECK(encrypted.Size() == 0);
+  }
+}
+
+TEST_CASE("crypto::rsa::ExportPublicKeyToPEM", "[vereign/crypto/rsa][vereign/crypto]") {
+  const std::string input{"foo bar"};
+  auto key = crypto::rsa::GenerateKey(2048);
+
+  auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get());
+  auto imported_key = crypto::rsa::ImportPublicKeyFromPEM(crypto::bio::View(bio.get()));
+
+  bytes::Buffer encrypted;
+  crypto::rsa::PublicKeyEncrypt(imported_key.get(), bytes::View(input), encrypted);
+
+  bytes::Buffer decrypted;
+  crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+
+  CHECK(decrypted.View() == bytes::View(input));
+}
+
+TEST_CASE("crypto::rsa::ExportPrivateKeyToPEM", "[vereign/crypto/rsa][vereign/crypto]") {
+  const std::string input{"foo bar"};
+  auto key = crypto::rsa::GenerateKey(2048);
+
+  auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+  auto imported_key = crypto::rsa::ImportPrivateKeyFromPEM(crypto::bio::View(bio.get()));
+
+  bytes::Buffer encrypted;
+  crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+
+  bytes::Buffer decrypted;
+  crypto::rsa::PrivateKeyDecrypt(imported_key.get(), encrypted.View(), decrypted);
+
+  CHECK(decrypted.View() == bytes::View(input));
+}
diff --git a/cpp/tests/vereign/encoding/base64_test.cc b/cpp/tests/vereign/encoding/base64_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0e6316b90d2de75e8f9cefcf9c4772259355982b
--- /dev/null
+++ b/cpp/tests/vereign/encoding/base64_test.cc
@@ -0,0 +1,86 @@
+#include <vereign/encoding/base64.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("base64::Encode", "[vereign/encoding/base64][vereign/encoding]") {
+  SECTION("RFC 4648 test cases plus some extras") {
+    struct Test {
+      std::string Input;
+      std::string Expected;
+    };
+
+    using namespace std::string_literals;
+
+    std::vector<Test> tests{
+      // taken from RFC 4648
+      {"f", "Zg=="},
+      {"fo", "Zm8="},
+      {"foo", "Zm9v"},
+      {"foob", "Zm9vYg=="},
+      {"fooba", "Zm9vYmE="},
+      {"foobar", "Zm9vYmFy"},
+
+      // additional test cases
+      {"", ""},
+      {"\x00\x42"s "foobar", "AEJmb29iYXI="}
+    };
+
+    for (auto& test : tests) {
+      bytes::Buffer encoded;
+      encoding::base64::Encode(bytes::View(test.Input), encoded);
+      CHECK(test.Expected == encoded.View().String());
+    }
+  }
+
+  SECTION("big input must not contain new lines") {
+    auto input = crypto::Rand(1024);
+    bytes::Buffer encoded;
+    encoding::base64::Encode(input.View(), encoded);
+
+    CHECK(encoded.View().String().find('\n') == std::string::npos);
+    CHECK(encoded.View().String().find('\r') == std::string::npos);
+
+    bytes::Buffer decoded;
+    encoding::base64::Decode(encoded.View(), decoded);
+
+    CHECK(input.View() == decoded.View());
+  }
+}
+
+TEST_CASE("base64::Decode", "[vereign/encoding/base64][vereign/encoding]") {
+  SECTION("RFC 4648 test cases plus some extras") {
+    struct Test {
+      std::string Expected;
+      std::string Input;
+    };
+
+    using namespace std::string_literals;
+
+    std::vector<Test> tests{
+      // taken from RFC 4648
+      {"f", "Zg=="},
+      {"fo", "Zm8="},
+      {"foo", "Zm9v"},
+      {"foob", "Zm9vYg=="},
+      {"fooba", "Zm9vYmE="},
+      {"foobar", "Zm9vYmFy"},
+
+      // additional test cases
+      {"", ""},
+      {"\x00\x42"s "foobar", "AEJmb29iYXI="}
+    };
+
+    for (auto& test : tests) {
+      bytes::Buffer encoded;
+      encoding::base64::Decode(bytes::View(test.Input), encoded);
+      CHECK(test.Expected == encoded.View().String());
+    }
+  }
+}
diff --git a/cpp/tests/vereign/encoding/hex_test.cc b/cpp/tests/vereign/encoding/hex_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9b4fefea84b7de42c502de478613888a736578d9
--- /dev/null
+++ b/cpp/tests/vereign/encoding/hex_test.cc
@@ -0,0 +1,96 @@
+#include <vereign/encoding/hex.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("encoding::hex::Encode", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Input;
+    std::string Expected;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "666f"},
+    {"foo", "666f6f"},
+    {"foob", "666f6f62"},
+    {"fooba", "666f6f6261"},
+    {"foobar", "666f6f626172"},
+    {"\x00\x42"s "foobar", "0042666f6f626172"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer encoded;
+    encoding::hex::Encode(bytes::View(test.Input), encoded);
+    CHECK(test.Expected == encoded.View().String());
+  }
+}
+
+TEST_CASE("encoding::hex::Decode", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Expected;
+    std::string Input;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "666f"},
+    {"foo", "666f6f"},
+    {"foob", "666f6f62"},
+    {"fooba", "666f6f6261"},
+    {"foobar", "666f6f626172"},
+    {"\x00\x42"s "foobar", "0042666f6f626172"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer decoded;
+    encoding::hex::Decode(bytes::View(test.Input), decoded);
+    CHECK(test.Expected == decoded.View().String());
+  }
+
+  auto input = crypto::Rand(16);
+
+  bytes::Buffer encoded;
+  encoding::hex::Encode(input.View(), encoded);
+
+  bytes::Buffer decoded;
+  encoding::hex::Decode(encoded.View(), decoded);
+
+  CHECK(input.View() == decoded.View());
+}
+
+TEST_CASE("encoding::hex::EncodeReverse", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Input;
+    std::string Expected;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "6f66"},
+    {"foo", "6f6f66"},
+    {"foob", "626f6f66"},
+    {"fooba", "61626f6f66"},
+    {"foobar", "7261626f6f66"},
+    {"\x00\x42"s "foobar", "7261626f6f664200"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer encoded;
+    encoding::hex::EncodeReverse(bytes::View(test.Input), encoded);
+    CHECK(test.Expected == encoded.View().String());
+  }
+}
diff --git a/cpp/tests/vereign/filesystem/temp.hh b/cpp/tests/vereign/filesystem/temp.hh
new file mode 100644
index 0000000000000000000000000000000000000000..62c03d0c08c812e86ae7f9268d6936989db3ac3b
--- /dev/null
+++ b/cpp/tests/vereign/filesystem/temp.hh
@@ -0,0 +1,31 @@
+#ifndef __VEREIGN_FILESYSTEM_TEMP_HH
+#define __VEREIGN_FILESYSTEM_TEMP_HH
+
+namespace vereign::filesystem {
+
+std::filesystem::path CreateTmpDir() {
+  // auto tmp_dir = std::filesystem::temp_directory_path();
+  // unsigned long long i = 0;
+  // std::random_device dev;
+  // std::mt19937 prng(dev());
+  // std::uniform_int_distribution<uint64_t> rand(0);
+  // std::filesystem::path path;
+  // while (true) {
+    // std::stringstream ss;
+    // ss << std::hex << rand(prng);
+    // path = tmp_dir / ss.str();
+    // // true if the directory was created.
+    // if (std::filesystem::create_directory(path)) {
+      // break;
+    // }
+    // if (i == max_tries) {
+      // throw std::runtime_error("could not find non-existing directory");
+    // }
+    // i++;
+  // }
+  return path;
+}
+
+} // namespace vereign::filesystem
+
+#endif // __VEREIGN_FILESYSTEM_TEMP_HH
diff --git a/cpp/tests/vereign/grpc/identity_api_test.cc b/cpp/tests/vereign/grpc/identity_api_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..516aa71338dbf10660e61d48b0451f55cb647fc6
--- /dev/null
+++ b/cpp/tests/vereign/grpc/identity_api_test.cc
@@ -0,0 +1,337 @@
+#include <vereign/grpc/server.hh>
+
+#include <vereign/grpc/error_code.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/client_library/passport_api.gen.grpc.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/client_library/identity_api.gen.grpc.pb.h>
+#include <vereign/service/identity_service.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+
+#ifdef _WIN32
+# include <vereign/ncrypt/rsa.hh>
+#endif
+
+#include <util/env.hh>
+#include <util/protobuf.hh>
+#include <grpcpp/create_channel.h>
+
+#include <catch2/catch.hpp>
+
+TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.integration]") {
+#ifdef _WIN32
+  SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    auto provider = vereign::ncrypt::rsa::OpenStorageProvider();
+    auto key = vereign::ncrypt::rsa::LoadKey(
+      provider.Get(),
+      std::string(vereign::kvstore::VereignKeyName)
+    );
+    if (key) {
+      vereign::ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() != "");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::DeviceNotRegistered
+    ));
+  }
+
+  SECTION("when the device key has been changed, it must fail with InvalidIdentity error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, "", storage_path);
+
+    auto provider = vereign::ncrypt::rsa::OpenStorageProvider();
+    auto key = vereign::ncrypt::rsa::LoadKey(
+      provider.Get(),
+      std::string(vereign::kvstore::VereignKeyName)
+    );
+    if (key) {
+      vereign::ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    vereign::ncrypt::rsa::CreateKey(
+      provider.Get(),
+      2048,
+      std::string(vereign::kvstore::VereignKeyName),
+      {}
+    );
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid identity");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidIdentity
+    ));
+  }
+#else
+  SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("foo");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() != "");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::DeviceNotRegistered
+    ));
+  }
+
+  SECTION("when the pin is wrong, it must fail with InvalidPinCode error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    const std::string pin = "foo";
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("invalid_pin");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+
+  SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    const std::string pin = "foo";
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+
+#endif
+
+  SECTION("when the device is not authorized, it must fail with DeviceNotRegistered error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    // the old device is used later for new device confirmation and authorization
+    auto old_storage_path = vereign::fs::TempFilePath("test_db_");
+    auto rm_old_storage_path = vereign::fs::RemoveFileGuard{old_storage_path};
+    auto old_device_ctx = vereign::test::ServiceContext{host, port, old_storage_path};
+    auto old_device = vereign::test::Device{old_device_ctx};
+    old_device.Login(public_key);
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    const std::string pin = "foo";
+
+    std::string qr_code;
+    std::string action_id;
+
+    {
+
+      vereign::grpc::Server server{"localhost:", host, port, storage_path};
+      auto on_exit = vereign::core::MakeScopeGuard([&server] {
+        server.Shutdown();
+      });
+
+      auto channel = ::grpc::CreateChannel(
+        "localhost:" + std::to_string(server.SelectedPort()),
+        ::grpc::InsecureChannelCredentials()
+      );
+
+      auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+
+      auto register_req = vereign::client_library::LoginFormNewDevice{};
+      auto register_resp = vereign::client_library::LoginFormNewDeviceResponse{};
+      register_req.set_pin(pin);
+
+      ::grpc::ClientContext register_ctx;
+      identity_client->LoginWithNewDevice(&register_ctx, register_req, &register_resp);
+
+      CHECK(register_resp.error() == "");
+      CHECK(register_resp.status() == "OK");
+      REQUIRE(register_resp.code() == "200");
+
+      qr_code = register_resp.data().qrcode();
+      action_id = register_resp.data().actionid();
+    }
+
+    {
+      vereign::grpc::Server server{"localhost:", host, port, storage_path};
+      auto on_exit = vereign::core::MakeScopeGuard([&server] {
+        server.Shutdown();
+      });
+
+      auto channel = ::grpc::CreateChannel(
+        "localhost:" + std::to_string(server.SelectedPort()),
+        ::grpc::InsecureChannelCredentials()
+      );
+
+      auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+      auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+      auto login_resp = vereign::client_library::EmptyResponse{};
+      login_req.set_pin(pin);
+
+      ::grpc::ClientContext login_ctx;
+      identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+      CHECK(login_resp.error() == "Error retrieving entity ");
+      CHECK(login_resp.status() == "Error while login");
+      REQUIRE(login_resp.code() == "400");
+
+      old_device.ConfirmNewDevice(qr_code, action_id);
+
+      ::grpc::ClientContext login_ctx_confirmed;
+      identity_client->LoginWithPreviouslyAddedDevice(&login_ctx_confirmed, login_req, &login_resp);
+
+      CHECK(login_resp.error() == "Device unauthorized");
+      CHECK(login_resp.status() == "Error while login");
+      REQUIRE(login_resp.code() == "400");
+    }
+  }
+}
+
+TEST_CASE("grpc::IdentityAPI::LoginWithNewDevice", "[vereign/grpc][.integration]") {
+#ifdef _WIN32
+#else
+  SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormNewDevice{};
+    auto login_resp = vereign::client_library::LoginFormNewDeviceResponse{};
+    login_req.set_pin("");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithNewDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+#endif // _WIN32
+}
diff --git a/cpp/tests/vereign/grpc/json/encoder_test.cc b/cpp/tests/vereign/grpc/json/encoder_test.cc
index c11b41e95013417b294c28f454a9722c50c510d5..7cd3095d85dff462f18cf8b8765a4fdbe1d485bb 100644
--- a/cpp/tests/vereign/grpc/json/encoder_test.cc
+++ b/cpp/tests/vereign/grpc/json/encoder_test.cc
@@ -2,8 +2,6 @@
 #include <vereign/grpc/json/pb/messages.pb.h>
 #include <boost/math/constants/constants.hpp>
 
-#include <vereign/core/hex.hh>
-
 #include <catch2/catch.hpp>
 
 using namespace vereign;
diff --git a/cpp/tests/vereign/grpc/server_test.cc b/cpp/tests/vereign/grpc/server_test.cc
index 61e2ea3874131d4aaff740fea19b6364c26b7273..0f3a18589adfb98ebe6d39bb064c91257439b7f5 100644
--- a/cpp/tests/vereign/grpc/server_test.cc
+++ b/cpp/tests/vereign/grpc/server_test.cc
@@ -1,20 +1,32 @@
 #include <vereign/grpc/server.hh>
+
 #include <vereign/core/scope_guard.hh>
 #include <vereign/client_library/passport_api.gen.grpc.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/client_library/identity_api.gen.grpc.pb.h>
+#include <vereign/service/identity_service.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+
 #include <util/env.hh>
 #include <util/protobuf.hh>
+#include <grpcpp/create_channel.h>
 
 #include <catch2/catch.hpp>
-#include "vereign/client_library/types.gen.pb.h"
-
-#include <grpcpp/create_channel.h>
 
 TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
   auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
   auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
-  vereign::grpc::Server server{"localhost:", host, port, publicKey};
+  const std::string pin = "foo";
+  auto storage_path = vereign::fs::TempDir("test_db_");
+  auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+  vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+
+  vereign::grpc::Server server{"localhost:", host, port, storage_path};
   auto on_exit = vereign::core::MakeScopeGuard([&server] {
     server.Shutdown();
   });
@@ -24,35 +36,33 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
     ::grpc::InsecureChannelCredentials()
   );
 
-  auto client = vereign::client_library::PassportAPI::NewStub(channel);
+  // register new device
+  auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+  auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+  auto login_resp = vereign::client_library::EmptyResponse{};
+  login_req.set_pin(pin);
 
-  vereign::client_library::ListPassportsForm req;
-  vereign::client_library::ListPassportsFormResponse resp;
-  ::grpc::ClientContext ctx;
-  auto status = client->ListPassports(&ctx, req, &resp);
+  ::grpc::ClientContext login_ctx;
+  identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
 
-  // std::cout << vereign::test::ProtobufToJson(resp) << std::endl;
+  CHECK(login_resp.error() == "");
+  CHECK(login_resp.status() == "OK");
+  REQUIRE(login_resp.code() == "200");
 
-  REQUIRE(status.error_message() == "");
-  REQUIRE(resp.error() == "");
-  CHECK(resp.status() == "OK");
-  CHECK(resp.code() == "200");
-  CHECK(resp.data().size() > 0);
-  for (auto& passport : resp.data()) {
-    CHECK(passport.uuid().size() == 36);
-  }
+  auto passport_client = vereign::client_library::PassportAPI::NewStub(channel);
 
-  req.Clear();
-  resp.Clear();
-  ::grpc::ClientContext manually_ctx;
-  status = client->ListPassportsManually(&manually_ctx, req, &resp);
+  vereign::client_library::ListPassportsForm req;
+  vereign::client_library::ListPassportsFormResponse resp;
+  ::grpc::ClientContext ctx;
+  auto status = passport_client->ListPassports(&ctx, req, &resp);
 
   // std::cout << vereign::test::ProtobufToJson(resp) << std::endl;
 
   REQUIRE(status.error_message() == "");
-  REQUIRE(resp.error() == "");
+  REQUIRE(status.ok() == true);
+  CHECK(resp.error() == "");
   CHECK(resp.status() == "OK");
-  CHECK(resp.code() == "200");
+  REQUIRE(resp.code() == "200");
   CHECK(resp.data().size() > 0);
   for (auto& passport : resp.data()) {
     CHECK(passport.uuid().size() == 36);
@@ -62,7 +72,7 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
   getInterReq.set_uuid(resp.data().at(0).uuid());
   vereign::client_library::GetInteractionsFormResponse getInterResp;
   ::grpc::ClientContext getInterCtx;
-  status = client->GetInteractions(&getInterCtx, getInterReq, &getInterResp);
+  status = passport_client->GetInteractions(&getInterCtx, getInterReq, &getInterResp);
   CHECK(status.error_message() == "");
   CHECK(getInterResp.error() == "");
   CHECK(getInterResp.status() == "OK");
diff --git a/cpp/tests/vereign/identity/provider_test.cc b/cpp/tests/vereign/identity/provider_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f34a8450b557e96bbf996aca0e2e43bf6d1481a4
--- /dev/null
+++ b/cpp/tests/vereign/identity/provider_test.cc
@@ -0,0 +1,40 @@
+#include <vereign/identity/provider.hh>
+
+#include <vereign/fs/util.hh>
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/kvstore/crypto_storage.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  std::string expected;
+  std::string actual;
+
+  {
+    kvstore::SqliteStorage kvstorage{storage_path};
+    kvstore::CryptoStorage storage{kvstorage, true};
+
+    identity::Provider provider{storage};
+    expected = provider.RecreateIdentity("foo");
+  }
+
+  {
+    kvstore::SqliteStorage kvstorage{storage_path};
+    kvstore::CryptoStorage storage{kvstorage, true};
+
+    identity::Provider provider{storage};
+    actual = provider.LoadIdentity("foo");
+  }
+
+  CHECK(expected.size() > 0);
+  CHECK(actual.size() > 0);
+  CHECK(expected == actual);
+}
diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8f9d305d0488004739afe3ae142e982ab7405630
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -0,0 +1,99 @@
+#include <vereign/kvstore/crypto_storage.hh>
+
+#include <vereign/encoding/errors.hh>
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/core/scope_guard.hh>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("kvstore::CryptoStorage::Recreate", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  // put value
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path);
+    kvstore::CryptoStorage storage{kvstorage, true};
+
+    storage.Recreate("foo");
+    std::string v{"test value"};
+    storage.PutBytes("test", bytes::View(v));
+  }
+
+  // with another storage instance get the value
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path);
+    kvstore::CryptoStorage storage{kvstorage, true};
+
+    bytes::Buffer v;
+    storage.Open("foo");
+    storage.GetBytes("test", v);
+
+    CHECK(v.View().String() == "test value");
+  }
+}
+
+TEST_CASE("kvstore::CryptoStorage::PutBytes", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto big_value = crypto::Rand(100000);
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+  kvstore::CryptoStorage storage{kvstorage, true};
+
+  storage.Recreate("foo");
+  storage.PutBytes("test", big_value.View());
+
+  bytes::Buffer v;
+  storage.GetBytes("test", v);
+
+  REQUIRE(v.Size() == big_value.Size());
+
+  auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
+  CHECK(cmp == 0);
+}
+
+TEST_CASE("kvstore::CryptoStorage::GetBytes", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+  kvstore::CryptoStorage storage{kvstorage, true};
+
+  storage.Recreate("foo");
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(storage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value cannot be decoded, it must throw encoding::Error") {
+    kvstorage.PutInt64("foo", 1);
+
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(storage.GetBytes("foo", v), encoding::Error);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    storage.PutBytes("foo", bytes::View("bar"));
+
+    bytes::Buffer v;
+    storage.GetBytes("foo", v);
+
+    CHECK(v.View() == bytes::View("bar"));
+  }
+}
diff --git a/cpp/tests/vereign/kvstore/lock_test.cc b/cpp/tests/vereign/kvstore/lock_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d15ed5ef9da02727c382e713c2401e2083ce7b5d
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/lock_test.cc
@@ -0,0 +1,61 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+
+#include <vereign/kvstore/lock.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/core/lock_guard.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/sqlite/errors.hh>
+#include <util/error.hh>
+#include <sqlite3.h>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <thread>
+#include <chrono>
+#include <limits>
+#include <optional>
+
+using namespace vereign;
+
+TEST_CASE("kvstore::Lock", "[vereign/kvstore]") {
+
+  SECTION("when the lock is released within the allowed retrials, the lock succeeds") {
+    auto storage_path = fs::TempFilePath("test_db_");
+    fs::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path);
+    auto bar_storage = kvstore::SqliteStorage(storage_path);
+
+    foo_storage.Lock();
+
+    bool err = false;
+    auto th = std::thread{[&bar_storage, &err]() {
+      try {
+        kvstore::Lock l{bar_storage, std::numeric_limits<int>::max(), std::chrono::milliseconds{10}};
+      } catch (...) {
+        err = true;
+      }
+    }};
+
+    foo_storage.Unlock();
+    th.join();
+  }
+
+  SECTION("when the lock is not released within the allowed retrials, the lock fails") {
+    auto storage_path = fs::TempFilePath("test_db_");
+    fs::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path);
+    auto bar_storage = kvstore::SqliteStorage(storage_path);
+
+    foo_storage.Lock();
+
+    CHECK_THROWS_AS(
+      kvstore::Lock(bar_storage, 2, std::chrono::milliseconds{1}),
+      kvstore::LockError
+    );
+  }
+}
diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f747ef2e4225b2b4c38b68551ab5cec2fcb538ae
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
@@ -0,0 +1,138 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+
+#include <vereign/kvstore/lock.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/sqlite/errors.hh>
+#include <util/error.hh>
+#include <sqlite3.h>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <thread>
+#include <optional>
+
+using namespace vereign;
+
+TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(kvstorage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    kvstorage.PutBytes("foo", bytes::View("bar"));
+
+    bytes::Buffer v;
+    kvstorage.GetBytes("foo", v);
+
+    CHECK(v.View() == bytes::View("bar"));
+  }
+}
+
+TEST_CASE("kvstore::SqliteStorage::GetInt64", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    CHECK_THROWS_AS(kvstorage.GetInt64("does_not_exists"), kvstore::ValueNotFoundError);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    kvstorage.PutInt64("foo", 42);
+
+    auto v = kvstorage.GetInt64("foo");
+
+    CHECK(v == 42);
+  }
+}
+
+TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+  kvstorage.PutInt64("foo", 42);
+  kvstorage.PutInt64("bar", 422);
+
+  CHECK(kvstorage.GetInt64("foo") == 42);
+  CHECK(kvstorage.GetInt64("bar") == 422);
+
+  kvstorage.DeleteAll();
+
+  CHECK_THROWS_AS(kvstorage.GetInt64("foo"), kvstore::ValueNotFoundError);
+  CHECK_THROWS_AS(kvstorage.GetInt64("bar"), kvstore::ValueNotFoundError);
+}
+
+TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
+
+  SECTION("when locked using lock guard, it must unlock on scope exit") {
+    auto storage_path = fs::TempFilePath("test_db_");
+    fs::RemoveFileGuard rm{storage_path};
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+      kvstore::Lock l{kvstorage};
+
+      kvstorage.PutInt64("foo", 42);
+      kvstorage.PutInt64("bar", 422);
+    }
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+      kvstore::Lock l{kvstorage};
+
+      CHECK(kvstorage.GetInt64("foo") == 42);
+      CHECK(kvstorage.GetInt64("bar") == 422);
+    }
+  }
+
+  SECTION("when locked, it must unlock on scope exit") {
+    auto storage_path = fs::TempFilePath("test_db_");
+    fs::RemoveFileGuard rm{storage_path};
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path);
+      kvstorage.Lock();
+
+      kvstorage.PutInt64("foo", 42);
+      kvstorage.PutInt64("bar", 422);
+    }
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path);
+      kvstorage.Lock();
+
+      CHECK(kvstorage.GetInt64("foo") == 42);
+      CHECK(kvstorage.GetInt64("bar") == 422);
+    }
+  }
+
+  SECTION("when the storage is already locked, it must fail with LockError") {
+    auto storage_path = fs::TempFilePath("test_db_");
+    fs::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path);
+    auto bar_storage = kvstore::SqliteStorage(storage_path);
+
+    foo_storage.Lock();
+
+    CHECK_THROWS_AS(bar_storage.Lock(), kvstore::LockError);
+  }
+}
diff --git a/cpp/tests/vereign/ncrypt/rsa_test.cc b/cpp/tests/vereign/ncrypt/rsa_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..116ed579ca8a3dd518c5e04c40585d34096d706e
--- /dev/null
+++ b/cpp/tests/vereign/ncrypt/rsa_test.cc
@@ -0,0 +1,209 @@
+#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") {
+    auto input = crypto::Rand(214);
+    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") {
+    auto input = crypto::Rand(215);
+    bytes::Buffer encrypted;
+
+    CHECK_THROWS_WITH(
+      ncrypt::rsa::PublicKeyEncrypt(key.Get(), input.View(), encrypted),
+      "encryption failed: NTE_INVALID_PARAMETER"
+    );
+
+    CHECK(encrypted.Size() == 0);
+  }
+}
diff --git a/cpp/tests/vereign/restapi/client_test.cc b/cpp/tests/vereign/restapi/client_test.cc
index 175501b247e0b321e04bd7aa39649094212f2783..c0c1a17198abca5ee1e50f85fe57675308d4d597 100644
--- a/cpp/tests/vereign/restapi/client_test.cc
+++ b/cpp/tests/vereign/restapi/client_test.cc
@@ -129,7 +129,7 @@ TEST_CASE("Client load test", "[vereign/restapi/client][.bench]") {
 
     threads.emplace_back(
       [&client, th]() mutable {
-        auto req = std::make_unique<client_library::LoginForm>();
+        auto req = std::make_unique<client_library::EmptyRequest>();
         auto resp = std::make_unique<client_library::LoginFormPreviousAddedDeviceResponse>();
 
         for (int i = 0; i < 1000; i++) {
@@ -137,7 +137,6 @@ TEST_CASE("Client load test", "[vereign/restapi/client][.bench]") {
 
           req->Clear();
           resp->Clear();
-          req->set_mode(id);
 
           auto result = client.Post(
             "/",
diff --git a/cpp/tests/vereign/service/gen/passport_service_test.cc b/cpp/tests/vereign/service/gen/passport_service_test.cc
index ec0375b559d44d09d6452604b38af0660932da7e..bbe2df7c0aaf5bb31dad33039778e439b6606ce6 100644
--- a/cpp/tests/vereign/service/gen/passport_service_test.cc
+++ b/cpp/tests/vereign/service/gen/passport_service_test.cc
@@ -1,11 +1,17 @@
 #include <vereign/service/gen/passport_service.hh>
+
+#include <vereign/fs/util.hh>
 #include <vereign/core/scope_guard.hh>
 #include <vereign/client_library/types.gen.pb.h>
+#include <vereign/client_library/identity_types.pb.h>
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/service/identity_service.hh>
+#include <vereign/identity/provider.hh>
+
 #include <util/env.hh>
 #include <util/protobuf.hh>
 
 #include <catch2/catch.hpp>
-
 #include <boost/asio/io_context.hpp>
 #include <boost/asio/ssl/context.hpp>
 #include <fmt/core.h>
@@ -25,9 +31,17 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration]
   // ctx.set_verify_mode(ssl::verify_peer);
 
   vereign::restapi::Client client{ioc, ctx, host, port};
-  vereign::restapi::ClientSession client_session{client, publicKey};
+  vereign::restapi::ClientSession client_session{client};
   vereign::service::gen::PassportService service{client_session};
 
+  auto storage_path = vereign::fs::TempFilePath("test_db_");
+  vereign::fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = vereign::kvstore::SqliteStorage(storage_path);
+  vereign::kvstore::CryptoStorage storage{kvstorage};
+  vereign::identity::Provider provider{storage};
+  vereign::service::IdentityService idenity_service{client_session, provider};
+
   std::thread ioc_thread([&ioc]{
     ioc.run();
   });
@@ -39,6 +53,15 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration]
     }
   );
 
+  // login
+  auto req = std::make_unique<vereign::client_library::LoginWithExistingPubKeyForm>();
+  auto resp = std::make_unique<vereign::client_library::EmptyResponse>();
+  req->set_pubkey(publicKey);
+  idenity_service.LoginWithExistingPubKey(req.get(), resp.get());
+  CHECK(resp->error() == "");
+  CHECK(resp->status() == "OK");
+  REQUIRE(resp->code() == "200");
+
   for (int i = 0; i < 2; i++) {
     auto req = std::make_unique<vereign::client_library::ListPassportsForm>();
     auto resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>();
@@ -50,9 +73,9 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration]
     // std::cout << vereign::test::ProtobufToJson(*result.Response) << std::endl;
 
     auto& list = result.Response;
-    REQUIRE(list->error() == "");
+    CHECK(list->error() == "");
     CHECK(list->status() == "OK");
-    CHECK(list->code() == "200");
+    REQUIRE(list->code() == "200");
     CHECK(list->data().size() > 0);
     for (auto& passport : list->data()) {
       CHECK(passport.uuid().size() == 36);
diff --git a/cpp/tests/vereign/service/identity_service_test.cc b/cpp/tests/vereign/service/identity_service_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4fa38954ab5c0ec1af69addc32ffccd0a23c8e5a
--- /dev/null
+++ b/cpp/tests/vereign/service/identity_service_test.cc
@@ -0,0 +1,129 @@
+#include <vereign/service/identity_service.hh>
+
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/identity_types.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/service/gen/passport_service.hh>
+#include <vereign/restapi/client_session.hh>
+#include <vereign/identity/provider.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+#include <vereign/fs/util.hh>
+
+#include <util/env.hh>
+#include <util/protobuf.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("service::IdentityService::LoginWithNewDevice", "[vereign/service][.integration]") {
+  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+  // the old device is used later for new device confirmation and authorization
+  auto old_storage_path = fs::TempFilePath("test_db_");
+  auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path};
+  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto storage_path = fs::TempFilePath("test_db_");
+  auto rm_storage_path = fs::RemoveFileGuard{storage_path};
+  auto service_context = test::ServiceContext{host, port, storage_path};
+  auto service = service::IdentityService{
+    service_context.ClientSession(),
+    service_context.IdentityProvider()
+  };
+
+  // register new device
+  auto req = std::make_unique<vereign::client_library::LoginFormNewDevice>();
+  auto resp = std::make_unique<vereign::client_library::LoginFormNewDeviceResponse>();
+  req->set_pin("foo");
+
+  service.LoginWithNewDevice(req.get(), resp.get());
+
+  CHECK(resp->error() == "");
+  CHECK(resp->status() == "OK");
+  REQUIRE(resp->code() == "200");
+  REQUIRE(resp->has_data() == true);
+
+  // confirm and authorize the new device using an old device
+  old_device.ConfirmNewDevice(resp->data().qrcode(), resp->data().actionid());
+  old_device.AuthorizeDevice(service_context.IdentityProvider().GetDeviceHash());
+
+  // list passports with the new device
+  auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>();
+  auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>();
+
+  auto passport_service = service::gen::PassportService{service_context.ClientSession()};
+  auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get());
+
+  auto result = list_result.get();
+
+  // std::cout << vereign::test::ProtobufToJson(*result.Response) << std::endl;
+
+  auto& list = result.Response;
+  CHECK(list->error() == "");
+  CHECK(list->status() == "OK");
+  REQUIRE(list->code() == "200");
+  CHECK(list->data().size() > 0);
+  for (const auto& passport : list->data()) {
+    CHECK(passport.uuid().size() == 36);
+  }
+}
+
+TEST_CASE("service::IdentityService::LoginWithPreviouslyAddedDevice", "[vereign/service][.integration]") {
+  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+  auto storage_path = fs::TempFilePath("test_db_");
+  auto rm_storage_path = fs::RemoveFileGuard{storage_path};
+
+  // prepare new device
+  auto old_storage_path = fs::TempFilePath("test_db_");
+  auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path};
+  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+  auto old_service_context = test::ServiceContext{host, port, storage_path};
+  old_device.CreateNewDevice(old_service_context, "pin");
+
+  auto service_context = test::ServiceContext{host, port, storage_path};
+  auto service = service::IdentityService{
+    service_context.ClientSession(),
+    service_context.IdentityProvider()
+  };
+
+  auto req = std::make_unique<vereign::client_library::LoginFormPreviousAddedDevice>();
+  req->set_pin("pin");
+  auto resp = std::make_unique<vereign::client_library::EmptyResponse>();
+
+  service.LoginWithPreviouslyAddedDevice(req.get(), resp.get());
+
+  CHECK(resp->error() == "");
+  CHECK(resp->status() == "OK");
+  REQUIRE(resp->code() == "200");
+
+  // list passports with the logged device
+  auto list_req = std::make_unique<vereign::client_library::ListPassportsForm>();
+  auto list_resp = std::make_unique<vereign::client_library::ListPassportsFormResponse>();
+
+  auto passport_service = service::gen::PassportService{service_context.ClientSession()};
+  auto list_result = passport_service.ListPassports(list_req.get(), list_resp.get());
+
+  auto result = list_result.get();
+
+  // std::cout << vereign::test::ProtobufToJson(*result.Response) << std::endl;
+
+  auto& list = result.Response;
+  CHECK(list->error() == "");
+  CHECK(list->status() == "OK");
+  REQUIRE(list->code() == "200");
+  CHECK(list->data().size() > 0);
+  for (const auto& passport : list->data()) {
+    CHECK(passport.uuid().size() == 36);
+  }
+}
diff --git a/cpp/tests/vereign/test/device.cc b/cpp/tests/vereign/test/device.cc
new file mode 100644
index 0000000000000000000000000000000000000000..bc47ef4c530a24b7f77e06fd1e9b305fe9b8b2a7
--- /dev/null
+++ b/cpp/tests/vereign/test/device.cc
@@ -0,0 +1,143 @@
+#include <vereign/test/device.hh>
+
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/fs/util.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/test/service_context.hh>
+#include <vereign/restapi/client_session.hh>
+#include <vereign/service/identity_service.hh>
+#include <vereign/identity/provider.hh>
+#include <vereign/client_library/identity_types.pb.h>
+
+#include <boost/filesystem/operations.hpp>
+
+namespace vereign::test {
+
+Device::Device(ServiceContext& service_context)
+  : service_context_{service_context},
+    identity_service_{std::make_unique<service::IdentityService>(
+      service_context_.ClientSession(),
+      service_context_.IdentityProvider()
+    )}
+{
+}
+
+Device::~Device() = default;
+
+void Device::Login(const std::string& public_key) {
+  auto req = std::make_unique<vereign::client_library::LoginWithExistingPubKeyForm>();
+  auto resp = std::make_unique<vereign::client_library::EmptyResponse>();
+  req->set_pubkey(public_key);
+
+  identity_service_->LoginWithExistingPubKey(req.get(), resp.get());
+
+  if (resp->code() != "200") {
+    throw std::runtime_error("login to test old device failed with: " + resp->error());
+  }
+}
+
+void Device::ConfirmNewDevice(const std::string& qr_code, const std::string& action_id) {
+  auto req = std::make_unique<vereign::client_library::ConfirmNewDeviceForm>();
+  req->set_code(qr_code);
+  req->set_actionid(action_id);
+  auto resp = std::make_unique<vereign::client_library::EmptyResponse>();
+
+  auto result = identity_service_->ConfirmNewDevice(req.get(), resp.get());
+  result.wait();
+
+  if (resp->code() != "200") {
+    throw std::runtime_error("confirm new device failed with: " + resp->error());
+  }
+}
+
+void Device::AuthorizeDevice(const std::string& device_hash) {
+  auto req = std::make_unique<client_library::EmptyRequest>();
+  auto resp = std::make_unique<client_library::ListDevicesHandlerFormResponse>();
+
+  auto result = identity_service_->ListDevices(req.get(), resp.get());
+  result.wait();
+  if (resp->code() != "200") {
+    throw std::runtime_error("authorize device failed with: " + resp->error());
+  }
+
+  std::string device_id;
+  for (auto& device : resp->data()) {
+    if (device.fingerprint() == device_hash) {
+      device_id = device.deviceid();
+      break;
+    }
+  }
+
+  if (device_id.empty()) {
+    throw std::runtime_error("authorize device failed with: device not found");
+  }
+
+  auto auth_req = std::make_unique<client_library::AuthorizeDeviceForm>();
+  auth_req->set_deviceid(device_id);
+  auto auth_resp = std::make_unique<client_library::EmptyResponse>();
+
+  auto auth_result = identity_service_->AuthorizeDevice(auth_req.get(), auth_resp.get());
+  auth_result.wait();
+  if (auth_resp->code() != "200") {
+    throw std::runtime_error("authorize device failed with: " + auth_resp->error());
+  }
+}
+
+void Device::CreateNewDevice(ServiceContext& service_context, const std::string& pin) {
+  auto service = service::IdentityService{
+    service_context.ClientSession(),
+    service_context.IdentityProvider()
+  };
+
+  // register new device
+  auto req = std::make_unique<vereign::client_library::LoginFormNewDevice>();
+  auto resp = std::make_unique<vereign::client_library::LoginFormNewDeviceResponse>();
+  req->set_pin(pin);
+
+  service.LoginWithNewDevice(req.get(), resp.get());
+  if (resp->code() != "200") {
+    throw std::runtime_error("creating new device failed with: " + resp->error());
+  }
+
+  // confirm and authorize the new device using an old device
+  ConfirmNewDevice(resp->data().qrcode(), resp->data().actionid());
+  AuthorizeDevice(service_context.IdentityProvider().GetDeviceHash());
+}
+
+void PrepareNewDevice(
+  const std::string& host,
+  const std::string& port,
+  const std::string& public_key,
+  const std::string& pin,
+  const std::string& storage_path
+) {
+  // the old device is used later for new device confirmation and authorization
+  auto old_storage_path = fs::TempFilePath("test_db_");
+  auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path};
+  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto service_context = test::ServiceContext{host, port, fs::path::Join(storage_path, "db")};
+  auto identity_service = service::IdentityService{
+    service_context.ClientSession(),
+    service_context.IdentityProvider()
+  };
+
+  // register new device
+  auto register_req = std::make_unique<vereign::client_library::LoginFormNewDevice>();
+  auto register_resp = std::make_unique<vereign::client_library::LoginFormNewDeviceResponse>();
+  register_req->set_pin(pin);
+
+  identity_service.LoginWithNewDevice(register_req.get(), register_resp.get());
+  if (register_resp->code() != "200") {
+    throw std::runtime_error("register new device failed with: " + register_resp->error());
+  }
+
+  // confirm and authorize the new device using an old device
+  old_device.ConfirmNewDevice(register_resp->data().qrcode(), register_resp->data().actionid());
+  old_device.AuthorizeDevice(service_context.IdentityProvider().GetDeviceHash());
+}
+
+} // namespace vereign::test
diff --git a/cpp/tests/vereign/test/device.hh b/cpp/tests/vereign/test/device.hh
new file mode 100644
index 0000000000000000000000000000000000000000..9ad85bd8a42142f35f7c9a589244c2a8b25fd294
--- /dev/null
+++ b/cpp/tests/vereign/test/device.hh
@@ -0,0 +1,43 @@
+#ifndef __TESTS_VEREIGN_TEST_DEVICE_HH
+#define __TESTS_VEREIGN_TEST_DEVICE_HH
+
+#include <memory>
+#include <string>
+
+namespace vereign::service {
+class IdentityService;
+}
+
+namespace vereign::test {
+
+class ServiceContext;
+
+class Device {
+public:
+  Device(ServiceContext& service_context);
+  ~Device();
+
+  Device(const Device&) = delete;
+  auto operator=(const Device&) -> Device& = delete;
+
+  void Login(const std::string& public_key);
+  void ConfirmNewDevice(const std::string& qr_code, const std::string& action_id);
+  void AuthorizeDevice(const std::string& device_hash);
+  void CreateNewDevice(ServiceContext& service_context, const std::string& pin);
+
+private:
+  ServiceContext& service_context_;
+  std::unique_ptr<service::IdentityService> identity_service_;
+};
+
+void PrepareNewDevice(
+  const std::string& host,
+  const std::string& port,
+  const std::string& public_key,
+  const std::string& pin,
+  const std::string& storage_path
+);
+
+} // namespace vereign::test
+
+#endif // __TESTS_VEREIGN_TEST_DEVICE_HH
diff --git a/cpp/tests/vereign/test/service_context.cc b/cpp/tests/vereign/test/service_context.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a6b5fc85a675920a667cd0890ffe0b632cda516a
--- /dev/null
+++ b/cpp/tests/vereign/test/service_context.cc
@@ -0,0 +1,54 @@
+#include <vereign/test/service_context.hh>
+
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/kvstore/crypto_storage.hh>
+#include <vereign/restapi/client.hh>
+#include <vereign/restapi/client_session.hh>
+#include <vereign/identity/provider.hh>
+
+#include <boost/filesystem/operations.hpp>
+
+namespace vereign::test {
+
+ServiceContext::ServiceContext(
+  const std::string& vereign_host,
+  const std::string& vereign_port,
+  std::string storage_path
+) : work_guard_{boost::asio::make_work_guard(ioc_)},
+    ssl_context_{boost::asio::ssl::context::tlsv12_client},
+    client_{std::make_unique<restapi::Client>(
+        ioc_, ssl_context_, vereign_host, vereign_port
+    )},
+    client_session_{std::make_unique<restapi::ClientSession>(*client_)},
+    storage_path_{std::move(storage_path)},
+    sqlite_storage_{std::make_unique<kvstore::SqliteStorage>(storage_path_)},
+    storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_, true)},
+    identity_provider_{std::make_unique<identity::Provider>(*storage_)}
+{
+  service_thread_ = std::thread([this]() {
+    ioc_.run();
+  });
+}
+
+auto ServiceContext::IdentityProvider() -> identity::Provider& {
+  return *identity_provider_;
+}
+
+auto ServiceContext::ClientSession() -> restapi::ClientSession& {
+  return *client_session_;
+}
+
+void ServiceContext::Shutdown() {
+  client_session_->Close();
+
+  work_guard_.reset();
+  if (service_thread_.joinable()) {
+    service_thread_.join();
+  }
+}
+
+ServiceContext::~ServiceContext() {
+  Shutdown();
+}
+
+}
diff --git a/cpp/tests/vereign/test/service_context.hh b/cpp/tests/vereign/test/service_context.hh
new file mode 100644
index 0000000000000000000000000000000000000000..7666e29eba7aa135f3a048ef13d1162fd346ce4d
--- /dev/null
+++ b/cpp/tests/vereign/test/service_context.hh
@@ -0,0 +1,63 @@
+#ifndef __TESTS_VEREIGN_TEST_SERVICE_CONTEXT_HH
+#define __TESTS_VEREIGN_TEST_SERVICE_CONTEXT_HH
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/ssl/context.hpp>
+#include <boost/asio/executor_work_guard.hpp>
+#include <boost/filesystem/path.hpp>
+
+#include <thread>
+
+namespace vereign {
+
+namespace restapi {
+class Client;
+class ClientSession;
+}
+
+namespace kvstore {
+class SqliteStorage;
+class CryptoStorage;
+}
+
+namespace identity {
+class Provider;
+}
+
+}
+
+namespace vereign::test {
+
+class ServiceContext {
+public:
+  ServiceContext(
+    const std::string& vereign_host,
+    const std::string& vereign_port,
+    std::string storage_path
+  );
+  ~ServiceContext();
+
+  ServiceContext(const ServiceContext&) = delete;
+  auto operator=(const ServiceContext&) -> ServiceContext& = delete;
+
+  auto IdentityProvider() -> identity::Provider&;
+  auto ClientSession() -> restapi::ClientSession&;
+
+  void Shutdown();
+
+private:
+  boost::asio::io_context ioc_;
+  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard_;
+  boost::asio::ssl::context ssl_context_;
+  std::unique_ptr<restapi::Client> client_;
+  std::unique_ptr<restapi::ClientSession> client_session_;
+  std::string storage_path_;
+  std::unique_ptr<kvstore::SqliteStorage> sqlite_storage_;
+  std::unique_ptr<kvstore::CryptoStorage> storage_;
+  std::unique_ptr<identity::Provider> identity_provider_;
+  std::thread service_thread_;
+};
+
+} // namespace vereign::test
+
+#endif // __TESTS_VEREIGN_TEST_SERVICE_CONTEXT_HH
diff --git a/cpp/vendor/CMakeLists.txt b/cpp/vendor/CMakeLists.txt
index 7d4cb218c8cc4ae5f18c2a2d6a792071df35ab0d..c0f4ec13688b907308716fb24ca8e50b1f074f5a 100644
--- a/cpp/vendor/CMakeLists.txt
+++ b/cpp/vendor/CMakeLists.txt
@@ -53,6 +53,7 @@ include(boring_ssl.cmake)
 include(boost.cmake)
 include(grpc.cmake)
 include(nlohmann.cmake)
+include(sqlite3.cmake)
 
 string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type)
 message(STATUS "Summary:
diff --git a/cpp/vendor/boost.cmake b/cpp/vendor/boost.cmake
index 430902d781c72e6b107b4709b13a84f7160a1220..4d49081cc71229db423067033cdcd9551195644a 100644
--- a/cpp/vendor/boost.cmake
+++ b/cpp/vendor/boost.cmake
@@ -1,6 +1,6 @@
 include(ExternalProject)
 
-set(_boost_libs regex system thread date_time)
+set(_boost_libs regex system thread date_time filesystem)
 
 if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
   list(TRANSFORM _boost_libs PREPEND --with-)
diff --git a/cpp/vendor/cmake/sqlite3/CMakeLists.txt b/cpp/vendor/cmake/sqlite3/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c203d8643bb166579d9785a215cedc7ddfa5b104
--- /dev/null
+++ b/cpp/vendor/cmake/sqlite3/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required (VERSION 3.16.5)
+
+project (sqlite3)
+
+add_library(sqlite3 STATIC
+  sqlite3.c
+)
+
+install(TARGETS sqlite3 DESTINATION lib)
+install(FILES sqlite3.h sqlite3ext.h DESTINATION include)
diff --git a/cpp/vendor/sqlite3.cmake b/cpp/vendor/sqlite3.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6c39ac93514e8c9795628a98d9007df8181fddf0
--- /dev/null
+++ b/cpp/vendor/sqlite3.cmake
@@ -0,0 +1,29 @@
+
+ExternalProject_Add(sqlite3lib
+  PREFIX sqlite3
+  URL https://www.sqlite.org/2020/sqlite-amalgamation-3320300.zip
+  URL_HASH SHA1=0c805bea134712a903290a26b2a61c3a8a3bd8cc
+  INSTALL_DIR ${VENDOR_INSTALL_DIR}/sqlite3
+
+  USES_TERMINAL_DOWNLOAD ON
+  USES_TERMINAL_UPDATE ON
+  USES_TERMINAL_CONFIGURE ON
+  USES_TERMINAL_BUILD ON
+  USES_TERMINAL_INSTALL ON
+
+  UPDATE_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/cmake/sqlite3/CMakeLists.txt <SOURCE_DIR>/CMakeLists.txt
+
+  CMAKE_CACHE_ARGS
+  -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+  -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${CMAKE_MSVC_RUNTIME_LIBRARY}
+  -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER}
+  -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER}
+  -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}
+  -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
+  -DCMAKE_C_FLAGS_DEBUG:STRING=${CMAKE_C_FLAGS_DEBUG}
+  -DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG}
+  -DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE}
+  -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE}
+  -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
+  -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
+)