Members key share retrieval, decryption and submission (#932)

This commit is contained in:
Julien Maffre 2020-03-11 08:39:20 +00:00 коммит произвёл GitHub
Родитель 66dc54915f
Коммит 7cb881bf7a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
37 изменённых файлов: 649 добавлений и 134 удалений

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

@ -337,7 +337,7 @@ if(BUILD_TESTS)
)
target_link_libraries(
frontend_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} evercrypt.host lua.host
secp256k1.host http_parser.host
secp256k1.host http_parser.host sss.host
)
add_unit_test(
@ -346,7 +346,7 @@ if(BUILD_TESTS)
)
target_link_libraries(
membervoting_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} evercrypt.host lua.host
secp256k1.host http_parser.host
secp256k1.host http_parser.host sss.host
)
add_unit_test(
@ -482,7 +482,14 @@ if(BUILD_TESTS)
NAME recovery_tests
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/recovery.py
CONSENSUS raft
ADDITIONAL_ARGS ${RECOVERY_ARGS}
ADDITIONAL_ARGS --recovery 2
)
add_e2e_test(
NAME recovery_share_tests
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/recovery.py
CONSENSUS raft
ADDITIONAL_ARGS --recovery 1 --use-shares
)
add_e2e_test(
@ -606,8 +613,4 @@ if(BUILD_TESTS)
--repetitions
1000
)
if(EXTENSIVE_TESTS)
set_tests_properties(recovery_tests PROPERTIES TIMEOUT 2000)
endif()
endif()

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

@ -175,14 +175,6 @@ else()
set(TEST_ENCLAVE_TYPE -e virtual)
endif()
# Test-only option to enable extensive tests
option(EXTENSIVE_TESTS "Enable extensive tests" OFF)
if(EXTENSIVE_TESTS)
set(RECOVERY_ARGS --recovery 5 --msgs-per-recovery 10)
else()
set(RECOVERY_ARGS --recovery 2 --msgs-per-recovery 5)
endif()
# Lua module
set(LUA_DIR ${CCF_DIR}/3rdparty/lua)
set(LUA_SOURCES

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

@ -18,6 +18,9 @@ enclave {
[out, count=network_cert_size] uint8_t * network_cert,
size_t network_cert_size,
[out] size_t* network_cert_len,
[out, count=network_enc_pubk_size] uint8_t * network_enc_pubk,
size_t network_enc_pubk_size,
[out] size_t* network_enc_pubk_len,
StartType start_type,
ConsensusType consensus_type,
size_t num_worker_thread

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

@ -49,3 +49,18 @@ withdraw
.. jsonschema:: ../schemas/withdraw_params.json
.. jsonschema:: ../schemas/withdraw_result.json
getEncryptedRecoveryShare
-------------------------
.. warning:: Experimental
.. jsonschema:: ../schemas/getEncryptedRecoveryShare_result.json
submitRecoveryShare
-------------------
.. warning:: Experimental
.. jsonschema:: ../schemas/submitRecoveryShare_params.json
.. jsonschema:: ../schemas/submitRecoveryShare_result.json

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

@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"encrypted_share": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "number"
},
"type": "array"
},
"nonce": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "number"
},
"type": "array"
}
},
"required": [
"nonce",
"encrypted_share"
],
"title": "getEncryptedRecoveryShare/result",
"type": "object"
}

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

@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"share": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "number"
},
"type": "array"
}
},
"required": [
"share"
],
"title": "submitRecoveryShare/params",
"type": "object"
}

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

@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "submitRecoveryShare/result",
"type": "boolean"
}

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

@ -74,6 +74,9 @@ extern "C"
uint8_t*,
size_t,
size_t*,
uint8_t*,
size_t,
size_t*,
StartType,
ConsensusType,
size_t);
@ -113,6 +116,9 @@ extern "C"
uint8_t* network_cert,
size_t network_cert_size,
size_t* network_cert_len,
uint8_t* network_enc_pubk,
size_t network_enc_pubk_size,
size_t* network_enc_pubk_len,
StartType start_type,
ConsensusType consensus_type,
size_t num_worker_thread)
@ -130,6 +136,9 @@ extern "C"
network_cert,
network_cert_size,
network_cert_len,
network_enc_pubk,
network_enc_pubk_size,
network_enc_pubk_len,
start_type,
consensus_type,
num_worker_thread);

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

@ -89,7 +89,10 @@ namespace enclave
size_t* node_cert_len,
uint8_t* network_cert,
size_t network_cert_size,
size_t* network_cert_len)
size_t* network_cert_len,
uint8_t* network_enc_pubk,
size_t network_enc_pubk_size,
size_t* network_enc_pubk_len)
{
// node_cert_size and network_cert_size are ignored here, but we pass them
// in because it allows us to set EDL an annotation so that node_cert_len
@ -131,6 +134,20 @@ namespace enclave
r.first.network_cert.data(),
r.first.network_cert.size());
*network_cert_len = r.first.network_cert.size();
if (r.first.network_enc_pubk.size() > network_enc_pubk_size)
{
LOG_FAIL_FMT(
"Insufficient space ({}) to copy network enc pubk out ({})",
network_enc_pubk_size,
r.first.network_enc_pubk.size());
return false;
}
::memcpy(
network_enc_pubk,
r.first.network_enc_pubk.data(),
r.first.network_enc_pubk.size());
*network_enc_pubk_len = r.first.network_enc_pubk.size();
}
return true;

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

@ -31,6 +31,9 @@ extern "C"
uint8_t* network_cert,
size_t network_cert_size,
size_t* network_cert_len,
uint8_t* network_enc_pubk,
size_t network_enc_pubk_size,
size_t* network_enc_pubk_len,
StartType start_type,
ConsensusType consensus_type,
size_t num_worker_threads)
@ -73,7 +76,10 @@ extern "C"
node_cert_len,
network_cert,
network_cert_size,
network_cert_len);
network_cert_len,
network_enc_pubk,
network_enc_pubk_size,
network_enc_pubk_len);
e.store(enclave);
return result;

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

@ -73,6 +73,7 @@ namespace host
const CCFConfig& ccf_config,
std::vector<uint8_t>& node_cert,
std::vector<uint8_t>& network_cert,
std::vector<uint8_t>& network_enc_pubk,
StartType start_type,
ConsensusType consensus_type,
size_t num_worker_thread)
@ -80,6 +81,7 @@ namespace host
bool ret;
size_t node_cert_len = 0;
size_t network_cert_len = 0;
size_t network_enc_pubk_len = 0;
msgpack::sbuffer sbuf;
msgpack::pack(sbuf, ccf_config);
@ -96,6 +98,9 @@ namespace host
network_cert.data(),
network_cert.size(),
&network_cert_len,
network_enc_pubk.data(),
network_enc_pubk.size(),
&network_enc_pubk_len,
start_type,
consensus_type,
num_worker_thread);
@ -113,6 +118,7 @@ namespace host
node_cert.resize(node_cert_len);
network_cert.resize(network_cert_len);
network_enc_pubk.resize(network_enc_pubk_len);
return ret;
}

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

@ -228,13 +228,22 @@ int main(int argc, char** argv)
// The network certificate file can either be an input or output parameter,
// depending on the subcommand.
std::string network_cert_file = "networkcert.pem";
std::string network_enc_pubk_file = "network_enc_pubk.pem";
auto start = app.add_subcommand("start", "Start new network");
start
->add_option(
"--network-cert-file",
network_cert_file,
"Destination path where fresh network certificate will be created",
"Destination path to freshly created network certificate",
true)
->check(CLI::NonexistentPath);
start
->add_option(
"--network-enc-pubk-file",
network_enc_pubk_file,
"Destination path to freshly created network encryption public key",
true)
->check(CLI::NonexistentPath);
@ -291,6 +300,14 @@ int main(int argc, char** argv)
true)
->check(CLI::NonexistentPath);
recover
->add_option(
"--network-enc-pubk-file",
network_enc_pubk_file,
"Destination path to freshly created network encryption public key",
true)
->check(CLI::NonexistentPath);
CLI11_PARSE(app, argc, argv);
if (!(*public_rpc_address_option))
@ -365,8 +382,10 @@ int main(int argc, char** argv)
// Initialise the enclave and create a CCF node in it
const size_t certificate_size = 4096;
const size_t pubk_size = 1024;
std::vector<uint8_t> node_cert(certificate_size);
std::vector<uint8_t> network_cert(certificate_size);
std::vector<uint8_t> network_enc_pubk(certificate_size);
StartType start_type;
ConsensusType consensus_type;
@ -437,6 +456,7 @@ int main(int argc, char** argv)
ccf_config,
node_cert,
network_cert,
network_enc_pubk,
start_type,
consensus_type,
num_worker_threads);
@ -465,6 +485,7 @@ int main(int argc, char** argv)
if (*start || *recover)
{
files::dump(network_cert, network_cert_file);
files::dump(network_enc_pubk, network_enc_pubk_file);
}
auto enclave_thread_start = [&]() {

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

@ -3,14 +3,12 @@
#define PICOBENCH_IMPLEMENT_WITH_MAIN
#include "consensus/test/stub_consensus.h"
#include "enclave/appinterface.h"
#include "kv/kv.h"
#include "node/encryptor.h"
#include <picobench/picobench.hpp>
#include <string>
using namespace ccfapp;
using namespace ccf;
// Helper functions to use a dummy encryption key

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

@ -16,6 +16,7 @@ namespace ccf
{
std::vector<uint8_t> node_cert;
std::vector<uint8_t> network_cert;
std::vector<uint8_t> network_enc_pubk;
};
};

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

@ -263,6 +263,23 @@ namespace ccf
codeid_view->put(node_code_id, CodeStatus::ACCEPTED);
}
size_t get_active_members_count()
{
auto members_view = tx.get_view(tables.members);
size_t active_members_count = 0;
members_view->foreach(
[&active_members_count](const MemberId& mid, const MemberInfo& mi) {
if (mi.status == MemberStatus::ACTIVE)
{
active_members_count++;
}
return true;
});
return active_members_count;
}
auto get_active_members_keyshare()
{
auto members_view = tx.get_view(tables.members);
@ -281,11 +298,8 @@ namespace ccf
void add_key_share_info(const KeyShareInfo& key_share_info)
{
auto [shares_view, values_view] =
tx.get_view(tables.shares, tables.values);
auto keyshare_id = get_next_id(values_view, ValueIds::NEXT_KEYSHARE_ID);
shares_view->put(keyshare_id, key_share_info);
auto shares_view = tx.get_view(tables.shares);
shares_view->put(0, key_share_info);
}
};
}

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

@ -22,8 +22,26 @@ 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;
std::vector<uint8_t> master; // Referred to as "sd" in TR
bool operator==(const LedgerSecret& other) const
@ -35,7 +53,7 @@ namespace ccf
{
if (random)
{
master = tls::create_entropy()->random(crypto::GCM_SIZE_KEY);
master = tls::create_entropy()->random(MASTER_KEY_SIZE);
}
}

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

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "crypto/cryptobox.h"
#include "tls/25519.h"
#include "tls/entropy.h"
namespace ccf
{
struct NetworkEncryptionKey
{
private:
static constexpr auto KEY_SIZE = crypto::BoxKey::KEY_SIZE;
public:
std::vector<uint8_t> private_raw;
bool operator==(const NetworkEncryptionKey& other) const
{
return private_raw == other.private_raw;
}
NetworkEncryptionKey(bool random = false)
{
if (random)
{
private_raw = tls::create_entropy()->random(crypto::BoxKey::KEY_SIZE);
}
}
std::vector<uint8_t> get_public_pem()
{
return tls::PublicX25519::write(
crypto::BoxKey::public_from_private(private_raw))
.raw();
}
};
}

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

@ -4,6 +4,7 @@
#include "identity.h"
#include "ledgersecrets.h"
#include "networkencryption.h"
#include "networktables.h"
namespace ccf
@ -12,7 +13,7 @@ namespace ccf
{
std::unique_ptr<NetworkIdentity> identity;
std::shared_ptr<LedgerSecrets> ledger_secrets;
std::vector<uint8_t> encryption_priv_key;
std::unique_ptr<NetworkEncryptionKey> encryption_key;
// default set to Raft
ConsensusType consensus_type = ConsensusType::RAFT;

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

@ -268,8 +268,7 @@ namespace ccf
network.identity =
std::make_unique<NetworkIdentity>("CN=CCF Network");
network.ledger_secrets = std::make_shared<LedgerSecrets>(seal);
network.encryption_priv_key =
tls::create_entropy()->random(crypto::BoxKey::KEY_SIZE);
network.encryption_key = std::make_unique<NetworkEncryptionKey>(true);
self = 0; // The first node id is always 0
@ -295,7 +294,10 @@ namespace ccf
reset_quote();
sm.advance(State::partOfNetwork);
return Success<CreateNew::Out>({node_cert, network.identity->cert});
return Success<CreateNew::Out>(
{node_cert,
network.identity->cert,
network.encryption_key->get_public_pem()});
}
case StartType::Join:
{
@ -315,6 +317,7 @@ namespace ccf
std::make_unique<NetworkIdentity>("CN=CCF Network");
// Create temporary network secrets but do not seal yet
network.ledger_secrets = std::make_shared<LedgerSecrets>(seal, false);
network.encryption_key = std::make_unique<NetworkEncryptionKey>(true);
setup_history();
setup_encryptor(network.consensus_type);
@ -327,7 +330,10 @@ namespace ccf
sm.advance(State::readingPublicLedger);
return Success<CreateNew::Out>({node_cert, network.identity->cert});
return Success<CreateNew::Out>(
{node_cert,
network.identity->cert,
network.encryption_key->get_public_pem()});
}
default:
{
@ -398,7 +404,8 @@ namespace ccf
std::make_unique<NetworkIdentity>(resp.network_info.identity);
network.ledger_secrets = std::make_shared<LedgerSecrets>(
std::move(resp.network_info.ledger_secrets), seal);
network.encryption_priv_key = resp.network_info.encryption_priv_key;
network.encryption_key = std::make_unique<NetworkEncryptionKey>(
resp.network_info.encryption_key);
self = resp.node_id;
@ -1008,8 +1015,7 @@ namespace ccf
// Once sealing is completely removed, this can be called from the
// LedgerSecrets class directly
crypto::GcmCipher encrypted_ls(
network.ledger_secrets->get_secret(1)->master.size());
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
@ -1021,7 +1027,7 @@ namespace ccf
GenesisGenerator g(network, tx);
auto active_members = g.get_active_members_keyshare();
SecretSharing::SecretToSplit secret_to_split = {};
SecretSharing::SplitSecret secret_to_split = {};
std::copy_n(
share_wrapping_key_raw.begin(),
share_wrapping_key_raw.size(),
@ -1042,9 +1048,12 @@ namespace ccf
auto share_raw = std::vector<uint8_t>(
shares[share_index].begin(), shares[share_index].end());
auto enc_pub_key_raw = tls::parse_25519_public(tls::Pem(enc_pub_key));
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_priv_key);
share_raw,
nonce,
enc_pub_key_raw,
network.encryption_key->private_raw);
encrypted_shares[member_id] = {nonce, encrypted_share};
share_index++;

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

@ -34,6 +34,10 @@ namespace ccf
static constexpr auto ACK = "ack";
static constexpr auto UPDATE_ACK_STATE_DIGEST = "updateAckStateDigest";
static constexpr auto GET_ENCRYPTED_RECOVERY_SHARE =
"getEncryptedRecoveryShare";
static constexpr auto SUBMIT_RECOVERY_SHARE = "submitRecoveryShare";
};
struct NodeProcs

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

@ -7,6 +7,7 @@
#include "node/members.h"
#include "node/nodes.h"
#include "node/quoteverification.h"
#include "node/secretshare.h"
#include "tls/keypair.h"
#include <exception>
@ -27,6 +28,13 @@ namespace ccf
DECLARE_JSON_REQUIRED_FIELDS(SetUserData, user_id)
DECLARE_JSON_OPTIONAL_FIELDS(SetUserData, user_data)
struct SubmitRecoveryShare
{
std::vector<uint8_t> share;
};
DECLARE_JSON_TYPE(SubmitRecoveryShare)
DECLARE_JSON_REQUIRED_FIELDS(SubmitRecoveryShare, share)
class MemberHandlers : public CommonHandlerRegistry
{
private:
@ -411,6 +419,8 @@ namespace ccf
NetworkTables& network;
AbstractNodeState& node;
const lua::TxScriptRunner tsr;
// For now, shares are not stored in the KV
std::vector<SecretSharing::Share> pending_shares;
static constexpr auto SIZE_NONCE = 16;
@ -684,6 +694,113 @@ namespace ccf
json_adapter(update_state_digest),
Write);
auto get_encrypted_recovery_share =
[this](RequestArgs& args, const nlohmann::json& params) {
// This check should depend on whether new shares are emitted when a
// new member is added (status = Accepted) or when the new member acks
// (status = Active).
if (!check_member_active(args.tx, args.caller_id))
{
return make_error(HTTP_STATUS_FORBIDDEN, "Member is not active");
}
std::optional<EncryptedShare> enc_s;
auto current_keyshare =
args.tx.get_view(this->network.shares)->get(0);
for (auto const& s : current_keyshare->encrypted_shares)
{
if (s.first == args.caller_id)
{
enc_s = s.second;
}
}
if (!enc_s.has_value())
{
return make_error(
HTTP_STATUS_BAD_REQUEST,
fmt::format(
"Recovery share not found for member {}", args.caller_id));
}
return make_success(enc_s.value());
};
install_with_auto_schema<void, EncryptedShare>(
MemberProcs::GET_ENCRYPTED_RECOVERY_SHARE,
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");
}
const auto in = params.get<SubmitRecoveryShare>();
SecretSharing::Share share;
std::copy_n(
in.share.begin(), SecretSharing::SHARE_LENGTH, share.begin());
pending_shares.emplace_back(share);
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);
}
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");
}
pending_shares.clear();
return make_success(true);
};
install_with_auto_schema<SubmitRecoveryShare, bool>(
MemberProcs::SUBMIT_RECOVERY_SHARE,
json_adapter(submit_recovery_share),
Write);
auto create = [this](Store::Tx& tx, const nlohmann::json& params) {
LOG_DEBUG_FMT("Processing create RPC");
const auto in = params.get<CreateNetworkNodeToNode::In>();

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

@ -5,6 +5,7 @@
#include "node/identity.h"
#include "node/ledgersecrets.h"
#include "node/members.h"
#include "node/networkencryption.h"
#include "node/nodeinfonetwork.h"
#include <nlohmann/json.hpp>
@ -86,13 +87,13 @@ namespace ccf
{
LedgerSecrets ledger_secrets;
NetworkIdentity identity;
std::vector<uint8_t> encryption_priv_key;
NetworkEncryptionKey encryption_key;
bool operator==(const NetworkInfo& other) const
{
return ledger_secrets == other.ledger_secrets &&
identity == other.identity &&
encryption_priv_key == other.encryption_priv_key;
encryption_key == other.encryption_key;
}
bool operator!=(const NetworkInfo& other) const

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

@ -121,7 +121,7 @@ namespace ccf
this->network.consensus_type,
{*this->network.ledger_secrets.get(),
*this->network.identity.get(),
this->network.encryption_priv_key}}));
*this->network.encryption_key.get()}}));
}
else
{
@ -201,7 +201,7 @@ namespace ccf
this->network.consensus_type,
{*this->network.ledger_secrets.get(),
*this->network.identity.get(),
this->network.encryption_priv_key}}));
*this->network.encryption_key.get()}}));
}
return add_node(args.tx, caller_pem_raw, in, joining_node_status);
@ -227,7 +227,7 @@ namespace ccf
this->network.consensus_type,
{*this->network.ledger_secrets.get(),
*this->network.identity.get(),
this->network.encryption_priv_key}}));
*this->network.encryption_key.get()}}));
}
else if (node_status == NodeStatus::PENDING)
{

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

@ -37,12 +37,14 @@ namespace ccf
DECLARE_JSON_REQUIRED_FIELDS(LedgerSecret, master)
DECLARE_JSON_TYPE(LedgerSecrets)
DECLARE_JSON_REQUIRED_FIELDS(LedgerSecrets, secrets_map)
DECLARE_JSON_TYPE(NetworkEncryptionKey)
DECLARE_JSON_REQUIRED_FIELDS(NetworkEncryptionKey, private_raw)
DECLARE_JSON_TYPE(JoinNetworkNodeToNode::Out::NetworkInfo)
DECLARE_JSON_REQUIRED_FIELDS(
JoinNetworkNodeToNode::Out::NetworkInfo,
ledger_secrets,
identity,
encryption_priv_key)
encryption_key)
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(JoinNetworkNodeToNode::Out)
DECLARE_JSON_REQUIRED_FIELDS(
JoinNetworkNodeToNode::Out,

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

@ -23,7 +23,6 @@ extern "C"
#include <evercrypt/EverCrypt_AutoConfig2.h>
}
auto dummy_encryption_priv_key = std::vector<uint8_t>(crypto::BoxKey::KEY_SIZE);
using TResponse = http::SimpleResponseProcessor::Response;
void check_error(const TResponse& r, http_status expected)
@ -89,7 +88,7 @@ TEST_CASE("Add a node to an opening service")
network.ledger_secrets = std::make_shared<LedgerSecrets>();
network.ledger_secrets->set_secret(0, std::vector<uint8_t>(16, 0x42));
network.ledger_secrets->set_secret(10, std::vector<uint8_t>(16, 0x44));
network.encryption_priv_key = dummy_encryption_priv_key;
network.encryption_key = std::make_unique<NetworkEncryptionKey>();
// Node certificate
tls::KeyPairPtr kp = tls::make_key_pair();
@ -141,7 +140,7 @@ TEST_CASE("Add a node to an opening service")
response.network_info.ledger_secrets == *network.ledger_secrets.get());
CHECK(response.network_info.identity == *network.identity.get());
CHECK(
response.network_info.encryption_priv_key == dummy_encryption_priv_key);
response.network_info.encryption_key == *network.encryption_key.get());
CHECK(response.node_status == NodeStatus::TRUSTED);
CHECK(response.public_only == false);
@ -173,7 +172,7 @@ TEST_CASE("Add a node to an opening service")
response.network_info.ledger_secrets == *network.ledger_secrets.get());
CHECK(response.network_info.identity == *network.identity.get());
CHECK(
response.network_info.encryption_priv_key == dummy_encryption_priv_key);
response.network_info.encryption_key == *network.encryption_key.get());
CHECK(response.node_status == NodeStatus::TRUSTED);
}
@ -211,7 +210,7 @@ TEST_CASE("Add a node to an open service")
network.ledger_secrets = std::make_shared<LedgerSecrets>();
network.ledger_secrets->set_secret(0, std::vector<uint8_t>(16, 0x42));
network.ledger_secrets->set_secret(10, std::vector<uint8_t>(16, 0x44));
network.encryption_priv_key = dummy_encryption_priv_key;
network.encryption_key = std::make_unique<NetworkEncryptionKey>();
gen.create_service({});
gen.open_service();
@ -299,7 +298,7 @@ TEST_CASE("Add a node to an open service")
response.network_info.ledger_secrets == *network.ledger_secrets.get());
CHECK(response.network_info.identity == *network.identity.get());
CHECK(
response.network_info.encryption_priv_key == dummy_encryption_priv_key);
response.network_info.encryption_key == *network.encryption_key.get());
CHECK(response.node_status == NodeStatus::TRUSTED);
CHECK(response.public_only == true);
}

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

@ -33,10 +33,10 @@ namespace ccf
static constexpr size_t MAX_NUMBER_SHARES = 255; // As per sss documentation
using Share = std::array<uint8_t, SHARE_LENGTH>;
using SecretToSplit = std::array<uint8_t, SECRET_TO_SPLIT_LENGTH>;
using SplitSecret = std::array<uint8_t, SECRET_TO_SPLIT_LENGTH>;
static std::vector<Share> split(
const SecretToSplit& secret_to_share, size_t n, size_t k)
const SplitSecret& secret_to_split, size_t n, size_t k)
{
if (n == 0 || n > MAX_NUMBER_SHARES)
{
@ -53,14 +53,14 @@ namespace ccf
sss_create_shares(
reinterpret_cast<sss_Share*>(shares.data()),
secret_to_share.data(),
secret_to_split.data(),
n,
k);
return shares;
}
static SecretToSplit combine(const std::vector<Share>& shares, size_t k)
static SplitSecret combine(const std::vector<Share>& shares, size_t k)
{
if (k == 0 || k > shares.size())
{
@ -68,7 +68,7 @@ namespace ccf
fmt::format("k not in 1-n range (n: {})", shares.size()));
}
SecretToSplit restored_secret;
SplitSecret restored_secret;
if (
sss_combine_shares(

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

@ -20,15 +20,22 @@ namespace ccf
MSGPACK_DEFINE(nonce, encrypted_share);
};
DECLARE_JSON_TYPE(EncryptedShare)
DECLARE_JSON_REQUIRED_FIELDS(EncryptedShare, nonce, encrypted_share)
using EncryptedSharesMap = std::map<MemberId, EncryptedShare>;
struct KeyShareInfo
{
// For now, only one encrypted ledger secret is stored in the ledger
std::vector<uint8_t> encrypted_ledger_secret;
EncryptedSharesMap encrypted_shares;
MSGPACK_DEFINE(encrypted_ledger_secret, encrypted_shares);
};
using Shares = Store::Map<KeyShareIndex, KeyShareInfo>;
// The key for this table will always be 0 since we never need to access
// historical key shares info since all ledger secrets since the beginning of
// time are re-encrypted each time the service issues new shares.
using Shares = Store::Map<size_t, KeyShareInfo>;
}

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

@ -11,7 +11,7 @@ TEST_CASE("Simple test")
{
size_t n = 5;
size_t k = 3;
ccf::SecretSharing::SecretToSplit data_to_split;
ccf::SecretSharing::SplitSecret data_to_split;
INFO("Data to split must be have fixed length");
{
@ -36,7 +36,7 @@ TEST_CASE("Edge cases")
{
size_t n = 3;
size_t k = 2;
ccf::SecretSharing::SecretToSplit data_to_split;
ccf::SecretSharing::SplitSecret data_to_split;
INFO("n = 0 and n too large");
{

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

@ -18,7 +18,6 @@ namespace ccf
NEXT_NODE_ID = 2,
NEXT_PROPOSAL_ID = 3,
NEXT_CODE_ID = 4,
NEXT_KEYSHARE_ID = 5,
// not to be used
END_ID
};

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

@ -7,71 +7,185 @@
#include "tls.h"
#include <fmt/format_header_only.h>
#include <mbedtls/asn1write.h>
#include <mbedtls/pem.h>
namespace tls
{
// This function parses x25519 PEM keys generated by openssl (e.g. for
// members' public encryption key) and returns the raw 32-byte key.
// Because the mbedtls version shipped with Open Enclave does not (yet)
// support 25519 keys, we parse the key manually here.
static std::vector<uint8_t> parse_25519_public(const Pem& public_pem)
// This class parses and writes x25519 PEM keys following openssl
// SubjectPublicKeyInfo DER format, generated by keygenerator.sh (e.g. for
// members' public encryption key). Because the mbedtls version shipped with
// Open Enclave does not (yet) support x25519 keys, we parse the key manually
// here.
class PublicX25519
{
mbedtls_pem_context pem;
mbedtls_pem_init(&pem);
auto x25519_oid = std::vector<uint8_t>({0x2b, 0x65, 0x6e});
auto pem_len = public_pem.size();
private:
static constexpr auto PUBLIC_KEY_PEM_HEADER = "-----BEGIN PUBLIC KEY-----";
static constexpr auto PUBLIC_KEY_PEM_FOOTER = "-----END PUBLIC KEY-----";
static constexpr auto PUBLIC_KEY_PEM_HEADER_WRITE =
"-----BEGIN PUBLIC KEY-----\n";
static constexpr auto PUBLIC_KEY_PEM_FOOTER_WRITE =
"-----END PUBLIC KEY-----\n";
static constexpr auto max_25519_der_len = 64;
static constexpr auto max_25519_pem_len = 128;
static constexpr auto x25519_oid_len = 3;
static constexpr char x25519_oid[x25519_oid_len] = {0x2b, 0x65, 0x6e};
int rc = mbedtls_pem_read_buffer(
&pem,
"-----BEGIN PUBLIC KEY-----",
"-----END PUBLIC KEY-----",
public_pem.data(),
nullptr,
0,
&pem_len);
if (rc != 0)
static int parse_subject_public_key_info_der(
uint8_t** buf, size_t* len, mbedtls_asn1_buf* alg_oid)
{
throw std::logic_error(
fmt::format("mbedtls_pem_read_buffer failed: {}", error_string(rc)));
mbedtls_asn1_buf alg_params;
uint8_t* end = *buf + *len;
int ret = mbedtls_asn1_get_tag(
buf, end, len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
if (ret != 0)
{
return ret;
}
ret = mbedtls_asn1_get_alg(buf, end, alg_oid, &alg_params);
if (ret != 0)
{
return ret;
}
return mbedtls_asn1_get_bitstring_null(buf, end, len);
}
auto p = pem.buf;
auto len = pem.buflen;
auto end = pem.buf + pem.buflen;
rc = mbedtls_asn1_get_tag(
&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
if (rc != 0)
static int write_subject_public_key_info_der(
uint8_t* buf,
size_t size,
size_t* len,
const uint8_t* raw_public_key,
size_t raw_public_key_size_bytes)
{
throw std::logic_error(
fmt::format("mbedtls_asn1_get_tag failed: {}", error_string(rc)));
int ret = 0;
// mbedtls asn1 write API writes backward in pubk_buf
uint8_t* pc = buf + size;
MBEDTLS_ASN1_CHK_ADD(
*len,
mbedtls_asn1_write_bitstring(
&pc, buf, raw_public_key, raw_public_key_size_bytes * 8));
// mbedtls_asn1_write_algorithm_identifier() is not used here as openssl
// does not write algorithm parameters for x25519 at all if these are not
// set
auto pc_ = pc;
MBEDTLS_ASN1_CHK_ADD(
*len, mbedtls_asn1_write_oid(&pc, buf, x25519_oid, x25519_oid_len));
MBEDTLS_ASN1_CHK_ADD(*len, mbedtls_asn1_write_len(&pc, buf, pc_ - pc));
MBEDTLS_ASN1_CHK_ADD(
*len,
mbedtls_asn1_write_tag(
&pc, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));
MBEDTLS_ASN1_CHK_ADD(*len, mbedtls_asn1_write_len(&pc, buf, *len));
MBEDTLS_ASN1_CHK_ADD(
*len,
mbedtls_asn1_write_tag(
&pc, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));
return ret;
}
mbedtls_pk_type_t pk_alg = MBEDTLS_PK_NONE;
mbedtls_asn1_buf alg_oid;
mbedtls_asn1_buf alg_params;
rc = mbedtls_asn1_get_alg(&p, end, &alg_oid, &alg_params);
if (rc != 0)
public:
static std::vector<uint8_t> parse(const Pem& public_pem)
{
throw std::logic_error(
fmt::format("mbedtls_asn1_get_alg failed: {}", error_string(rc)));
auto pem_len = public_pem.size();
uint8_t* raw_public_key;
size_t raw_public_key_size;
mbedtls_pem_context pem;
try
{
mbedtls_pem_init(&pem);
int rc = mbedtls_pem_read_buffer(
&pem,
PUBLIC_KEY_PEM_HEADER,
PUBLIC_KEY_PEM_FOOTER,
public_pem.data(),
nullptr,
0,
&pem_len);
if (rc != 0)
{
throw std::logic_error(fmt::format(
"mbedtls_pem_read_buffer failed: {}", error_string(rc)));
}
raw_public_key = pem.buf;
raw_public_key_size = pem.buflen;
mbedtls_asn1_buf alg_oid;
rc = parse_subject_public_key_info_der(
&raw_public_key, &raw_public_key_size, &alg_oid);
if (rc != 0)
{
throw std::logic_error(fmt::format(
"Parsing public key info failed: {}", error_string(rc)));
}
if (memcmp(x25519_oid, alg_oid.p, x25519_oid_len) != 0)
{
throw std::logic_error(
"Parsing public key failed: Key is not x25519");
}
}
catch (const std::exception& e)
{
mbedtls_pem_free(&pem);
throw;
}
std::vector<uint8_t> public_raw(
raw_public_key, raw_public_key + raw_public_key_size);
mbedtls_pem_free(&pem);
return public_raw;
}
if (memcmp(x25519_oid.data(), alg_oid.p, 3) != 0)
static Pem write(const std::vector<uint8_t>& raw_public_key)
{
throw std::logic_error("parse_25519_public(): Key is not x25519");
std::vector<uint8_t> public_der(max_25519_der_len);
std::vector<uint8_t> public_pem(max_25519_pem_len);
size_t der_len = 0;
auto rc = write_subject_public_key_info_der(
public_der.data(),
public_der.size(),
&der_len,
raw_public_key.data(),
raw_public_key.size());
if (rc < 0)
{
throw std::logic_error(fmt::format(
"Error writing x25519 SubjectPublicKeyInfo DER {}",
error_string(rc)));
}
auto pem_len = public_pem.size();
rc = mbedtls_pem_write_buffer(
PUBLIC_KEY_PEM_HEADER_WRITE,
PUBLIC_KEY_PEM_FOOTER_WRITE,
public_der.data() + public_der.size() - der_len,
der_len,
public_pem.data(),
public_pem.size(),
&pem_len);
if (rc != 0)
{
throw std::logic_error(
fmt::format("mbedtls_pem_write_buffer failed: {}", error_string(rc)));
}
// mbedtls includes the terminating null, but tls::Pem provides this
// already
pem_len--;
return Pem({public_pem.data(), pem_len});
}
rc = mbedtls_asn1_get_bitstring_null(&p, end, &len);
if (rc != 0)
{
throw std::logic_error(
fmt::format("mbedtls_asn1_get_tag failed: {}", error_string(rc)));
}
std::vector<uint8_t> public_raw(p, end);
mbedtls_pem_free(&pem);
return public_raw;
}
};
}

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

@ -359,11 +359,15 @@ TEST_CASE("Parse public x25519 PEM")
auto x25519_public_key_pem = std::string(
"-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VuAyEAUgaVkiQ9K8UO3qEYD3C34vJT/CwiCr3AWnVn/1QMTl0=\n"
"-----END PUBLIC KEY-----");
"-----END PUBLIC KEY-----\n");
auto x25519_public_key =
tls::raw_from_b64("UgaVkiQ9K8UO3qEYD3C34vJT/CwiCr3AWnVn/1QMTl0=");
auto raw_key = tls::PublicX25519::parse(tls::Pem(x25519_public_key_pem));
REQUIRE(
tls::parse_25519_public(tls::Pem(x25519_public_key_pem)) ==
raw_key ==
std::vector<uint8_t>(x25519_public_key.begin(), x25519_public_key.end()));
REQUIRE(tls::PublicX25519::write(raw_key).str() == x25519_public_key_pem);
}

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

@ -247,7 +247,7 @@ class CurlClient:
raise CCFConnectionException(
f"Connection still failing after {self.connection_timeout}s: {e}"
)
LOG.error(f"Got SSLError exception: {e}")
LOG.warning(f"Got SSLError exception: {e}")
time.sleep(0.1)
def request(self, request):
@ -321,7 +321,7 @@ class RequestClient:
raise CCFConnectionException(
f"Connection still failing after {self.connection_timeout}s: {e}"
)
LOG.error(f"Got SSLError exception: {e}")
LOG.warning(f"Got SSLError exception: {e}")
time.sleep(0.1)
except requests.exceptions.ReadTimeout as e:
raise TimeoutError

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

@ -293,6 +293,41 @@ class Consortium:
response = self.propose(member_id, remote_node, script, sealed_secrets)
self.vote_using_majority(remote_node, response.result["proposal_id"])
def store_current_network_encryption_key(self):
cmd = [
"cp",
os.path.join(self.common_dir, f"network_enc_pubk.pem"),
os.path.join(self.common_dir, f"network_enc_pubk_orig.pem"),
]
infra.proc.ccall(*cmd).check_returncode()
def get_and_decrypt_shares(self, remote_node):
for m in self.members:
with remote_node.member_client(member_id=m) as mc:
r = mc.rpc("getEncryptedRecoveryShare", params={})
# For now, members rely on a copy of the original network encryption public key
ctx = infra.crypto.CryptoBoxCtx(
os.path.join(self.common_dir, f"member{m}_kshare_priv.pem"),
os.path.join(self.common_dir, f"network_enc_pubk_orig.pem"),
)
nonce_bytes = bytes(r.result["nonce"])
encrypted_share_bytes = bytes(r.result["encrypted_share"])
decrypted_share = ctx.decrypt(encrypted_share_bytes, nonce_bytes,)
r = mc.rpc(
"submitRecoveryShare", params={"share": list(decrypted_share)}
)
if m == 2:
assert (
r.result == True
), "Shares should be combined when all members have submitted their shares"
else:
assert (
r.result == False
), "Shares should not be combined until all members have submitted their shares"
def add_new_code(self, member_id, remote_node, new_code_id):
script = """
tables, code_digest = ...

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

@ -8,33 +8,47 @@ import coincurve
from coincurve._libsecp256k1 import ffi, lib
from coincurve.context import GLOBAL_CONTEXT
from nacl.public import PrivateKey, PublicKey, Box
from nacl.encoding import RawEncoder
from cryptography.x509 import load_der_x509_certificate, load_pem_x509_certificate
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import (
load_pem_private_key,
load_pem_public_key,
Encoding,
PrivateFormat,
PublicFormat,
NoEncryption,
)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
class Cert:
def __init__(self, cert_path):
with open(cert_path, "rb") as cert:
self.cert = load_pem_x509_certificate(
cert.read(), backend=default_backend()
)
def get_hash_alg(self):
return self.cert.signature_hash_algorithm
class PrivateKey:
def __init__(self, privk_path, password=None):
class CryptoBoxCtx:
def __init__(self, privk_path, pubk_path):
with open(privk_path, "rb") as privk:
self.privk = load_pem_private_key(
privk.read(), password=None, backend=default_backend()
self.privk = PrivateKey(
load_pem_private_key(
privk.read(), password=None, backend=default_backend(),
).private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption()),
RawEncoder,
)
def sign(self, bytes_to_sign, hash_alg):
return self.privk.sign(bytes_to_sign, ec.ECDSA(hash_alg))
with open(pubk_path, "rb") as pubk:
self.pubk = PublicKey(
load_pem_public_key(
pubk.read(), backend=default_backend(),
).public_bytes(Encoding.Raw, PublicFormat.Raw),
RawEncoder,
)
self.box = Box(self.privk, self.pubk)
def encrypt(self, plain, nonce):
return self.box.encrypt(plain, nonce)
def decrypt(self, cipher, nonce):
return self.box.decrypt(cipher, nonce)
# As per mbedtls md_type_t

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

@ -705,6 +705,7 @@ class CCFRemote(object):
self.remote.get(self.pem, dst_path)
if self.start_type in {StartType.new, StartType.recover}:
self.remote.get("networkcert.pem", dst_path)
self.remote.get("network_enc_pubk.pem", dst_path)
def debug_node_cmd(self):
return self.remote._dbg()

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

@ -18,7 +18,10 @@ from loguru import logger as LOG
@reqs.description("Recovering a network")
@reqs.recover(number_txs=2)
def test(network, args, txs=None):
def test(network, args, use_shares=False):
if use_shares:
LOG.warning("Using member key shares for recovery (experimental)")
primary, backups = network.find_nodes()
ledger = primary.get_ledger()
@ -39,6 +42,9 @@ def test(network, args, txs=None):
LOG.info("Members verify that the new nodes have joined the network")
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
@ -67,8 +73,11 @@ def run(args):
) as network:
network.start_and_join(args)
if args.use_shares:
network.consortium.store_current_network_encryption_key()
for recovery_idx in range(args.recovery):
recovered_network = test(network, args)
recovered_network = test(network, args, use_shares=args.use_shares)
network.stop_all_nodes()
network = recovered_network
@ -94,7 +103,13 @@ checked. Note that the key for each logging message is unique (per table).
type=int,
default=5,
)
parser.add_argument(
"--use-shares",
help="Use member key shares (experimental)",
action="store_true",
)
args = infra.e2e_args.cli_args(add)
args.package = "liblogging"
run(args)

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

@ -9,3 +9,4 @@ cimetrics>=0.2.1
requests-http-signature
flatbuffers
websocket-client
pynacl