diff --git a/cpp/proto b/cpp/proto
index 3e82fc7d60056e08b041577e332f1ec5029633ee..c581306f8816e5e8b708c9097e402354bb41b84a 160000
--- a/cpp/proto
+++ b/cpp/proto
@@ -1 +1 @@
-Subproject commit 3e82fc7d60056e08b041577e332f1ec5029633ee
+Subproject commit c581306f8816e5e8b708c9097e402354bb41b84a
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index 520ad243c7ff982e01c06d0d7539274c96135545..1e0e2b78089ee9986c27cdbed8308de8d7a7ffaa 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -49,6 +49,7 @@ endif()
 set(VEREIGNLIB_SRC
   vereign/core/rand.cc
   vereign/core/string.cc
+  vereign/core/time.cc
   vereign/fs/util.cc
   vereign/fs/operations.cc
   vereign/fs/path.cc
@@ -76,6 +77,7 @@ set(VEREIGNLIB_SRC
   vereign/crypto/rsa.cc
   vereign/crypto/bio.cc
   vereign/crypto/digest.cc
+  vereign/crypto/cert.cc
 
   vereign/kvstore/lock.cc
   vereign/kvstore/detail/base_crypto_storage.cc
@@ -116,7 +118,7 @@ target_link_libraries(vereignlib PUBLIC
   gRPC::grpc++_reflection
   gRPC::grpc++
   Boost::filesystem
-  $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
+  Boost::date_time
   SQLite::SQLite3
   $<$<CXX_COMPILER_ID:MSVC>:ncrypt.lib>
   $<$<CXX_COMPILER_ID:MSVC>:cryptui.lib>
@@ -145,10 +147,12 @@ set(csandbox_sources
 
 add_executable(csandbox ${csandbox_sources})
 
-target_link_libraries(csandbox
-  PRIVATE vereignlib
-  $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
-  Boost::filesystem
+target_link_libraries(csandbox PRIVATE
+  # OpenSSL::Crypto
+  # OpenSSL::SSL
+  vereignlib
+  # $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time>
+  # Boost::filesystem
   # Boost::file
   # Boost::thread
   # vereign
diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc
index 70382932622480f68ad621fb1f2582e0999d247d..5b7057af6b90f36d495c2110344a6dfa3abd31d6 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -1,17 +1,22 @@
-#include "vereign/crypto/rand.hh"
-#include <vereign/bytes/view.hh>
-#include <vereign/bytes/buffer.hh>
+#include <vereign/crypto/cert.hh>
+
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/bio.hh>
 #include <vereign/bytes/view_dump.hh>
-#include <vereign/fs/path.hh>
 #include <vereign/fs/util.hh>
+#include <vereign/core/time.hh>
+#include <boost/date_time.hpp>
+
+#include <openssl/x509v3.h>
+
+#include <chrono>
 #include <iostream>
+#include <fstream>
 
 auto main(int argc, char** argv) -> int {
   argc = 0;
   argv = nullptr;
-  auto path = vereign::fs::TempFilePath(vereign::fs::path::Join("tmp", "foo"), "test_db_");
-
-  std::cout << path << std::endl;
 
   return 0;
 }
diff --git a/cpp/src/vereign/core/time.cc b/cpp/src/vereign/core/time.cc
new file mode 100644
index 0000000000000000000000000000000000000000..33030082a682d2bc3091602a24d5e39c22da4b96
--- /dev/null
+++ b/cpp/src/vereign/core/time.cc
@@ -0,0 +1,73 @@
+#include <vereign/core/time.hh>
+
+namespace vereign::time {
+
+auto Epoch() -> boost::posix_time::ptime {
+  static const auto epoch = boost::posix_time::ptime{boost::gregorian::date{1970, 1, 1}};
+
+  return epoch;
+}
+
+auto PosixTimeToTime(boost::posix_time::ptime t) -> time_t {
+  return time_t((t - Epoch()).total_seconds());
+}
+
+auto MakePosixTime(
+  int year,
+  int month,
+  int day,
+  int hours,
+  int minutes,
+  int seconds
+) -> boost::posix_time::ptime {
+  using namespace boost::posix_time;
+  using namespace boost::gregorian;
+
+  return ptime(
+    date{
+      static_cast<date::year_type>(year),
+      static_cast<date::month_type>(month),
+      static_cast<date::day_type>(day)
+    },
+    time_duration(hours, minutes, seconds)
+  );
+}
+
+auto MakeTimeUTC(
+  int year,
+  int month,
+  int day,
+  int hours,
+  int seconds,
+  int milliseconds
+) -> time_t {
+  using namespace boost::posix_time;
+  using namespace boost::gregorian;
+
+  return PosixTimeToTime(
+    ptime(
+      date{
+        static_cast<date::year_type>(year),
+        static_cast<date::month_type>(month),
+        static_cast<date::day_type>(day)
+      },
+      time_duration(hours, seconds, milliseconds)
+    )
+  );
+}
+
+auto MakeTimeUTCFromString(const std::string& str) -> time_t {
+  using namespace boost::posix_time;
+  using namespace boost::gregorian;
+
+  return PosixTimeToTime(boost::posix_time::time_from_string(str));
+}
+
+auto MakeTimeUTCFromISO(const std::string& str) -> time_t {
+  using namespace boost::posix_time;
+  using namespace boost::gregorian;
+
+  return PosixTimeToTime(from_iso_string(str));
+}
+
+} // vereign::time
diff --git a/cpp/src/vereign/core/time.hh b/cpp/src/vereign/core/time.hh
new file mode 100644
index 0000000000000000000000000000000000000000..5b7d775606d25a7c2ac264d543bbbdeac833088c
--- /dev/null
+++ b/cpp/src/vereign/core/time.hh
@@ -0,0 +1,72 @@
+#ifndef __VEREIGN_CORE_TIME_HH
+#define __VEREIGN_CORE_TIME_HH
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace vereign::time {
+
+/**
+ * Returns the gregorian epoch boost posix time - 1970:01:01.
+ */
+auto Epoch() -> boost::posix_time::ptime;
+
+/**
+ * Converts boost posix time to time_t timestamp.
+ *
+ * @param t The source boost posix time.
+ * @returns timestamp.
+ */
+auto PosixTimeToTime(boost::posix_time::ptime t) -> time_t;
+
+/**
+ * Creates boost posix timetamp.
+ *
+ * @param year Year component.
+ * @param month Month component 1-12.
+ * @param day Day component 1-31.
+ * @param hours The hours component 0-23.
+ * @param minutes The minutes component 0-59.
+ * @param seconds The minutes component 0-59.
+ * @returns posix time.
+ */
+auto MakePosixTime(
+  int year,
+  int month,
+  int day,
+  int hours,
+  int minutes,
+  int seconds
+) -> boost::posix_time::ptime;
+
+/**
+ * Creates UTC timestamp.
+ *
+ * @param year Year component.
+ * @param month Month component 1-12.
+ * @param day Day component 1-31.
+ * @param hours The hours component 0-23.
+ * @param minutes The minutes component 0-59.
+ * @param seconds The minutes component 0-59.
+ * @returns timestamp.
+ */
+auto MakeTimeUTC(int year, int month, int day, int hours, int seconds, int milliseconds) -> time_t;
+
+/**
+ * Creates UTC timestamp from a string.
+ *
+ * @param str Time string representation, example: "2002-01-20 23:59:59.000".
+ * @returns timestamp.
+ */
+auto MakeTimeUTCFromString(const std::string& str) -> time_t;
+
+/**
+ * Creates UTC timestamp from ISO formatted string.
+ *
+ * @param str Time string representation, example: "20020120T235959".
+ * @returns timestamp.
+ */
+auto MakeTimeUTCFromISO(const std::string& str) -> time_t;
+
+} // vereign::time
+
+#endif // __VEREIGN_CORE_TIME_HH
diff --git a/cpp/src/vereign/crypto/cert.cc b/cpp/src/vereign/crypto/cert.cc
new file mode 100644
index 0000000000000000000000000000000000000000..bc6f84d2d6f74904056f6b5bfe7c4c9d5402d2a4
--- /dev/null
+++ b/cpp/src/vereign/crypto/cert.cc
@@ -0,0 +1,428 @@
+#include <vereign/crypto/cert.hh>
+
+#include <vereign/crypto/bio.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/encoding/base64.hh>
+
+#include <openssl/x509v3.h>
+#include <openssl/digest.h>
+#include <openssl/pem.h>
+#include <sstream>
+
+namespace {
+  constexpr const char* certDefaultHashAlg = "SHA256";
+  constexpr const int certVersion = 2;
+}
+
+namespace vereign::crypto::cert {
+
+static auto addCertExtension(
+  const X509* issuer_cert,
+  X509* cert,
+  int nid,
+  const std::string& value
+) -> int {
+  X509V3_CTX ctx;
+
+  // This sets the 'context' of the extensions.
+  // No configuration database
+  X509V3_set_ctx_nodb(&ctx);
+
+  // Issuer and subject certs: both the target since it is self signed,
+  // no request and no CRL
+  X509V3_set_ctx(&ctx, const_cast<X509*>(issuer_cert), cert, nullptr, nullptr, 0);
+  bssl::UniquePtr<X509_EXTENSION> ex{X509V3_EXT_nconf_nid(
+    nullptr,
+    &ctx,
+    nid,
+    const_cast<std::string&>(value).data()
+  )};
+  if (!ex) {
+    return 0;
+  }
+
+  return X509_add_ext(cert, ex.get(), -1);
+}
+
+static void addSubjAltNameExt(X509* cert, const std::string& email, const std::string& url) {
+  auto gens = bssl::UniquePtr<GENERAL_NAMES>(sk_GENERAL_NAME_new_null());
+  if (!gens) {
+    throw OpenSSLError("creating GENERAL_NAMES stack failed");
+  }
+
+  if (!email.empty()) {
+    auto gen = bssl::UniquePtr<GENERAL_NAME>(GENERAL_NAME_new());
+    if (!gen) {
+      throw OpenSSLError("creating GENERAL_NAME failed");
+    }
+
+    auto ia5 = bssl::UniquePtr<ASN1_IA5STRING>(ASN1_IA5STRING_new());
+    if (!ia5) {
+      throw OpenSSLError("creating ASN1_IA5STRING failed");
+    }
+
+    auto r = ASN1_STRING_set(ia5.get(), email.data(), -1);
+    if (r != 1) {
+      throw OpenSSLError("set certificate alternative name email part failed");
+    }
+
+    GENERAL_NAME_set0_value(gen.get(), GEN_EMAIL, ia5.release());
+    r = sk_GENERAL_NAME_push(gens.get(), gen.release());
+    if (r == 0) {
+      throw OpenSSLError("pushing email to certificate subject alternative name failed");
+    }
+  }
+
+  if (!url.empty()) {
+    auto gen = bssl::UniquePtr<GENERAL_NAME>(GENERAL_NAME_new());
+    if (!gen) {
+      throw OpenSSLError("creating GENERAL_NAME failed");
+    }
+
+    auto ia5 = bssl::UniquePtr<ASN1_IA5STRING>(ASN1_IA5STRING_new());
+    if (!ia5) {
+      throw OpenSSLError("creating ASN1_IA5STRING failed");
+    }
+
+    auto r = ASN1_STRING_set(ia5.get(), url.data(), -1);
+    if (r != 1) {
+      throw OpenSSLError("set certificate alternative name URL part failed");
+    }
+
+    GENERAL_NAME_set0_value(gen.get(), GEN_DNS, ia5.release());
+    r = sk_GENERAL_NAME_push(gens.get(), gen.release());
+    if (r == 0) {
+      throw OpenSSLError("pushing URL to certificate subject alternative name failed");
+    }
+  }
+
+  auto r = X509_add1_ext_i2d(cert, NID_subject_alt_name, gens.get(), 0, 0);
+  if (r != 1) {
+    throw OpenSSLError("set certificate alternative name failed");
+  }
+}
+
+static void setCertSubject(const CertData& cert_data, X509* cert) {
+  const CertSubject& subject = cert_data.Subject;
+  auto subject_name = X509_get_subject_name(cert);
+  int r = 0;
+
+  if (!subject.CommonName.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_commonName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.CommonName.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Common Name failed");
+    }
+  }
+
+  if (!subject.Country.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_countryName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.Country.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Country Name failed");
+    }
+  }
+
+  if (!subject.Locality.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_localityName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.Locality.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Locality Name failed");
+    }
+  }
+
+  if (!subject.State.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_stateOrProvinceName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.State.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject State Name failed");
+    }
+  }
+
+  if (!subject.Organization.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_organizationName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.Organization.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Organization Name failed");
+    }
+  }
+
+  if (!subject.OrganizationUnit.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_organizationalUnitName,
+      MBSTRING_ASC,
+      (unsigned char*) subject.OrganizationUnit.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Organizational Unit Name failed");
+    }
+  }
+
+
+  if (!cert_data.Email.empty()) {
+    r = X509_NAME_add_entry_by_NID(
+      subject_name,
+      NID_pkcs9_emailAddress,
+      MBSTRING_ASC,
+      (unsigned char*) cert_data.Email.data(),
+      -1,
+      -1,
+      0
+    );
+    if (r != 1) {
+      throw OpenSSLError("set certificate subject Email Address failed");
+    }
+  }
+}
+
+static void addCertExtensions(const CertData& cert_data, const X509* issuer_cert, X509* cert) {
+  // basic constraints
+  const std::string basic_constraints = cert_data.IsCA ? "critical,CA:TRUE" : "critical,CA:FALSE";
+  auto r = addCertExtension(issuer_cert, cert, NID_basic_constraints, basic_constraints);
+  if (r != 1) {
+    throw OpenSSLError("set certificate basic constraints failed");
+  }
+
+  // key usage
+  const std::string key_usage = cert_data.IsCA
+    ? "critical,digitalSignature,keyCertSign,cRLSign"
+    : "critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment";
+  r = addCertExtension(issuer_cert, cert, NID_key_usage, key_usage);
+  if (r != 1) {
+    throw OpenSSLError("set certificate key usage failed");
+  }
+
+  // extended key usage
+  if (!cert_data.IsCA && !cert_data.Email.empty()) {
+    r = addCertExtension(issuer_cert, cert, NID_ext_key_usage, "emailProtection");
+    if (r != 1) {
+      throw OpenSSLError("set certificate extended key usage failed");
+    }
+  }
+
+  // subject alternative name
+  if (!cert_data.Email.empty() || !cert_data.Url.empty()) {
+    addSubjAltNameExt(cert, cert_data.Email, cert_data.Url);
+  }
+
+  // subject key identifier
+  r = addCertExtension(issuer_cert, cert, NID_subject_key_identifier, "hash");
+  if (r != 1) {
+    throw OpenSSLError("set certificate key usage failed");
+  }
+
+  // authority key identifier
+  r = addCertExtension(issuer_cert, cert, NID_authority_key_identifier, "keyid,issuer:always");
+  if (r != 1) {
+    throw OpenSSLError("set certificate key usage failed");
+  }
+}
+
+static void setCertValidity(const CertData& cert_data, X509* cert) {
+  using clock = std::chrono::system_clock;
+
+  long not_before_adj = 0;
+  time_t not_before = cert_data.Validity.NotBefore;
+
+  if (not_before == 0) {
+    auto now = clock::now();
+    not_before = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
+    not_before_adj = -(not_before % (24 * 60 * 60));
+  }
+
+  auto timeret = X509_time_adj(X509_get_notBefore(cert), not_before_adj, &not_before);
+  if (timeret == nullptr) {
+    throw OpenSSLError("adjusting certificate not before validity failed");
+  }
+
+  long not_after_adj = 0;
+  time_t not_after = cert_data.Validity.NotAfter;
+
+  if (not_after == 0) {
+    not_after = not_before;
+
+    int valid_years = cert_data.Validity.ValidYears;
+    if (valid_years == 0) {
+      valid_years = 1;
+    }
+
+    not_after_adj = not_before_adj + valid_years * 365 * 24 * 60 * 60;
+  }
+
+  timeret = X509_time_adj(X509_get_notAfter(cert), not_after_adj, &not_after);
+  if (timeret == nullptr) {
+    throw OpenSSLError("adjusting certificate not after validity failed");
+  }
+}
+
+static auto createCert(
+  const CertData& cert_data,
+  const X509* issuer_cert,
+  const EVP_PKEY* issuer_pkey,
+  const EVP_PKEY* pkey
+) -> bssl::UniquePtr<X509> {
+  const EVP_MD* hash_alg = nullptr;
+  if (cert_data.Algorithms.HashAlg.empty()) {
+    hash_alg = EVP_get_digestbyname(certDefaultHashAlg);
+  } else {
+    hash_alg = EVP_get_digestbyname(cert_data.Algorithms.HashAlg.data());
+  }
+
+  if (hash_alg == nullptr) {
+    throw crypto::OpenSSLError("cannot find hash algorithm");
+  }
+
+  auto cert = bssl::UniquePtr<X509>(X509_new());
+
+  if (issuer_cert == nullptr || issuer_pkey == nullptr) {
+    issuer_cert = cert.get();
+    issuer_pkey = pkey;
+  }
+
+  // set public key
+  auto r = X509_set_pubkey(cert.get(), const_cast<EVP_PKEY*>(pkey));
+  if (r != 1) {
+    throw OpenSSLError("set public key to certificate failed");
+  }
+
+  // set certificate version
+  r = X509_set_version(cert.get(), certVersion);
+  if (r != 1) {
+    throw OpenSSLError("set version to certificate failed");
+  }
+
+  // set serial number
+  auto serial_number = cert_data.SerialNumber;
+  if (serial_number == 0) {
+    serial_number = crypto::RandUint64();
+  }
+  r = ASN1_INTEGER_set_uint64(X509_get_serialNumber(cert.get()), serial_number);
+  if (r != 1) {
+    throw OpenSSLError("set certificate serial number");
+  }
+
+  // set subject
+  setCertSubject(cert_data, cert.get());
+
+  // set issuer name
+  auto issuer_name = X509_get_subject_name(const_cast<X509*>(issuer_cert));
+  r = X509_set_issuer_name(cert.get(), issuer_name);
+  if (r != 1) {
+    throw OpenSSLError("set certificate issuer name failed");
+  }
+
+  // set validity
+  setCertValidity(cert_data, cert.get());
+
+  // add extensions
+  addCertExtensions(cert_data, issuer_cert, cert.get());
+
+  r = X509_sign(cert.get(), const_cast<EVP_PKEY*>(issuer_pkey), hash_alg);
+  if (r == 0) {
+    throw OpenSSLError("certificate sign failed");
+  }
+
+  return cert;
+}
+
+auto CreateCert(
+  const CertData& cert_data,
+  const X509* issuer_cert,
+  const EVP_PKEY* issuer_pkey,
+  const EVP_PKEY* pkey
+) -> bssl::UniquePtr<X509> {
+
+  return createCert(cert_data, issuer_cert, issuer_pkey, pkey);
+}
+
+auto CreateSelfSignedCert(
+  const CertData& cert_data,
+  const EVP_PKEY* pkey
+) -> bssl::UniquePtr<X509> {
+
+  return createCert(cert_data, nullptr, nullptr, pkey);
+}
+
+auto ExportCertToPEM(const X509* cert) -> 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_X509(mem.get(), const_cast<X509*>(cert));
+  if (r != 1) {
+    throw OpenSSLError("exporting certificate to PEM failed");
+  }
+
+  return mem;
+}
+
+auto ImportCertFromPEM(bytes::View pem) -> bssl::UniquePtr<X509> {
+  bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
+  if (mem == nullptr) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  auto cert = bssl::UniquePtr<X509>(PEM_read_bio_X509(mem.get(), nullptr, nullptr, nullptr));
+  if (cert == nullptr) {
+    throw OpenSSLError("importing certificate from PEM failed");
+  }
+
+  return cert;
+}
+
+void PrintCert(std::ostream& os, const X509* cert) {
+  bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
+  if (!mem) {
+    throw OpenSSLError("creating memory buffer failed");
+  }
+
+  auto r = X509_print(mem.get(), const_cast<X509*>(cert));
+  if (r != 1) {
+    throw OpenSSLError("printing certificate failed");
+  }
+
+  os << bio::View(mem.get()).String();
+}
+
+} // vereign::crypto::cert
diff --git a/cpp/src/vereign/crypto/cert.hh b/cpp/src/vereign/crypto/cert.hh
new file mode 100644
index 0000000000000000000000000000000000000000..6a5ed4e010a68dce886223936afbb6f537ded563
--- /dev/null
+++ b/cpp/src/vereign/crypto/cert.hh
@@ -0,0 +1,170 @@
+#ifndef __VEREIGN_CRYPTO_CERT_HH
+#define __VEREIGN_CRYPTO_CERT_HH
+
+#include <vereign/bytes/buffer.hh>
+
+#include <openssl/base.h>
+#include <openssl/x509.h>
+#include <chrono>
+
+namespace vereign::crypto::cert {
+
+/**
+ * Certificate algorithms configuration used for creating certificates.
+ *
+ * Used in CertData struct.
+ */
+struct CertAlgorithms {
+  /**
+   * The hash algorithm, if it is empty the default SHA256 will be used.
+   */
+  std::string HashAlg;
+
+  // FIXME: SignAlg is unused
+  std::string SignAlg;
+  // FIXME: KeyLength is unused
+  int KeyLength;
+};
+
+/**
+ * Certificate subject configuration used for creating certificates.
+ *
+ * All fields are optional for leaf, recommended for CA.
+ *
+ * Used in CertData struct.
+ */
+struct CertSubject {
+  std::string CommonName;
+  std::string Country;
+  std::string Locality;
+  std::string State;
+  std::string Organization;
+  std::string OrganizationUnit;
+};
+
+/**
+ * Certificate validity configuration used for creating certificates.
+ *
+ * Used in CertData struct.
+ */
+struct CertValidity {
+  /**
+   * If 0, the default is today at 00:00:00.
+   */
+  time_t NotBefore;
+
+  /**
+   * If 0, the default is NotBefore + ValidYears at 23:59:59.
+   */
+  time_t NotAfter;
+
+  /**
+   * If 0, the default is 1.
+   */
+  int ValidYears;
+};
+
+/**
+ * Certificate configuration used for creating certificates.
+ *
+ * @see CreateCert
+ * @see CreateSelfSignedCert
+ */
+struct CertData {
+  CertAlgorithms Algorithms;
+  CertSubject Subject;
+  CertValidity Validity;
+
+  /**
+   * If 0, the default is the current milliseconds timestamp - the number of milliseconds since epoch.
+   */
+  uint64_t SerialNumber;
+
+  /**
+   * Added to DN and Subject Alternative Name extension.
+   * Optional for CA. Mandatory for leaf certificate, used for email protection.
+   */
+  std::string Email;
+
+  /**
+   * Optional, recommended for CA, added to Subject Alternative Name extension.
+   */
+  std::string Url;
+
+  /**
+   * Denotes if the created certificate is CA or leaf certificate. The default is false.
+   */
+  bool IsCA;
+};
+
+/**
+ * Creates and signs a certificate.
+ *
+ * @param cert_data Certificate configuration.
+ * @param issuer_cert The issuer certificate.
+ * @param issuer_pkey The issuer private key.
+ * @param pkey The private key for the newly created certificate.
+ * @returns new certificate signed by the provided issuer.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto CreateCert(
+  const CertData& cert_data,
+  const X509* issuer_cert,
+  const EVP_PKEY* issuer_pkey,
+  const EVP_PKEY* pkey
+) -> bssl::UniquePtr<X509>;
+
+/**
+ * Creates self signed certificate.
+ *
+ * @param cert_data Certificate configuration.
+ * @param pkey The private key for the newly created certificate.
+ * @returns the new certificate signed by its own private key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto CreateSelfSignedCert(
+  const CertData& cert_data,
+  const EVP_PKEY* pkey
+) -> bssl::UniquePtr<X509>;
+
+/**
+ * Exports certificate to PEM format.
+ *
+ * @param cert The certificate to export.
+ * @returns a memory BIO with the exported certificate.
+ */
+auto ExportCertToPEM(const X509* cert) -> bssl::UniquePtr<BIO>;
+
+/**
+ * Exports certificate to PEM format into string.
+ *
+ * @param cert The certificate to export.
+ * @returns a string with the exported certificate.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ExportCertToPEMString(const X509* cert) -> std::string;
+
+/**
+ * Imports certificate from PEM format.
+ *
+ * @param pem PEM encoded certificate.
+ * @returns imported X509 certificate.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ImportCertFromPEM(bytes::View pem) -> bssl::UniquePtr<X509>;
+
+/**
+ * Print textual representation of the certificate into output stream.
+ *
+ * @param os The stream where to write the textual representation of the certificate.
+ * @param cert The certificate.
+ */
+void PrintCert(std::ostream& os, const X509* cert);
+
+} // vereign::crypto::cert
+
+#endif // __VEREIGN_CRYPTO_CERT_HH
diff --git a/cpp/src/vereign/crypto/errors.hh b/cpp/src/vereign/crypto/errors.hh
index 949eb248cd8a0caf3014701a8e962c47f866f1ad..825af7e98baf03a468ace2ced84cea76960b5c4f 100644
--- a/cpp/src/vereign/crypto/errors.hh
+++ b/cpp/src/vereign/crypto/errors.hh
@@ -23,9 +23,19 @@ public:
 class OpenSSLError : public Error {
 public:
   OpenSSLError(const std::string& what)
-    : Error(what + ": " + ERR_reason_error_string(ERR_get_error()))
+    : Error(getOpenSSLErrorString(what))
   {
   }
+
+private:
+  static auto getOpenSSLErrorString(const std::string& what) -> std::string {
+    auto error = ERR_get_error();
+    if (error == 0) {
+      return what + ": no openssl error in the error queue";
+    }
+
+    return what + ": " + ERR_reason_error_string(error);
+  }
 };
 
 } // vereign::crypto
diff --git a/cpp/src/vereign/crypto/rand.cc b/cpp/src/vereign/crypto/rand.cc
index 2acc168ed6d9df6905c3ad25e7053c041e29a715..f61274887d5a826a22e0db178c86564ae2adcad8 100644
--- a/cpp/src/vereign/crypto/rand.cc
+++ b/cpp/src/vereign/crypto/rand.cc
@@ -23,4 +23,15 @@ auto Rand(std::size_t size) -> bytes::Buffer {
   return buf;
 }
 
+auto RandUint64() -> uint64_t {
+  uint64_t x = 0;
+  int result = RAND_bytes((uint8_t*) &x, sizeof(x));
+  if (result == 0) {
+    ERR_clear_error();
+    throw Error("crypto rand failed");
+  }
+
+  return x;
+}
+
 } // vereign::crypto
diff --git a/cpp/src/vereign/crypto/rand.hh b/cpp/src/vereign/crypto/rand.hh
index 4c3979900f1464d2e54c9957f689f503af3e7e2f..e377ac88ce42544b57f337ef82f8ba7c9b24f71c 100644
--- a/cpp/src/vereign/crypto/rand.hh
+++ b/cpp/src/vereign/crypto/rand.hh
@@ -43,6 +43,15 @@ void Rand(bytes::Buffer& buf, std::size_t size);
  */
 auto Rand(std::size_t size) -> bytes::Buffer;
 
+/**
+ * Generates random uint64_t.
+ *
+ * @returns random unsigned 64 bit integer.
+ *
+ * @throws crypto::Error on failure.
+ */
+auto RandUint64() -> uint64_t;
+
 } // vereign::crypto
 
 #endif // __VEREIGN_CRYPTO_RAND_HH
diff --git a/cpp/src/vereign/crypto/rsa.cc b/cpp/src/vereign/crypto/rsa.cc
index e9a519f6b5887486f37819759e65d33108264602..2c89dfcb8078be5b4f232a33eb9c23781690b8cc 100644
--- a/cpp/src/vereign/crypto/rsa.cc
+++ b/cpp/src/vereign/crypto/rsa.cc
@@ -3,6 +3,7 @@
 #include <vereign/bytes/view.hh>
 #include <vereign/crypto/errors.hh>
 #include <vereign/bytes/buffer.hh>
+#include <vereign/crypto/bio.hh>
 
 #include <openssl/base.h>
 #include <openssl/bn.h>
@@ -150,6 +151,12 @@ auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> {
   return mem;
 }
 
+auto ExportPrivateKeyToPEMString(EVP_PKEY* key) -> std::string {
+  auto key_bio = ExportPublicKeyToPEM(key);
+
+  return std::string{crypto::bio::View(key_bio.get()).String()};
+}
+
 auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
   bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
   if (mem == nullptr) {
@@ -158,7 +165,7 @@ auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
 
   auto key_ptr = PEM_read_bio_PrivateKey(mem.get(), nullptr, nullptr, nullptr);
   if (key_ptr == nullptr) {
-    throw OpenSSLError("importing public key from PEM failed");
+    throw OpenSSLError("importing private key from PEM failed");
   }
 
   return bssl::UniquePtr<EVP_PKEY>(key_ptr);
diff --git a/cpp/src/vereign/crypto/rsa.hh b/cpp/src/vereign/crypto/rsa.hh
index 4d8e18fc38701d7a921dc2ea42a2781030ee32a6..03a6c136503f3b0bfdda1b1df80d3a8a640b80f1 100644
--- a/cpp/src/vereign/crypto/rsa.hh
+++ b/cpp/src/vereign/crypto/rsa.hh
@@ -125,6 +125,23 @@ auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
  */
 auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
 
+/**
+ * Export private key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto pem  = crypto::rsa::ExportPrivateKeyToPEMString(key.get());
+ * std::cout << pem << std::endl;
+ * @endcode
+ *
+ * @param key The key to export.
+ * @returns the exported key in PEM format.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
+auto ExportPrivateKeyToPEMString(EVP_PKEY* key) -> std::string;
+
 /**
  * Import private key from PEM format.
  *
diff --git a/cpp/src/vereign/encoding/base64.cc b/cpp/src/vereign/encoding/base64.cc
index 47ef85a53f69d204c474b6ad862a1cc0942ebbfa..88e1e539f83ac5c8cb960a500a7b605fde093fcc 100644
--- a/cpp/src/vereign/encoding/base64.cc
+++ b/cpp/src/vereign/encoding/base64.cc
@@ -14,6 +14,19 @@ void Encode(bytes::View src, bytes::Buffer& encoded) {
   encoded.IncSize(written);
 }
 
+auto EncodeToString(bytes::View src) -> std::string {
+  if (src.Size() == 0) {
+    return "";
+  }
+
+  std::string encoded;
+  encoded.resize(boost::beast::detail::base64::encoded_size(src.Size()));
+  auto written = boost::beast::detail::base64::encode(encoded.data(), src.Data(), src.Size());
+  encoded.resize(written);
+
+  return encoded;
+}
+
 void Decode(bytes::View src, bytes::Buffer& decoded) {
   if (src.Size() == 0) {
     return ;
diff --git a/cpp/src/vereign/encoding/base64.hh b/cpp/src/vereign/encoding/base64.hh
index a69a559403f9126045cdb69e89bbf084a519fd08..d3ee3ce4b13a22628dca0c1fe76653e762c3aacd 100644
--- a/cpp/src/vereign/encoding/base64.hh
+++ b/cpp/src/vereign/encoding/base64.hh
@@ -25,6 +25,24 @@ namespace vereign::encoding::base64 {
  */
 void Encode(bytes::View src, bytes::Buffer& encoded);
 
+/**
+ * Encodes source bytes into base64 encoding.
+ *
+ * No new lines are inserted.
+ *
+ * Example:
+ * @code
+ * std::string s{"foob"};
+ * auto encoded = encoding::base64::Encode(bytes::View(s));
+ *
+ * assert(encoded == "Zm9vYg==")
+ * @endcode
+ *
+ * @param src The source bytes that will be encoded.
+ * @returns the encoded string.
+ */
+auto EncodeToString(bytes::View src) -> std::string;
+
 /**
  * Decodes base64 encoded bytes.
  *
diff --git a/cpp/src/vereign/fs/errors.hh b/cpp/src/vereign/fs/errors.hh
index 035dc67edc34f39c03bbd90bbc649896af48cc3b..ef72c8c00655fd544e4cca926ca1493f94161d73 100644
--- a/cpp/src/vereign/fs/errors.hh
+++ b/cpp/src/vereign/fs/errors.hh
@@ -1,25 +1,32 @@
 #ifndef __VEREIGN_FS_ERRORS_HH
 #define __VEREIGN_FS_ERRORS_HH
 
-#include <exception>
+#include <string>
+#include <stdexcept>
 
 namespace vereign::fs {
 
-class Error : public std::exception {
-  auto what() const noexcept -> const char* override {
-    return "filesystem error";
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error{what}
+  {
   }
 };
 
-class HomeNotFoundError : public std::exception {
-  auto what() const noexcept -> const char* override {
-    return "cannot find user's home dir";
+class HomeNotFoundError : public Error {
+public:
+  HomeNotFoundError()
+    : Error{"cannot find user's home dir"}
+  {
   }
 };
 
-class TempDirNotFoundError : public std::exception {
-  auto what() const noexcept -> const char* override {
-    return "cannot find user's temp dir";
+class TempDirNotFoundError : public Error {
+public:
+  TempDirNotFoundError()
+    : Error{"cannot find user's temp dir"}
+  {
   }
 };
 
diff --git a/cpp/src/vereign/fs/util.cc b/cpp/src/vereign/fs/util.cc
index 2e39a022f9ecd8d38388467b85beaedb955856d6..8d60e3e827c1228e2f5db2a00444ebab66674db2 100644
--- a/cpp/src/vereign/fs/util.cc
+++ b/cpp/src/vereign/fs/util.cc
@@ -7,6 +7,10 @@
 #include <vereign/core/rand.hh>
 #include <vereign/core/string.hh>
 #include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+// FIXME: remove
+#include <iostream>
 
 #ifdef _WIN32
 # include <shlobj_core.h>
@@ -110,4 +114,40 @@ auto HomePath() -> std::string {
 #endif
 }
 
+auto ReadFile(std::string_view path) -> bytes::Buffer {
+  boost::filesystem::ifstream file{
+    detail::StringToPath(path),
+    boost::filesystem::fstream::binary | boost::filesystem::fstream::ate,
+  };
+
+  if (!file.is_open()) {
+    throw fs::Error("open file for reading failed");
+  }
+
+  auto size = file.tellg();
+  bytes::Buffer result{static_cast<size_t>(size)};
+  file.seekg(0);
+
+  file.read((char*) result.end(), size);
+  result.IncSize(file.gcount());
+
+  file.close();
+
+  return result;
+}
+
+void WriteFile(std::string_view path, bytes::View data) {
+  boost::filesystem::ofstream file{
+    detail::StringToPath(path),
+    boost::filesystem::fstream::binary | boost::filesystem::fstream::trunc,
+  };
+
+  if (!file.is_open()) {
+    throw fs::Error("open file for writing failed");
+  }
+
+  file.write(data.CharData(), data.Size());
+  file.close();
+}
+
 } // namespace vereign::fs
diff --git a/cpp/src/vereign/fs/util.hh b/cpp/src/vereign/fs/util.hh
index c4e9d3dbb03b08f32a63de99fd6a1da389cc8f35..d665902476cd8f2760a7729fe3649027463c13cc 100644
--- a/cpp/src/vereign/fs/util.hh
+++ b/cpp/src/vereign/fs/util.hh
@@ -1,6 +1,7 @@
 #ifndef __VEREIGN_FS_UTIL_HH
 #define __VEREIGN_FS_UTIL_HH
 
+#include <vereign/bytes/buffer.hh>
 #include <boost/filesystem/path.hpp>
 #include <string>
 #include <string_view>
@@ -108,6 +109,9 @@ auto TempDir(std::string_view prefix) -> std::string;
  */
 auto HomePath() -> std::string;
 
+auto ReadFile(std::string_view path) -> bytes::Buffer;
+void WriteFile(std::string_view path, bytes::View data);
+
 } // namespace vereign::fs
 
 #endif // __VEREIGN_FS_UTIL_HH
diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc
index f0fae25a176de979bd9274693687a1d1ef6b272f..a20883987ce7586e61bacb63eda286956bbc8e3a 100644
--- a/cpp/src/vereign/grpc/server.cc
+++ b/cpp/src/vereign/grpc/server.cc
@@ -60,7 +60,7 @@ public:
 
     kvstorage_ = std::make_unique<kvstore::SqliteStorage>(storage_path);
     crypto_storage_ = std::make_unique<kvstore::CryptoStorage>(*kvstorage_);
-    identity_provider_ = std::make_unique<identity::Provider>(*crypto_storage_);
+    identity_provider_ = std::make_unique<identity::Provider>(*client_session_, *crypto_storage_);
 
     // FIXME: Verify the remote server's certificate
     // ssl_context.set_verify_mode(ssl::verify_peer);
diff --git a/cpp/src/vereign/identity/errors.hh b/cpp/src/vereign/identity/errors.hh
index 7140afec338eeef55150bc988beb260de2b901ac..ffc59e9f9564d081c8c3467747cab9055e5a8598 100644
--- a/cpp/src/vereign/identity/errors.hh
+++ b/cpp/src/vereign/identity/errors.hh
@@ -13,6 +13,25 @@ public:
   }
 };
 
+class ServerSignCertificateError : public Error {
+public:
+  std::string Code;
+  std::string Status;
+  std::string Error;
+
+  ServerSignCertificateError(
+    std::string code,
+    std::string status,
+    std::string error
+  )
+    : identity::Error(error),
+      Code{std::move(code)},
+      Status{std::move(status)},
+      Error{std::move(error)}
+  {
+  }
+};
+
 } // namespace vereign::identity
 
 #endif // __VEREIGN_IDENTITY_ERRORS_HH
diff --git a/cpp/src/vereign/identity/provider.cc b/cpp/src/vereign/identity/provider.cc
index e69494ad66845c3fdd56216b20559ffffb887942..f50c045aeab7bfbcf63dbf647b13e547d4a0faa5 100644
--- a/cpp/src/vereign/identity/provider.cc
+++ b/cpp/src/vereign/identity/provider.cc
@@ -1,18 +1,54 @@
 #include <vereign/identity/provider.hh>
 
+#include <vereign/identity/errors.hh>
+
 #include <vereign/crypto/digest.hh>
 #include <vereign/crypto/bio.hh>
 #include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/cert.hh>
 #include <vereign/encoding/base64.hh>
 
+#include <vereign/client_library/sign_types.pb.h>
+
 namespace {
-  constexpr int rsaKeySizeBits = 2048;
+
+constexpr int rsaKeySizeBits = 2048;
+constexpr const auto profileCertCountry = std::string_view{"CH"};
+constexpr const auto profileCertState = std::string_view{"Zug"};
+constexpr const auto profileCertLocality = std::string_view{"Zug"};
+constexpr const auto profileCertOrganization = std::string_view{"Vereign AG"};
+constexpr const auto profileCertOrganizationUnit = std::string_view{"Business Dep"};
+constexpr const int profileCertValidityYears = 5;
+
+const auto signCertificatePath = std::string{"/sign/signCertificate"};
+
 }
 
 namespace vereign::identity {
 
-Provider::Provider(kvstore::CryptoStorage& storage)
-  : storage_{storage}
+static auto makeProfileCertificate(
+  bytes::View key_pem,
+  const client_library::SignCertificateFormResponsePayload& msg
+) -> std::unique_ptr<ProfileCertificate> {
+
+  auto profile_cert = std::make_unique<ProfileCertificate>();
+
+  profile_cert->PrivateKeyPEM = bytes::Buffer{key_pem};
+  encoding::base64::Decode(bytes::View(msg.signedcertificate()), profile_cert->CertificatePEM);
+  profile_cert->CertificateUUID = msg.certificateuuid();
+
+  for (auto& cert : msg.chain()) {
+    bytes::Buffer buf;
+    encoding::base64::Decode(bytes::View(cert), buf);
+    profile_cert->Chain.push_back(std::move(buf));
+  }
+
+  return profile_cert;
+}
+
+Provider::Provider(restapi::ClientSession& client_session, kvstore::CryptoStorage& storage)
+  : client_session_{client_session},
+    storage_{storage}
 {}
 
 Provider::~Provider() = default;
@@ -77,4 +113,129 @@ auto Provider::GetDeviceHash() -> std::string {
   return std::string(encoded.View().String());
 }
 
+auto Provider::getProfileCertificateFromStorage(
+  const std::string& profile_uuid
+) -> std::unique_ptr<ProfileCertificate> {
+
+  std::lock_guard<std::mutex> l{mu_};
+
+  auto key_pem = bytes::Buffer{};
+  auto cert_binary = bytes::Buffer{};
+
+  storage_.GetBytes("profile-key-" + profile_uuid, key_pem);
+  storage_.GetBytes("profile-cert-" + profile_uuid, cert_binary);
+
+  if (key_pem.Size() == 0 || cert_binary.Size() == 0) {
+    return nullptr;
+  }
+
+  auto cert_msg = client_library::SignCertificateFormResponsePayload{};
+  auto result = cert_msg.ParseFromArray(cert_binary.View().Data(), cert_binary.View().Size());
+  if (!result) {
+    return nullptr;
+  }
+
+  return makeProfileCertificate(key_pem.View(), cert_msg);
+}
+
+auto Provider::GetProfileCertificate(
+  const std::string& profile_uuid
+) -> std::unique_ptr<ProfileCertificate> {
+  auto profile_cert = getProfileCertificateFromStorage(profile_uuid);
+  if (profile_cert) {
+    return profile_cert;
+  }
+
+  auto key = crypto::rsa::GenerateKey(rsaKeySizeBits);
+
+  crypto::cert::CertData cert_data{};
+  cert_data.Subject.CommonName = profile_uuid + "-userdevice";
+  cert_data.Subject.Country = profileCertCountry;
+  cert_data.Subject.State = profileCertState;
+  cert_data.Subject.Locality = profileCertLocality;
+  cert_data.Subject.Organization = profileCertOrganization;
+  cert_data.Subject.OrganizationUnit = profileCertOrganizationUnit;
+
+  // cert_data.Email = "ca@vereign.com"; // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
+  // cert_data.Url = "www.vereign.com"; // optional url, recommended for CA, added to Subject Alternative Name extension
+
+  cert_data.Validity.ValidYears = profileCertValidityYears;
+  cert_data.IsCA = true;
+
+  auto cert = crypto::cert::CreateSelfSignedCert(cert_data, key.get());
+  auto cert_pem_bio = crypto::cert::ExportCertToPEM(cert.get());
+  auto cert_pem = encoding::base64::EncodeToString(crypto::bio::View(cert_pem_bio.get()));
+
+  // sign the new profile certificate
+  auto req = client_library::SignCertificateForm{};
+  req.set_passportuuid(profile_uuid);
+  req.set_certificate(cert_pem);
+  auto resp = client_library::SignCertificateFormResponse{};
+
+  auto result = client_session_.Post(signCertificatePath, &req, &resp);
+  result.wait();
+
+  if (resp.code() != "200") {
+    throw ServerSignCertificateError(resp.code(), resp.status(), resp.error());
+  }
+
+  // serialize cert data
+  auto payload_size = resp.data().ByteSizeLong();
+  auto cert_binary = bytes::Buffer{payload_size};
+  resp.data().SerializeToArray(cert_binary.end(), payload_size);
+  cert_binary.IncSize(payload_size);
+
+  auto key_bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+
+  {
+    std::lock_guard<std::mutex> l{mu_};
+
+    storage_.PutBytes("profile-key-" + profile_uuid, crypto::bio::View(key_bio.get()));
+    storage_.PutBytes("profile-cert-" + profile_uuid, cert_binary.View());
+  }
+
+  return makeProfileCertificate(crypto::bio::View(key_bio.get()), resp.data());
+}
+
+auto Provider::GetProfileOneTimeCertificate(
+  const std::string& profile_uuid,
+  const std::string& email
+) -> std::unique_ptr<ProfileCertificate> {
+  auto profile_cert = GetProfileCertificate(profile_uuid);
+
+  auto key = crypto::rsa::GenerateKey(rsaKeySizeBits);
+
+  crypto::cert::CertData cert_data{};
+  cert_data.Subject.CommonName = profile_uuid + "-onetime";
+  cert_data.Subject.Country = profileCertCountry;
+  cert_data.Subject.State = profileCertState;
+  cert_data.Subject.Locality = profileCertLocality;
+  cert_data.Subject.Organization = profileCertOrganization;
+  cert_data.Subject.OrganizationUnit = profileCertOrganizationUnit;
+  cert_data.Email = email;
+
+  cert_data.Validity.ValidYears = profileCertValidityYears;
+  cert_data.IsCA = false;
+
+  auto issuer_cert = crypto::cert::ImportCertFromPEM(profile_cert->CertificatePEM.View());
+  auto issuer_pkey = crypto::rsa::ImportPrivateKeyFromPEM(profile_cert->PrivateKeyPEM.View());
+
+  auto cert = crypto::cert::CreateCert(cert_data, issuer_cert.get(), issuer_pkey.get(),  key.get());
+  auto cert_bio = crypto::cert::ExportCertToPEM(cert.get());
+
+  auto key_bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+
+  auto result = std::make_unique<ProfileCertificate>();
+  result->PrivateKeyPEM = crypto::bio::View(key_bio.get());
+  result->CertificatePEM = crypto::bio::View(cert_bio.get());
+
+  result->Chain.emplace_back(profile_cert->CertificatePEM.View());
+
+  for (const auto& chain_cert : profile_cert->Chain) {
+    result->Chain.emplace_back(chain_cert.View());
+  }
+
+  return result;
+}
+
 } // namespace vereign::identity
diff --git a/cpp/src/vereign/identity/provider.hh b/cpp/src/vereign/identity/provider.hh
index c526e41fa0e2353ca8dc2258609e9076a6a3f17a..b49471733a536a36de849377c6caaf41716ca597 100644
--- a/cpp/src/vereign/identity/provider.hh
+++ b/cpp/src/vereign/identity/provider.hh
@@ -1,12 +1,26 @@
 #ifndef __VEREIGN_IDENTITY_PROVIDER_HH
 #define __VEREIGN_IDENTITY_PROVIDER_HH
 
+#include <vereign/restapi/client_session.hh>
 #include <vereign/kvstore/crypto_storage.hh>
 
 #include <mutex>
 
 namespace vereign::identity {
 
+/**
+ * A DTO representing a profile/onetime certificate with its private key, UUID and certificate chain.
+ *
+ * @see Provider::GetProfileCertificate
+ * @see Provider::GetProfileOneTimeCertificate
+ */
+struct ProfileCertificate {
+  bytes::Buffer PrivateKeyPEM;
+  bytes::Buffer CertificatePEM;
+  std::string CertificateUUID;
+  std::vector<bytes::Buffer> Chain;
+};
+
 /**
  * Identity provider that manages the locally stored user identity.
  *
@@ -19,7 +33,7 @@ public:
    *
    * @param storage The crypto storage used for read/write identity properties.
    */
-  Provider(kvstore::CryptoStorage& storage);
+  Provider(restapi::ClientSession& client_session, kvstore::CryptoStorage& storage);
 
   /**
    * Default constructor.
@@ -64,7 +78,66 @@ public:
    */
   auto GetDeviceHash() -> std::string;
 
+  /**
+   * Retrieve profile certificate.
+   *
+   * When the profile certificate does not exist in the crypto storage just yet, a new private key
+   * and certificate is created and signed by Vereign server. Then the private key, the certificate
+   * and the issuers certificate chain are stored into the crypto storage.
+   *
+   * This means that the profile certificate is reused for the lifetime of the device registration.
+   *
+   * The profile certificate is a CA certificate and is used for signing one-time certificates.
+   *
+   * The `Common Name` of the certificate is derived from the
+   * `profile_uuid` - "<profile_uuid>-userdevice".
+   *
+   * Currently the profile certificate is part of the following certificate chain:
+   * 1. profile-userdevice
+   * 2. profile-serverside
+   * 3. vereign
+   *
+   * @param profile_uuid The profile UUID.
+   * @returns the profile certificate, its private key and the issuers certificate chain.
+   */
+  auto GetProfileCertificate(const std::string& profile_uuid) -> std::unique_ptr<ProfileCertificate>;
+
+  /**
+   * Creates and returns a one-time certificate for given profile.
+   *
+   * The one-time certificate is signed by the user-device profile certificate.
+   *
+   * When the profile certificate does not exist in the crypto storage just yet, a new private key
+   * and certificate is created and signed by Vereign server. Then the private key, the certificate
+   * and the issuers certificate chain are stored into the crypto storage.
+   *
+   * This means that the profile certificate is reused for the lifetime of the device registration.
+   *
+   * The one-time certificate is a leaf certificate and must be used one only sign operation.
+   *
+   * The `Common Name` of the certificate is derived from the
+   * `profile_uuid` - "<profile_uuid>-onetime".
+   *
+   * Currently the one-time certificate is part of the following certificate chain:
+   * 1. profile-onetime
+   * 2. profile-userdevice
+   * 3. profile-serverside
+   * 4. vereign
+   *
+   */
+  auto GetProfileOneTimeCertificate(
+    const std::string& profile_uuid,
+    const std::string& email
+  ) -> std::unique_ptr<ProfileCertificate>;
+
 private:
+  auto getProfileCertificateFromStorage(
+    const std::string& profile_uuid
+  ) -> std::unique_ptr<ProfileCertificate>;
+
+private:
+  restapi::ClientSession& client_session_;
+
   std::mutex mu_;
 
   kvstore::CryptoStorage& storage_;
diff --git a/cpp/src/vereign/kvstore/crypto_storage.cc b/cpp/src/vereign/kvstore/crypto_storage.cc
index 7db16463cd4b0cc9947ff11dc2e9e56c54fb5a6c..1990c7f86211005f96ed3618757b1eeacf1af8ba 100644
--- a/cpp/src/vereign/kvstore/crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/crypto_storage.cc
@@ -26,8 +26,8 @@ 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);
+auto CryptoStorage::GetBytes(const std::string& key, bytes::Buffer& value) -> bool {
+  return 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
index f0c03648c9d2afe06e7fde140157c691f66d954a..23f0e2fa5af3858d3d0677d012e00e6a3eed02f8 100644
--- a/cpp/src/vereign/kvstore/crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/crypto_storage.hh
@@ -97,11 +97,10 @@ public:
    * @param value Buffer where the value will be returned.
    *
    * @throws StorageNotInitializedError when the storage is not initialized.
-   * @throws ValueNotFoundError when there is no value under the provided key.
    * @throws encoding::Error when the encrypted value cannot be decoded.
    * @throws crypto::OpenSSLError when the value cannot be decrypted.
    */
-  void GetBytes(const std::string& key, bytes::Buffer& value);
+  auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool;
 
 private:
   std::unique_ptr<detail::CryptoStorageImpl> impl_;
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
index 83bb4c7b1930b76829af0192b6357b674a42f7ae..44243c88497a01759a559f8e6ece6d430e5a237e 100644
--- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
@@ -56,7 +56,7 @@ void BaseCryptoStorageImpl::PutBytes(const std::string& key, bytes::View value)
   encryptBytes(key, value);
 }
 
-void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) const {
+auto BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) const -> bool {
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   if (key_.Size() == 0) {
@@ -64,7 +64,10 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu
   }
 
   bytes::Buffer encoded;
-  storage_.GetBytes(key, encoded);
+  auto found = storage_.GetBytes(key, encoded);
+  if (!found) {
+    return false;
+  }
 
   bytes::View iv;
   bytes::View tag;
@@ -72,6 +75,8 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu
   DecodeEncryptedValue(encoded.View(), iv, tag, encrypted);
 
   crypto::aes::GCM256Decrypt(encrypted, key_.View(), iv, tag, value);
+
+  return true;
 }
 
 void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) {
@@ -81,9 +86,8 @@ void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) {
 auto BaseCryptoStorageImpl::isTagValid() const -> bool {
   bytes::Buffer tag;
   try {
-    GetBytes("__tag", tag);
+    return GetBytes("__tag", tag);
 
-    return true;
   } catch (const crypto::Error&) {
     return false;
   }
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
index 49278c3f6847d4555dfd3ba58fc52a38a7d05e07..3039b8fe0fbb82f9207689dc9faa213795fab5c5 100644
--- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
@@ -14,7 +14,7 @@ public:
   auto operator=(const BaseCryptoStorageImpl&) -> BaseCryptoStorageImpl& = delete;
 
   void PutBytes(const std::string& key, bytes::View value);
-  void GetBytes(const std::string& key, bytes::Buffer& value) const;
+  auto GetBytes(const std::string& key, bytes::Buffer& value) const -> bool;
 
 protected:
   void initKey(bytes::Buffer&& key);
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
index 1d59144d03098d016c2d7d1c29942fb7e95bac07..eb49ca7171ff49c3009c6bd5fbbf7cb9a9303e76 100644
--- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -15,13 +15,15 @@
 #include <chrono>
 
 namespace {
-  // FIXME: should these be injected and provided by the integrator
-  constexpr int iterations = 1 << 18;
-  constexpr int saltSizeBytes = 16;
-  constexpr int aesKeySizeBytes = 32;
 
-  constexpr int lockRetryCount = 10;
-  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+// FIXME: should these be injected and provided by the integrator
+constexpr int iterations = 1 << 18;
+constexpr int saltSizeBytes = 16;
+constexpr int aesKeySizeBytes = 32;
+
+constexpr int lockRetryCount = 10;
+constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+
 }
 
 namespace vereign::kvstore::detail {
@@ -40,16 +42,16 @@ void CryptoStorageImpl::Open(const std::string& pin) {
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   bytes::Buffer salt;
-
   int64_t iterations = 0;
+
   try {
-    iterations = storage_.GetInt64("__master_key_iterations");
+    storage_.GetInt64("__master_key_iterations", iterations);
     storage_.GetBytes("__master_key_salt", salt);
   } catch (const std::exception& e) {
     throw StorageNotInitializedError{e.what()};
   }
 
-  if (iterations == 0) {
+  if (iterations == 0 || salt.Size() == 0) {
     throw StorageNotInitializedError{"iterations cannot be zero"};
   }
 
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
index 6d8651f38261282aa1de180d208e1a3506df1b94..1e78842bec9d48ad965f2486df9836da57d266d0 100644
--- a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
@@ -18,19 +18,21 @@
 #include <chrono>
 
 namespace {
-  // FIXME: should these be injected and provided by the integrator
-  constexpr int keySizeBits = 2048;
-  constexpr int aesKeySizeBytes = 32;
-
-  constexpr int lockRetryCount = 10;
-  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
-
-  // FIXME: ask business for these values
-  constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"};
-  constexpr const auto vereignKeyDescription = std::string_view{
-    "Vereign Client will use this key to authenticate with the Vereign Services"
-  };
-  constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"};
+
+// FIXME: should these be injected and provided by the integrator
+constexpr int keySizeBits = 2048;
+constexpr int aesKeySizeBytes = 32;
+
+constexpr int lockRetryCount = 10;
+constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+
+// FIXME: ask business for these values
+constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"};
+constexpr const auto vereignKeyDescription = std::string_view{
+  "Vereign Client will use this key to authenticate with the Vereign Services"
+};
+constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"};
+
 }
 
 namespace vereign::kvstore::detail {
diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh
index c119c510273cce58d67071e56da2ad16f43983da..50ec7271532c6b55cc6b8fc3adc7a1ddb1a569db 100644
--- a/cpp/src/vereign/kvstore/errors.hh
+++ b/cpp/src/vereign/kvstore/errors.hh
@@ -13,14 +13,6 @@ public:
   }
 };
 
-class ValueNotFoundError : public Error {
-public:
-  ValueNotFoundError()
-    : Error{"value not found"}
-  {
-  }
-};
-
 class StorageNotInitializedError : public Error {
 public:
   StorageNotInitializedError(const std::string& reason)
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc
index 4789346fef43cde99a8701c620d2d93eea7f0fb7..21371363d40fc84cb497c283b869b999baed8ab3 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.cc
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -83,7 +83,7 @@ void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
   stmt.Step();
 }
 
-void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
+auto SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) -> bool {
   kvstore::Lock l{*this};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
@@ -91,10 +91,12 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
   auto end = stmt.Step();
 
   if (end) {
-    throw ValueNotFoundError{};
+    return false;
   }
 
   value.Write(stmt.GetColumnBlob(0));
+
+  return true;
 }
 
 void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
@@ -109,7 +111,7 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
   stmt.Step();
 }
 
-auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
+auto SqliteStorage::GetInt64(const std::string& key, int64_t& value) -> bool {
   kvstore::Lock l{*this};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
@@ -117,17 +119,17 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
   auto end = stmt.Step();
 
   if (end) {
-    throw ValueNotFoundError{};
+    return false;
   }
 
   auto buf = stmt.GetColumnBlob(0);
   if (buf.Size() != 8) {
-    throw Error("cannot decode bytes size");
+    throw Error("cannot decode int64 value");
   }
 
-  int64_t result = encoding::binary::DecodeUint64(buf);
+  value = encoding::binary::DecodeUint64(buf);
 
-  return result;
+  return true;
 }
 
 } // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh
index 6f7d13992cb82dd6c1507e1f66183da9ca4a5012..6fdfce9d8e996351f99c85f0f9dc1f7e6c2525c8 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.hh
+++ b/cpp/src/vereign/kvstore/sqlite_storage.hh
@@ -72,10 +72,9 @@ public:
    * @param key The key of the value that will be retrieved.
    * @param value Buffer where the value will be returned.
    *
-   * @throws ValueNotFoundError when the key does not exist.
    * @throws sqlite::Error on failure.
    */
-  void GetBytes(const std::string& key, bytes::Buffer& value) override;
+  auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool override;
 
   /**
    * Store int64_t value into the storage.
@@ -93,10 +92,9 @@ public:
    * @param key The key of the value that will be retrieved.
    * @param value Buffer where the value will be returned.
    *
-   * @throws ValueNotFoundError when the key does not exist.
    * @throws sqlite::Error on failure.
    */
-  auto GetInt64(const std::string& key) -> int64_t override;
+  auto GetInt64(const std::string& key, int64_t& value) -> bool override;
 
 private:
   sqlite::Connection db_;
diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh
index 2e8c55344f0c8481e924fb0c4673e263faa05585..ccb809468144a940243ee0e7d4769726056b8673 100644
--- a/cpp/src/vereign/kvstore/storage.hh
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -53,10 +53,8 @@ public:
    *
    * @param key The key of the value that will be retrieved.
    * @param value Buffer where the value will be returned.
-   *
-   * @throws ValueNotFoundError when the key does not exist.
    */
-  virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
+  virtual auto GetBytes(const std::string& key, bytes::Buffer& value) -> bool = 0;
 
   /**
    * Store int64_t value into the storage.
@@ -71,10 +69,8 @@ public:
    *
    * @param key The key of the value that will be retrieved.
    * @param value Buffer where the value will be returned.
-   *
-   * @throws ValueNotFoundError when the key does not exist.
    */
-  virtual auto GetInt64(const std::string& key) -> int64_t = 0;
+  virtual auto GetInt64(const std::string& key, int64_t& value) -> bool = 0;
 
   /**
    * Destroy and cleanup.
diff --git a/cpp/src/vereign/restapi/detail/post_task.hh b/cpp/src/vereign/restapi/detail/post_task.hh
index 74f1ea70ae57b105207529f64c86aa27aaaa48bc..14d91fcc2411db7bbec59bfd9ef601b7dd8e8e26 100644
--- a/cpp/src/vereign/restapi/detail/post_task.hh
+++ b/cpp/src/vereign/restapi/detail/post_task.hh
@@ -125,6 +125,14 @@ public:
 private:
 
   void decodeErrorResponse(const HttpResponse& httpResp) {
+    if (httpResp.result() == beast::http::status::not_found) {
+      resp_->set_code("404");
+      resp_->set_status(httpResp.body());
+      resp_->set_error(httpResp.body());
+
+      return;
+    }
+
     google::protobuf::util::JsonParseOptions options;
 
     client_library::ErrorStringResponse stringResp;
diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt
index 62bd6f48b027be1b1605a1f083be7c52d0ecaf00..c7bbbfcf2f6008b9cefbca1edce83566eed8f885 100644
--- a/cpp/tests/CMakeLists.txt
+++ b/cpp/tests/CMakeLists.txt
@@ -1,7 +1,20 @@
 
 include_directories(
   ${CMAKE_CURRENT_SOURCE_DIR}
-  "${VENDOR_INSTALL_DIR}/boost/include"
+)
+
+set(TESTUTIL_SRC
+  experiment/array.cc
+
+  testutil/protobuf.cc
+  testutil/golden.cc
+)
+add_library(testutil STATIC ${TESTUTIL_SRC})
+target_link_libraries(testutil PRIVATE
+  vereignlib
+)
+target_include_directories(testutil PRIVATE
+  ${CMAKE_SOURCE_DIR}/src
 )
 
 add_subdirectory("protobuf")
diff --git a/cpp/tests/integration/integration_test.cc b/cpp/tests/integration/integration_test.cc
index e3db4498e0af99e6d0e70d425f56ee9a55933a91..482e13d76750910dcf20cf7a3c6e23142142d27a 100644
--- a/cpp/tests/integration/integration_test.cc
+++ b/cpp/tests/integration/integration_test.cc
@@ -85,8 +85,8 @@ TEST_CASE("C API integration", "[.integration]") {
 
   // std::cout << vereign::test::ProtobufToJson(getDIDsResp) << std::endl;
 
-  grpc_shutdown();
-  google::protobuf::ShutdownProtobufLibrary();
+  // grpc_shutdown();
+  // google::protobuf::ShutdownProtobufLibrary();
 }
 
 TEST_CASE("vereign_service_start") {
diff --git a/cpp/tests/util/env.hh b/cpp/tests/testutil/env.hh
similarity index 73%
rename from cpp/tests/util/env.hh
rename to cpp/tests/testutil/env.hh
index d32fd97f8d66d0f25e76758425e426ae8b449079..fdb3d27635542fc84c448408fdd2ab152872f11e 100644
--- a/cpp/tests/util/env.hh
+++ b/cpp/tests/testutil/env.hh
@@ -1,10 +1,10 @@
-#ifndef __VEREIGN_TEST_UTIL_ENV_HH
-#define __VEREIGN_TEST_UTIL_ENV_HH
+#ifndef __VEREIGN_TESTUTIL_ENV_HH
+#define __VEREIGN_TESTUTIL_ENV_HH
 
 #include <string>
 #include <fmt/core.h>
 
-namespace vereign::test {
+namespace vereign::testutil {
 
 inline auto RequireEnv(const std::string& name) -> std::string {
   auto var = std::getenv(name.c_str());
@@ -24,6 +24,6 @@ inline auto GetEnv(const std::string& name, const std::string& default_) -> std:
   return var;
 }
 
-} // namespace test
+} // namespace vereign::testutil
 
-#endif // __VEREIGN_TEST_UTIL_ENV_HH
+#endif // __VEREIGN_TESTUTIL_ENV_HH
diff --git a/cpp/tests/util/error.hh b/cpp/tests/testutil/error.hh
similarity index 53%
rename from cpp/tests/util/error.hh
rename to cpp/tests/testutil/error.hh
index 85d7951d333c60aa7c222cab272ff6737798fd95..90a1fdb1beead8a790005b22c57baefc8cfcf08f 100644
--- a/cpp/tests/util/error.hh
+++ b/cpp/tests/testutil/error.hh
@@ -1,9 +1,9 @@
-#ifndef __VEREIGN_TEST_UTIL_ERROR_HH
-#define __VEREIGN_TEST_UTIL_ERROR_HH
+#ifndef __VEREIGN_TESTUTIL_ERROR_HH
+#define __VEREIGN_TESTUTIL_ERROR_HH
 
 #include <optional>
 
-namespace vereign::test {
+namespace vereign::testutil {
 
 template <class Error, class Fun>
 auto CatchError(Fun fn) -> std::optional<Error> {
@@ -17,6 +17,6 @@ auto CatchError(Fun fn) -> std::optional<Error> {
   }
 }
 
-} // namespace test
+} // namespace vereign::testutil
 
-#endif // __VEREIGN_TEST_UTIL_ERROR_HH
+#endif // __VEREIGN_TESTUTIL_ERROR_HH
diff --git a/cpp/tests/testutil/golden.cc b/cpp/tests/testutil/golden.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e5a011263d8cf9211b0082da94c5b67c898e8204
--- /dev/null
+++ b/cpp/tests/testutil/golden.cc
@@ -0,0 +1,26 @@
+#include <testutil/golden.hh>
+
+#include <vereign/fs/util.hh>
+#include <testutil/env.hh>
+
+#include <catch2/catch.hpp>
+
+namespace {
+  constexpr const char* GoldenUpdateEnvVar = "GOLDEN_UPDATE";
+}
+
+namespace vereign::testutil::golden {
+
+void Assert(const std::string& golden_file_path, bytes::View src) {
+  auto update = vereign::testutil::GetEnv(GoldenUpdateEnvVar, "");
+  if (!update.empty()) {
+    fs::WriteFile(golden_file_path, src);
+  }
+
+  auto data = fs::ReadFile(golden_file_path);
+
+  // TODO: create nice patch like diff on failure
+  CHECK(data.View().String() == src.String());
+}
+
+} // namespace vereign::test
diff --git a/cpp/tests/testutil/golden.hh b/cpp/tests/testutil/golden.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2fb33bc97c6a742d35a6f569fa026431ece91a11
--- /dev/null
+++ b/cpp/tests/testutil/golden.hh
@@ -0,0 +1,13 @@
+#ifndef __VEREIGN_TESTUTIL_GOLDEN_HH
+#define __VEREIGN_TESTUTIL_GOLDEN_HH
+
+#include <vereign/bytes/view.hh>
+#include <string>
+
+namespace vereign::testutil::golden {
+
+void Assert(const std::string& golden_file, bytes::View src);
+
+} // namespace vereign::test
+
+#endif // __VEREIGN_TESTUTIL_GOLDEN_HH
diff --git a/cpp/tests/util/protobuf.cc b/cpp/tests/testutil/protobuf.cc
similarity index 74%
rename from cpp/tests/util/protobuf.cc
rename to cpp/tests/testutil/protobuf.cc
index 05e1c791a91878f1d09a4c0068227de89e7937e1..c6ecf9991f052196651cb71af76dbf7598b1ac94 100644
--- a/cpp/tests/util/protobuf.cc
+++ b/cpp/tests/testutil/protobuf.cc
@@ -1,12 +1,11 @@
 #include <vereign/grpc/json/encoder.hh>
-#include <util/protobuf.hh>
+#include <testutil/protobuf.hh>
 
 #include <google/protobuf/util/json_util.h>
 
 #include <catch2/catch.hpp>
 
-namespace vereign {
-namespace test {
+namespace vereign::testutil {
 
 auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string {
   std::string result;
@@ -15,5 +14,4 @@ auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string {
   return result;
 }
 
-} // namespace vereign
-} // namespace test
+} // namespace vereign::testutil
diff --git a/cpp/tests/testutil/protobuf.hh b/cpp/tests/testutil/protobuf.hh
new file mode 100644
index 0000000000000000000000000000000000000000..264f7f6e51011c8678e68e0d943176ab0217e497
--- /dev/null
+++ b/cpp/tests/testutil/protobuf.hh
@@ -0,0 +1,13 @@
+#ifndef __VEREIGN_TEST_UTIL_PROTOBUF_HH
+#define __VEREIGN_TEST_UTIL_PROTOBUF_HH
+
+#include <string>
+#include <google/protobuf/message.h>
+
+namespace vereign::testutil {
+
+auto ProtobufToJson(const google::protobuf::Message& msg) -> std::string;
+
+} // namespace vereign::testutil
+
+#endif // __VEREIGN_TESTUTIL_PROTOBUF_HH
diff --git a/cpp/tests/util/protobuf.hh b/cpp/tests/util/protobuf.hh
deleted file mode 100644
index 09ca9be1f1b36d9d1444160333d0017ffc396b8e..0000000000000000000000000000000000000000
--- a/cpp/tests/util/protobuf.hh
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef __VEREIGN_TEST_UTIL_PROTOBUF_HH
-#define __VEREIGN_TEST_UTIL_PROTOBUF_HH
-
-#include <string>
-#include <google/protobuf/message.h>
-
-namespace vereign {
-namespace test {
-
-std::string ProtobufToJson(const google::protobuf::Message& msg);
-
-} // namespace vereign
-} // namespace test
-
-#endif // __VEREIGN_TEST_UTIL_PROTOBUF_HH
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 0bcfbc6e919333d7cae2525229b4c53b0cf21247..b7c43c069858265bed7a23715e8832f9b4505105 100644
--- a/cpp/tests/vereign/CMakeLists.txt
+++ b/cpp/tests/vereign/CMakeLists.txt
@@ -12,11 +12,10 @@ include_directories(
 
 list(APPEND TESTS_SRC
   init_tests.cc
-  ../util/protobuf.cc
-  ../experiment/array.cc
   test/device.cc
   test/service_context.cc
 
+  core/time_test.cc
   sync/channel_test.cc
 
   encoding/base64_test.cc
@@ -25,9 +24,11 @@ list(APPEND TESTS_SRC
   bytes/buffer_test.cc
   bytes/view_test.cc
 
+  crypto/errors_test.cc
   crypto/aes_test.cc
   crypto/rsa_test.cc
   crypto/digest_test.cc
+  crypto/cert_test.cc
 
   restapi/client_test.cc
   restapi/client_session_test.cc
@@ -64,7 +65,7 @@ target_proto_generate(
 
 target_link_libraries(tests
   vereignlib
-  Threads::Threads
+  testutil
 )
 
 if (VEREIGN_ENABLE_BENCHMARKING)
diff --git a/cpp/tests/vereign/core/time_test.cc b/cpp/tests/vereign/core/time_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..09637b9fb892cc8a86a27281587d003326c15642
--- /dev/null
+++ b/cpp/tests/vereign/core/time_test.cc
@@ -0,0 +1,42 @@
+#include <vereign/core/time.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("time::MakePosixTime", "[vereign/time][vereign/core]") {
+  auto ptime = time::MakePosixTime(2020, 1, 1, 0, 1, 1);
+
+  CHECK(ptime.date().year() == 2020);
+  CHECK(ptime.date().month() == boost::gregorian::Jan);
+  CHECK(ptime.date().day() == 1);
+  CHECK(ptime.time_of_day().hours() == 0);
+  CHECK(ptime.time_of_day().minutes() == 1);
+  CHECK(ptime.time_of_day().seconds() == 1);
+
+  CHECK(time::PosixTimeToTime(ptime) == 1577836861);
+  CHECK(time::MakeTimeUTCFromISO("20200101T000101") == time::PosixTimeToTime(ptime));
+
+  ptime = time::MakePosixTime(2020, 1, 1, 24, 60, 60);
+  CHECK(boost::posix_time::to_iso_string(ptime) == "20200102T010100");
+}
+
+TEST_CASE("time::MakeTimeUTC", "[vereign/time][vereign/core]") {
+  auto timestamp = time::MakeTimeUTC(2020, 1, 1, 0, 1, 1);
+
+  CHECK(timestamp == 1577836861);
+  CHECK(time::MakeTimeUTCFromISO("20200101T000101") == timestamp);
+}
+
+TEST_CASE("time::MakeTimeUTCFromString", "[vereign/time][vereign/core]") {
+  auto timestamp = time::MakeTimeUTCFromString("2020-1-1 0:1:1");
+
+  CHECK(timestamp == 1577836861);
+  CHECK(time::MakeTimeUTCFromISO("20200101T000101") == timestamp);
+}
+
+TEST_CASE("time::MakeTimeUTCFromISO", "[vereign/time][vereign/core]") {
+  auto timestamp = time::MakeTimeUTCFromISO("20200101T000101");
+
+  CHECK(timestamp == 1577836861);
+}
diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc
index cfbb694c4e2eaddfd922670ad528a6984ebb0850..b9f4c41bd0334cd29d4a75e839572bf1f4ea8ba5 100644
--- a/cpp/tests/vereign/crypto/aes_test.cc
+++ b/cpp/tests/vereign/crypto/aes_test.cc
@@ -8,7 +8,7 @@
 
 using namespace vereign;
 
-TEST_CASE("aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") {
+TEST_CASE("crypto::aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") {
   const std::string data{"foo bar"};
   auto key = crypto::Rand(32);
 
diff --git a/cpp/tests/vereign/crypto/cert_test.cc b/cpp/tests/vereign/crypto/cert_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..da6bec076b79072fff0f925e9f20965b3ec84927
--- /dev/null
+++ b/cpp/tests/vereign/crypto/cert_test.cc
@@ -0,0 +1,84 @@
+#include <vereign/crypto/cert.hh>
+
+#include <vereign/core/time.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/bio.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/fs/util.hh>
+#include <testutil/golden.hh>
+
+#include <catch2/catch.hpp>
+
+#include <iostream>
+#include <fstream>
+
+using namespace vereign;
+
+namespace {
+   const std::string testDataPath = fs::path::Join("..", "tests", "vereign", "crypto", "test_data");
+}
+
+TEST_CASE("crypto::cert::CreateSelfSignedCert", "[vereign/crypto/cert][vereign/crypto]") {
+  auto key = crypto::rsa::ImportPrivateKeyFromPEM(
+    fs::ReadFile(fs::path::Join(testDataPath, "ca_key.pem")).View()
+  );
+
+  crypto::cert::CertData cert_data{};
+  cert_data.Email = "ca@example.com";
+  cert_data.Subject.CommonName = "ca-cn.example.com";
+  cert_data.Subject.Country = "BG";
+  cert_data.Subject.State = "Sofia";
+  cert_data.Subject.Locality = "Sofia";
+  cert_data.Subject.Organization = "Vereign AG";
+  cert_data.Subject.OrganizationUnit = "CA";
+  cert_data.Url = "ca.example.com";
+  cert_data.Validity.ValidYears = 5;
+  cert_data.SerialNumber = 42;
+  cert_data.Validity.NotBefore = vereign::time::MakeTimeUTCFromString("2020-07-01 00:00:00");
+  cert_data.IsCA = true;
+
+  auto cert = crypto::cert::CreateSelfSignedCert(cert_data, key.get());
+
+  std::stringstream ss;
+  crypto::cert::PrintCert(ss, cert.get());
+  testutil::golden::Assert(fs::path::Join(testDataPath, "ca_cert.txt"), bytes::View(ss.str()));
+
+  auto pem = crypto::cert::ExportCertToPEM(cert.get());
+  testutil::golden::Assert(fs::path::Join(testDataPath, "ca.crt"), crypto::bio::View(pem.get()));
+}
+
+TEST_CASE("crypto::cert::CreateCert", "[vereign/crypto/cert][vereign/crypto]") {
+  auto ca_key = crypto::rsa::ImportPrivateKeyFromPEM(
+    fs::ReadFile(fs::path::Join(testDataPath, "ca_key.pem")).View()
+  );
+
+  auto ca_cert = crypto::cert::ImportCertFromPEM(
+    fs::ReadFile(fs::path::Join(testDataPath, "ca.crt")).View()
+  );
+
+  auto key = crypto::rsa::ImportPrivateKeyFromPEM(
+    fs::ReadFile(fs::path::Join(testDataPath, "leaf_key.pem")).View()
+  );
+
+  crypto::cert::CertData cert_data{};
+  cert_data.Email = "test@example.com";
+  cert_data.Subject.CommonName = "example.com";
+  cert_data.Subject.Country = "BG";
+  cert_data.Subject.State = "Sofia";
+  cert_data.Subject.Locality = "Sofia";
+  cert_data.Subject.Organization = "Vereign AG";
+  cert_data.Subject.OrganizationUnit = "Engineering";
+  cert_data.Url = "api.example.com";
+  cert_data.Validity.ValidYears = 5;
+  cert_data.SerialNumber = 73;
+  cert_data.Validity.NotBefore = vereign::time::MakeTimeUTCFromString("2020-07-02 00:00:00");
+
+  auto cert = crypto::cert::CreateCert(cert_data, ca_cert.get(), ca_key.get(), key.get());
+
+  std::stringstream ss;
+  crypto::cert::PrintCert(ss, cert.get());
+
+  testutil::golden::Assert(fs::path::Join(testDataPath, "leaf_cert.txt"), bytes::View(ss.str()));
+}
diff --git a/cpp/tests/vereign/crypto/digest_test.cc b/cpp/tests/vereign/crypto/digest_test.cc
index 0bf6018f3bcc7c1120dc699e8d80024f2c4f3cbb..cea92371b19c2290900f0047a587a54522ad537b 100644
--- a/cpp/tests/vereign/crypto/digest_test.cc
+++ b/cpp/tests/vereign/crypto/digest_test.cc
@@ -9,7 +9,7 @@
 
 using namespace vereign;
 
-TEST_CASE("digest::sha1", "[vereign/crypto/digest][vereign/crypto]") {
+TEST_CASE("crypto::digest::sha1", "[vereign/crypto/digest][vereign/crypto]") {
   const std::string data{"foo bar"};
 
   bytes::Buffer hash;
diff --git a/cpp/tests/vereign/crypto/errors_test.cc b/cpp/tests/vereign/crypto/errors_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1b9f32a3523e27b14a9985047ccb0caca2f1dd46
--- /dev/null
+++ b/cpp/tests/vereign/crypto/errors_test.cc
@@ -0,0 +1,24 @@
+#include <vereign/crypto/errors.hh>
+#include <openssl/evp.h>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("crypto::OpenSSLError", "[vereign/crypto]") {
+  SECTION("it must return error with message that included the last OpenSSL error") {
+    EVP_PKEY_CTX* ctx = nullptr;
+    auto r = EVP_PKEY_encrypt_init(ctx);
+    CHECK(r == 0);
+
+    auto err = crypto::OpenSSLError{"pkey encrypt failed"};
+    CHECK(std::string(err.what()) == "pkey encrypt failed: OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE");
+  }
+
+  SECTION("when there was no OpenSSL error,"
+          " it must create an error with `no openssl error in the error queue`") {
+    auto err = crypto::OpenSSLError{"test error"};
+
+    CHECK(std::string(err.what()) == "test error: no openssl error in the error queue");
+  }
+}
diff --git a/cpp/tests/vereign/crypto/test_data/.gitattributes b/cpp/tests/vereign/crypto/test_data/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..fa1385d99a319b43c06f5309d1aae9fdd3adea46
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/.gitattributes
@@ -0,0 +1 @@
+* -text
diff --git a/cpp/tests/vereign/crypto/test_data/ca.crt b/cpp/tests/vereign/crypto/test_data/ca.crt
new file mode 100644
index 0000000000000000000000000000000000000000..affc0044449d58fa031c69a13afa931baf587d3e
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/ca.crt
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEvDCCA6SgAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBijEaMBgGA1UEAwwRY2Et
+Y24uZXhhbXBsZS5jb20xCzAJBgNVBAYTAkJHMQ4wDAYDVQQHDAVTb2ZpYTEOMAwG
+A1UECAwFU29maWExEzARBgNVBAoMClZlcmVpZ24gQUcxCzAJBgNVBAsMAkNBMR0w
+GwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbTAeFw0yMDA3MDEwMDAwMDBaFw0y
+NTA2MzAwMDAwMDBaMIGKMRowGAYDVQQDDBFjYS1jbi5leGFtcGxlLmNvbTELMAkG
+A1UEBhMCQkcxDjAMBgNVBAcMBVNvZmlhMQ4wDAYDVQQIDAVTb2ZpYTETMBEGA1UE
+CgwKVmVyZWlnbiBBRzELMAkGA1UECwwCQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4
+YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxINphs0i
+1HrDcvsG1mUnNrcnwhyitN5ZX8FDgtk+r5RXNk+zRvmlDz+kKq/UNXgIH5v1LbRi
+Vcmh9njnLqh4uPW2Khkxna3yjnWF3NqV780uOCkUEZp0CC2B2RRXCjmxF3wBtggb
+FjBSoh5nhYUtJZkeDgY+AaFlfBKHWWQpzLJWxdHFW/Ej33MubilPTKOaP2Nh9U9x
+bxiwAHS3ijnhpW2Xh6uwf1XMQVwwsbPtEd81BBU86KIeQzOQrYkUEl81nqOzt9Ct
+67fUJs0yPPTtoEekWAYCdFD0hrVLxz1ilQtYF+shXL/6GyrKz0putQhO2wOqINXV
+rHUv1SgEMwD/TwIDAQABo4IBKTCCASUwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAYYwKQYDVR0RBCIwIIEOY2FAZXhhbXBsZS5jb22CDmNhLmV4YW1wbGUu
+Y29tMB0GA1UdDgQWBBR+ivGqlvajtAUhWQthghHP6zZrdjCBtwYDVR0jBIGvMIGs
+gBR+ivGqlvajtAUhWQthghHP6zZrdqGBkKSBjTCBijEaMBgGA1UEAwwRY2EtY24u
+ZXhhbXBsZS5jb20xCzAJBgNVBAYTAkJHMQ4wDAYDVQQHDAVTb2ZpYTEOMAwGA1UE
+CAwFU29maWExEzARBgNVBAoMClZlcmVpZ24gQUcxCzAJBgNVBAsMAkNBMR0wGwYJ
+KoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbYIBKjANBgkqhkiG9w0BAQsFAAOCAQEA
+wHKy0Gmj5Gs+DglsqzOfmUeqy8EsmaS6ufxV084umpnNmaQrdBSQFH0P8sQJROpH
+1bgibRLQFwQLKvAC6dxCvnH6njW0lCZ/53OkrNyky53eZ6I6DHrT0kjkC9KqShEf
++MhHBniVCX2w8HxFgZgMKGiwL+D8qo5QzHmR+AzXAZGlKKZReKuPUehfO02cVWPH
+uz/dSl2gd7xdAz3lwayVPiNNCu4r0/gwvEiHuwCvjIQPIxsUBlm5BwB0ga0OlMJu
+nGVv3dNkS2KjIEBms4jvXxk9w51OQqrcVUhuj8qHenTtM6ho5QTtDi2z/NbM0oW1
+zAmLUoPW97IgXtp4ghOQ7w==
+-----END CERTIFICATE-----
diff --git a/cpp/tests/vereign/crypto/test_data/ca_cert.txt b/cpp/tests/vereign/crypto/test_data/ca_cert.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9ed51e64c82cf5d0d352d31b632f74c18daa62e8
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/ca_cert.txt
@@ -0,0 +1,63 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 42 (0x2a)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com
+        Validity
+            Not Before: Jul  1 00:00:00 2020 GMT
+            Not After : Jun 30 00:00:00 2025 GMT
+        Subject: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c4:83:69:86:cd:22:d4:7a:c3:72:fb:06:d6:65:
+                    27:36:b7:27:c2:1c:a2:b4:de:59:5f:c1:43:82:d9:
+                    3e:af:94:57:36:4f:b3:46:f9:a5:0f:3f:a4:2a:af:
+                    d4:35:78:08:1f:9b:f5:2d:b4:62:55:c9:a1:f6:78:
+                    e7:2e:a8:78:b8:f5:b6:2a:19:31:9d:ad:f2:8e:75:
+                    85:dc:da:95:ef:cd:2e:38:29:14:11:9a:74:08:2d:
+                    81:d9:14:57:0a:39:b1:17:7c:01:b6:08:1b:16:30:
+                    52:a2:1e:67:85:85:2d:25:99:1e:0e:06:3e:01:a1:
+                    65:7c:12:87:59:64:29:cc:b2:56:c5:d1:c5:5b:f1:
+                    23:df:73:2e:6e:29:4f:4c:a3:9a:3f:63:61:f5:4f:
+                    71:6f:18:b0:00:74:b7:8a:39:e1:a5:6d:97:87:ab:
+                    b0:7f:55:cc:41:5c:30:b1:b3:ed:11:df:35:04:15:
+                    3c:e8:a2:1e:43:33:90:ad:89:14:12:5f:35:9e:a3:
+                    b3:b7:d0:ad:eb:b7:d4:26:cd:32:3c:f4:ed:a0:47:
+                    a4:58:06:02:74:50:f4:86:b5:4b:c7:3d:62:95:0b:
+                    58:17:eb:21:5c:bf:fa:1b:2a:ca:cf:4a:6e:b5:08:
+                    4e:db:03:aa:20:d5:d5:ac:75:2f:d5:28:04:33:00:
+                    ff:4f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Subject Alternative Name: 
+                email:ca@example.com, DNS:ca.example.com
+            X509v3 Subject Key Identifier: 
+                7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76
+            X509v3 Authority Key Identifier: 
+                keyid:7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76
+                DirName:/CN=ca-cn.example.com/C=BG/L=Sofia/ST=Sofia/O=Vereign AG/OU=CA/emailAddress=ca@example.com
+                serial:2A
+
+    Signature Algorithm: sha256WithRSAEncryption
+         c0:72:b2:d0:69:a3:e4:6b:3e:0e:09:6c:ab:33:9f:99:47:aa:
+         cb:c1:2c:99:a4:ba:b9:fc:55:d3:ce:2e:9a:99:cd:99:a4:2b:
+         74:14:90:14:7d:0f:f2:c4:09:44:ea:47:d5:b8:22:6d:12:d0:
+         17:04:0b:2a:f0:02:e9:dc:42:be:71:fa:9e:35:b4:94:26:7f:
+         e7:73:a4:ac:dc:a4:cb:9d:de:67:a2:3a:0c:7a:d3:d2:48:e4:
+         0b:d2:aa:4a:11:1f:f8:c8:47:06:78:95:09:7d:b0:f0:7c:45:
+         81:98:0c:28:68:b0:2f:e0:fc:aa:8e:50:cc:79:91:f8:0c:d7:
+         01:91:a5:28:a6:51:78:ab:8f:51:e8:5f:3b:4d:9c:55:63:c7:
+         bb:3f:dd:4a:5d:a0:77:bc:5d:03:3d:e5:c1:ac:95:3e:23:4d:
+         0a:ee:2b:d3:f8:30:bc:48:87:bb:00:af:8c:84:0f:23:1b:14:
+         06:59:b9:07:00:74:81:ad:0e:94:c2:6e:9c:65:6f:dd:d3:64:
+         4b:62:a3:20:40:66:b3:88:ef:5f:19:3d:c3:9d:4e:42:aa:dc:
+         55:48:6e:8f:ca:87:7a:74:ed:33:a8:68:e5:04:ed:0e:2d:b3:
+         fc:d6:cc:d2:85:b5:cc:09:8b:52:83:d6:f7:b2:20:5e:da:78:
+         82:13:90:ef
diff --git a/cpp/tests/vereign/crypto/test_data/ca_key.pem b/cpp/tests/vereign/crypto/test_data/ca_key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..2c73e6ead4478b7c4d32a2ad3b3031c7eff054e0
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/ca_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDEg2mGzSLUesNy
++wbWZSc2tyfCHKK03llfwUOC2T6vlFc2T7NG+aUPP6Qqr9Q1eAgfm/UttGJVyaH2
+eOcuqHi49bYqGTGdrfKOdYXc2pXvzS44KRQRmnQILYHZFFcKObEXfAG2CBsWMFKi
+HmeFhS0lmR4OBj4BoWV8EodZZCnMslbF0cVb8SPfcy5uKU9Mo5o/Y2H1T3FvGLAA
+dLeKOeGlbZeHq7B/VcxBXDCxs+0R3zUEFTzooh5DM5CtiRQSXzWeo7O30K3rt9Qm
+zTI89O2gR6RYBgJ0UPSGtUvHPWKVC1gX6yFcv/obKsrPSm61CE7bA6og1dWsdS/V
+KAQzAP9PAgMBAAECggEAOBj9hGZF9ZsP9WBUrNpWbeeuGVscVX6Ny+h7UbybiPrT
+RKVO28mDRY2Y2zizXwojY4adYI04bx3uttH/yNF+GOrHtE/Z2pXgAqvo6Uma+Mg9
+U/niCT5qtjTrduE4Eaqzc61KPcElnllwFWiRi8ufPjbuHfnJNKTyuA8ABVQzHIQi
+Z+Rhw/IASS2aMuD1A4XicneZ43Vo83mz7xfiebVW7WRyIIcxlWruHQM+BYCTR+y5
+vGdq+P0e4ZjAdwudxB6MfmF5lO16M8CiH5SAJghmBkbEpwdkZuMX6YQLGnw2+C6m
+NHRRb0WSGX12PEJcxA4mvM4L2+m7ihZTSJg5fYSLAQKBgQDmV77TXc6xAfq1O3es
+AV69a5rNgtJ3I7wnv42ZLpQLVSVQ1eqRzhaMIItbX3hwb9STWagIuuGbTG590k00
+lsHRD0/Z4mapyk43b9Zs90ug1L0ojqrVfmjb5uJDzj4fbHO1QtJ2Dt2llMveGCaE
+j5LYz+AKdBjZdxxU6NDws8W1AQKBgQDaZwQjMAx7usp+prVVe33QchTxeziaFh+L
+q53zimoCsXjTSz040dkdk+yhj0DOT3z4Of41+PkWmbLoZh1ar9yFSQPC3fVvz4Sx
+mY5gCIy1D9a3SSPYy6+V6PiCSkYB3JsGONKARl1oxNswewGcBVSxEYMH0INGccoc
+Aqv0WYokTwKBgE/QtdHd3oIdUnc8HPKgIuj2AVUW8MDRxB/t0y3yIuBuZ5jEFxzg
+0ZVKrZ9CzKQBTCKm3X/w6b37VQoKNjBz0MMVUDEKr1JERKgW+7EUW8NmFAmarf1+
+aO1R6rNeIs6WsN1tQyofapQ2pBHYQniKm3K/TT2yDu5DxLBzX/sX//kBAoGAfNsL
+uHoNiOZ+QO1ZGSdiA7pUUsDY2O3w1s4jnEjy1dtTcae/ti3xa0FXs/zLVaaHkPjG
+ZWCSNeNkLgvCmEeVT/Uv20U8vNDr0QQyKqB4oEIPw/RiM9wt4wuulf2UDBhr36C4
+ZnDTkcOIcyy+/MznX9rdB6iig7Elk+HLIAPYP20CgYAL+/AizILmdI7lBvderLfw
+2oslABhYJCjaFX5XWTvOJpPdZPZSqZUgSvzgBM57f1gtRmn6NS1oM6qagoYlOpF4
+rjIcuz86j8tFTp45TjMz1Sf3afyi+iiFhdjTeDTdyjcWK0sBghyljc4jq5scQsYH
+SUOdYY70Kenpzx6iOaWe/g==
+-----END PRIVATE KEY-----
+
diff --git a/cpp/tests/vereign/crypto/test_data/leaf_cert.txt b/cpp/tests/vereign/crypto/test_data/leaf_cert.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fb718f515d1240b1ea27e79859552b2d87d7c06e
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/leaf_cert.txt
@@ -0,0 +1,65 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 73 (0x49)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=ca-cn.example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=CA/emailAddress=ca@example.com
+        Validity
+            Not Before: Jul  2 00:00:00 2020 GMT
+            Not After : Jul  1 00:00:00 2025 GMT
+        Subject: CN=example.com, C=BG, L=Sofia, ST=Sofia, O=Vereign AG, OU=Engineering/emailAddress=test@example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:be:ab:51:25:32:3c:75:bb:49:25:1b:b9:9f:6e:
+                    af:ff:38:07:f0:2c:49:e7:3b:c8:3a:19:02:f1:aa:
+                    96:8d:9d:bd:37:b0:97:2a:11:05:71:65:94:ee:32:
+                    6b:4b:d7:94:43:a1:8c:bb:ed:fe:94:d6:93:f8:7b:
+                    ca:48:7d:fc:99:00:88:d5:d1:73:55:31:ca:eb:69:
+                    5f:0d:1b:6a:24:39:db:b9:f1:7a:34:c7:1f:8e:a4:
+                    12:ab:1d:e8:29:b6:aa:05:d3:fb:7d:bb:7e:2b:db:
+                    c8:18:15:ba:8e:6d:b4:df:98:3c:34:6c:50:07:e9:
+                    7a:7a:57:99:8b:cf:6d:d7:c3:e1:c5:90:ae:d3:94:
+                    dc:b9:fc:90:20:ec:63:24:e5:8b:42:d3:bb:4d:e6:
+                    e2:f0:b6:48:a9:c0:27:2e:89:bc:45:db:b6:25:be:
+                    7d:55:40:6d:72:e4:5b:49:81:d4:aa:e0:3a:33:93:
+                    f9:4b:b2:82:b4:45:e1:5e:0c:14:b0:6e:78:f9:1a:
+                    98:72:8c:ef:c5:10:ab:7f:9d:e7:9b:68:17:30:21:
+                    f0:95:bf:ab:c6:45:bb:3c:cf:f1:96:1f:8d:22:88:
+                    72:b3:52:ff:20:c0:40:5b:11:0d:62:24:ec:5c:53:
+                    81:fa:ca:ec:a5:00:b8:7c:1e:e2:a8:b7:01:42:5e:
+                    1d:01
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+            X509v3 Key Usage: critical
+                Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment
+            X509v3 Extended Key Usage: 
+                E-mail Protection
+            X509v3 Subject Alternative Name: 
+                email:test@example.com, DNS:api.example.com
+            X509v3 Subject Key Identifier: 
+                A5:BD:8A:35:84:98:B6:6C:13:51:A2:4A:5D:C9:51:C6:61:7E:5D:AE
+            X509v3 Authority Key Identifier: 
+                keyid:7E:8A:F1:AA:96:F6:A3:B4:05:21:59:0B:61:82:11:CF:EB:36:6B:76
+                DirName:/CN=ca-cn.example.com/C=BG/L=Sofia/ST=Sofia/O=Vereign AG/OU=CA/emailAddress=ca@example.com
+                serial:2A
+
+    Signature Algorithm: sha256WithRSAEncryption
+         8d:90:e7:a7:a6:57:ec:b3:db:38:37:87:bc:1e:0b:e2:bd:c4:
+         88:e3:95:f0:35:01:a7:fe:51:92:b1:4c:37:d7:21:cf:80:77:
+         51:98:62:61:2c:99:58:60:89:01:35:3e:56:8c:dd:07:21:ad:
+         a1:e3:e5:02:3c:e5:cf:c8:ee:46:11:07:16:f5:37:8c:75:22:
+         e0:af:85:e2:85:31:21:a2:f3:e4:b5:1b:de:ef:31:86:88:84:
+         18:11:bc:67:5a:a7:0b:d1:16:fc:5c:12:51:8d:1c:26:d5:45:
+         f8:55:28:7f:85:a2:c7:a7:e4:bc:6b:98:b4:c4:d9:3e:f6:72:
+         a8:f9:24:f5:9b:38:bf:aa:76:9c:68:c8:67:4f:fa:79:9c:7c:
+         52:f2:45:6c:05:26:4f:64:4c:ea:52:d0:c2:75:45:21:e5:8e:
+         d7:60:0a:b2:6c:d2:f8:1c:01:9b:b1:72:1d:4e:95:79:18:34:
+         b6:1c:bc:ea:5d:12:1f:33:90:63:dc:01:b9:9b:33:dd:62:31:
+         69:c8:b2:1d:ce:3d:38:57:60:c5:75:7d:84:95:3d:db:36:0a:
+         a3:77:7c:67:b6:e3:7a:c0:b4:a5:51:05:78:6b:29:28:43:b6:
+         74:d8:74:18:8e:4f:44:62:55:49:bb:f9:f3:1b:37:63:49:ff:
+         c9:2e:73:a2
diff --git a/cpp/tests/vereign/crypto/test_data/leaf_key.pem b/cpp/tests/vereign/crypto/test_data/leaf_key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..5d83ade2c640588ac9df74b38aa7dd62dc23600c
--- /dev/null
+++ b/cpp/tests/vereign/crypto/test_data/leaf_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+q1ElMjx1u0kl
+G7mfbq//OAfwLEnnO8g6GQLxqpaNnb03sJcqEQVxZZTuMmtL15RDoYy77f6U1pP4
+e8pIffyZAIjV0XNVMcrraV8NG2okOdu58Xo0xx+OpBKrHegptqoF0/t9u34r28gY
+FbqObbTfmDw0bFAH6Xp6V5mLz23Xw+HFkK7TlNy5/JAg7GMk5YtC07tN5uLwtkip
+wCcuibxF27Ylvn1VQG1y5FtJgdSq4Dozk/lLsoK0ReFeDBSwbnj5GphyjO/FEKt/
+neebaBcwIfCVv6vGRbs8z/GWH40iiHKzUv8gwEBbEQ1iJOxcU4H6yuylALh8HuKo
+twFCXh0BAgMBAAECggEAAuEIMLktYl4MLh2DpBrng5bMwI1mpfFy5fUD5J1zDbCJ
++iSLj8oh1yhrGIgAiJdjfibwaVO1Z6Qa0rfJzOZBcWz+4/L4RxshSYSLN8yIFy5x
+eSwNMJf+IROsqCf43g0LobAQU4HIggXc+Qd2TEQZ+lNXO2Jpo0jyUr/DtNxSYrLk
+PQOiZUeTRApEFMH/KR7E3CINnFWEsyBktr4UGNT+KbuS+h24QFgeMZS03ELCER/E
+GtYgjYE8Njb+LLC82ZB0jVobo7sXqEu4xhunsysJMID7XF7cRbVH4jS4owBik+MM
+0C4x3IWcUlPTRwCwz7ThdNmuaWysdcm6YKmDTjkg7QKBgQDgObGyYADOBVC986xD
+rHQ6HsHjJoS7UA98OKtpwUfoHgmzCn787bL+28payYLjBQg8Lu0V4wz6IzryCjEY
+DBDWT376lsIElYQBVO80JR+sQHveRKHj3nKxin8RFv+P1iR5l06MLFuI7lpgnfjf
+zAJAMtob6hXTfhKd6ttw3LMJtQKBgQDZsEsv3iWEtJOlEk32b+cBtC9m3+3BRrsl
+vhMqJRRiz3q8ESw4UeYnjHTMJPKixsNCTwJ+swLz1HzPjAPOn8J5g+ehx9szx04g
++SOjO0hYxiuMyEY1RcgQDz8W2eDIY79vQHK6eIdsDX9aDM8TKl0SVkX8yeZVH/l4
+ZrS0GcIlnQKBgHwO78YlX8ydHEM8or7+l3/grJ2lHiMU9LxiDlUA32wJ0owA7qm6
+SiLAMIFypLw+eIPz4rENYoyzGVX5VZhcdKReb37uEoK+xr6F46MSNyfytZYQmxGc
+bhDU8+DcvKE0dhHgKVFc6XgliFOUk7IfCc/DUvHG3jSZuGsvGiKjuVtRAoGAL5DF
+3P+9UvHiEP+e6WoqH8UvVls3qDO8UNLnfR39Blovh0h5URUUJTAblT+9hLPJloON
+GAIlcwHLA86kRlHCPKsFwePZkcbK24mUr+YUONGUUn6wWJOW02uBbYzOAPtKqRR7
+/SK49HM+kH6cAKQREiCnykSTf6Ys7JIEihXWxkECgYEAo/PjT9L+QuYn9muv4p50
+UMJLGQ6kE+63n2dvcEUhXRtEx/i4KzgTkdJ0gBOiaBhJYjzI4BZt/DgtSrzqNC1Y
+UUk5i9sJJF5Ypw0n3dX1mYEFlsJ1xFKDU+lwMakdY+X6b+VECKGIQZsh5DOrFRrv
+QzRzF4x09HztPUADv4gCL40=
+-----END PRIVATE KEY-----
diff --git a/cpp/tests/vereign/grpc/identity_api_test.cc b/cpp/tests/vereign/grpc/identity_api_test.cc
index 516aa71338dbf10660e61d48b0451f55cb647fc6..2593b8f0781a19ee0886945e5ec15d8f1e23aca5 100644
--- a/cpp/tests/vereign/grpc/identity_api_test.cc
+++ b/cpp/tests/vereign/grpc/identity_api_test.cc
@@ -15,8 +15,8 @@
 # include <vereign/ncrypt/rsa.hh>
 #endif
 
-#include <util/env.hh>
-#include <util/protobuf.hh>
+#include <testutil/env.hh>
+#include <testutil/protobuf.hh>
 #include <grpcpp/create_channel.h>
 
 #include <catch2/catch.hpp>
@@ -24,8 +24,8 @@
 TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.integration]") {
 #ifdef _WIN32
   SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     auto storage_path = vereign::fs::TempDir("test_db_");
     auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
@@ -64,9 +64,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.
   }
 
   SECTION("when the device key has been changed, it must fail with InvalidIdentity error") {
-    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     auto storage_path = vereign::fs::TempDir("test_db_");
     auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
@@ -113,8 +113,8 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.
   }
 #else
   SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     auto storage_path = vereign::fs::TempDir("test_db_");
     auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
@@ -145,9 +145,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.
   }
 
   SECTION("when the pin is wrong, it must fail with InvalidPinCode error") {
-    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     const std::string pin = "foo";
     auto storage_path = vereign::fs::TempDir("test_db_");
@@ -180,9 +180,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.
   }
 
   SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
-    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     const std::string pin = "foo";
     auto storage_path = vereign::fs::TempDir("test_db_");
@@ -217,9 +217,9 @@ TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.
 #endif
 
   SECTION("when the device is not authorized, it must fail with DeviceNotRegistered error") {
-    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     // the old device is used later for new device confirmation and authorization
     auto old_storage_path = vereign::fs::TempFilePath("test_db_");
@@ -303,8 +303,8 @@ TEST_CASE("grpc::IdentityAPI::LoginWithNewDevice", "[vereign/grpc][.integration]
 #ifdef _WIN32
 #else
   SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
-    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+    auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
     auto storage_path = vereign::fs::TempDir("test_db_");
     auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
diff --git a/cpp/tests/vereign/grpc/server_test.cc b/cpp/tests/vereign/grpc/server_test.cc
index 0f3a18589adfb98ebe6d39bb064c91257439b7f5..de3c47db053a0494fbaeb059d1f3535fc0bdcaee 100644
--- a/cpp/tests/vereign/grpc/server_test.cc
+++ b/cpp/tests/vereign/grpc/server_test.cc
@@ -10,16 +10,16 @@
 #include <vereign/test/device.hh>
 #include <vereign/test/service_context.hh>
 
-#include <util/env.hh>
-#include <util/protobuf.hh>
+#include <testutil/env.hh>
+#include <testutil/protobuf.hh>
 #include <grpcpp/create_channel.h>
 
 #include <catch2/catch.hpp>
 
 TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
-  auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto public_key = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   const std::string pin = "foo";
   auto storage_path = vereign::fs::TempDir("test_db_");
@@ -84,6 +84,6 @@ TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
 
   // std::cout << vereign::test::ProtobufToJson(getDIDsResp) << std::endl;
 
-  grpc_shutdown();
-  google::protobuf::ShutdownProtobufLibrary();
+  // grpc_shutdown();
+  // google::protobuf::ShutdownProtobufLibrary();
 }
diff --git a/cpp/tests/vereign/identity/provider_test.cc b/cpp/tests/vereign/identity/provider_test.cc
index f34a8450b557e96bbf996aca0e2e43bf6d1481a4..c4de48c2568c74315d2894410f4ff301b939d207 100644
--- a/cpp/tests/vereign/identity/provider_test.cc
+++ b/cpp/tests/vereign/identity/provider_test.cc
@@ -5,15 +5,24 @@
 #include <vereign/kvstore/crypto_storage.hh>
 #include <vereign/crypto/rand.hh>
 #include <vereign/bytes/view_dump.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+#include <vereign/service/gen/passport_service.hh>
+#include <vereign/crypto/cert.hh>
+#include <vereign/crypto/rsa.hh>
+#include <testutil/env.hh>
+#include <testutil/protobuf.hh>
 
 #include <catch2/catch.hpp>
 #include <iostream>
 
 using namespace vereign;
 
-TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") {
+TEST_CASE("identity::Provider::RecreateIdentity", "[vereign/identity]") {
   auto storage_path = fs::TempFilePath("test_db_");
-  fs::RemoveFileGuard rm{storage_path};
+  auto rm_storage_path = fs::RemoveFileGuard{storage_path};
+
+  auto device_ctx = test::ServiceContext{"", "", storage_path};
 
   std::string expected;
   std::string actual;
@@ -22,7 +31,7 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") {
     kvstore::SqliteStorage kvstorage{storage_path};
     kvstore::CryptoStorage storage{kvstorage, true};
 
-    identity::Provider provider{storage};
+    identity::Provider provider{device_ctx.ClientSession(), storage};
     expected = provider.RecreateIdentity("foo");
   }
 
@@ -30,7 +39,7 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") {
     kvstore::SqliteStorage kvstorage{storage_path};
     kvstore::CryptoStorage storage{kvstorage, true};
 
-    identity::Provider provider{storage};
+    identity::Provider provider{device_ctx.ClientSession(), storage};
     actual = provider.LoadIdentity("foo");
   }
 
@@ -38,3 +47,169 @@ TEST_CASE("Provider::RecreateIdentity", "[vereign/identity]") {
   CHECK(actual.size() > 0);
   CHECK(expected == actual);
 }
+
+TEST_CASE("identity::Provider::GetProfileCertificate", "[vereign/identity][.integration]") {
+  auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+  // prepare new device
+  auto old_storage_path = fs::TempFilePath("test_db_");
+  auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path};
+  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto storage_path = fs::TempFilePath("test_db_");
+  auto rm_storage_path = fs::RemoveFileGuard{storage_path};
+  auto service_context = test::ServiceContext{host, port, storage_path};
+  old_device.CreateNewDevice(service_context, "pin");
+
+  // retrieve a profile uuid
+  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();
+  auto& list = result.Response;
+  REQUIRE(list->code() == "200");
+  REQUIRE(list->data().size() > 0);
+
+  auto profileUUID = list->data().at(0).uuid();
+
+  auto& identity_provider = service_context.IdentityProvider();
+
+  auto cert = identity_provider.GetProfileCertificate(profileUUID);
+
+  REQUIRE(cert.get() != nullptr);
+  CHECK(cert->PrivateKeyPEM.Size() > 0);
+  CHECK(cert->CertificatePEM.Size() > 0);
+  CHECK(cert->CertificateUUID.size() > 0);
+  CHECK(cert->Chain.size() == 2);
+
+  SECTION("the certificate private key must be importable") {
+    bssl::UniquePtr<EVP_PKEY> cert_pkey;
+    CHECK_NOTHROW(
+      cert_pkey = crypto::rsa::ImportPrivateKeyFromPEM(cert->PrivateKeyPEM.View())
+    );
+    CHECK(cert_pkey.get() != nullptr);
+  }
+
+  SECTION("the certificate and the certificates from the chain must be importable") {
+    bssl::UniquePtr<X509> x509_cert;
+    CHECK_NOTHROW(
+      x509_cert = crypto::cert::ImportCertFromPEM(cert->CertificatePEM.View())
+    );
+    CHECK(x509_cert.get() != nullptr);
+
+    for (auto& cert_pem : cert->Chain) {
+      x509_cert.reset();
+
+      CHECK_NOTHROW(
+        x509_cert = crypto::cert::ImportCertFromPEM(cert_pem.View())
+      );
+      CHECK(x509_cert.get() != nullptr);
+    }
+  }
+
+
+  SECTION("it must reuse already created profile certificates") {
+    auto new_cert = identity_provider.GetProfileCertificate(profileUUID);
+
+    REQUIRE(new_cert.get() != nullptr);
+    CHECK(new_cert->PrivateKeyPEM.View() == cert->PrivateKeyPEM.View());
+    CHECK(new_cert->CertificatePEM.View() == cert->CertificatePEM.View());
+    CHECK(new_cert->CertificateUUID == cert->CertificateUUID);
+    REQUIRE(new_cert->Chain.size() == cert->Chain.size());
+    for (std::size_t i = 0; i < new_cert->Chain.size(); i++) {
+      CHECK(new_cert->Chain[i].View() == cert->Chain[i].View());
+    }
+  }
+}
+
+TEST_CASE("identity::Provider::GetProfileOneTimeCertificate", "[vereign/identity][.integration]") {
+  auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+  // prepare new device
+  auto old_storage_path = fs::TempFilePath("test_db_");
+  auto rm_old_storage_path = fs::RemoveFileGuard{old_storage_path};
+  auto old_device_ctx = test::ServiceContext{host, port, old_storage_path};
+  auto old_device = test::Device{old_device_ctx};
+  old_device.Login(public_key);
+
+  auto storage_path = fs::TempFilePath("test_db_");
+  auto rm_storage_path = fs::RemoveFileGuard{storage_path};
+  auto service_context = test::ServiceContext{host, port, storage_path};
+  old_device.CreateNewDevice(service_context, "pin");
+
+  // retrieve a profile uuid
+  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();
+  auto& list = result.Response;
+  REQUIRE(list->code() == "200");
+  REQUIRE(list->data().size() > 0);
+
+  auto profileUUID = list->data().at(0).uuid();
+
+  auto& identity_provider = service_context.IdentityProvider();
+
+  auto cert = identity_provider.GetProfileOneTimeCertificate(profileUUID, "foo@example.com");
+
+  REQUIRE(cert.get() != nullptr);
+  CHECK(cert->PrivateKeyPEM.Size() > 0);
+  CHECK(cert->CertificatePEM.Size() > 0);
+  CHECK(cert->CertificateUUID.size() == 0);
+  CHECK(cert->Chain.size() == 3);
+
+  SECTION("the certificate private key must be importable") {
+    bssl::UniquePtr<EVP_PKEY> cert_pkey;
+    CHECK_NOTHROW(
+      cert_pkey = crypto::rsa::ImportPrivateKeyFromPEM(cert->PrivateKeyPEM.View())
+    );
+    CHECK(cert_pkey.get() != nullptr);
+  }
+
+  // fs::WriteFile("onetime", cert->CertificatePEM.View());
+  // fs::WriteFile("userdevice", cert->Chain[0].View());
+  // fs::WriteFile("serverside", cert->Chain[1].View());
+  // fs::WriteFile("vereign", cert->Chain[2].View());
+
+  SECTION("the certificate and the certificates from the chain must be importable") {
+    bssl::UniquePtr<X509> x509_cert;
+    CHECK_NOTHROW(
+      x509_cert = crypto::cert::ImportCertFromPEM(cert->CertificatePEM.View())
+    );
+    CHECK(x509_cert.get() != nullptr);
+
+    for (auto& cert_pem : cert->Chain) {
+      x509_cert.reset();
+
+      CHECK_NOTHROW(
+        x509_cert = crypto::cert::ImportCertFromPEM(cert_pem.View())
+      );
+      CHECK(x509_cert.get() != nullptr);
+    }
+  }
+
+  SECTION("it must create new certificate all the time, but the issuers chain must be the same") {
+    auto new_cert = identity_provider.GetProfileOneTimeCertificate(profileUUID, "bar@example.com");
+
+    REQUIRE(new_cert.get() != nullptr);
+    CHECK(new_cert->PrivateKeyPEM.View() != cert->PrivateKeyPEM.View());
+    CHECK(new_cert->CertificatePEM.View() != cert->CertificatePEM.View());
+    CHECK(new_cert->CertificateUUID.size() == 0);
+    REQUIRE(new_cert->Chain.size() == cert->Chain.size());
+    for (std::size_t i = 0; i < new_cert->Chain.size(); i++) {
+      CHECK(new_cert->Chain[i].View() == cert->Chain[i].View());
+    }
+  }
+}
diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
index 8f9d305d0488004739afe3ae142e982ab7405630..01524db2f8dc171002b49e71addfa33289b20737 100644
--- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -70,10 +70,10 @@ TEST_CASE("kvstore::CryptoStorage::GetBytes", "[vereign/kvstore]") {
 
   storage.Recreate("foo");
 
-  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+  SECTION("when the value does not exists, it must return false") {
     bytes::Buffer v;
 
-    CHECK_THROWS_AS(storage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+    CHECK(storage.GetBytes("does_not_exists", v) == false);
 
     CHECK(v.Size() == 0);
   }
diff --git a/cpp/tests/vereign/kvstore/lock_test.cc b/cpp/tests/vereign/kvstore/lock_test.cc
index d15ed5ef9da02727c382e713c2401e2083ce7b5d..7dd70ed7943d1ec7400c2e3db7534875d979a6a0 100644
--- a/cpp/tests/vereign/kvstore/lock_test.cc
+++ b/cpp/tests/vereign/kvstore/lock_test.cc
@@ -8,7 +8,7 @@
 #include <vereign/core/scope_guard.hh>
 #include <vereign/crypto/rand.hh>
 #include <vereign/sqlite/errors.hh>
-#include <util/error.hh>
+#include <testutil/error.hh>
 #include <sqlite3.h>
 
 #include <catch2/catch.hpp>
diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
index f747ef2e4225b2b4c38b68551ab5cec2fcb538ae..dfe2d3d117038493c6338a3e202b8a1a2e8eec99 100644
--- a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
@@ -7,7 +7,7 @@
 #include <vereign/core/scope_guard.hh>
 #include <vereign/crypto/rand.hh>
 #include <vereign/sqlite/errors.hh>
-#include <util/error.hh>
+#include <testutil/error.hh>
 #include <sqlite3.h>
 
 #include <catch2/catch.hpp>
@@ -23,11 +23,12 @@ TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") {
 
   auto kvstorage = kvstore::SqliteStorage(storage_path);
 
-  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+  SECTION("when the value does not exists, it must return false") {
     bytes::Buffer v;
 
-    CHECK_THROWS_AS(kvstorage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+    auto found = kvstorage.GetBytes("does_not_exists", v);
 
+    CHECK(found == false);
     CHECK(v.Size() == 0);
   }
 
@@ -35,8 +36,9 @@ TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") {
     kvstorage.PutBytes("foo", bytes::View("bar"));
 
     bytes::Buffer v;
-    kvstorage.GetBytes("foo", v);
+    auto found = kvstorage.GetBytes("foo", v);
 
+    CHECK(found == true);
     CHECK(v.View() == bytes::View("bar"));
   }
 }
@@ -47,15 +49,21 @@ TEST_CASE("kvstore::SqliteStorage::GetInt64", "[vereign/kvstore]") {
 
   auto kvstorage = kvstore::SqliteStorage(storage_path);
 
-  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
-    CHECK_THROWS_AS(kvstorage.GetInt64("does_not_exists"), kvstore::ValueNotFoundError);
+  SECTION("when the value does not exists, it must return false") {
+    int64_t value = 0;
+    auto found = kvstorage.GetInt64("does_not_exists", value);
+
+    CHECK(found == false);
+    CHECK(value == 0);
   }
 
   SECTION("when the value exists, it must retrieve the value") {
     kvstorage.PutInt64("foo", 42);
 
-    auto v = kvstorage.GetInt64("foo");
+    int64_t v = 0;
+    auto result = kvstorage.GetInt64("foo", v);
 
+    CHECK(result == true);
     CHECK(v == 42);
   }
 }
@@ -69,13 +77,16 @@ TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") {
   kvstorage.PutInt64("foo", 42);
   kvstorage.PutInt64("bar", 422);
 
-  CHECK(kvstorage.GetInt64("foo") == 42);
-  CHECK(kvstorage.GetInt64("bar") == 422);
+  int64_t v = 0;
+  kvstorage.GetInt64("foo", v);
+  CHECK(v == 42);
+  kvstorage.GetInt64("bar", v);
+  CHECK(v == 422);
 
   kvstorage.DeleteAll();
 
-  CHECK_THROWS_AS(kvstorage.GetInt64("foo"), kvstore::ValueNotFoundError);
-  CHECK_THROWS_AS(kvstorage.GetInt64("bar"), kvstore::ValueNotFoundError);
+  CHECK(kvstorage.GetInt64("foo", v) == false);
+  CHECK(kvstorage.GetInt64("bar", v) == false);
 }
 
 TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
@@ -98,8 +109,12 @@ TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
 
       kvstore::Lock l{kvstorage};
 
-      CHECK(kvstorage.GetInt64("foo") == 42);
-      CHECK(kvstorage.GetInt64("bar") == 422);
+      int64_t v = 0;
+      kvstorage.GetInt64("foo", v);
+      CHECK(v == 42);
+
+      kvstorage.GetInt64("bar", v);
+      CHECK(v == 422);
     }
   }
 
@@ -119,8 +134,12 @@ TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
       auto kvstorage = kvstore::SqliteStorage(storage_path);
       kvstorage.Lock();
 
-      CHECK(kvstorage.GetInt64("foo") == 42);
-      CHECK(kvstorage.GetInt64("bar") == 422);
+      int64_t v = 0;
+      kvstorage.GetInt64("foo", v);
+      CHECK(v == 42);
+
+      kvstorage.GetInt64("bar", v);
+      CHECK(v == 422);
     }
   }
 
diff --git a/cpp/tests/vereign/restapi/client_session_test.cc b/cpp/tests/vereign/restapi/client_session_test.cc
index 36a824999a5c725e0d5443d91c91369fac619c51..88b361497a33b2c64e7e51677b12faa1fcf537b5 100644
--- a/cpp/tests/vereign/restapi/client_session_test.cc
+++ b/cpp/tests/vereign/restapi/client_session_test.cc
@@ -2,8 +2,8 @@
 #include <vereign/restapi/client_session.hh>
 #include <vereign/core/scope_guard.hh>
 #include <vereign/client_library/types.gen.pb.h>
-#include <util/env.hh>
-#include <util/protobuf.hh>
+#include <testutil/env.hh>
+#include <testutil/protobuf.hh>
 
 #include <catch2/catch.hpp>
 
@@ -11,22 +11,20 @@
 #include <boost/asio/ssl/context.hpp>
 #include <fmt/core.h>
 
-TEST_CASE("ClientSession::Post", "[vereign/restapi/client_session][.integration]") {
+TEST_CASE("restapi::ClientSession::Post", "[vereign/restapi/client_session][.integration]") {
   namespace asio = boost::asio;
   namespace beast = boost::beast;
   asio::io_context ioc;
   auto work_guard = boost::asio::make_work_guard(ioc);
   asio::ssl::context ctx(asio::ssl::context::tlsv12_client);
 
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
-
-  // Verify the remote server's certificate
-  // ctx.set_verify_mode(ssl::verify_peer);
+  auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   vereign::restapi::Client client{ioc, ctx, host, port};
-  vereign::restapi::ClientSession client_session{client, publicKey};
+  vereign::restapi::ClientSession client_session{client};
+  client_session.SetPubKey(publicKey);
 
   std::thread ioc_thread([&ioc]{
     ioc.run();
diff --git a/cpp/tests/vereign/restapi/client_test.cc b/cpp/tests/vereign/restapi/client_test.cc
index c0c1a17198abca5ee1e50f85fe57675308d4d597..27b71a716c6d4a7c22e3e8fbef88f8ae4dd83147 100644
--- a/cpp/tests/vereign/restapi/client_test.cc
+++ b/cpp/tests/vereign/restapi/client_test.cc
@@ -4,7 +4,7 @@
 #include <vereign/client_library/identity_types.pb.h>
 #include <vereign/client_library/passport_api.gen.pb.h>
 #include <vereign/core/scope_guard.hh>
-#include <util/env.hh>
+#include <testutil/env.hh>
 
 #include "boost/asio/executor_work_guard.hpp"
 #include "boost/beast/core/error.hpp"
@@ -25,9 +25,9 @@ TEST_CASE("Client::Post", "[vereign/restapi/client][.integration]") {
   auto work_guard = boost::asio::make_work_guard(ioc);
   asio::ssl::context ctx(asio::ssl::context::tlsv12_client);
 
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   // Verify the remote server's certificate
   // ctx.set_verify_mode(ssl::verify_peer);
@@ -102,9 +102,9 @@ TEST_CASE("Client load test", "[vereign/restapi/client][.bench]") {
   auto work_guard = boost::asio::make_work_guard(ioc);
   asio::ssl::context ctx(asio::ssl::context::tlsv12_client);
 
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   host = "localhost";
   port = "9292";
diff --git a/cpp/tests/vereign/service/gen/passport_service_test.cc b/cpp/tests/vereign/service/gen/passport_service_test.cc
index bbe2df7c0aaf5bb31dad33039778e439b6606ce6..2953fe0e466c60bba2797104b7e556b970d21bf4 100644
--- a/cpp/tests/vereign/service/gen/passport_service_test.cc
+++ b/cpp/tests/vereign/service/gen/passport_service_test.cc
@@ -8,8 +8,8 @@
 #include <vereign/service/identity_service.hh>
 #include <vereign/identity/provider.hh>
 
-#include <util/env.hh>
-#include <util/protobuf.hh>
+#include <testutil/env.hh>
+#include <testutil/protobuf.hh>
 
 #include <catch2/catch.hpp>
 #include <boost/asio/io_context.hpp>
@@ -23,9 +23,9 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration]
   auto work_guard = boost::asio::make_work_guard(ioc);
   asio::ssl::context ctx(asio::ssl::context::tlsv12_client);
 
-  auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto publicKey = vereign::testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   // Verify the remote server's certificate
   // ctx.set_verify_mode(ssl::verify_peer);
@@ -39,7 +39,7 @@ TEST_CASE("PassportService::ListPassports", "[vereign/service/gen][.integration]
 
   auto kvstorage = vereign::kvstore::SqliteStorage(storage_path);
   vereign::kvstore::CryptoStorage storage{kvstorage};
-  vereign::identity::Provider provider{storage};
+  vereign::identity::Provider provider{client_session, storage};
   vereign::service::IdentityService idenity_service{client_session, provider};
 
   std::thread ioc_thread([&ioc]{
diff --git a/cpp/tests/vereign/service/identity_service_test.cc b/cpp/tests/vereign/service/identity_service_test.cc
index 4fa38954ab5c0ec1af69addc32ffccd0a23c8e5a..4f48b025e33140f7a77bd8897ed393f7476c5ed3 100644
--- a/cpp/tests/vereign/service/identity_service_test.cc
+++ b/cpp/tests/vereign/service/identity_service_test.cc
@@ -10,17 +10,17 @@
 #include <vereign/test/service_context.hh>
 #include <vereign/fs/util.hh>
 
-#include <util/env.hh>
-#include <util/protobuf.hh>
+#include <testutil/env.hh>
+#include <testutil/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");
+  auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   // the old device is used later for new device confirmation and authorization
   auto old_storage_path = fs::TempFilePath("test_db_");
@@ -75,9 +75,9 @@ TEST_CASE("service::IdentityService::LoginWithNewDevice", "[vereign/service][.in
 }
 
 TEST_CASE("service::IdentityService::LoginWithPreviouslyAddedDevice", "[vereign/service][.integration]") {
-  auto public_key = test::RequireEnv("TEST_VEREIGN_PUB_KEY");
-  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+  auto public_key = testutil::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = testutil::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = testutil::GetEnv("TEST_VEREIGN_API_PORT", "https");
 
   auto storage_path = fs::TempFilePath("test_db_");
   auto rm_storage_path = fs::RemoveFileGuard{storage_path};
diff --git a/cpp/tests/vereign/test/service_context.cc b/cpp/tests/vereign/test/service_context.cc
index a6b5fc85a675920a667cd0890ffe0b632cda516a..934ff9084eb440a3c536d4e0f58baaf9c3518b0a 100644
--- a/cpp/tests/vereign/test/service_context.cc
+++ b/cpp/tests/vereign/test/service_context.cc
@@ -23,7 +23,7 @@ ServiceContext::ServiceContext(
     storage_path_{std::move(storage_path)},
     sqlite_storage_{std::make_unique<kvstore::SqliteStorage>(storage_path_)},
     storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_, true)},
-    identity_provider_{std::make_unique<identity::Provider>(*storage_)}
+    identity_provider_{std::make_unique<identity::Provider>(*client_session_, *storage_)}
 {
   service_thread_ = std::thread([this]() {
     ioc_.run();