Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

WebAuthn key and signature support #7421

Merged
merged 12 commits into from
Jun 29, 2019
Merged
13 changes: 13 additions & 0 deletions libraries/chain/authorization_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <eosio/chain/generated_transaction_object.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <eosio/chain/database_utils.hpp>
#include <eosio/chain/protocol_state_object.hpp>


namespace eosio { namespace chain {
Expand Down Expand Up @@ -140,6 +141,10 @@ namespace eosio { namespace chain {
time_point initial_creation_time
)
{
for(const key_weight& k: auth.keys)
EOS_ASSERT(k.key.which() < _db.get<protocol_state_object>().num_supported_key_types, unactivated_key_type,
"Unactivated key type used when creating permission");

auto creation_time = initial_creation_time;
if( creation_time == time_point() ) {
creation_time = _control.pending_block_time();
Expand Down Expand Up @@ -167,6 +172,10 @@ namespace eosio { namespace chain {
time_point initial_creation_time
)
{
for(const key_weight& k: auth.keys)
EOS_ASSERT(k.key.which() < _db.get<protocol_state_object>().num_supported_key_types, unactivated_key_type,
"Unactivated key type used when creating permission");

auto creation_time = initial_creation_time;
if( creation_time == time_point() ) {
creation_time = _control.pending_block_time();
Expand All @@ -188,6 +197,10 @@ namespace eosio { namespace chain {
}

void authorization_manager::modify_permission( const permission_object& permission, const authority& auth ) {
for(const key_weight& k: auth.keys)
EOS_ASSERT(k.key.which() < _db.get<protocol_state_object>().num_supported_key_types, unactivated_key_type,
"Unactivated key type used when modifying permission");

_db.modify( permission, [&](permission_object& po) {
po.auth = auth;
po.last_updated = _control.pending_block_time();
Expand Down
27 changes: 16 additions & 11 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ struct controller_impl {
set_activation_handler<builtin_protocol_feature_t::preactivate_feature>();
set_activation_handler<builtin_protocol_feature_t::replace_deferred>();
set_activation_handler<builtin_protocol_feature_t::get_sender>();
set_activation_handler<builtin_protocol_feature_t::webauthn_key>();

self.irreversible_block.connect([this](const block_state_ptr& bsp) {
wasmif.current_lib(bsp->block_num);
Expand Down Expand Up @@ -584,16 +585,8 @@ struct controller_impl {
// check database version
const auto& header_idx = db.get_index<database_header_multi_index>().indices().get<by_id>();

if (database_header_object::minimum_version != 0) {
EOS_ASSERT(header_idx.begin() != header_idx.end(), bad_database_version_exception,
"state database version pre-dates versioning, please restore from a compatible snapshot or replay!");
} else if ( header_idx.empty() ) {
// temporary code to upgrade from existing un-versioned state database
static_assert(database_header_object::minimum_version == 0, "remove this path once the minimum version moves");
db.create<database_header_object>([](const auto& header){
// nothing to do here
});
}
EOS_ASSERT(header_idx.begin() != header_idx.end(), bad_database_version_exception,
"state database version pre-dates versioning, please restore from a compatible snapshot or replay!");

const auto& header_itr = header_idx.begin();
header_itr->validate();
Expand Down Expand Up @@ -1085,7 +1078,8 @@ struct controller_impl {
|| (code == contract_whitelist_exception::code_value)
|| (code == contract_blacklist_exception::code_value)
|| (code == action_blacklist_exception::code_value)
|| (code == key_blacklist_exception::code_value);
|| (code == key_blacklist_exception::code_value)
|| (code == sig_variable_size_limit_exception::code_value);
}

bool scheduled_failure_is_subjective( const fc::exception& e ) const {
Expand Down Expand Up @@ -2976,6 +2970,10 @@ bool controller::is_ram_billing_in_notify_allowed()const {
return my->conf.disable_all_subjective_mitigations || !is_producing_block() || my->conf.allow_ram_billing_in_notify;
}

uint32_t controller::configured_subjective_signature_length_limit()const {
return my->conf.maximum_variable_signature_length;
}

void controller::validate_expiration( const transaction& trx )const { try {
const auto& chain_configuration = get_global_properties().configuration;

Expand Down Expand Up @@ -3117,6 +3115,13 @@ void controller_impl::on_activation<builtin_protocol_feature_t::replace_deferred
}
}

template<>
void controller_impl::on_activation<builtin_protocol_feature_t::webauthn_key>() {
db.modify( db.get<protocol_state_object>(), [&]( auto& ps ) {
ps.num_supported_key_types = 3;
} );
}

/// End of protocol feature activation handlers

} } /// eosio::chain
111 changes: 109 additions & 2 deletions libraries/chain/include/eosio/chain/authority.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,78 @@

namespace eosio { namespace chain {

using shared_public_key_data = fc::static_variant<fc::ecc::public_key_shim, fc::crypto::r1::public_key_shim, shared_string>;

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should consider moving struct overloaded to at least types.hpp or, more ideally, fc/static_variant.hpp it seems broadly useful and generalized

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

struct shared_public_key {
shared_public_key( shared_public_key_data&& p ) :
pubkey(std::move(p)) {}

operator public_key_type() const {
fc::crypto::public_key::storage_type public_key_storage;
pubkey.visit(overloaded {
[&](const auto& k1r1) {
public_key_storage = k1r1;
},
[&](const shared_string& wa) {
fc::datastream ds(wa.data(), wa.size());
fc::crypto::webauthn::public_key pub;
fc::raw::unpack(ds, pub);
public_key_storage = pub;
}
});
return std::move(public_key_storage);
}

operator string() const {
return (string)this->operator public_key_type();
}

shared_public_key_data pubkey;

friend bool operator == ( const shared_public_key& lhs, const shared_public_key& rhs ) {
if(lhs.pubkey.which() != rhs.pubkey.which())
return false;

return lhs.pubkey.visit<bool>(overloaded {
[&](const fc::ecc::public_key_shim& k1) {
return k1._data == rhs.pubkey.get<fc::ecc::public_key_shim>()._data;
},
[&](const fc::crypto::r1::public_key_shim& r1) {
return r1._data == rhs.pubkey.get<fc::crypto::r1::public_key_shim>()._data;
},
[&](const shared_string& wa) {
return wa == rhs.pubkey.get<shared_string>();
}
});
}

friend bool operator==(const shared_public_key& l, const public_key_type& r) {
if(l.pubkey.which() != r._storage.which())
return false;

return l.pubkey.visit<bool>(overloaded {
[&](const fc::ecc::public_key_shim& k1) {
return k1._data == r._storage.get<fc::ecc::public_key_shim>()._data;
},
[&](const fc::crypto::r1::public_key_shim& r1) {
return r1._data == r._storage.get<fc::crypto::r1::public_key_shim>()._data;
},
[&](const shared_string& wa) {
fc::datastream ds(wa.data(), wa.size());
fc::crypto::webauthn::public_key pub;
fc::raw::unpack(ds, pub);
return pub == r._storage.get<fc::crypto::webauthn::public_key>();
}
});
}

friend bool operator==(const public_key_type& l, const shared_public_key& r) {
return r == l;
}
};

struct permission_level_weight {
permission_level permission;
Expand All @@ -30,6 +102,23 @@ struct key_weight {
}
};


struct shared_key_weight {
shared_key_weight(shared_public_key_data&& k, const weight_type& w) :
key(std::move(k)), weight(w) {}

operator key_weight() const {
return key_weight{key, weight};
}

shared_public_key key;
weight_type weight;

friend bool operator == ( const shared_key_weight& lhs, const shared_key_weight& rhs ) {
return tie( lhs.key, lhs.weight ) == tie( rhs.key, rhs.weight );
}
};

struct wait_weight {
uint32_t wait_sec;
weight_type weight;
Expand Down Expand Up @@ -100,14 +189,30 @@ struct shared_authority {

shared_authority& operator=(const authority& a) {
threshold = a.threshold;
keys = decltype(keys)(a.keys.begin(), a.keys.end(), keys.get_allocator());
keys.clear();
keys.reserve(a.keys.size());
for(const key_weight& k : a.keys) {
k.key._storage.visit(overloaded {
[&](const auto& k1r1) {
keys.emplace_back(k1r1, k.weight);
},
[&](const fc::crypto::webauthn::public_key& wa) {
size_t psz = fc::raw::pack_size(wa);
shared_string wa_ss(psz, boost::container::default_init, keys.get_allocator());
fc::datastream<char*> ds(wa_ss.data(), wa_ss.size());
fc::raw::pack(ds, wa);

keys.emplace_back(std::move(wa_ss), k.weight);
}
});
}
accounts = decltype(accounts)(a.accounts.begin(), a.accounts.end(), accounts.get_allocator());
waits = decltype(waits)(a.waits.begin(), a.waits.end(), waits.get_allocator());
return *this;
}

uint32_t threshold = 0;
shared_vector<key_weight> keys;
shared_vector<shared_key_weight> keys;
shared_vector<permission_level_weight> accounts;
shared_vector<wait_weight> waits;

Expand Down Expand Up @@ -202,4 +307,6 @@ FC_REFLECT(eosio::chain::permission_level_weight, (permission)(weight) )
FC_REFLECT(eosio::chain::key_weight, (key)(weight) )
FC_REFLECT(eosio::chain::wait_weight, (wait_sec)(weight) )
FC_REFLECT(eosio::chain::authority, (threshold)(keys)(accounts)(waits) )
FC_REFLECT(eosio::chain::shared_key_weight, (key)(weight) )
FC_REFLECT(eosio::chain::shared_authority, (threshold)(keys)(accounts)(waits) )
FC_REFLECT(eosio::chain::shared_public_key, (pubkey))
55 changes: 22 additions & 33 deletions libraries/chain/include/eosio/chain/authority_checker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,9 @@
namespace eosio { namespace chain {

namespace detail {

// Order of the template types in the static_variant matters to meta_permission_comparator.
using meta_permission = static_variant<permission_level_weight, key_weight, wait_weight>;

struct get_weight_visitor {
using result_type = uint32_t;

template<typename Permission>
uint32_t operator()( const Permission& permission ) { return permission.weight; }
};

// Orders permissions descending by weight, and breaks ties with Wait permissions being less than
// Key permissions which are in turn less than Account permissions
struct meta_permission_comparator {
bool operator()( const meta_permission& lhs, const meta_permission& rhs ) const {
get_weight_visitor scale;
auto lhs_weight = lhs.visit(scale);
auto lhs_type = lhs.which();
auto rhs_weight = rhs.visit(scale);
auto rhs_type = rhs.which();
return std::tie( lhs_weight, lhs_type ) > std::tie( rhs_weight, rhs_type );
}
};

using meta_permission_set = boost::container::flat_multiset<meta_permission, meta_permission_comparator>;
using meta_permission_key = std::tuple<uint32_t, int>;
using meta_permission_value = std::function<uint32_t()>;
using meta_permission_map = boost::container::flat_multimap<meta_permission_key, meta_permission_value, std::greater<>>;

} /// namespace detail

Expand Down Expand Up @@ -186,17 +164,27 @@ namespace detail {
});

// Sort key permissions and account permissions together into a single set of meta_permissions
detail::meta_permission_set permissions;
detail::meta_permission_map permissions;

permissions.insert(authority.waits.begin(), authority.waits.end());
permissions.insert(authority.keys.begin(), authority.keys.end());
permissions.insert(authority.accounts.begin(), authority.accounts.end());
weight_tally_visitor visitor(*this, cached_permissions, depth);
auto emplace_permission = [&permissions, &visitor](int priority, const auto& mp) {
permissions.emplace(
std::make_tuple(mp.weight, priority),
[&mp, &visitor]() {
return visitor(mp);
}
);
};

permissions.reserve(authority.waits.size() + authority.keys.size() + authority.accounts.size());
std::for_each(authority.accounts.begin(), authority.accounts.end(), boost::bind<void>(emplace_permission, 1, _1));
std::for_each(authority.keys.begin(), authority.keys.end(), boost::bind<void>(emplace_permission, 2, _1));
std::for_each(authority.waits.begin(), authority.waits.end(), boost::bind<void>(emplace_permission, 3, _1));

// Check all permissions, from highest weight to lowest, seeing if provided authorization factors satisfies them or not
weight_tally_visitor visitor(*this, cached_permissions, depth);
for( const auto& permission : permissions )
for( const auto& p: permissions )
// If we've got enough weight, to satisfy the authority, return!
if( permission.visit(visitor) >= authority.threshold ) {
if( p.second() >= authority.threshold ) {
KeyReverter.cancel();
return true;
}
Expand Down Expand Up @@ -224,7 +212,8 @@ namespace detail {
return total_weight;
}

uint32_t operator()(const key_weight& permission) {
template<typename KeyWeight, typename = std::enable_if_t<detail::is_any_of_v<KeyWeight, shared_key_weight, key_weight>>>
uint32_t operator()(const KeyWeight& permission) {
auto itr = boost::find( checker.provided_keys, permission.key );
if( itr != checker.provided_keys.end() ) {
checker._used_keys[itr - checker.provided_keys.begin()] = true;
Expand Down
4 changes: 3 additions & 1 deletion libraries/chain/include/eosio/chain/chain_snapshot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ struct chain_snapshot_header {
* 2: Updated chain snapshot for v1.8.0 initial protocol features release:
* - Incompatible with version 1.
* - Adds new indices for: protocol_state_object and account_ram_correction_object
* 3: Updated for v2.0.0 protocol features:
* - WebAuthn keys
*/

static constexpr uint32_t minimum_compatible_version = 2;
static constexpr uint32_t current_version = 2;
static constexpr uint32_t current_version = 3;

uint32_t version = current_version;

Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/eosio/chain/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const static uint16_t default_max_inline_action_depth = 4;
const static uint16_t default_max_auth_depth = 6;
const static uint32_t default_sig_cpu_bill_pct = 50 * percent_1; // billable percentage of signature recovery
const static uint16_t default_controller_thread_pool_size = 2;
const static uint32_t default_max_variable_signature_length = 16384u;

const static uint32_t min_net_usage_delta_between_base_and_max_for_trx = 10*1024;
// Should be large enough to allow recovery from badly set blockchain parameters without a hard fork
Expand Down
6 changes: 6 additions & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ namespace eosio { namespace chain {
bool disable_replay_opts = false;
bool contracts_console = false;
bool allow_ram_billing_in_notify = false;
uint32_t maximum_variable_signature_length = chain::config::default_max_variable_signature_length;
bool disable_all_subjective_mitigations = false; //< for testing purposes only

genesis_state genesis;
Expand Down Expand Up @@ -240,6 +241,11 @@ namespace eosio { namespace chain {

bool is_ram_billing_in_notify_allowed()const;

//This is only an accessor to the user configured subjective limit: i.e. it does not do a
// check similar to is_ram_billing_in_notify_allowed() to check if controller is currently
// producing a block
uint32_t configured_subjective_signature_length_limit()const;

void add_resource_greylist(const account_name &name);
void remove_resource_greylist(const account_name &name);
bool is_resource_greylisted(const account_name &name) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ namespace eosio { namespace chain {
* - 0 : implied version when this header is absent
* - 1 : initial version, prior to this no `database_header_object` existed in the shared memory file but
* no changes to its format were made so it can be safely added to existing databases
* - 2 : shared_authority now holds shared_key_weights & shared_public_keys
*/

static constexpr uint32_t current_version = 1;
static constexpr uint32_t minimum_version = 0;
static constexpr uint32_t current_version = 2;
static constexpr uint32_t minimum_version = 2;

id_type id;
uint32_t version = current_version;
Expand Down
Loading