This commit is contained in:
Julien Maffre 2020-03-12 14:56:51 +00:00 коммит произвёл GitHub
Родитель e67b4f418e
Коммит 9e45c650d7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 531 добавлений и 168 удалений

Просмотреть файл

@ -355,7 +355,7 @@ if(BUILD_TESTS)
)
target_link_libraries(
nodefrontend_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} evercrypt.host lua.host
secp256k1.host http_parser.host
secp256k1.host http_parser.host sss.host
)
if(NOT ENV{RUNTIME_CONFIG_DIR})

Просмотреть файл

@ -183,6 +183,7 @@ namespace ccf
return active_nodes;
}
// Service status should use a state machine, very much like NodeState.
void create_service(
const std::vector<uint8_t>& network_cert, kv::Version version = 1)
{
@ -207,7 +208,9 @@ namespace ccf
return false;
}
if (active_service->status != ServiceStatus::OPENING)
if (
active_service->status != ServiceStatus::OPENING &&
active_service->status != ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
{
LOG_FAIL_FMT("Could not open current service: status is not OPENING");
return false;
@ -219,6 +222,43 @@ namespace ccf
return true;
}
std::optional<ServiceStatus> get_service_status()
{
auto service_view = tx.get_view(tables.service);
auto active_service = service_view->get(0);
if (!active_service.has_value())
{
LOG_FAIL_FMT("Failed to get active service");
return {};
}
return active_service->status;
}
bool service_wait_for_shares()
{
auto service_view = tx.get_view(tables.service);
auto active_service = service_view->get(0);
if (!active_service.has_value())
{
LOG_FAIL_FMT("Failed to get active service");
return false;
}
if (active_service->status != ServiceStatus::OPENING)
{
LOG_FAIL_FMT(
"Could not wait for shares on current service: status is not "
"OPENING");
return false;
}
active_service->status = ServiceStatus::WAITING_FOR_RECOVERY_SHARES;
service_view->put(0, active_service.value());
return true;
}
void trust_node(NodeId node_id)
{
auto nodes_view = tx.get_view(tables.nodes);

Просмотреть файл

@ -22,22 +22,6 @@ namespace ccf
const std::vector<uint8_t>& data) = 0;
};
struct LedgerSecretWrappingKey
{
static constexpr auto KZ_KEY_SIZE = crypto::GCM_SIZE_KEY;
std::vector<uint8_t> data; // Referred to as "kz" in TR
LedgerSecretWrappingKey() : data(tls::create_entropy()->random(KZ_KEY_SIZE))
{}
template <typename T>
LedgerSecretWrappingKey(const T& split_secret) :
data(
std::make_move_iterator(split_secret.begin()),
std::make_move_iterator(split_secret.begin() + split_secret.size()))
{}
};
struct LedgerSecret
{
static constexpr auto MASTER_KEY_SIZE = crypto::GCM_SIZE_KEY;
@ -57,9 +41,7 @@ namespace ccf
}
}
LedgerSecret(const std::vector<uint8_t>& ledger_master_) :
master(ledger_master_)
{}
LedgerSecret(const std::vector<uint8_t>& master_) : master(master_) {}
};
class LedgerSecrets

Просмотреть файл

@ -23,6 +23,7 @@
#include "rpc/serialization.h"
#include "seal.h"
#include "secretshare.h"
#include "sharemanager.h"
#include "timer.h"
#include "tls/25519.h"
#include "tls/client.h"
@ -181,6 +182,7 @@ namespace ccf
std::shared_ptr<kv::AbstractTxEncryptor> encryptor;
std::shared_ptr<Seal> seal;
ShareManager share_manager;
//
// join protocol
@ -218,7 +220,8 @@ namespace ccf
rpcsessions(rpcsessions),
notifier(notifier),
timers(timers),
seal(std::make_shared<Seal>(writer_factory))
seal(std::make_shared<Seal>(writer_factory)),
share_manager(network)
{
::EverCrypt_AutoConfig2_init();
}
@ -790,35 +793,78 @@ namespace ccf
}
bool finish_recovery(
Store::Tx& tx, const nlohmann::json& sealed_secrets) override
Store::Tx& tx,
const nlohmann::json& sealed_secrets,
bool with_shares) override
{
std::lock_guard<SpinLock> guard(lock);
sm.expect(State::partOfPublicNetwork);
LOG_INFO_FMT("Initiating end of recovery (primary)");
if (with_shares)
{
GenesisGenerator g(network, tx);
if (!g.service_wait_for_shares())
{
return false;
}
}
else
{
LOG_INFO_FMT("Initiating end of recovery (primary)");
// Unseal past network secrets
auto past_secrets_idx = network.ledger_secrets->restore(sealed_secrets);
// Unseal past network secrets
auto past_secrets_idx = network.ledger_secrets->restore(sealed_secrets);
// Emit signature to certify transactions that happened on public
// network
history->emit_signature();
// For all nodes in the new network, write all past network secrets to
// the secrets table, encrypted with the respective public keys
for (auto const& secret_idx : past_secrets_idx)
{
auto secret = network.ledger_secrets->get_secret(secret_idx);
if (!secret.has_value())
{
LOG_FAIL_FMT(
"Ledger secrets have not been restored: {}", secret_idx);
return false;
}
// Do not broadcast the ledger secrets to self since they were already
// restored from sealed file
broadcast_ledger_secret(tx, secret.value(), secret_idx, true);
}
// Setup new temporary store and record current version/root
setup_private_recovery_store();
// Start reading private security domain of ledger
ledger_idx = 0;
read_ledger_idx(++ledger_idx);
sm.advance(State::readingPrivateLedger);
}
return true;
}
bool finish_recovery_with_shares(
Store::Tx& tx, const LedgerSecret& ledger_secret)
{
std::lock_guard<SpinLock> guard(lock);
sm.expect(State::partOfPublicNetwork);
// For now, this only supports one recovery
LOG_INFO_FMT("Initiating end of recovery with shares (primary)");
// Emit signature to certify transactions that happened on public
// network
history->emit_signature();
// For all nodes in the new network, write all past network secrets to
// the secrets table, encrypted with the respective public keys
for (auto const& secret_idx : past_secrets_idx)
{
auto secret = network.ledger_secrets->get_secret(secret_idx);
if (!secret.has_value())
{
LOG_FAIL_FMT("Ledger secrets have not been restored: {}", secret_idx);
return false;
}
network.ledger_secrets->set_secret(0, ledger_secret.master);
// Do not broadcast the ledger secrets to self since they were already
// restored from sealed file
broadcast_ledger_secret(tx, secret.value(), secret_idx, true);
}
broadcast_ledger_secret(tx, ledger_secret, 0, true);
// Setup new temporary store and record current version/root
setup_private_recovery_store();
@ -1011,59 +1057,36 @@ namespace ccf
});
};
void split_ledger_secrets(Store::Tx& tx) override
bool split_ledger_secrets(Store::Tx& tx) override
{
auto share_wrapping_key_raw =
tls::create_entropy()->random(crypto::GCM_SIZE_KEY);
auto share_wrapping_key = crypto::KeyAesGcm(share_wrapping_key_raw);
// Once sealing is completely removed, this can be called from the
// LedgerSecrets class directly
crypto::GcmCipher encrypted_ls(LedgerSecret::MASTER_KEY_SIZE);
share_wrapping_key.encrypt(
encrypted_ls.hdr.get_iv(), // iv is always 0 here as the share wrapping
// key is never re-used for encryption
network.ledger_secrets->get_secret(1)->master,
nullb,
encrypted_ls.cipher.data(),
encrypted_ls.hdr.tag);
GenesisGenerator g(network, tx);
auto active_members = g.get_active_members_keyshare();
SecretSharing::SplitSecret secret_to_split = {};
std::copy_n(
share_wrapping_key_raw.begin(),
share_wrapping_key_raw.size(),
secret_to_split.begin());
// For now, the secret sharing threshold is set to the number of initial
// members
size_t threshold = active_members.size();
auto shares =
SecretSharing::split(secret_to_split, active_members.size(), threshold);
EncryptedSharesMap encrypted_shares;
auto nonce = tls::create_entropy()->random(crypto::Box::NONCE_SIZE);
size_t share_index = 0;
for (auto const& [member_id, enc_pub_key] : active_members)
try
{
auto share_raw = std::vector<uint8_t>(
shares[share_index].begin(), shares[share_index].end());
share_manager.create(tx);
}
catch (const std::logic_error& e)
{
LOG_FAIL_FMT("Failed to create shares: {}", e.what());
return false;
}
return true;
}
auto enc_pub_key_raw = tls::PublicX25519::parse(tls::Pem(enc_pub_key));
auto encrypted_share = crypto::Box::create(
share_raw,
nonce,
enc_pub_key_raw,
network.encryption_key->private_raw);
encrypted_shares[member_id] = {nonce, encrypted_share};
share_index++;
bool combine_recovery_shares(
Store::Tx& tx, const std::vector<SecretSharing::Share>& shares) override
{
LedgerSecret restored_ledger_secret;
try
{
restored_ledger_secret = share_manager.restore(tx, shares);
finish_recovery_with_shares(tx, restored_ledger_secret);
}
catch (const std::logic_error& e)
{
LOG_FAIL_FMT("Failed to restore shares: {}", e.what());
return false;
}
g.add_key_share_info({encrypted_ls.serialise(), encrypted_shares});
return true;
}
NodeId get_node_id() const override

Просмотреть файл

@ -11,7 +11,7 @@
#include "forwarder.h"
#include "node/clientsignatures.h"
#include "node/nodes.h"
#include "nodeinterface.h"
#include "notifierinterface.h"
#include "rpcexception.h"
#include "tls/verifier.h"

Просмотреть файл

@ -8,6 +8,8 @@
#include "node/nodes.h"
#include "node/quoteverification.h"
#include "node/secretshare.h"
#include "node/sharemanager.h"
#include "nodeinterface.h"
#include "tls/keypair.h"
#include <exception>
@ -219,7 +221,31 @@ namespace ccf
ObjectId proposal_id, Store::Tx& tx, const nlohmann::json& args) {
if (node.is_part_of_public_network())
{
const auto recovery_successful = node.finish_recovery(tx, args);
const auto recovery_successful =
node.finish_recovery(tx, args, false);
if (!recovery_successful)
{
LOG_FAIL_FMT("Proposal {}: Recovery failed", proposal_id);
}
return recovery_successful;
}
else
{
LOG_FAIL_FMT(
"Proposal {}: Node is not part of public network", proposal_id);
return false;
}
}},
// For now, members can propose to accept a recovery with shares. In
// that case, members will have to submit their shares after this
// proposal is accepted.
{"accept_recovery_with_shares",
[this](
ObjectId proposal_id, Store::Tx& tx, const nlohmann::json& args) {
if (node.is_part_of_public_network())
{
const auto recovery_successful =
node.finish_recovery(tx, nullptr, true);
if (!recovery_successful)
{
LOG_FAIL_FMT("Proposal {}: Recovery failed", proposal_id);
@ -707,6 +733,12 @@ namespace ccf
std::optional<EncryptedShare> enc_s;
auto current_keyshare =
args.tx.get_view(this->network.shares)->get(0);
if (!current_keyshare.has_value())
{
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
"Failed to retrieve current key share info");
}
for (auto const& s : current_keyshare->encrypted_shares)
{
if (s.first == args.caller_id)
@ -730,72 +762,52 @@ namespace ccf
json_adapter(get_encrypted_recovery_share),
Read);
auto submit_recovery_share =
[this](RequestArgs& args, const nlohmann::json& params) {
// Only active members can submit their shares for recovery
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(HTTP_STATUS_FORBIDDEN, "Member is not active");
}
auto submit_recovery_share = [this](
RequestArgs& args,
const nlohmann::json& params) {
// Only active members can submit their shares for recovery
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(HTTP_STATUS_FORBIDDEN, "Member is not active");
}
const auto in = params.get<SubmitRecoveryShare>();
GenesisGenerator g(this->network, args.tx);
if (
g.get_service_status() != ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
{
return make_error(
HTTP_STATUS_FORBIDDEN,
"Service is not waiting for recovery shares");
}
SecretSharing::Share share;
std::copy_n(
in.share.begin(), SecretSharing::SHARE_LENGTH, share.begin());
const auto in = params.get<SubmitRecoveryShare>();
pending_shares.emplace_back(share);
SecretSharing::Share share;
std::copy_n(
in.share.begin(), SecretSharing::SHARE_LENGTH, share.begin());
GenesisGenerator g(this->network, args.tx);
if (pending_shares.size() < g.get_active_members_count())
{
// The number of shares required to re-assemble the secret has not
// yet been reached
return make_success(false);
}
pending_shares.emplace_back(share);
if (pending_shares.size() < g.get_active_members_count())
{
// The number of shares required to re-assemble the secret has not
// yet been reached
return make_success(false);
}
LOG_DEBUG_FMT(
"Reached secret sharing threshold {}", pending_shares.size());
auto share_wrapping_key = LedgerSecretWrappingKey(
SecretSharing::combine(pending_shares, pending_shares.size()));
auto shares_view = args.tx.get_view(this->network.shares);
auto key_share_info = shares_view->get(0);
if (!key_share_info.has_value())
{
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR, "No key share info available");
}
std::vector<uint8_t> decrypted_ls(LedgerSecret::MASTER_KEY_SIZE);
crypto::GcmCipher encrypted_ls;
try
{
encrypted_ls.deserialise(key_share_info->encrypted_ledger_secret);
}
catch (const std::logic_error& e)
{
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
"Failed to deserialise ledger secrets");
}
if (!crypto::KeyAesGcm(share_wrapping_key.data)
.decrypt(
encrypted_ls.hdr.get_iv(),
encrypted_ls.hdr.tag,
encrypted_ls.cipher,
nullb,
decrypted_ls.data()))
{
LOG_FAIL_FMT("Decryption of ledger secrets failed");
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
"Decryption of ledger secrets failed");
}
LOG_DEBUG_FMT(
"Reached secret sharing threshold {}", pending_shares.size());
if (!node.combine_recovery_shares(args.tx, pending_shares))
{
pending_shares.clear();
return make_success(true);
};
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
"Failed to combine recovery shares");
}
pending_shares.clear();
return make_success(true);
};
install_with_auto_schema<SubmitRecoveryShare, bool>(
MemberProcs::SUBMIT_RECOVERY_SHARE,
json_adapter(submit_recovery_share),
@ -823,7 +835,13 @@ namespace ccf
g.add_consensus(in.consensus_type);
node.split_ledger_secrets(tx);
if (!node.split_ledger_secrets(tx))
{
LOG_FAIL_FMT("Error splitting ledger secrets");
return make_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
"Error splitting ledger secrets");
}
size_t self = g.add_node({in.node_info_network,
in.node_cert,

Просмотреть файл

@ -7,6 +7,7 @@
#include "node/entities.h"
#include "node/networkstate.h"
#include "node/quoteverification.h"
#include "nodeinterface.h"
namespace ccf
{

Просмотреть файл

@ -3,6 +3,7 @@
#pragma once
#include "node/entities.h"
#include "node/secretshare.h"
#include "nodecalltypes.h"
namespace ccf
@ -11,7 +12,8 @@ namespace ccf
{
public:
virtual ~AbstractNodeState() {}
virtual bool finish_recovery(Store::Tx& tx, const nlohmann::json& args) = 0;
virtual bool finish_recovery(
Store::Tx& tx, const nlohmann::json& args, bool with_shares) = 0;
virtual bool open_network(Store::Tx& tx) = 0;
virtual bool rekey_ledger(Store::Tx& tx) = 0;
virtual bool is_part_of_public_network() const = 0;
@ -23,14 +25,10 @@ namespace ccf
Store::Tx& tx,
GetQuotes::Out& result,
const std::optional<std::set<NodeId>>& filter = std::nullopt) = 0;
virtual void split_ledger_secrets(Store::Tx& tx) = 0;
virtual NodeId get_node_id() const = 0;
};
class AbstractNotifier
{
public:
virtual ~AbstractNotifier() {}
virtual void notify(const std::vector<uint8_t>& data) = 0;
virtual bool split_ledger_secrets(Store::Tx& tx) = 0;
virtual bool combine_recovery_shares(
Store::Tx& tx, const std::vector<SecretSharing::Share>& shares) = 0;
};
}

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include <stdint.h>
#include <vector>
namespace ccf
{
class AbstractNotifier
{
public:
virtual ~AbstractNotifier() {}
virtual void notify(const std::vector<uint8_t>& data) = 0;
};
}

Просмотреть файл

@ -1553,6 +1553,94 @@ DOCTEST_TEST_CASE("User data")
}
}
DOCTEST_TEST_CASE("Submit recovery shares")
{
// Setup original state
NetworkTables network;
auto node = StubNodeState(std::make_shared<NetworkTables>(network));
MemberRpcFrontend frontend(network, node);
std::map<size_t, ccf::Cert> members;
size_t member_count = 4;
std::map<size_t, EncryptedShare> retrieved_shares;
DOCTEST_INFO("Setup state");
{
Store::Tx gen_tx;
GenesisGenerator gen(network, gen_tx);
gen.init_values();
gen.create_service({});
for (size_t i = 0; i < member_count; i++)
{
auto cert = get_cert_data(i, kp);
members[gen.add_member(cert, {}, MemberStatus::ACTIVE)] = cert;
}
DOCTEST_REQUIRE(node.split_ledger_secrets(gen_tx));
gen.finalize();
frontend.open();
}
DOCTEST_INFO("Retrieve recovery shares");
{
const auto get_recovery_shares =
create_request(nullptr, "getEncryptedRecoveryShare");
for (auto const& m : members)
{
retrieved_shares[m.first] = parse_response_body<EncryptedShare>(
frontend_process(frontend, get_recovery_shares, m.second));
}
}
DOCTEST_INFO("Submit share before the service is in correct state");
{
MemberId member_id = 0;
const auto submit_recovery_share = create_request(
SubmitRecoveryShare({retrieved_shares[member_id].encrypted_share}),
"submitRecoveryShare");
check_error(
frontend_process(frontend, submit_recovery_share, members[member_id]),
HTTP_STATUS_FORBIDDEN);
}
DOCTEST_INFO("Change service state to waiting for recovery shares");
{
Store::Tx tx;
GenesisGenerator g(network, tx);
DOCTEST_REQUIRE(g.service_wait_for_shares());
g.finalize();
}
DOCTEST_INFO("Submit recovery shares");
{
for (auto const& m : members)
{
const auto submit_recovery_share = create_request(
SubmitRecoveryShare({retrieved_shares[m.first].encrypted_share}),
"submitRecoveryShare");
auto ret = parse_response_body<bool>(
frontend_process(frontend, submit_recovery_share, m.second));
// Share submission should only complete when last member submits their
// share
if (m.first != (member_count - 1))
{
DOCTEST_REQUIRE(!ret);
}
else
{
DOCTEST_REQUIRE(ret);
}
}
}
}
// We need an explicit main to initialize kremlib and EverCrypt
int main(int argc, char** argv)
{

Просмотреть файл

@ -3,6 +3,7 @@
#pragma once
#include "node/rpc/nodeinterface.h"
#include "node/secretshare.h"
namespace ccf
{
@ -10,9 +11,15 @@ namespace ccf
{
private:
bool is_public = false;
std::shared_ptr<NetworkTables> network;
public:
bool finish_recovery(Store::Tx& tx, const nlohmann::json& args) override
StubNodeState(std::shared_ptr<NetworkTables> network_ = nullptr) :
network(network_)
{}
bool finish_recovery(
Store::Tx& tx, const nlohmann::json& args, bool with_shares) override
{
return true;
}
@ -58,7 +65,40 @@ namespace ccf
const std::optional<std::set<NodeId>>& filter) override
{}
void split_ledger_secrets(Store::Tx& tx) override {}
bool split_ledger_secrets(Store::Tx& tx) override
{
auto [members_view, shares_view] =
tx.get_view(network->members, network->shares);
SecretSharing::SplitSecret secret_to_split = {};
GenesisGenerator g(*network.get(), tx);
auto active_member_count = g.get_active_members_count();
// All member shares are required to construct secrets
size_t threshold = active_member_count;
auto shares =
SecretSharing::split(secret_to_split, active_member_count, threshold);
// Here, shares are not encrypted and record in the ledger in plain text
EncryptedSharesMap recorded_shares;
MemberId member_id = 0;
for (auto const& s : shares)
{
auto share_raw = std::vector<uint8_t>(s.begin(), s.end());
recorded_shares[member_id] = {{}, share_raw};
member_id++;
}
g.add_key_share_info({{}, recorded_shares});
return true;
}
bool combine_recovery_shares(
Store::Tx& tx, const std::vector<SecretSharing::Share>& shares) override
{
return true;
}
NodeId get_node_id() const override
{

Просмотреть файл

@ -17,13 +17,16 @@ namespace ccf
{
OPENING = 1,
OPEN = 2,
CLOSED = 3 // For now, unused
WAITING_FOR_RECOVERY_SHARES = 3,
CLOSED = 4 // For now, unused
};
DECLARE_JSON_ENUM(
ServiceStatus,
{{ServiceStatus::OPENING, "OPENING"},
{ServiceStatus::OPEN, "OPEN"},
{ServiceStatus::WAITING_FOR_RECOVERY_SHARES,
"WAITING_FOR_RECOVERY_SHARES"},
{ServiceStatus::CLOSED, "CLOSED"}});
}

141
src/node/sharemanager.h Normal file
Просмотреть файл

@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "crypto/cryptobox.h"
#include "crypto/symmkey.h"
#include "ds/logger.h"
#include "genesisgen.h"
#include "ledgersecrets.h"
#include "networkstate.h"
#include "secretshare.h"
#include "tls/25519.h"
#include "tls/entropy.h"
#include <vector>
namespace ccf
{
struct LedgerSecretWrappingKey
{
private:
static constexpr auto KZ_KEY_SIZE = crypto::GCM_SIZE_KEY;
public:
std::vector<uint8_t> data; // Referred to as "kz" in TR
LedgerSecretWrappingKey() : data(tls::create_entropy()->random(KZ_KEY_SIZE))
{}
template <typename T>
LedgerSecretWrappingKey(const T& split_secret) :
data(
std::make_move_iterator(split_secret.begin()),
std::make_move_iterator(split_secret.begin() + split_secret.size()))
{}
};
class ShareManager
{
private:
NetworkState& network;
public:
ShareManager(NetworkState& network_) : network(network_) {}
void create(Store::Tx& tx)
{
// First, generated a fresh ledger secrets wrapping key and encrypt the
// current ledger secrets with it
auto ls_wrapping_key = LedgerSecretWrappingKey();
crypto::GcmCipher encrypted_ls(LedgerSecret::MASTER_KEY_SIZE);
crypto::KeyAesGcm(ls_wrapping_key.data)
.encrypt(
encrypted_ls.hdr
.get_iv(), // iv is always 0 here as the share wrapping
// key is never re-used for encryption
network.ledger_secrets->get_secret(1)->master,
nullb,
encrypted_ls.cipher.data(),
encrypted_ls.hdr.tag);
// Then, split the ledger secrets wrapping key, allocating a share to each
// active member
SecretSharing::SplitSecret secret_to_split = {};
std::copy_n(
ls_wrapping_key.data.begin(),
ls_wrapping_key.data.size(),
secret_to_split.begin());
GenesisGenerator g(network, tx);
auto active_members_info = g.get_active_members_keyshare();
// For now, the secret sharing threshold is set to the number of initial
// members
size_t threshold = active_members_info.size();
auto shares = SecretSharing::split(
secret_to_split, active_members_info.size(), threshold);
// Finally, encrypt each share with the public key of each member, using a
// random nonce, and record in the KV
EncryptedSharesMap encrypted_shares;
auto nonce = tls::create_entropy()->random(crypto::Box::NONCE_SIZE);
size_t share_index = 0;
for (auto const& [member_id, enc_pub_key] : active_members_info)
{
auto share_raw = std::vector<uint8_t>(
shares[share_index].begin(), shares[share_index].end());
auto enc_pub_key_raw = tls::PublicX25519::parse(tls::Pem(enc_pub_key));
auto encrypted_share = crypto::Box::create(
share_raw,
nonce,
enc_pub_key_raw,
network.encryption_key->private_raw);
encrypted_shares[member_id] = {nonce, encrypted_share};
share_index++;
}
g.add_key_share_info({encrypted_ls.serialise(), encrypted_shares});
}
// For now, the shares are passed directly to this function. Shares should
// be retrieved from the KV instead.
LedgerSecret restore(
Store::Tx& tx, const std::vector<SecretSharing::Share>& shares)
{
// First, re-assemble the ledger secrets wrapping key from the given
// shares
auto ls_wrapping_key =
LedgerSecretWrappingKey(SecretSharing::combine(shares, shares.size()));
// Then, decrypt the ledger secrets
auto shares_view = tx.get_view(network.shares);
auto key_share_info = shares_view->get(0);
if (!key_share_info.has_value())
{
throw std::logic_error("Failed to retrieve current key share info");
}
std::vector<uint8_t> decrypted_ls(LedgerSecret::MASTER_KEY_SIZE);
crypto::GcmCipher encrypted_ls;
encrypted_ls.deserialise(key_share_info->encrypted_ledger_secret);
if (!crypto::KeyAesGcm(ls_wrapping_key.data)
.decrypt(
encrypted_ls.hdr.get_iv(), // iv is 0
encrypted_ls.hdr.tag,
encrypted_ls.cipher,
nullb,
decrypted_ls.data()))
{
throw std::logic_error("Decryption of ledger secrets failed");
}
return LedgerSecret(std::move(decrypted_ls));
}
};
}

Просмотреть файл

@ -293,6 +293,14 @@ class Consortium:
response = self.propose(member_id, remote_node, script, sealed_secrets)
self.vote_using_majority(remote_node, response.result["proposal_id"])
def accept_recovery_with_shares(self, member_id, remote_node):
script = """
tables = ...
return Calls:call("accept_recovery_with_shares")
"""
response = self.propose(member_id, remote_node, script)
self.vote_using_majority(remote_node, response.result["proposal_id"])
def store_current_network_encryption_key(self):
cmd = [
"cp",
@ -301,7 +309,7 @@ class Consortium:
]
infra.proc.ccall(*cmd).check_returncode()
def get_and_decrypt_shares(self, remote_node):
def get_decrypt_and_submit_shares(self, remote_node):
for m in self.members:
with remote_node.member_client(member_id=m) as mc:
r = mc.rpc("getEncryptedRecoveryShare", params={})
@ -319,6 +327,7 @@ class Consortium:
r = mc.rpc(
"submitRecoveryShare", params={"share": list(decrypted_share)}
)
assert r.error is None, f"Error submitting recovery share: {r.error}"
if m == 2:
assert (
r.result == True

Просмотреть файл

@ -43,12 +43,16 @@ def test(network, args, use_shares=False):
recovered_network.wait_for_all_nodes_to_be_trusted()
if use_shares:
recovered_network.consortium.get_and_decrypt_shares(remote_node=primary)
LOG.info("Members vote to complete the recovery")
recovered_network.consortium.accept_recovery(
member_id=1, remote_node=primary, sealed_secrets=sealed_secrets
)
LOG.warning("Retrieve and submit recovery shares")
recovered_network.consortium.accept_recovery_with_shares(
member_id=1, remote_node=primary
)
recovered_network.consortium.get_decrypt_and_submit_shares(remote_node=primary)
else:
LOG.info("Members vote to complete the recovery")
recovered_network.consortium.accept_recovery(
member_id=1, remote_node=primary, sealed_secrets=sealed_secrets
)
for node in recovered_network.nodes:
recovered_network.wait_for_state(node, "partOfNetwork")