From fb789f1c15707d229cd6c031c4c12caf077b4603 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 7 May 2020 17:38:34 -0400 Subject: [PATCH 1/2] separate out signature provider from producer plugin --- plugins/CMakeLists.txt | 2 +- plugins/producer_plugin/CMakeLists.txt | 5 +- .../eosio/producer_plugin/producer_plugin.hpp | 4 +- plugins/producer_plugin/producer_plugin.cpp | 93 +------------ .../signature_provider_plugin/CMakeLists.txt | 10 ++ .../signature_provider_plugin.hpp | 33 +++++ .../signature_provider_plugin.cpp | 123 ++++++++++++++++++ 7 files changed, 175 insertions(+), 95 deletions(-) create mode 100644 plugins/signature_provider_plugin/CMakeLists.txt create mode 100644 plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp create mode 100644 plugins/signature_provider_plugin/signature_provider_plugin.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e102bb80888..5d1491a1e76 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -10,7 +10,7 @@ add_subdirectory(history_plugin) add_subdirectory(history_api_plugin) add_subdirectory(state_history_plugin) add_subdirectory(trace_api_plugin) - +add_subdirectory(signature_provider_plugin) add_subdirectory(wallet_plugin) add_subdirectory(wallet_api_plugin) add_subdirectory(txn_test_gen_plugin) diff --git a/plugins/producer_plugin/CMakeLists.txt b/plugins/producer_plugin/CMakeLists.txt index 94f398e2920..2477f3b5788 100644 --- a/plugins/producer_plugin/CMakeLists.txt +++ b/plugins/producer_plugin/CMakeLists.txt @@ -5,9 +5,6 @@ add_library( producer_plugin ${HEADERS} ) -target_link_libraries( producer_plugin chain_plugin http_client_plugin appbase eosio_chain ) -if(APPLE) - target_link_libraries( producer_plugin se-helpers ) -endif() +target_link_libraries( producer_plugin chain_plugin signature_provider_plugin appbase eosio_chain ) target_include_directories( producer_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" ) diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index e572fa2e5d8..648b5967768 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include @@ -11,7 +11,7 @@ using boost::signals2::signal; class producer_plugin : public appbase::plugin { public: - APPBASE_PLUGIN_REQUIRES((chain_plugin)(http_client_plugin)) + APPBASE_PLUGIN_REQUIRES((chain_plugin)(signature_provider_plugin)) struct runtime_options { fc::optional max_transaction_time; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 6fc894775c4..183aee0d56b 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -6,9 +6,6 @@ #include #include #include -#ifdef __APPLE__ -#include -#endif #include #include @@ -19,7 +16,6 @@ #include #include -#include #include #include #include @@ -196,7 +192,7 @@ class producer_plugin_impl : public std::enable_shared_from_this; + using signature_provider_type = signature_provider_plugin::signature_provider_type; std::map _signature_providers; std::set _producers; boost::asio::deadline_timer _timer; @@ -214,7 +210,6 @@ class producer_plugin_impl : public std::enable_shared_from_this _protocol_features_to_activate; bool _protocol_features_signaled = false; // to mark whether it has been signaled in start_block @@ -624,15 +619,7 @@ void producer_plugin::set_program_options( ("signature-provider", boost::program_options::value>()->composing()->multitoken()->default_value( {default_priv_key.get_public_key().to_string() + "=KEY:" + default_priv_key.to_string()}, default_priv_key.get_public_key().to_string() + "=KEY:" + default_priv_key.to_string()), - "Key=Value pairs in the form =\n" - "Where:\n" - " \tis a string form of a vaild EOSIO public key\n\n" - " \tis a string in the form :\n\n" - " \tis KEY, or KEOSD\n\n" - " KEY: \tis a string form of a valid EOSIO private key which maps to the provided public key\n\n" - " KEOSD: \tis the URL where keosd is available and the approptiate wallet(s) are unlocked") - ("keosd-provider-timeout", boost::program_options::value()->default_value(5), - "Limits the maximum time (in milliseconds) that is allowed for sending blocks to a keosd provider for signing") + app().get_plugin().signature_provider_help_text()) ("greylist-account", boost::program_options::value>()->composing()->multitoken(), "account that can not access to extended CPU/NET virtual resources") ("greylist-limit", boost::program_options::value()->default_value(1000), @@ -699,54 +686,6 @@ if( options.count(op_name) ) { \ } \ } -static producer_plugin_impl::signature_provider_type -make_key_signature_provider(const private_key_type& key) { - return [key]( const chain::digest_type& digest ) { - return key.sign(digest); - }; -} - -#ifdef __APPLE__ -static producer_plugin_impl::signature_provider_type -make_se_signature_provider(const public_key_type pubkey) { - EOS_ASSERT(secure_enclave::hardware_supports_secure_enclave(), secure_enclave_exception, "Secure Enclave not supported on this hardware"); - EOS_ASSERT(secure_enclave::application_signed(), secure_enclave_exception, "Application is not signed, Secure Enclave use not supported"); - - std::set allkeys = secure_enclave::get_all_keys(); - for(const auto& se_key : secure_enclave::get_all_keys()) - if(se_key.public_key() == pubkey) - return [se_key](const chain::digest_type& digest) { - return se_key.sign(digest); - }; - - EOS_THROW(secure_enclave_exception, "${k} configured for block signing not found in Secure Enclave", ("k", pubkey)); -} -#endif - -static producer_plugin_impl::signature_provider_type -make_keosd_signature_provider(const std::shared_ptr& impl, const string& url_str, const public_key_type pubkey) { - fc::url keosd_url; - if(boost::algorithm::starts_with(url_str, "unix://")) - //send the entire string after unix:// to http_plugin. It'll auto-detect which part - // is the unix socket path, and which part is the url to hit on the server - keosd_url = fc::url("unix", url_str.substr(7), ostring(), ostring(), ostring(), ostring(), ovariant_object(), fc::optional()); - else - keosd_url = fc::url(url_str); - std::weak_ptr weak_impl = impl; - - return [weak_impl, keosd_url, pubkey]( const chain::digest_type& digest ) { - auto impl = weak_impl.lock(); - if (impl) { - fc::variant params; - fc::to_variant(std::make_pair(digest, pubkey), params); - auto deadline = impl->_keosd_provider_timeout_us.count() >= 0 ? fc::time_point::now() + impl->_keosd_provider_timeout_us : fc::time_point::maximum(); - return app().get_plugin().get_client().post_sync(keosd_url, params, deadline).as(); - } else { - return signature_type(); - } - }; -} - void producer_plugin::plugin_initialize(const boost::program_options::variables_map& options) { try { my->chain_plug = app().find_plugin(); @@ -768,7 +707,7 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ { try { auto key_id_to_wif_pair = dejsonify>(key_id_to_wif_pair_string); - my->_signature_providers[key_id_to_wif_pair.first] = make_key_signature_provider(key_id_to_wif_pair.second); + my->_signature_providers[key_id_to_wif_pair.first] = app().get_plugin().signature_provider_for_private_key(key_id_to_wif_pair.second); auto blanked_privkey = std::string(key_id_to_wif_pair.second.to_string().size(), '*' ); wlog("\"private-key\" is DEPRECATED, use \"signature-provider=${pub}=KEY:${priv}\"", ("pub",key_id_to_wif_pair.first)("priv", blanked_privkey)); } catch ( fc::exception& e ) { @@ -781,28 +720,8 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ const std::vector key_spec_pairs = options["signature-provider"].as>(); for (const auto& key_spec_pair : key_spec_pairs) { try { - auto delim = key_spec_pair.find("="); - EOS_ASSERT(delim != std::string::npos, plugin_config_exception, "Missing \"=\" in the key spec pair"); - auto pub_key_str = key_spec_pair.substr(0, delim); - auto spec_str = key_spec_pair.substr(delim + 1); - - auto spec_delim = spec_str.find(":"); - EOS_ASSERT(spec_delim != std::string::npos, plugin_config_exception, "Missing \":\" in the key spec pair"); - auto spec_type_str = spec_str.substr(0, spec_delim); - auto spec_data = spec_str.substr(spec_delim + 1); - - auto pubkey = public_key_type(pub_key_str); - - if (spec_type_str == "KEY") { - my->_signature_providers[pubkey] = make_key_signature_provider(private_key_type(spec_data)); - } else if (spec_type_str == "KEOSD") { - my->_signature_providers[pubkey] = make_keosd_signature_provider(my, spec_data, pubkey); - } -#ifdef __APPLE__ - else if (spec_type_str == "SE") { - my->_signature_providers[pubkey] = make_se_signature_provider(pubkey); - } -#endif + auto [pubkey, provider] = app().get_plugin().signature_provider_for_specification(key_spec_pair); + my->_signature_providers[pubkey] = provider; } catch(secure_enclave_exception& e) { elog("Error with Secure Enclave signature provider: ${e}; ignoring ${val}", ("e", e.top_message())("val", key_spec_pair)); } catch (...) { @@ -811,8 +730,6 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ } } - my->_keosd_provider_timeout_us = fc::milliseconds(options.at("keosd-provider-timeout").as()); - my->_produce_time_offset_us = options.at("produce-time-offset-us").as(); EOS_ASSERT( my->_produce_time_offset_us <= 0 && my->_produce_time_offset_us >= -config::block_interval_us, plugin_config_exception, "produce-time-offset-us ${o} must be 0 .. -${bi}", ("bi", config::block_interval_us)("o", my->_produce_time_offset_us) ); diff --git a/plugins/signature_provider_plugin/CMakeLists.txt b/plugins/signature_provider_plugin/CMakeLists.txt new file mode 100644 index 00000000000..71447d6b2bc --- /dev/null +++ b/plugins/signature_provider_plugin/CMakeLists.txt @@ -0,0 +1,10 @@ +file(GLOB HEADERS "include/eosio/signature_provider_plugin/*.hpp") +add_library( signature_provider_plugin + signature_provider_plugin.cpp + ${HEADERS} ) + +target_link_libraries( signature_provider_plugin appbase fc http_client_plugin ) +target_include_directories( signature_provider_plugin PUBLIC include ) +if(APPLE) + target_link_libraries( signature_provider_plugin se-helpers ) +endif() diff --git a/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp b/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp new file mode 100644 index 00000000000..69b18d0e113 --- /dev/null +++ b/plugins/signature_provider_plugin/include/eosio/signature_provider_plugin/signature_provider_plugin.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +namespace eosio { + +using namespace appbase; + +class signature_provider_plugin : public appbase::plugin { +public: + signature_provider_plugin(); + virtual ~signature_provider_plugin(); + + APPBASE_PLUGIN_REQUIRES((http_client_plugin)) + virtual void set_program_options(options_description&, options_description& cfg) override; + + void plugin_initialize(const variables_map& options) {} + void plugin_startup() {} + void plugin_shutdown() {} + + const char* const signature_provider_help_text() const; + + using signature_provider_type = std::function; + + std::pair signature_provider_for_specification(const std::string& spec) const; + signature_provider_type signature_provider_for_private_key(const chain::private_key_type priv) const; + +private: + std::unique_ptr my; +}; + +} diff --git a/plugins/signature_provider_plugin/signature_provider_plugin.cpp b/plugins/signature_provider_plugin/signature_provider_plugin.cpp new file mode 100644 index 00000000000..ea94a457217 --- /dev/null +++ b/plugins/signature_provider_plugin/signature_provider_plugin.cpp @@ -0,0 +1,123 @@ +#include +#include + +#include +#include + +#include + +#ifdef __APPLE__ +#include +#endif + +namespace eosio { + static appbase::abstract_plugin& _signature_provider_plugin = app().register_plugin(); + +class signature_provider_plugin_impl { + public: + fc::microseconds _keosd_provider_timeout_us; + + signature_provider_plugin::signature_provider_type + make_key_signature_provider(const chain::private_key_type& key) const { + return [key]( const chain::digest_type& digest ) { + return key.sign(digest); + }; + } + +#ifdef __APPLE__ + signature_provider_plugin::signature_provider_type + make_se_signature_provider(const chain::public_key_type pubkey) const { + EOS_ASSERT(secure_enclave::hardware_supports_secure_enclave(), chain::secure_enclave_exception, "Secure Enclave not supported on this hardware"); + EOS_ASSERT(secure_enclave::application_signed(), chain::secure_enclave_exception, "Application is not signed, Secure Enclave use not supported"); + + std::set allkeys = secure_enclave::get_all_keys(); + for(const auto& se_key : secure_enclave::get_all_keys()) + if(se_key.public_key() == pubkey) + return [se_key](const chain::digest_type& digest) { + return se_key.sign(digest); + }; + + EOS_THROW(chain::secure_enclave_exception, "${k} not found in Secure Enclave", ("k", pubkey)); + } +#endif + + signature_provider_plugin::signature_provider_type + make_keosd_signature_provider(const string& url_str, const chain::public_key_type pubkey) const { + fc::url keosd_url; + if(boost::algorithm::starts_with(url_str, "unix://")) + //send the entire string after unix:// to http_plugin. It'll auto-detect which part + // is the unix socket path, and which part is the url to hit on the server + keosd_url = fc::url("unix", url_str.substr(7), fc::ostring(), fc::ostring(), fc::ostring(), fc::ostring(), fc::ovariant_object(), fc::optional()); + else + keosd_url = fc::url(url_str); + + return [to=_keosd_provider_timeout_us, keosd_url, pubkey](const chain::digest_type& digest) { + fc::variant params; + fc::to_variant(std::make_pair(digest, pubkey), params); + auto deadline = to.count() >= 0 ? fc::time_point::now() + to : fc::time_point::maximum(); + return app().get_plugin().get_client().post_sync(keosd_url, params, deadline).as(); + }; + } +}; + +signature_provider_plugin::signature_provider_plugin():my(new signature_provider_plugin_impl()){} +signature_provider_plugin::~signature_provider_plugin(){} + +void signature_provider_plugin::set_program_options(options_description&, options_description& cfg) { + cfg.add_options() + ("keosd-provider-timeout", boost::program_options::value()->default_value(5)->notifier([this](auto to){ + my->_keosd_provider_timeout_us = fc::milliseconds(to); + }), + "Limits the maximum time (in milliseconds) that is allowed for sending requests to a keosd provider for signing") + ; +} + +const char* const signature_provider_plugin::signature_provider_help_text() const { + return "Key=Value pairs in the form =\n" + "Where:\n" + " \tis a string form of a vaild EOSIO public key\n\n" + " \tis a string in the form :\n\n" + " \tis KEY, KEOSD, or SE\n\n" + " KEY: \tis a string form of a valid EOSIO private key which maps to the provided public key\n\n" + " KEOSD: \tis the URL where keosd is available and the approptiate wallet(s) are unlocked\n\n" +#ifdef __APPLE__ + " SE: \tindicates the key resides in Secure Enclave" +#endif + ; + +} + +std::pair +signature_provider_plugin::signature_provider_for_specification(const std::string& spec) const { + auto delim = spec.find("="); + EOS_ASSERT(delim != std::string::npos, chain::plugin_config_exception, "Missing \"=\" in the key spec pair"); + auto pub_key_str = spec.substr(0, delim); + auto spec_str = spec.substr(delim + 1); + + auto spec_delim = spec_str.find(":"); + EOS_ASSERT(spec_delim != std::string::npos, chain::plugin_config_exception, "Missing \":\" in the key spec pair"); + auto spec_type_str = spec_str.substr(0, spec_delim); + auto spec_data = spec_str.substr(spec_delim + 1); + + auto pubkey = chain::public_key_type(pub_key_str); + + if(spec_type_str == "KEY") { + chain::private_key_type priv(spec_data); + EOS_ASSERT(pubkey == priv.get_public_key(), chain::plugin_config_exception, "Private key does not match given public key for ${pub}", ("pub", pubkey)); + return std::make_pair(pubkey, my->make_key_signature_provider(priv)); + } + else if(spec_type_str == "KEOSD") + return std::make_pair(pubkey, my->make_keosd_signature_provider(spec_data, pubkey)); +#ifdef __APPLE__ + else if(spec_type_str == "SE") + return std::make_pair(pubkey, my->make_se_signature_provider(pubkey)); +#endif + EOS_THROW(chain::plugin_config_exception, "Unsupported key provider type \"${t}\"", ("t", spec_type_str)); +} + +signature_provider_plugin::signature_provider_type +signature_provider_plugin::signature_provider_for_private_key(const chain::private_key_type priv) const { + return my->make_key_signature_provider(priv); +} + +} From b6acf2292c0d232001334f5e5d629344d6c77381 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Fri, 8 May 2020 14:32:09 -0400 Subject: [PATCH 2/2] slight more optimial handling of signature_provider_for_specification return --- plugins/producer_plugin/producer_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 183aee0d56b..6434c1de241 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -720,7 +720,7 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ const std::vector key_spec_pairs = options["signature-provider"].as>(); for (const auto& key_spec_pair : key_spec_pairs) { try { - auto [pubkey, provider] = app().get_plugin().signature_provider_for_specification(key_spec_pair); + const auto& [pubkey, provider] = app().get_plugin().signature_provider_for_specification(key_spec_pair); my->_signature_providers[pubkey] = provider; } catch(secure_enclave_exception& e) { elog("Error with Secure Enclave signature provider: ${e}; ignoring ${val}", ("e", e.top_message())("val", key_spec_pair));