Skip to content

Commit

Permalink
Merge pull request AntelopeIO#541 from eosnetworkfoundation/AntelopeI…
Browse files Browse the repository at this point in the history
…OGH-540-sig-provider

[3.2] Separate out signature provider from producer plugin
  • Loading branch information
heifner authored Jun 28, 2022
2 parents 980fc79 + 52b2978 commit 4453b71
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 67 deletions.
1 change: 1 addition & 0 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_subdirectory(producer_api_plugin)
add_subdirectory(state_history_plugin)
add_subdirectory(trace_api_plugin)
add_subdirectory(resource_monitor_plugin)
add_subdirectory(signature_provider_plugin)
add_subdirectory(wallet_plugin)
add_subdirectory(wallet_api_plugin)
add_subdirectory(txn_test_gen_plugin)
Expand Down
2 changes: 1 addition & 1 deletion plugins/producer_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ add_library( producer_plugin
${HEADERS}
)

target_link_libraries( producer_plugin chain_plugin http_client_plugin appbase eosio_chain )
target_link_libraries( producer_plugin chain_plugin http_client_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" )

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <eosio/chain_plugin/chain_plugin.hpp>
#include <eosio/http_client_plugin/http_client_plugin.hpp>
#include <eosio/signature_provider_plugin/signature_provider_plugin.hpp>

#include <appbase/application.hpp>

Expand All @@ -11,7 +11,7 @@ using boost::signals2::signal;

class producer_plugin : public appbase::plugin<producer_plugin> {
public:
APPBASE_PLUGIN_REQUIRES((chain_plugin)(http_client_plugin))
APPBASE_PLUGIN_REQUIRES((chain_plugin)(signature_provider_plugin))

struct runtime_options {
std::optional<int32_t> max_transaction_time;
Expand Down
74 changes: 10 additions & 64 deletions plugins/producer_plugin/producer_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

#include <iostream>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/function_output_iterator.hpp>
#include <boost/multi_index_container.hpp>
Expand Down Expand Up @@ -310,7 +310,7 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
bool _production_enabled = false;
bool _pause_production = false;

using signature_provider_type = std::function<chain::signature_type(chain::digest_type)>;
using signature_provider_type = signature_provider_plugin::signature_provider_type;
std::map<chain::public_key_type, signature_provider_type> _signature_providers;
std::set<chain::account_name> _producers;
boost::asio::deadline_timer _timer;
Expand All @@ -331,7 +331,6 @@ class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin
bool _disable_subjective_p2p_billing = true;
bool _disable_subjective_api_billing = true;
fc::time_point _irreversible_block_time;
fc::microseconds _keosd_provider_timeout_us;

std::vector<chain::digest_type> _protocol_features_to_activate;
bool _protocol_features_signaled = false; // to mark whether it has been signaled in start_block
Expand Down Expand Up @@ -845,15 +844,7 @@ void producer_plugin::set_program_options(
("signature-provider", boost::program_options::value<vector<string>>()->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 <public-key>=<provider-spec>\n"
"Where:\n"
" <public-key> \tis a string form of a vaild EOSIO public key\n\n"
" <provider-spec> \tis a string in the form <provider-type>:<data>\n\n"
" <provider-type> \tis KEY, or KEOSD\n\n"
" KEY:<data> \tis a string form of a valid EOSIO private key which maps to the provided public key\n\n"
" KEOSD:<data> \tis the URL where keosd is available and the approptiate wallet(s) are unlocked")
("keosd-provider-timeout", boost::program_options::value<int32_t>()->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_plugin>().signature_provider_help_text())
("greylist-account", boost::program_options::value<vector<string>>()->composing()->multitoken(),
"account that can not access to extended CPU/NET virtual resources")
("greylist-limit", boost::program_options::value<uint32_t>()->default_value(1000),
Expand Down Expand Up @@ -939,37 +930,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);
};
}

static producer_plugin_impl::signature_provider_type
make_keosd_signature_provider(const std::shared_ptr<producer_plugin_impl>& 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(), std::optional<uint16_t>());
else
keosd_url = fc::url(url_str);
std::weak_ptr<producer_plugin_impl> 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<http_client_plugin>().get_client().post_sync(keosd_url, params, deadline).as<chain::signature_type>();
} else {
return signature_type();
}
};
}

void producer_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
my->chain_plug = app().find_plugin<chain_plugin>();
Expand All @@ -986,7 +946,7 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_
{
try {
auto key_id_to_wif_pair = dejsonify<std::pair<public_key_type, private_key_type>>(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_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 ( const std::exception& e ) {
Expand All @@ -999,32 +959,18 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_
const std::vector<std::string> key_spec_pairs = options["signature-provider"].as<std::vector<std::string>>();
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);
}

const auto& [pubkey, provider] = app().get_plugin<signature_provider_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 (fc::exception& e) {
elog("Malformed signature provider: \"${val}\": ${e}, ignoring!", ("val", key_spec_pair)("e", e));
} catch (...) {
elog("Malformed signature provider: \"${val}\", ignoring!", ("val", key_spec_pair));
}
}
}

my->_keosd_provider_timeout_us = fc::milliseconds(options.at("keosd-provider-timeout").as<int32_t>());

my->_account_fails.set_max_failures_per_account( options.at("subjective-account-max-failures").as<uint32_t>() );

my->_produce_time_offset_us = options.at("produce-time-offset-us").as<int32_t>();
Expand Down
10 changes: 10 additions & 0 deletions plugins/signature_provider_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
#include <appbase/application.hpp>
#include <eosio/http_client_plugin/http_client_plugin.hpp>
#include <eosio/chain/types.hpp>

namespace eosio {

using namespace appbase;

class signature_provider_plugin : public appbase::plugin<signature_provider_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<chain::signature_type(chain::digest_type)>;

std::pair<chain::public_key_type,signature_provider_type> 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<class signature_provider_plugin_impl> my;
};

}
125 changes: 125 additions & 0 deletions plugins/signature_provider_plugin/signature_provider_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include <eosio/signature_provider_plugin/signature_provider_plugin.hpp>
#include <eosio/chain/exceptions.hpp>

#include <fc/time.hpp>
#include <fc/network/url.hpp>

#include <boost/algorithm/string.hpp>

#ifdef __APPLE__
#include <eosio/se-helpers/se-helpers.hpp>
#endif

namespace eosio {
static appbase::abstract_plugin& _signature_provider_plugin = app().register_plugin<signature_provider_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<secure_enclave::secure_enclave_key> 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(), std::optional<uint16_t>());
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<http_client_plugin>().get_client().post_sync(keosd_url, params, deadline).as<chain::signature_type>();
};
}
};

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<int32_t>()->default_value(5),
"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 <public-key>=<provider-spec>\n"
"Where:\n"
" <public-key> \tis a string form of a vaild EOSIO public key\n\n"
" <provider-spec> \tis a string in the form <provider-type>:<data>\n\n"
" <provider-type> \tis KEY, KEOSD, or SE\n\n"
" KEY:<data> \tis a string form of a valid EOSIO private key which maps to the provided public key\n\n"
" KEOSD:<data> \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
;

}

void signature_provider_plugin::plugin_initialize(const variables_map& options) {
my->_keosd_provider_timeout_us = fc::milliseconds( options.at("keosd-provider-timeout").as<int32_t>() );
}

std::pair<chain::public_key_type,signature_provider_plugin::signature_provider_type>
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);
}

}

0 comments on commit 4453b71

Please sign in to comment.