diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 8a572172298812028952bbdab096defa03015dbc..ff7e8f66c2f273e25eabb9d537db05b46717f408 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 20b29579f5101e52a5f09c214d42a00c68066148..d4c1bbc0329a53fa9b18ed2ace887f396accf5c7 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 38d2b8439e50aabb66a73ae42bcf4b468cf339aa..a258e0ab49081a6b14b087cb24e318567c5fe6e8 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 b70efafb860153bc30415a7844f60a8a68b58a07..77275b1c476b988b8bc1dcfa6a7b08e60711b297 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 0000000000000000000000000000000000000000..e54416e0103262ca2ff0ada2ef60764a1fcb4f5e --- /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 0000000000000000000000000000000000000000..4ae120f9496d3dfb8168cbd1d70f317bddda7656 --- /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 d745434813e1c82f256267047f48e4ef0b716a21..a9e351d4a3e7f15f513167b05d154716a5d2d188 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 749eb512eca488b08b33df38eb0dbd24f8f2fd57..9ef0e5674c931a9cf0a30d93fa7fd85f838fcf22 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 af314d9690e521b349f9ed7696f8a1f72a072812..5b9b507a3bdf32bc47ac94fa6b2174ada201aede 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 0c3c4aae1143630b32bb4540560d2374b5f0b63b..19130f81cacdfed5da52ac99b6bb8c138d63229e 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 0e76643d1d6434b9b622a2c19076398aa06c439b..2524d8aa81d046c3ee2b6e7ced89a0b1df829229 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]() {