From 1d66cf16ef61c3f760cd98630faa2ba47565ee8d Mon Sep 17 00:00:00 2001
From: Daniel Lyubomirov <dennislt@gmail.com>
Date: Thu, 25 Jun 2020 19:29:34 +0300
Subject: [PATCH] [17] crypto storage windows implementation

---
 cpp/src/CMakeLists.txt                        |   2 +-
 cpp/src/vereign/kvstore/crypto_storage.cc     |   7 +-
 cpp/src/vereign/kvstore/crypto_storage.hh     |   2 +-
 .../kvstore/detail/linux_crypto_storage.cc    |   3 +-
 .../kvstore/detail/win_crypto_storage.cc      | 168 ++++++++++++++++++
 .../kvstore/detail/win_crypto_storage.hh      |  38 ++++
 cpp/src/vereign/ncrypt/rsa.cc                 |  49 ++---
 cpp/src/vereign/ncrypt/rsa.hh                 |  14 +-
 .../vereign/kvstore/crypto_storage_test.cc    |   6 +-
 cpp/tests/vereign/ncrypt/rsa_test.cc          |  14 +-
 cpp/tests/vereign/test/service_context.cc     |   2 +-
 11 files changed, 266 insertions(+), 39 deletions(-)
 create mode 100644 cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
 create mode 100644 cpp/src/vereign/kvstore/detail/win_crypto_storage.hh

diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index 8a57217..ff7e8f6 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -101,7 +101,7 @@ elseif (WIN32)
     vereign/ncrypt/errors.cc
     vereign/ncrypt/unique_ptr.cc
     vereign/ncrypt/rsa.cc
-    vereign/kvstore/detail/linux_crypto_storage.cc
+    vereign/kvstore/detail/win_crypto_storage.cc
   )
 endif()
 
diff --git a/cpp/src/vereign/kvstore/crypto_storage.cc b/cpp/src/vereign/kvstore/crypto_storage.cc
index 20b2957..d4c1bbc 100644
--- a/cpp/src/vereign/kvstore/crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/crypto_storage.cc
@@ -1,16 +1,15 @@
 #include <vereign/kvstore/crypto_storage.hh>
 
 #if defined(_WIN32)
-// # include <vereign/kvstore/detail/win_crypto_storage.hh>
-# include <vereign/kvstore/detail/linux_crypto_storage.hh>
+# include <vereign/kvstore/detail/win_crypto_storage.hh>
 #else
 # include <vereign/kvstore/detail/linux_crypto_storage.hh>
 #endif
 
 namespace vereign::kvstore {
 
-CryptoStorage::CryptoStorage(kvstore::Storage& storage)
-  : impl_{std::make_unique<detail::CryptoStorageImpl>(storage)}
+CryptoStorage::CryptoStorage(Storage& storage, bool disable_key_protection)
+  : impl_{std::make_unique<detail::CryptoStorageImpl>(storage, disable_key_protection)}
 {}
 
 CryptoStorage::~CryptoStorage() = default;
diff --git a/cpp/src/vereign/kvstore/crypto_storage.hh b/cpp/src/vereign/kvstore/crypto_storage.hh
index 38d2b84..a258e0a 100644
--- a/cpp/src/vereign/kvstore/crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/crypto_storage.hh
@@ -14,7 +14,7 @@ class CryptoStorageImpl;
 
 class CryptoStorage {
 public:
-  CryptoStorage(Storage& storage);
+  CryptoStorage(Storage& storage, bool disable_key_protection = false);
   ~CryptoStorage();
 
   void Reset(const std::string& pin);
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
index b70efaf..77275b1 100644
--- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -17,6 +17,7 @@ namespace {
   // FIXME: should these be injected and provided by the integrator
   constexpr int iterations = 1 << 18;
   constexpr int saltSizeBytes = 64;
+  constexpr int tagSizeBytes = 64;
   constexpr int aesKeySizeBytes = 32;
 
   constexpr int lockRetryCount = 10;
@@ -91,7 +92,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
   key.IncSize(aesKeySizeBytes);
   key_ = std::move(key);
 
-  bytes::Buffer tag{saltSizeBytes};
+  bytes::Buffer tag{tagSizeBytes};
   crypto::Rand(tag);
 
   {
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
new file mode 100644
index 0000000..e54416e
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
@@ -0,0 +1,168 @@
+#include <vereign/kvstore/detail/win_crypto_storage.hh>
+
+#include <vereign/kvstore/detail/value_encoder.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/errors.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/crypto/rsa.hh>
+#include <vereign/crypto/aes.hh>
+#include <vereign/crypto/bio.hh>
+
+#include <boost/core/ignore_unused.hpp>
+#include <vereign/ncrypt/rsa.hh>
+
+#include <chrono>
+
+namespace {
+  // FIXME: should these be injected and provided by the integrator
+  constexpr int keySizeBits = 2048;
+  constexpr int tagSizeBytes = 64;
+  constexpr int aesKeySizeBytes = 32;
+
+  constexpr int lockRetryCount = 10;
+  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
+
+  // FIXME: ask business for these values
+  constexpr const auto vereignKey = std::string_view{"vereign_key"};
+  constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"};
+  constexpr const auto vereignKeyDescription = std::string_view{
+    "Vereign Client will use this key to authenticate with the Vereign Services"
+  };
+  constexpr const auto vereignKeyFriendlyName = std::string_view{"Vereign Client Identity Key"};
+}
+
+namespace vereign::kvstore::detail {
+
+CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection)
+  : storage_{storage},
+    disable_key_protection_{disable_key_protection}
+{}
+
+void CryptoStorageImpl::Open(const std::string& pin) {
+  boost::ignore_unused(pin);
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey));
+  if (!rsa_key) {
+    throw IdentityChanged{};
+  }
+
+  bytes::Buffer encrypted_key;
+  storage_.GetBytes("__master_key", encrypted_key);
+
+  bytes::Buffer key;
+  ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key);
+  key_ = std::move(key);
+
+  // FIXME: write tests for tampering and regular identity change
+  try {
+    bytes::Buffer tag;
+    GetBytes("__tag", tag);
+    tag_ = std::move(tag);
+
+  } catch (const crypto::Error&) {
+    throw IdentityChanged{};
+  }
+}
+
+void CryptoStorageImpl::Reset(const std::string& pin) {
+  boost::ignore_unused(pin);
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  auto provider = ncrypt::rsa::OpenStorageProvider();
+  auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey));
+  if (old_key) {
+    ncrypt::rsa::DeleteKey(old_key.Get());
+  }
+
+  std::optional<ncrypt::rsa::KeyUIPolicy> ui_policy;
+  if (!disable_key_protection_) {
+    ui_policy = ncrypt::rsa::KeyUIPolicy{
+      std::string(vereignKeyCreationTitle),
+      std::string(vereignKeyDescription),
+      std::string(vereignKeyFriendlyName)
+    };
+  }
+
+  auto rsa_key = ncrypt::rsa::CreateKey(
+    provider.Get(),
+    keySizeBits,
+    std::string(vereignKey),
+    ui_policy
+  );
+
+  storage_.DeleteAll();
+
+  bytes::Buffer key{aesKeySizeBytes};
+  crypto::Rand(key);
+
+  bytes::Buffer encrypted_key;
+  ncrypt::rsa::PublicKeyEncrypt(rsa_key.Get(), key.View(), encrypted_key);
+  storage_.PutBytes("__master_key", encrypted_key.View());
+  key_ = std::move(key);
+
+  bytes::Buffer tag{tagSizeBytes};
+  crypto::Rand(tag);
+
+  encryptBytes("__tag", tag.View());
+  tag_ = std::move(tag);
+}
+
+void CryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) {
+  if (key_.Size() == 0) {
+    throw Error("crypto storage is not initialized");
+  }
+
+  bytes::Buffer iv;
+  bytes::Buffer tag;
+  bytes::Buffer encrypted;
+
+  crypto::aes::GCM256Encrypt(value, key_.View(), iv, encrypted, tag);
+
+  bytes::Buffer encoded_value;
+  EncodeEncryptedValue(encoded_value, iv.View(), tag.View(), encrypted.View());
+
+  storage_.PutBytes(key, encoded_value.View());
+}
+
+void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  bytes::Buffer tag;
+  try {
+    GetBytes("__tag", tag);
+  } catch (const crypto::Error&) {
+    throw IdentityChanged{};
+  }
+
+  if (tag.View() != tag_.View()) {
+    throw IdentityChanged{};
+  }
+
+  encryptBytes(key, value);
+}
+
+void CryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  if (key_.Size() == 0) {
+    throw Error("crypto storage is not initialized");
+  }
+
+  bytes::Buffer encoded;
+  storage_.GetBytes(key, encoded);
+
+  bytes::View iv;
+  bytes::View tag;
+  bytes::View encrypted;
+  DecodeEncryptedValue(encoded.View(), iv, tag, encrypted);
+
+  crypto::aes::GCM256Decrypt(encrypted, key_.View(), iv, tag, value);
+}
+
+
+}
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh
new file mode 100644
index 0000000..4ae120f
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.hh
@@ -0,0 +1,38 @@
+#ifndef __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
+#define __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
+
+#include <vereign/kvstore/storage.hh>
+
+namespace vereign::kvstore::detail {
+
+class CryptoStorageImpl {
+public:
+  CryptoStorageImpl(kvstore::Storage& storage, bool disable_key_protection);
+
+  // disable copying
+  CryptoStorageImpl(const CryptoStorageImpl&) = delete;
+  auto operator=(const CryptoStorageImpl&) -> CryptoStorageImpl& = delete;
+
+  void Reset(const std::string& pin);
+  void Open(const std::string& pin);
+
+  void PutBytes(const std::string& key, bytes::View value);
+  void GetBytes(const std::string& key, bytes::Buffer& value);
+
+private:
+  void encryptBytes(const std::string& key, bytes::View value);
+
+private:
+  kvstore::Storage& storage_;
+
+  bool disable_key_protection_ = false;
+
+  bytes::Buffer key_;
+
+  bytes::Buffer tag_;
+};
+
+} // namespace vereign::kvstore::detail
+
+
+#endif // __VEREIGN_KVSTORE_DETAIL_WIN_CRYPTO_STORAGE_HH
diff --git a/cpp/src/vereign/ncrypt/rsa.cc b/cpp/src/vereign/ncrypt/rsa.cc
index d745434..a9e351d 100644
--- a/cpp/src/vereign/ncrypt/rsa.cc
+++ b/cpp/src/vereign/ncrypt/rsa.cc
@@ -26,7 +26,12 @@ auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> Unique
   return key;
 }
 
-auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr {
+auto CreateKey(
+  NCRYPT_PROV_HANDLE provider,
+  int bits,
+  const std::string& key_name,
+  std::optional<KeyUIPolicy> ui_policy
+) -> UniquePtr {
   UniquePtr key{};
   auto wkey_name = string::widen(key_name);
   auto status = NCryptCreatePersistedKey(
@@ -53,25 +58,29 @@ auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_nam
     throw Error{status, "setup rsa key length failed"};
   }
 
-  // FIXME: add the ui policy
-  // NCRYPT_UI_POLICY ui_policy{};
-  // ui_policy.dwVersion = 1;
-  // ui_policy.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG;
-  // ui_policy.pszCreationTitle = L"Vereign Client";
-  // ui_policy.pszDescription =
-    // L"Vereign Client will use this key to authenticate with the Vereign Services";
-  // ui_policy.pszFriendlyName = L"Vereign Client Identity Key";
-
-  // status = NCryptSetProperty(
-    // key.Get(),
-    // NCRYPT_UI_POLICY_PROPERTY,
-    // (PBYTE)&ui_policy,
-    // sizeof(ui_policy),
-    // NCRYPT_PERSIST_FLAG
-  // );
-  // if (status != ERROR_SUCCESS) {
-    // throw Error{status, "configure key ui policy failed"};
-  // }
+  if (ui_policy) {
+    auto creation_title = vereign::string::widen(ui_policy->CreationTitle);
+    auto description = vereign::string::widen(ui_policy->Description);
+    auto friendly_name = vereign::string::widen(ui_policy->FriendlyName);
+
+    NCRYPT_UI_POLICY ui_policy_prop{};
+    ui_policy_prop.dwVersion = 1;
+    ui_policy_prop.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG;
+    ui_policy_prop.pszCreationTitle = creation_title.data();
+    ui_policy_prop.pszDescription = description.data();
+    ui_policy_prop.pszFriendlyName = friendly_name.data();
+
+    status = NCryptSetProperty(
+      key.Get(),
+      NCRYPT_UI_POLICY_PROPERTY,
+      (PBYTE)&ui_policy_prop,
+      sizeof(ui_policy_prop),
+      NCRYPT_PERSIST_FLAG
+    );
+    if (status != ERROR_SUCCESS) {
+      throw Error{status, "configure key ui policy failed"};
+    }
+  }
 
   status = NCryptFinalizeKey(key.Get(), 0);
   if (status != ERROR_SUCCESS) {
diff --git a/cpp/src/vereign/ncrypt/rsa.hh b/cpp/src/vereign/ncrypt/rsa.hh
index 749eb51..9ef0e56 100644
--- a/cpp/src/vereign/ncrypt/rsa.hh
+++ b/cpp/src/vereign/ncrypt/rsa.hh
@@ -7,12 +7,24 @@
 #include <windows.h>
 #include <ncrypt.h>
 #include <string>
+#include <optional>
 
 namespace vereign::ncrypt::rsa {
 
+struct KeyUIPolicy {
+  std::string CreationTitle;
+  std::string Description;
+  std::string FriendlyName;
+};
+
 auto OpenStorageProvider() -> UniquePtr;
 auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr;
-auto CreateKey(NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name) -> UniquePtr;
+auto CreateKey(
+  NCRYPT_PROV_HANDLE provider,
+  int bits,
+  const std::string& key_name,
+  std::optional<KeyUIPolicy> ui_policy
+) -> UniquePtr;
 void DeleteKey(NCRYPT_KEY_HANDLE key);
 
 void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted);
diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
index af314d9..5b9b507 100644
--- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -20,7 +20,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
   // put value
   {
     auto kvstorage = kvstore::SqliteStorage(storage_path.string());
-    kvstore::CryptoStorage storage{kvstorage};
+    kvstore::CryptoStorage storage{kvstorage, true};
 
     storage.Reset("foo");
     std::string v{"test value"};
@@ -30,7 +30,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
   // with another storage instance get the value
   {
     auto kvstorage = kvstore::SqliteStorage(storage_path.string());
-    kvstore::CryptoStorage storage{kvstorage};
+    kvstore::CryptoStorage storage{kvstorage, true};
 
     bytes::Buffer v;
     storage.Open("foo");
@@ -47,7 +47,7 @@ TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") {
   bytes::Buffer big_value{100000};
   crypto::Rand(big_value);
   auto kvstorage = kvstore::SqliteStorage(storage_path.string());
-  kvstore::CryptoStorage storage{kvstorage};
+  kvstore::CryptoStorage storage{kvstorage, true};
 
   storage.Reset("foo");
   storage.PutBytes("test", big_value.View());
diff --git a/cpp/tests/vereign/ncrypt/rsa_test.cc b/cpp/tests/vereign/ncrypt/rsa_test.cc
index 0c3c4aa..19130f8 100644
--- a/cpp/tests/vereign/ncrypt/rsa_test.cc
+++ b/cpp/tests/vereign/ncrypt/rsa_test.cc
@@ -27,7 +27,7 @@ TEST_CASE("ncrypt::CreateKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
       ncrypt::rsa::DeleteKey(key.Get());
     }
 
-    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
     CHECK(new_key.Get() != 0);
 
     key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
@@ -46,11 +46,11 @@ TEST_CASE("ncrypt::CreateKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
       ncrypt::rsa::DeleteKey(key.Get());
     }
 
-    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
     CHECK(new_key.Get() != 0);
 
     CHECK_THROWS_WITH(
-      ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key),
+      ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {}),
       "creating rsa key failed: NTE_EXISTS"
     );
 
@@ -74,7 +74,7 @@ TEST_CASE("ncrypt::rsa::LoadKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
       ncrypt::rsa::DeleteKey(key.Get());
     }
 
-    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
     CHECK(new_key.Get() != 0);
 
     key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
@@ -110,7 +110,7 @@ TEST_CASE("ncrypt::rsa::DeleteKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
       ncrypt::rsa::DeleteKey(key.Get());
     }
 
-    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
     CHECK(new_key.Get() != 0);
 
     ncrypt::rsa::DeleteKey(new_key.Get());
@@ -128,7 +128,7 @@ TEST_CASE("ncrypt::rsa::DeleteKey", "[vereign/ncrypt/rsa][vereign/ncrypt]") {
       ncrypt::rsa::DeleteKey(key.Get());
     }
 
-    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+    auto new_key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
     CHECK(new_key.Get() != 0);
 
     key = ncrypt::rsa::LoadKey(provider.Get(), test_key);
@@ -151,7 +151,7 @@ TEST_CASE("ncrypt::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/ncrypt/rsa
     ncrypt::rsa::DeleteKey(key.Get());
   }
 
-  key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key);
+  key = ncrypt::rsa::CreateKey(provider.Get(), 2048, test_key, {});
   REQUIRE(key.Get() != 0);
   auto delete_key = core::ScopeGuard([&key] { ncrypt::rsa::DeleteKey(key.Get()); });
 
diff --git a/cpp/tests/vereign/test/service_context.cc b/cpp/tests/vereign/test/service_context.cc
index 0e76643..2524d8a 100644
--- a/cpp/tests/vereign/test/service_context.cc
+++ b/cpp/tests/vereign/test/service_context.cc
@@ -23,7 +23,7 @@ ServiceContext::ServiceContext(
     client_session_{std::make_unique<restapi::ClientSession>(*client_)},
     storage_path_{std::move(storage_path)},
     sqlite_storage_{std::make_unique<kvstore::SqliteStorage>(storage_path_.string())},
-    storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_)},
+    storage_{std::make_unique<kvstore::CryptoStorage>(*sqlite_storage_, true)},
     identity_provider_{std::make_unique<identity::Provider>(*storage_)}
 {
   service_thread_ = std::thread([this]() {
-- 
GitLab