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