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..3a833a5df6edb8f99e0f56fe8df2f83280f2ce73 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)
@@ -138,6 +143,9 @@ endif()
 
 find_package(nlohmann_json 3.7.3 REQUIRED)
 
+set(SQLite3_DIR ${VENDOR_INSTALL_DIR})
+find_package(SQLite3 REQUIRED)
+
 add_subdirectory("src")
 add_subdirectory("tests")
 
@@ -176,6 +184,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/proto b/cpp/proto
index e861100984116aacf6d84cb8a09dc0ef81041509..9ef33cf108f8665204929ea56d9df812cb378821 160000
--- a/cpp/proto
+++ b/cpp/proto
@@ -1 +1 @@
-Subproject commit e861100984116aacf6d84cb8a09dc0ef81041509
+Subproject commit 9ef33cf108f8665204929ea56d9df812cb378821
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index 555515506f7c1d62b01e54cf2eeaeeb5401e783b..62c0db650441125d6066b1826169ddeabe99525f 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -6,6 +6,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
   add_definitions(-DNOGDI)
 endif()
 
+add_definitions(-DBOOST_FILESYSTEM_NO_DEPRECATED)
+
 include_directories(
   ${CMAKE_CURRENT_BINARY_DIR}
   ${CMAKE_SOURCE_DIR}/src
@@ -45,17 +47,56 @@ if (VEREIGN_USE_PRECOMPILED_HEADERS)
 endif()
 
 set(VEREIGNLIB_SRC
+  vereign/core/rand.cc
+  vereign/core/temp.cc
+  vereign/core/fs.cc
+
   vereign/restapi/detail/http_reader.cc
   vereign/restapi/client.cc
-  vereign/service/passport_service.cc
-  vereign/grpc/gen/gen.cc
+
+  # vereign/grpc/gen/gen.cc
   vereign/grpc/json/encoder.cc
   vereign/grpc/service_registry.cc
   vereign/grpc/server.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/aes.cc
+  vereign/crypto/rsa.cc
+  vereign/crypto/bio.cc
+  vereign/crypto/digest.cc
+
+
+  vereign/kvstore/sqlite_storage.cc
+  vereign/kvstore/crypto_storage.cc
+
+  vereign/identity/provider.cc
+
+  vereign/service/gen/passport_service.cc
+  vereign/service/passport_service.cc
+  vereign/service/identity_service.cc
+
+  # FIXME: remove this
+  vereign/service/gen/identity_service.cc
+  vereign/service/gen/passport_service.cc
 )
 
-file(GLOB GENERATED_SERVICES_SRC vereign/service/gen/*.cc)
-list(APPEND VEREIGNLIB_SRC ${GENERATED_SERVICES_SRC})
+
+if (LINUX)
+  list(APPEND VEREIGNLIB_SRC
+    vereign/kvstore/detail/linux_crypto_storage.cc
+  )
+endif()
+
+# file(GLOB GENERATED_SERVICES_SRC vereign/service/gen/*.cc)
+# list(APPEND VEREIGNLIB_SRC ${GENERATED_SERVICES_SRC})
 
 add_library(vereignlib STATIC ${VEREIGNLIB_SRC})
 set_property(TARGET vereignlib PROPERTY POSITION_INDEPENDENT_CODE ON)
@@ -67,7 +108,9 @@ target_link_libraries(vereignlib PUBLIC
   fmt::fmt
   gRPC::grpc++_reflection
   gRPC::grpc++
+  Boost::filesystem
   $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
+  SQLite::SQLite3
 )
 
 add_library(vereign SHARED
@@ -96,6 +139,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..0f3226d364e1b95441b80b2706f5f0222b347e8a 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -1,4 +1,20 @@
+#include <boost/core/ignore_unused.hpp>
+#include <iostream>
+#include <boost/filesystem.hpp>
 
-auto main(int argc, char* argv[]) -> int {
-  return 0;
+namespace fs = boost::filesystem;
+
+auto main(int argc, char** argv) -> int {
+  boost::ignore_unused(argc);
+  boost::ignore_unused(argv);
+
+  // fs::path ;
+
+  std::cout << "Temp directory is " << fs::temp_directory_path() << '\n';
+  // std::cout << "Home directory is " << fs::user << '\n';
+
+  // p.wstring();
+  // std::cout << p.string() << std::endl;;
+
+  return 1;
 }
diff --git a/cpp/src/vereign/bytes/buffer.cc b/cpp/src/vereign/bytes/buffer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..30e202dcd66dc63869a99cdfcde147d11a0e663e
--- /dev/null
+++ b/cpp/src/vereign/bytes/buffer.cc
@@ -0,0 +1,179 @@
+#include <memory>
+#include <new>
+#include <stdexcept>
+#include <vereign/bytes/buffer.hh>
+
+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_;
+  while (size > cap - size_) {
+    cap = cap * 2;
+  }
+
+  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 size) {
+  if (size_ + size > cap_) {
+    throw std::runtime_error("cannot increment size pass the capacity");
+  }
+
+  size_ += size;
+}
+
+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 std::runtime_error("index out of bounds");
+  }
+
+  return data_[index];
+}
+
+auto Buffer::operator[](std::size_t index) const -> const uint8_t& {
+  if (index >= cap_) {
+    throw std::runtime_error("index out of bounds");
+  }
+
+  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..c2fe622b87d3d7bb15c9b094b1d458c1bc22f9eb
--- /dev/null
+++ b/cpp/src/vereign/bytes/buffer.hh
@@ -0,0 +1,54 @@
+#ifndef __VEREIGN_BYTES_BUFFER_HH
+#define __VEREIGN_BYTES_BUFFER_HH
+
+#include <vereign/bytes/view.hh>
+#include <cstring>
+
+namespace vereign::bytes {
+
+class Buffer {
+public:
+  Buffer() noexcept;
+  Buffer(std::size_t cap);
+  Buffer(View src);
+
+  Buffer(Buffer&& other) noexcept;
+  auto operator=(Buffer&& other) noexcept -> Buffer&;
+
+  // disable copying
+  Buffer(const Buffer&) = delete;
+  auto operator=(const Buffer&) -> Buffer& = delete;
+
+  ~Buffer();
+
+  auto begin() noexcept -> uint8_t*;
+  auto begin() const noexcept -> const uint8_t*;
+
+  auto end() noexcept -> uint8_t*;
+  auto end() const noexcept -> const uint8_t*;
+
+  auto operator[](std::size_t index) -> uint8_t&;
+  auto operator[](std::size_t index) const -> const uint8_t&;
+
+  auto Size() const noexcept -> std::size_t;
+  auto Cap() const noexcept -> std::size_t;
+  void Reserve(std::size_t size);
+  void Reset();
+
+  void IncSize(std::size_t size);
+  auto FreeCap() const noexcept -> std::size_t;
+  auto WriteWithinCap(bytes::View src) noexcept -> std::size_t;
+  auto Write(bytes::View src) -> std::size_t;
+
+  auto View() const noexcept -> bytes::View;
+  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/view.hh b/cpp/src/vereign/bytes/view.hh
new file mode 100644
index 0000000000000000000000000000000000000000..fb3f5b95ed72eed0da837cb984d4c557a69156d3
--- /dev/null
+++ b/cpp/src/vereign/bytes/view.hh
@@ -0,0 +1,91 @@
+#ifndef __VEREIGN_BYTES_VIEW_HH
+#define __VEREIGN_BYTES_VIEW_HH
+
+#include <cstring>
+#include <string>
+#include <string_view>
+#include <stdexcept>
+
+namespace vereign::bytes {
+
+class View {
+public:
+  View() noexcept : size_{0}, data_{nullptr} {}
+
+  View(const uint8_t* data, std::size_t size) noexcept
+    : size_{size},
+      data_{data}
+  {
+  }
+
+  View(std::string_view str) noexcept
+    : size_{str.length()},
+      data_{str.length() > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
+  {
+  }
+
+  View(const void* ptr, std::size_t size) noexcept
+    : size_{size},
+      data_{static_cast<const uint8_t*>(ptr)}
+  {
+  }
+
+  View(const View&) = default;
+  auto operator=(const View&) -> View& = default;
+
+  auto Slice(std::size_t start) const -> View {
+    if (start >= size_) {
+      return View(data_, 0);
+    }
+
+    return View(data_ + start, size_ - start);
+  }
+
+  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);
+  }
+
+  auto Data() const noexcept -> const uint8_t* {
+    return data_;
+  }
+
+  auto CharData() const noexcept -> const char* {
+    return reinterpret_cast<const char*>(data_);
+  }
+
+  auto Size() const noexcept -> std::size_t {
+    return size_;
+  }
+
+  auto String() noexcept -> std::string_view {
+    return std::string_view{CharData(), size_};
+  }
+
+  auto operator==(View other) const noexcept -> bool {
+    if (size_ != other.size_) {
+      return false;
+    }
+
+    return std::memcmp(data_, other.data_, size_) == 0;
+  }
+
+  auto operator[](std::size_t index) const -> const uint8_t& {
+    if (index >= size_ ) {
+      throw std::runtime_error("index out of bounds");
+    }
+
+    return data_[index];
+  }
+
+private:
+  std::size_t size_;
+  const uint8_t* data_;
+};
+
+} // 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..5f2708842ff04778acf323aa2d777d33fadb5d69
--- /dev/null
+++ b/cpp/src/vereign/bytes/view_dump.hh
@@ -0,0 +1,38 @@
+#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 {
+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
+
+
+auto dump(View buf) -> detail::ViewDump;
+auto dump(const Buffer& buf) -> detail::ViewDump;
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_VIEW_DUMP_HH
diff --git a/cpp/src/vereign/core/fs.cc b/cpp/src/vereign/core/fs.cc
new file mode 100644
index 0000000000000000000000000000000000000000..bf79c66acc39b0d7784c995822d8a0a1994dc898
--- /dev/null
+++ b/cpp/src/vereign/core/fs.cc
@@ -0,0 +1,16 @@
+#include <vereign/core/fs.hh>
+
+#include <boost/filesystem/operations.hpp>
+
+namespace vereign::core {
+
+RemoveFileGuard::RemoveFileGuard(boost::filesystem::path path)
+  : path_{std::move(path)}
+{
+}
+
+RemoveFileGuard::~RemoveFileGuard() {
+  boost::filesystem::remove(path_);
+}
+
+} // namespace vereign::core
diff --git a/cpp/src/vereign/core/fs.hh b/cpp/src/vereign/core/fs.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6eb1baded98a2c7a0bf70729f3f5784eab88d9c3
--- /dev/null
+++ b/cpp/src/vereign/core/fs.hh
@@ -0,0 +1,24 @@
+#ifndef __VEREIGN_CORE_FS_HH
+#define __VEREIGN_CORE_FS_HH
+
+#include <boost/filesystem/path.hpp>
+
+namespace vereign::core {
+
+/**
+ * A RAII guard that deletes a file upon destruction.
+ */
+class RemoveFileGuard {
+public:
+  RemoveFileGuard(boost::filesystem::path path);
+  ~RemoveFileGuard();
+
+  RemoveFileGuard(const RemoveFileGuard&) = delete;
+  auto operator=(const RemoveFileGuard&) -> RemoveFileGuard& = delete;
+private:
+  boost::filesystem::path path_;
+};
+
+} // namespace vereign::core
+
+#endif // __VEREIGN_CORE_FS_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/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/temp.cc b/cpp/src/vereign/core/temp.cc
new file mode 100644
index 0000000000000000000000000000000000000000..bce1880273af9a2ead7a7c8d9027fdf2dc199d61
--- /dev/null
+++ b/cpp/src/vereign/core/temp.cc
@@ -0,0 +1,16 @@
+#include <vereign/core/temp.hh>
+
+#include <vereign/core/rand.hh>
+#include <boost/filesystem/operations.hpp>
+
+namespace vereign::core {
+
+auto TempFilePath(boost::filesystem::path dir, std::string_view prefix) -> boost::filesystem::path {
+  return dir.append(prefix).concat(RandLowerAlpha(10));
+}
+
+auto TempFilePath(std::string_view prefix) -> boost::filesystem::path {
+  return TempFilePath(boost::filesystem::temp_directory_path(), prefix);
+}
+
+} // namespace vereign::core
diff --git a/cpp/src/vereign/core/temp.hh b/cpp/src/vereign/core/temp.hh
new file mode 100644
index 0000000000000000000000000000000000000000..bc8dec74849a05263c84368006bf66eaeb25e448
--- /dev/null
+++ b/cpp/src/vereign/core/temp.hh
@@ -0,0 +1,14 @@
+#ifndef __VEREIGN_CORE_TEMP_HH
+#define __VEREIGN_CORE_TEMP_HH
+
+#include <boost/filesystem/path.hpp>
+#include <string_view>
+
+namespace vereign::core {
+
+auto TempFilePath(boost::filesystem::path dir, std::string_view prefix) -> boost::filesystem::path;
+auto TempFilePath(std::string_view prefix) -> boost::filesystem::path;
+
+} // namespace vereign::core
+
+#endif // __VEREIGN_CORE_TEMP_HH
diff --git a/cpp/src/vereign/crypto/aes.cc b/cpp/src/vereign/crypto/aes.cc
new file mode 100644
index 0000000000000000000000000000000000000000..59875fb3dd19c268bf8d86f591f008c3109de9ae
--- /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& encrypted,
+  bytes::Buffer& tag
+) {
+  iv.Reserve(gcmIVSizeBytes);
+  crypto::Rand(iv);
+
+  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, tag.FreeCap(), 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..861c749b8757cb7188824de79aa430ed246d6d41
--- /dev/null
+++ b/cpp/src/vereign/crypto/aes.hh
@@ -0,0 +1,27 @@
+#ifndef __VEREIGN_CRYPTO_AES_HH
+#define __VEREIGN_CRYPTO_AES_HH
+
+#include "vereign/bytes/view.hh"
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::crypto::aes {
+
+void GCM256Encrypt(
+  bytes::View src,
+  bytes::View key,
+  bytes::Buffer& iv,
+  bytes::Buffer& encrypted,
+  bytes::Buffer& tag
+);
+
+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..8c1721430878d422d48de31e13d6f0931fa978b4
--- /dev/null
+++ b/cpp/src/vereign/crypto/bio.hh
@@ -0,0 +1,13 @@
+#ifndef __VEREIGN_CRYPTO_BIO_HH
+#define __VEREIGN_CRYPTO_BIO_HH
+
+#include <vereign/bytes/view.hh>
+#include <openssl/bio.h>
+
+namespace vereign::crypto::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..53f15d5b73791c6c3453665b541effe1e4f01968
--- /dev/null
+++ b/cpp/src/vereign/crypto/digest.hh
@@ -0,0 +1,12 @@
+#ifndef __VEREIGN_CRYPTO_DIGEST_HH
+#define __VEREIGN_CRYPTO_DIGEST_HH
+
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::crypto::digest {
+
+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..c5282ade4ae3a4ab91ecc0b60fbb479e8b88a47c
--- /dev/null
+++ b/cpp/src/vereign/crypto/errors.hh
@@ -0,0 +1,27 @@
+#ifndef __VEREIGN_CRYPTO_ERRORS_HH
+#define __VEREIGN_CRYPTO_ERRORS_HH
+
+#include <stdexcept>
+#include <openssl/err.h>
+
+namespace vereign::crypto {
+
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+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.hh b/cpp/src/vereign/crypto/rand.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4067b039adf2b80688b3f7af93340730b05081eb
--- /dev/null
+++ b/cpp/src/vereign/crypto/rand.hh
@@ -0,0 +1,23 @@
+#ifndef __VEREIGN_CRYPTO_RAND_HH
+#define __VEREIGN_CRYPTO_RAND_HH
+
+#include <vereign/bytes/buffer.hh>
+#include <vereign/crypto/errors.hh>
+
+#include <openssl/rand.h>
+
+namespace vereign::crypto {
+
+inline void Rand(bytes::Buffer& buf) {
+  int result = RAND_bytes(buf.end(), buf.FreeCap());
+  if (result == 0) {
+    ERR_clear_error();
+    throw Error("crypto rand failed");
+  }
+
+  buf.IncSize(buf.FreeCap());
+}
+
+} // 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..ab41d09e702ca3b6a9106a77ff5cbb2d1bd93fc8
--- /dev/null
+++ b/cpp/src/vereign/crypto/rsa.hh
@@ -0,0 +1,25 @@
+#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 {
+
+auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY>;
+
+void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted);
+void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted);
+
+auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
+
+auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+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..2bfaab5f159151b2a10330bf176ef7e0394ae167
--- /dev/null
+++ b/cpp/src/vereign/encoding/base64.hh
@@ -0,0 +1,14 @@
+#ifndef __VEREIGN_ENCODING_BASE64_HH
+#define __VEREIGN_ENCODING_BASE64_HH
+
+#include <vereign/bytes/buffer.hh>
+#include <string>
+
+namespace vereign::encoding::base64 {
+
+void Encode(bytes::View src, bytes::Buffer& encoded);
+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..0358fc236762f3d09cbf6ddd8a66f970e5f9a570
--- /dev/null
+++ b/cpp/src/vereign/encoding/binary.cc
@@ -0,0 +1,72 @@
+#include <vereign/encoding/binary.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 std::runtime_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) {
+    // FIXME: add encoding::Error exception
+    throw std::runtime_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 std::runtime_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..8d59c655446028fedec33532674166111b752930
--- /dev/null
+++ b/cpp/src/vereign/encoding/binary.hh
@@ -0,0 +1,24 @@
+#ifndef __VEREIGN_ENCODING_BINARY_HH
+#define __VEREIGN_ENCODING_BINARY_HH
+
+#include <stdexcept>
+#include <vereign/bytes/buffer.hh>
+
+// FIXME: add docs
+namespace vereign::encoding::binary {
+
+void EncodeUint8(bytes::Buffer& out, uint8_t v);
+auto DecodeUint8(bytes::View& b) -> uint8_t;
+
+void EncodeUint64(uint8_t* out, uint64_t v);
+void EncodeUint64(bytes::Buffer& out, uint64_t v);
+
+auto DecodeUint64(const uint8_t* b) -> uint64_t;
+auto DecodeUint64(bytes::View b) -> uint64_t;
+
+void EncodeBytes(bytes::Buffer& out, bytes::View bytes);
+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/hex.cc b/cpp/src/vereign/encoding/hex.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6c8dc18cf76bc8d93ae78e82c739ea70744e77a1
--- /dev/null
+++ b/cpp/src/vereign/encoding/hex.cc
@@ -0,0 +1,54 @@
+#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(std::string_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);
+}
+
+} // 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..495f09027acb88f9c7f4066fc33d29a05f476b49
--- /dev/null
+++ b/cpp/src/vereign/encoding/hex.hh
@@ -0,0 +1,14 @@
+#ifndef __VEREIGN_ENCODING_HEX_HH
+#define __VEREIGN_ENCODING_HEX_HH
+
+#include <vereign/bytes/buffer.hh>
+#include <string>
+
+namespace vereign::encoding::hex {
+
+void Encode(bytes::View src, bytes::Buffer& encoded);
+void Decode(bytes::View src, bytes::Buffer& decoded);
+
+} // vereign::encoding::hex
+
+#endif // __VEREIGN_ENCODING_HEX_HH
diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc
index fd3c911aae22ff35bb513772a27b7d8d3555b985..b5a85975a5927e5ee90355f235baeec5d7bf4ecd 100644
--- a/cpp/src/vereign/grpc/server.cc
+++ b/cpp/src/vereign/grpc/server.cc
@@ -2,8 +2,8 @@
 
 #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/service/gen/gen.hh>
+// #include <vereign/grpc/gen/gen.hh>
 #include <vereign/grpc/service_registry.hh>
 
 #include <vereign/service/passport_service.hh>
@@ -19,8 +19,7 @@
 
 #include <boost/asio/executor_work_guard.hpp>
 
-namespace vereign {
-namespace grpc {
+namespace vereign::grpc {
 
 namespace asio = boost::asio;
 
@@ -29,18 +28,14 @@ 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& vereignPort
   ) : 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
-    )},
+    client_session_{std::make_unique<restapi::ClientSession>(*client_)},
     server_{nullptr}
   {
 
@@ -57,7 +52,7 @@ public:
     services_registry_.RegisterIfNotExist<PassportAPI<service::PassportService>>(*client_session_);
 
     // register all generated services
-    grpc::gen::RegisterAll(*client_session_, services_registry_);
+    // grpc::gen::RegisterAll(*client_session_, services_registry_);
 
     services_registry_.RegisterIntoBuilder(builder);
 
@@ -109,11 +104,9 @@ private:
 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& vereignPort
 )
-  : impl_{std::make_unique<Impl>(listenAddress, vereignHost, vereignPort, publicKey)}
+  : impl_{std::make_unique<Impl>(listenAddress, vereignHost, vereignPort)}
 {
 }
 
@@ -129,5 +122,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..5ab7301ba1e614ad8f340948ab67d5508b613231 100644
--- a/cpp/src/vereign/grpc/server.hh
+++ b/cpp/src/vereign/grpc/server.hh
@@ -4,8 +4,7 @@
 #include <string>
 #include <memory>
 
-namespace vereign {
-namespace grpc {
+namespace vereign::grpc {
 
 /**
  * BindError is thrown when the Server::Server could not start listening.
@@ -39,9 +38,7 @@ public:
   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& vereignPort
   );
 
   /**
@@ -77,7 +74,6 @@ private:
   std::unique_ptr<Impl> impl_;
 };
 
-} // namespace grpc
 } // namespace vereign
 
 #endif
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..dd19a5f9b2c37f4c42ab6d181e7ee5e4c500b732
--- /dev/null
+++ b/cpp/src/vereign/identity/provider.cc
@@ -0,0 +1,80 @@
+#include "vereign/crypto/digest.hh"
+#include <vereign/identity/provider.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::ResetIdentity(const std::string& pin) -> std::string {
+  std::lock_guard<std::mutex> l{mu_};
+
+  storage_.Reset(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..80e17200920a334a7efd5f99ab84a6088971cc94
--- /dev/null
+++ b/cpp/src/vereign/identity/provider.hh
@@ -0,0 +1,33 @@
+#ifndef __VEREIGN_IDENTITY_PROVIDER_HH
+#define __VEREIGN_IDENTITY_PROVIDER_HH
+
+#include <vereign/kvstore/crypto_storage.hh>
+
+#include <mutex>
+
+namespace vereign::identity {
+
+class Provider {
+public:
+  Provider(kvstore::CryptoStorage& storage);
+
+  // disable copying
+  Provider(const kvstore::Storage&) = delete;
+  auto operator=(const kvstore::Storage&) -> Provider& = delete;
+
+  auto ResetIdentity(const std::string& pin) -> std::string;
+  auto LoadIdentity(const std::string& pin) -> std::string;
+  auto GetIdentityPublicKeyBase64() -> std::string;
+  auto GetDeviceHash() -> std::string;
+
+  ~Provider();
+
+private:
+  std::mutex mu_;
+
+  kvstore::CryptoStorage& storage_;
+};
+
+} // namespace vereign::identity
+
+#endif // __VEREIGN_IDENTITY_PROVIDER_HH
diff --git a/cpp/src/vereign/kvstore/crypto_storage.cc b/cpp/src/vereign/kvstore/crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4a24ea5d01b9b907b24929a33d991cafc1c81a52
--- /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(kvstore::Storage& storage)
+  : impl_{std::make_unique<detail::CryptoStorageImpl>(storage)}
+{}
+
+CryptoStorage::~CryptoStorage() = default;
+
+void CryptoStorage::Reset(const std::string& pin) {
+  impl_->Reset(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..38d2b8439e50aabb66a73ae42bcf4b468cf339aa
--- /dev/null
+++ b/cpp/src/vereign/kvstore/crypto_storage.hh
@@ -0,0 +1,32 @@
+#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;
+
+}
+
+class CryptoStorage {
+public:
+  CryptoStorage(Storage& storage);
+  ~CryptoStorage();
+
+  void Reset(const std::string& pin);
+  void Open(const std::string& pin);
+
+  void PutBytes(const std::string& key, bytes::View value);
+  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/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..66ecd53e8e80c36fca05871db698fcbda057bc4e
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -0,0 +1,120 @@
+#include <vereign/kvstore/detail/linux_crypto_storage.hh>
+
+#include <vereign/kvstore/detail/value_encoder.hh>
+#include <vereign/kvstore/errors.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/bio.hh>
+
+#include <openssl/base.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+
+namespace {
+  constexpr int iterations = 1 << 18;
+  constexpr int saltSizeBytes = 64;
+  constexpr int aesKeySizeBytes = 32;
+}
+
+namespace vereign::kvstore::detail {
+
+CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage)
+  : storage_{storage}
+{}
+
+void CryptoStorageImpl::Open(const std::string& pin) {
+  bytes::Buffer salt;
+  auto iterations = storage_.GetInt64("master_key_iterations");
+  if (iterations == 0) {
+    throw StorageNotInitializedError{};
+  }
+  storage_.GetBytes("master_key_salt", salt);
+
+  bytes::Buffer key{aesKeySizeBytes};
+
+  int result = PKCS5_PBKDF2_HMAC_SHA1(
+    pin.data(),
+    pin.length(),
+    salt.View().Data(),
+    salt.View().Size(),
+    iterations,
+    key.FreeCap(),
+    key.end()
+  );
+  if (result == 0) {
+    throw Error("key derivation failed");
+  }
+
+  key.IncSize(aesKeySizeBytes);
+
+  key_ = std::move(key);
+}
+
+void CryptoStorageImpl::Reset(const std::string& pin) {
+  bytes::Buffer salt{saltSizeBytes};
+  crypto::Rand(salt);
+
+  bytes::Buffer key{aesKeySizeBytes};
+
+  int result = PKCS5_PBKDF2_HMAC_SHA1(
+    pin.data(),
+    pin.length(),
+    salt.View().Data(),
+    salt.View().Size(),
+    iterations,
+    key.FreeCap(),
+    key.end()
+  );
+  if (result == 0) {
+    throw Error("key derivation failed");
+  }
+
+  key.IncSize(aesKeySizeBytes);
+
+  storage_.PutInt64("master_key_iterations", iterations);
+  storage_.PutBytes("master_key_salt", salt.View());
+
+  key_ = std::move(key);
+}
+
+void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
+  if (key_.Size() == 0) {
+    throw Error("crypto storage is not initialized");
+  }
+
+  bytes::Buffer iv;
+  bytes::Buffer tag;
+  bytes::Buffer encrypted;
+
+  crypto::aes::GCM256Encrypt(value, key_.View(), iv, encrypted, tag);
+
+  bytes::Buffer encoded_value;
+  EncodeEncryptedValue(encoded_value, iv.View(), tag.View(), encrypted.View());
+
+  storage_.PutBytes(key, encoded_value.View());
+}
+
+void CryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) {
+  if (key_.Size() == 0) {
+    throw Error("crypto storage is 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);
+}
+
+
+} // 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..1a0cdc926400d2a3f235afa3180a4ecacce91064
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
@@ -0,0 +1,31 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+
+namespace vereign::kvstore::detail {
+
+class CryptoStorageImpl {
+public:
+  CryptoStorageImpl(kvstore::Storage& storage);
+
+  // disable copying
+  CryptoStorageImpl(const CryptoStorageImpl&) = delete;
+  auto operator=(const CryptoStorageImpl&) -> CryptoStorageImpl& = delete;
+
+  void Reset(const std::string& pin);
+  void Open(const std::string& pin);
+
+  void PutBytes(const std::string& key, bytes::View value);
+  void GetBytes(const std::string& key, bytes::Buffer& value);
+
+private:
+  kvstore::Storage& storage_;
+
+  bytes::Buffer key_;
+};
+
+} // namespace vereign::kvstore::detail
+
+
+#endif // __VEREIGN_KVSTORE_DETAIL_LINUX_CRYPTO_STORAGE_HH
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..101b77fa08f04c73ecf940239625c6c2de8c5c71
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/value_encoder.hh
@@ -0,0 +1,49 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
+#define __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/buffer.hh>
+#include <vereign/encoding/binary.hh>
+
+#include <vereign/kvstore/errors.hh>
+
+namespace vereign::kvstore::detail {
+
+// FIXME: move to implementation file
+inline 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);
+}
+
+inline 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
+
+#endif // __VEREIGN_KVSTORE_DETAIL_VALUE_ENCODER_HH
diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..694b3c5d38c0a4b91c134ed56ae9b397d0c17a49
--- /dev/null
+++ b/cpp/src/vereign/kvstore/errors.hh
@@ -0,0 +1,26 @@
+#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 StorageNotInitializedError : public Error {
+public:
+  StorageNotInitializedError()
+    : Error{"storage is not initialized"}
+  {
+  }
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_ERRORS_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..390c2cb4fd462068457288aefe93b9d200393bf0
--- /dev/null
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -0,0 +1,81 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/encoding/binary.hh>
+
+#include <array>
+
+namespace vereign::kvstore {
+
+SqliteStorage::SqliteStorage(const std::string& db_path)
+  : db_{db_path}
+{
+  db_.Execute(R"(
+CREATE TABLE IF NOT EXISTS storage (
+  key TEXT PRIMARY KEY NOT NULL,
+  value BLOB
+);
+  )");
+}
+
+void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
+  auto tr = db_.BeginExplicitTransaction();
+
+  auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
+  stmt.BindText(1, key);
+  stmt.BindBlob(2, value);
+  stmt.Step();
+
+  tr.Commit();
+}
+
+void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
+  auto tr = db_.BeginExplicitTransaction();
+
+  auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
+  stmt.BindText(1, key);
+  auto end = stmt.Step();
+
+  if (!end) {
+    value.Write(stmt.GetColumnBlob(0));
+  }
+
+  tr.Commit();
+}
+
+void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
+  auto tr = db_.BeginExplicitTransaction();
+  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();
+
+  tr.Commit();
+}
+
+auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
+  auto tr = db_.BeginExplicitTransaction();
+
+  auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
+  stmt.BindText(1, key);
+  auto end = stmt.Step();
+
+  if (end) {
+    return 0;
+  }
+
+  auto buf = stmt.GetColumnBlob(0);
+  if (buf.Size() != 8) {
+    // FIXME: probably throw an exception here
+    return 0;
+  }
+
+  int64_t result = encoding::binary::DecodeUint64(buf);
+
+  tr.Commit();
+
+  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..ef91c50e0a40dec5b07c16355a13de1bf01b7d4a
--- /dev/null
+++ b/cpp/src/vereign/kvstore/sqlite_storage.hh
@@ -0,0 +1,25 @@
+#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 {
+
+class SqliteStorage : public Storage {
+public:
+  SqliteStorage(const std::string& db_path);
+
+  void PutBytes(const std::string& key, bytes::View value) override;
+  void GetBytes(const std::string& key, bytes::Buffer& value) override;
+
+  void PutInt64(const std::string& key, int64_t value) override;
+  auto GetInt64(const std::string& key) -> int64_t override;
+
+private:
+  sqlite::Connection db_;
+};
+
+} // 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..a8adf3d756d070b949f1ac9c443ec284fb357b07
--- /dev/null
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -0,0 +1,21 @@
+#ifndef __VEREIGN_KVSTORE_STORAGE_HH
+#define __VEREIGN_KVSTORE_STORAGE_HH
+
+#include <vereign/bytes/buffer.hh>
+
+namespace vereign::kvstore {
+
+class Storage {
+public:
+  virtual void PutBytes(const std::string& key, bytes::View value) = 0;
+  virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
+
+  virtual void PutInt64(const std::string& key, int64_t value) = 0;
+  virtual auto GetInt64(const std::string& key) -> int64_t = 0;
+
+  virtual ~Storage() = default;
+};
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_STORAGE_HH
diff --git a/cpp/src/vereign/restapi/client.cc b/cpp/src/vereign/restapi/client.cc
index 1dd1b8cc9e874ce972ff6725af6ce3768759a4c5..fe22698c3330d009e9a727024e60853a39896029 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,12 @@
 #include <boost/beast/version.hpp>
 #include <boost/asio/dispatch.hpp>
 
+#include <chrono>
+
+namespace {
+  constexpr std::string_view httpUserAgent = "Vereign Client Library";
+}
+
 namespace vereign {
 namespace restapi {
 
@@ -21,7 +25,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 +47,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 +66,10 @@ Client::~Client() noexcept {
   Close();
 }
 
+auto Client::UserAgent() const -> const std::string& {
+  return user_agent_;
+}
+
 void Client::Close() {
   asio::post(
     executor_,
@@ -213,7 +223,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..cc8fe0d267bf2da188beb499e6fbcb68298b9a7c 100644
--- a/cpp/src/vereign/restapi/client.hh
+++ b/cpp/src/vereign/restapi/client.hh
@@ -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..3b6eae9866c4a068c7ef8ea6eae45d7b90722228
--- /dev/null
+++ b/cpp/src/vereign/service/identity_service.cc
@@ -0,0 +1,66 @@
+#include "vereign/service/gen/identity_service.hh"
+#include "vereign/client_library/common_types.pb.h"
+#include <vereign/service/identity_service.hh>
+
+#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(
+  identity::Provider& identity_provider,
+  restapi::ClientSession& client_session
+)
+  : gen::IdentityService{client_session},
+    identity_provider_{identity_provider},
+    client_session_{client_session}
+{}
+
+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_.ResetIdentity(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..02acf73d485a3d301657a7b975fb5edf70ed9a77
--- /dev/null
+++ b/cpp/src/vereign/service/identity_service.hh
@@ -0,0 +1,58 @@
+#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:
+  IdentityService(
+    identity::Provider& identity_provider,
+    restapi::ClientSession& client_session
+  );
+
+  IdentityService(const IdentityService&) = delete;
+  auto operator=(const IdentityService&) -> IdentityService& = delete;
+
+  void LoginWithExistingPubKey(
+    const client_library::LoginWithExistingPubKeyForm* req,
+    client_library::EmptyResponse* resp
+  );
+
+  void LoginWithNewDevice(
+    const client_library::LoginFormNewDevice* req,
+    client_library::LoginFormNewDeviceResponse* resp
+  );
+
+  void LoginWithPreviouslyAddedDevice(
+    const client_library::LoginFormPreviousAddedDevice* req,
+    client_library::EmptyResponse* resp
+  );
+
+private:
+  identity::Provider& identity_provider_;
+  restapi::ClientSession& client_session_;
+};
+
+} // namespace service
+} // namespace vereign
+
+
+#endif // __VEREIGN_SERVICE_IDENITY_SERVICE_HH
diff --git a/cpp/src/vereign/sqlite/connection.cc b/cpp/src/vereign/sqlite/connection.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d5af4f388aff59a068d51de0879176379dead23f
--- /dev/null
+++ b/cpp/src/vereign/sqlite/connection.cc
@@ -0,0 +1,169 @@
+#include <vereign/sqlite/connection.hh>
+#include <vereign/sqlite/errors.hh>
+
+#include <vereign/core/scope_guard.hh>
+
+#include <fmt/format.h>
+#include <sqlite3.h>
+#include <memory>
+
+namespace vereign::sqlite {
+
+Transaction::Transaction(sqlite3* db)
+  : finished_{false},
+    db_{db}
+{}
+
+Transaction::Transaction(Transaction&& other)
+  : finished_{other.finished_},
+    db_{other.db_}
+{
+  other.finished_ = true;
+}
+
+void Transaction::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));
+  }
+
+  finished_ = true;
+}
+
+void Transaction::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));
+  }
+
+  finished_ = true;
+}
+
+Transaction::~Transaction() {
+  if (!finished_) {
+    Rollback();
+  }
+}
+
+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_))};
+  }
+}
+
+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_);
+}
+
+auto Connection::BeginExplicitTransaction() -> Transaction {
+  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));
+  }
+
+  return Transaction{db_};
+}
+
+
+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..ffa4ef85b885675e8bd662c9bd436ec492a1a675
--- /dev/null
+++ b/cpp/src/vereign/sqlite/connection.hh
@@ -0,0 +1,78 @@
+#ifndef __VEREIGN_SQLITE_CONNECTION_HH
+#define __VEREIGN_SQLITE_CONNECTION_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/bytes.hh>
+
+#include <string>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+namespace vereign::sqlite {
+
+class Transaction {
+private:
+  friend class Connection;
+
+  explicit Transaction(sqlite3* db);
+  Transaction(Transaction&&);
+
+public:
+  void Commit();
+  void Rollback();
+
+  ~Transaction();
+
+  Transaction(const Transaction&) = delete;
+  auto operator=(const Transaction&) -> Transaction& = delete;
+
+private:
+  bool finished_;
+  sqlite3* db_;
+};
+
+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_;
+};
+
+class Connection {
+public:
+  Connection(const std::string& path);
+  ~Connection() noexcept;
+
+  Connection(const Connection&) = delete;
+  auto operator=(const Connection&) -> Connection& = delete;
+
+  auto BeginExplicitTransaction() -> Transaction;
+  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..14fa1d81431e2cb4dfe555d292b1952f10bd80c7
--- /dev/null
+++ b/cpp/src/vereign/sqlite/errors.hh
@@ -0,0 +1,30 @@
+#ifndef __VEREIGN_SQLITE_ERROR_HH
+#define __VEREIGN_SQLITE_ERROR_HH
+
+#include <stdexcept>
+
+namespace vereign::sqlite {
+
+class Error : virtual std::exception {
+public:
+  Error(int code, std::string&& msg)
+    : code_{code},
+      msg_{std::move(msg)}
+  {}
+
+  auto what() const noexcept -> const char* override {
+    return msg_.data();
+  }
+
+  auto code() const noexcept -> int {
+    return code_;
+  }
+
+private:
+  int code_;
+  std::string msg_;
+};
+
+} // namespace vereign::sqlite
+
+#endif // __VEREIGN_SQLITE_CONNECTION_HH
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 2c2b6fce316e7721ed05a80e53330200081a4486..31ba5f294ee8062cf252096588ebfa17d9f289f9 100644
--- a/cpp/tests/vereign/CMakeLists.txt
+++ b/cpp/tests/vereign/CMakeLists.txt
@@ -14,11 +14,27 @@ 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
+
+  crypto/aes_test.cc
+  crypto/rsa_test.cc
+  crypto/digest_test.cc
+
   restapi/client_test.cc
   restapi/client_session_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
 )
diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4989c0675fcca437d527000e0924f3662b311e74
--- /dev/null
+++ b/cpp/tests/vereign/crypto/aes_test.cc
@@ -0,0 +1,26 @@
+#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"};
+  bytes::Buffer key{32};
+  crypto::Rand(key);
+
+  bytes::Buffer iv;
+  bytes::Buffer tag;
+  bytes::Buffer encrypted;
+
+  crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, encrypted, tag);
+
+  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..c9356e1f456547b6137ca8423709e7c75ba9de55
--- /dev/null
+++ b/cpp/tests/vereign/crypto/rsa_test.cc
@@ -0,0 +1,102 @@
+#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(4096);
+
+    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(4096);
+
+    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(4096);
+
+    bytes::Buffer input{470};
+    crypto::Rand(input);
+    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(4096);
+
+    bytes::Buffer input{471};
+    crypto::Rand(input);
+    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..2038a634f9a27e24b85b77aec670b7d432e2be52
--- /dev/null
+++ b/cpp/tests/vereign/encoding/base64_test.cc
@@ -0,0 +1,87 @@
+#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") {
+    bytes::Buffer input{1024};
+    crypto::Rand(input);
+    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/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/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..e00956cea6e85bed97ea8598a73c3397f0ec6405 100644
--- a/cpp/tests/vereign/grpc/server_test.cc
+++ b/cpp/tests/vereign/grpc/server_test.cc
@@ -14,7 +14,7 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
   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};
+  vereign::grpc::Server server{"localhost:", host, port};
   auto on_exit = vereign::core::MakeScopeGuard([&server] {
     server.Shutdown();
   });
@@ -34,9 +34,9 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
   // std::cout << vereign::test::ProtobufToJson(resp) << std::endl;
 
   REQUIRE(status.error_message() == "");
-  REQUIRE(resp.error() == "");
+  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);
diff --git a/cpp/tests/vereign/identity/provider_test.cc b/cpp/tests/vereign/identity/provider_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d72215ea12119f3c592228a27ac24db60cc05285
--- /dev/null
+++ b/cpp/tests/vereign/identity/provider_test.cc
@@ -0,0 +1,41 @@
+#include <vereign/identity/provider.hh>
+
+#include <vereign/core/fs.hh>
+#include <vereign/core/temp.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::ResetIdentity", "[vereign/identity]") {
+  auto storage_path = core::TempFilePath("test_db_");
+  core::RemoveFileGuard rm{storage_path};
+
+  std::string expected;
+  std::string actual;
+
+  {
+    kvstore::SqliteStorage kvstorage{storage_path.string()};
+    kvstore::CryptoStorage storage{kvstorage};
+
+    identity::Provider provider{storage};
+    expected = provider.ResetIdentity("foo");
+  }
+
+  {
+    kvstore::SqliteStorage kvstorage{storage_path.string()};
+    kvstore::CryptoStorage storage{kvstorage};
+
+    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..b381e8e373d18c05e4525f4458eab50f88dd5e98
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -0,0 +1,63 @@
+#include <vereign/kvstore/crypto_storage.hh>
+
+#include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/core/temp.hh>
+#include <vereign/core/fs.hh>
+#include <vereign/core/scope_guard.hh>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <iostream>
+
+using namespace vereign;
+
+TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
+  auto storage_path = core::TempFilePath("test_db_");
+  core::RemoveFileGuard rm{storage_path};
+
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+    kvstore::CryptoStorage storage{kvstorage};
+
+    storage.Reset("foo");
+    std::string v{"test value"};
+    storage.PutBytes("test", bytes::View(v));
+  }
+
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+    kvstore::CryptoStorage storage{kvstorage};
+
+    bytes::Buffer v;
+    storage.Open("foo");
+    storage.GetBytes("test", v);
+
+    CHECK(v.View().String() == "test value");
+  }
+
+  bytes::Buffer big_value{100000};
+  crypto::Rand(big_value);
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+    kvstore::CryptoStorage storage{kvstorage};
+
+    storage.Reset("foo");
+    storage.PutBytes("test", big_value.View());
+  }
+
+  {
+    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+    kvstore::CryptoStorage storage{kvstorage};
+
+    bytes::Buffer v;
+    storage.Open("foo");
+    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);
+  }
+}
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..74a9516db0ed7a86f5b0a8f32fdf6918f8627586 100644
--- a/cpp/tests/vereign/service/gen/passport_service_test.cc
+++ b/cpp/tests/vereign/service/gen/passport_service_test.cc
@@ -1,11 +1,18 @@
 #include <vereign/service/gen/passport_service.hh>
+
+#include <vereign/core/temp.hh>
+#include <vereign/core/fs.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 +32,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::core::TempFilePath("test_db_");
+  vereign::core::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = vereign::kvstore::SqliteStorage(storage_path.string());
+  vereign::kvstore::CryptoStorage storage{kvstorage};
+  vereign::identity::Provider provider{storage};
+  vereign::service::IdentityService idenity_service{provider, client_session};
+
   std::thread ioc_thread([&ioc]{
     ioc.run();
   });
@@ -39,6 +54,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 +74,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..f793944772decf11e213a7f5502190d8873bae61
--- /dev/null
+++ b/cpp/tests/vereign/service/identity_service_test.cc
@@ -0,0 +1,121 @@
+#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/core/temp.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_device_ctx = test::ServiceContext{host, port, core::TempFilePath("test_db_")};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto service_context = test::ServiceContext{host, port, core::TempFilePath("test_db_")};
+  auto service = service::IdentityService{
+    service_context.IdentityProvider(),
+    service_context.ClientSession()
+  };
+
+  // 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 (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");
+
+  // prepare new device
+  auto storage_path = core::TempFilePath("test_db_");
+  auto old_device_ctx = test::ServiceContext{host, port, core::TempFilePath("test_db_")};
+  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.IdentityProvider(),
+    service_context.ClientSession()
+  };
+
+  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 (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..6713ffbe147db3d8ea22fe91e4c7b6865e1ec044
--- /dev/null
+++ b/cpp/tests/vereign/test/device.cc
@@ -0,0 +1,122 @@
+#include <vereign/test/device.hh>
+
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/core/temp.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_.IdentityProvider(),
+      service_context_.ClientSession()
+    )}
+{
+}
+
+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.IdentityProvider(),
+    service_context.ClientSession()
+  };
+
+  // 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 boost::filesystem::path& storage_path
+) {
+  auto old_device_ctx = test::ServiceContext{host, port, core::TempFilePath("test_db_")};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto service_context = test::ServiceContext{host, port, storage_path};
+  old_device.CreateNewDevice(service_context, pin);
+}
+
+} // 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..225289f7d1325072b4520ce48d1f403e1d206bb5
--- /dev/null
+++ b/cpp/tests/vereign/test/device.hh
@@ -0,0 +1,48 @@
+#ifndef __TESTS_VEREIGN_TEST_DEVICE_HH
+#define __TESTS_VEREIGN_TEST_DEVICE_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 <memory>
+#include <thread>
+
+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 boost::filesystem::path& 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..7f326c9cf81259d2c4fa76f1da9f0c05ff88ae2b
--- /dev/null
+++ b/cpp/tests/vereign/test/service_context.cc
@@ -0,0 +1,61 @@
+#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 <vereign/core/temp.hh>
+
+#include <boost/filesystem/operations.hpp>
+
+namespace vereign::test {
+
+ServiceContext::ServiceContext(
+  const std::string& vereign_host,
+  const std::string& vereign_port,
+  boost::filesystem::path 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_.string())},
+    storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_)},
+    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_;
+}
+
+auto ServiceContext::StoragePath() const -> const boost::filesystem::path& {
+  return storage_path_;
+}
+
+void ServiceContext::Shutdown() {
+  client_session_->Close();
+
+  work_guard_.reset();
+  if (service_thread_.joinable()) {
+    service_thread_.join();
+  }
+
+  boost::filesystem::remove(storage_path_);
+}
+
+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..a80e43657a49ce4f052e963c26fca8fafc8ea998
--- /dev/null
+++ b/cpp/tests/vereign/test/service_context.hh
@@ -0,0 +1,64 @@
+#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,
+    boost::filesystem::path storage_path
+  );
+  ~ServiceContext();
+
+  ServiceContext(const ServiceContext&) = delete;
+  auto operator=(const ServiceContext&) -> ServiceContext& = delete;
+
+  auto IdentityProvider() -> identity::Provider&;
+  auto ClientSession() -> restapi::ClientSession&;
+  auto StoragePath() const -> const boost::filesystem::path&;
+
+  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_;
+  boost::filesystem::path 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>
+)