Secret Sharing C++ API and first integration in CCF (#846)

This commit is contained in:
Julien Maffre 2020-02-18 09:28:05 +00:00 коммит произвёл GitHub
Родитель 8c560a2ce3
Коммит 4dfe62e589
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 338 добавлений и 77 удалений

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

@ -300,6 +300,12 @@ if(BUILD_TESTS)
history_test PRIVATE ${CRYPTO_LIBRARY} evercrypt.host secp256k1.host
)
add_unit_test(
secretsharing_test
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/secretshare.cpp
)
target_link_libraries(secretsharing_test PRIVATE sss.host)
add_unit_test(
encryptor_test ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/encryptor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/symmkey.cpp

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

@ -9,15 +9,15 @@ struct mbedtls_gcm_context;
namespace crypto
{
constexpr size_t GCM_SIZE_KEY = 32;
constexpr size_t GCM_SIZE_TAG = 16;
constexpr size_t GCM_SIZE_KEY = 16;
constexpr size_t GCM_SIZE_IV = 12;
template <size_t SIZE_IV = GCM_SIZE_IV>
struct GcmHeader
{
uint8_t tag[GCM_SIZE_TAG];
uint8_t iv[SIZE_IV];
uint8_t tag[GCM_SIZE_TAG] = {};
uint8_t iv[SIZE_IV] = {};
// 12 bytes IV with 8 LSB are unique sequence number
// and 4 MSB are 4 LSB of NodeId

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

@ -60,6 +60,7 @@ namespace ccf
static constexpr auto CODE_IDS = "ccf.code_ids";
static constexpr auto VOTING_HISTORY = "ccf.voting_history";
static constexpr auto SERVICE = "ccf.service";
static constexpr auto SHARES = "ccf.shares";
};
using StoreSerialiser = kv::KvStoreSerialiser;

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

@ -251,5 +251,30 @@ namespace ccf
auto codeid_view = tx.get_view(tables.code_ids);
codeid_view->put(node_code_id, CodeStatus::ACCEPTED);
}
auto get_active_members_keyshare()
{
auto members_view = tx.get_view(tables.members);
std::map<MemberId, std::vector<uint8_t>> active_members_info;
members_view->foreach(
[&active_members_info](const MemberId& mid, const MemberInfo& mi) {
if (mi.status != MemberStatus::ACTIVE)
{
active_members_info[mid] = mi.keyshare;
}
return true;
});
return active_members_info;
}
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);
}
};
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 License.
#pragma once
#include "crypto/symmkey.h"
#include "ds/logger.h"
#include "kv/kvtypes.h"
#include "tls/entropy.h"
@ -34,7 +35,7 @@ namespace ccf
{
if (random)
{
master = tls::create_entropy()->random(32);
master = tls::create_entropy()->random(crypto::GCM_SIZE_KEY);
}
}

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

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "../ds/hash.h"
#include "ds/hash.h"
#include "entities.h"
#include "rawsignature.h"
#include "rpc/jsonrpc.h"

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

@ -16,6 +16,7 @@
#include "scripts.h"
#include "secrets.h"
#include "service.h"
#include "shares.h"
#include "signatures.h"
#include "users.h"
#include "values.h"
@ -45,6 +46,7 @@ namespace ccf
MemberAcks& member_acks;
VotingHistoryTable& voting_history;
ClientSignatures& member_client_signatures;
Shares& shares;
//
// User tables
@ -104,6 +106,8 @@ namespace ccf
Tables::VOTING_HISTORY, kv::SecurityDomain::PUBLIC)),
member_client_signatures(
tables->create<ClientSignatures>(Tables::MEMBER_CLIENT_SIGNATURES)),
shares(
tables->create<Shares>(Tables::SHARES, kv::SecurityDomain::PUBLIC)),
users(tables->create<Users>(Tables::USERS)),
user_certs(tables->create<Certs>(Tables::USER_CERTS)),
user_client_signatures(

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

@ -19,6 +19,7 @@
#include "rpc/memberfrontend.h"
#include "rpc/serialization.h"
#include "seal.h"
#include "secretshare.h"
#include "timer.h"
#include "tls/client.h"
#include "tls/entropy.h"
@ -35,13 +36,6 @@
#include <chrono>
#include <fmt/format_header_only.h>
#include <nlohmann/json.hpp>
extern "C"
{
#include "tls/randombytes.h"
#include <sss/sss.h>
}
#include <stdexcept>
#include <unordered_set>
#include <vector>
@ -252,69 +246,6 @@ namespace ccf
sm.advance(State::initialized);
}
std::vector<uint8_t> serialize_create_request(
const CreateNew::In& args, const std::vector<uint8_t>& quote)
{
jsonrpc::ProcedureCall<CreateNetworkNodeToNode::In> create_rpc;
create_rpc.id = 0;
create_rpc.method = ccf::MemberProcs::CREATE;
for (auto& m_info : args.config.genesis.members_info)
{
create_rpc.params.members_info.push_back(m_info);
}
create_rpc.params.gov_script = args.config.genesis.gov_script;
create_rpc.params.node_cert = node_cert;
create_rpc.params.network_cert = network.identity->cert;
create_rpc.params.quote = quote;
create_rpc.params.public_encryption_key =
node_encrypt_kp->public_key_pem().raw();
create_rpc.params.code_digest =
std::vector<uint8_t>(std::begin(node_code_id), std::end(node_code_id));
create_rpc.params.node_info_network = args.config.node_info_network;
nlohmann::json j = create_rpc;
auto contents = nlohmann::json::to_msgpack(j);
auto sig_contents = node_sign_kp->sign(contents);
nlohmann::json sj;
sj["req"] = j;
sj["sig"] = sig_contents;
return jsonrpc::pack(sj, jsonrpc::Pack::Text);
}
void send_create_request(const std::vector<uint8_t>& packed)
{
constexpr auto actor = ccf::ActorsType::members;
auto handler = this->rpc_map->find(actor);
if (!handler.has_value())
{
throw std::logic_error("Handler has no value");
}
auto frontend = handler.value();
const enclave::SessionContext node_session(
enclave::InvalidSessionId, node_cert);
auto ctx = enclave::make_rpc_context(node_session, packed);
ctx->is_create_request = true;
ctx->actor = actor;
frontend->process(ctx);
}
bool create_and_send_request(
const CreateNew::In& args, const std::vector<uint8_t>& quote)
{
send_create_request(serialize_create_request(args, quote));
return true;
}
//
// funcs in state "initialized"
//
@ -343,6 +274,9 @@ namespace ccf
std::make_unique<NetworkIdentity>("CN=CCF Network");
network.ledger_secrets = std::make_shared<LedgerSecrets>(seal);
auto key_share_info =
generate_key_share_info(args.config.genesis.members_info);
self = 0; // The first node id is always 0
#ifdef PBFT
@ -360,7 +294,7 @@ namespace ccf
// network
open_member_frontend();
if (!create_and_send_request(args, quote))
if (!create_and_send_request(args, quote, key_share_info))
{
return Fail<CreateNew::Out>(
"Genesis transaction could not be committed");
@ -1162,6 +1096,122 @@ namespace ccf
secrets_view->put(version, secret_set);
}
KeyShareInfo generate_key_share_info(
const std::vector<ccf::MemberPubInfo>& members_info)
{
auto share_wrapping_key_raw =
tls::create_entropy()->random(crypto::GCM_SIZE_KEY);
auto share_wrapping_key = crypto::KeyAesGcm(share_wrapping_key_raw);
// Encrypt initial ledger secrets with k_z
// Once sealing is completely removed, this can be called to the
// LedgerSecrets class directly
crypto::GcmCipher encrypted_ls(
network.ledger_secrets->get_secret(1)->master.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);
auto active_members = members_info.size();
// For now, the secret sharing threshold is set to the number of initial
// members
size_t threshold = active_members;
// For now, since members key shares are not yet encrypted, create
// share of dummy empty secret
SecretSharing::SecretToSplit secret_to_split = {};
auto shares =
SecretSharing::split(secret_to_split, active_members, threshold);
assert(SecretSharing::combine(shares, threshold) == secret_to_split);
// For now, shares are recorded in plain text. Instead, they should be
// encrypted with each member's public encryption key
std::vector<std::vector<uint8_t>> encrypted_shares;
for (auto const& s : shares)
{
encrypted_shares.emplace_back(s.begin(), s.end());
}
return {encrypted_ls.serialise(), encrypted_shares};
}
std::vector<uint8_t> serialize_create_request(
const CreateNew::In& args,
const std::vector<uint8_t>& quote,
const KeyShareInfo& genesis_key_share_info)
{
jsonrpc::ProcedureCall<CreateNetworkNodeToNode::In> create_rpc;
create_rpc.id = 0;
create_rpc.method = ccf::MemberProcs::CREATE;
for (auto& m_info : args.config.genesis.members_info)
{
create_rpc.params.members_info.push_back(m_info);
}
create_rpc.params.gov_script = args.config.genesis.gov_script;
create_rpc.params.node_cert = node_cert;
create_rpc.params.network_cert = network.identity->cert;
create_rpc.params.quote = quote;
create_rpc.params.public_encryption_key =
node_encrypt_kp->public_key_pem().raw();
create_rpc.params.code_digest =
std::vector<uint8_t>(node_code_id.begin(), node_code_id.end());
create_rpc.params.node_info_network = args.config.node_info_network;
create_rpc.params.genesis_key_share_info = genesis_key_share_info;
nlohmann::json j = create_rpc;
auto contents = nlohmann::json::to_msgpack(j);
auto sig_contents = node_sign_kp->sign(contents);
nlohmann::json sj;
sj["req"] = j;
sj["sig"] = sig_contents;
return jsonrpc::pack(sj, jsonrpc::Pack::Text);
}
void send_create_request(const std::vector<uint8_t>& packed)
{
constexpr auto actor = ccf::ActorsType::members;
auto handler = this->rpc_map->find(actor);
if (!handler.has_value())
{
throw std::logic_error("Handler has no value");
}
auto frontend = handler.value();
const enclave::SessionContext node_session(
enclave::InvalidSessionId, node_cert);
auto ctx = enclave::make_rpc_context(node_session, packed);
ctx->is_create_request = true;
ctx->actor = actor;
frontend->process(ctx);
}
bool create_and_send_request(
const CreateNew::In& args,
const std::vector<uint8_t>& quote,
const KeyShareInfo& genesis_key_share_info)
{
send_create_request(
serialize_create_request(args, quote, genesis_key_share_info));
return true;
}
void backup_finish_recovery()
{
if (!consensus->is_backup())

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

@ -567,6 +567,8 @@ namespace ccf
g.create_service(in.network_cert);
g.add_key_share_info(in.genesis_key_share_info);
args.rpc_ctx->set_response_result(true);
return;
};

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

@ -61,6 +61,7 @@ namespace ccf
std::vector<uint8_t> public_encryption_key;
std::vector<uint8_t> code_digest;
NodeInfoNetwork node_info_network;
KeyShareInfo genesis_key_share_info;
};
};

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

@ -51,7 +51,8 @@ namespace ccf
quote,
public_encryption_key,
code_digest,
node_info_network)
node_info_network,
genesis_key_share_info)
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(GetCommit::In)
DECLARE_JSON_REQUIRED_FIELDS(GetCommit::In)

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

@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "tls/entropy.h"
#include <array>
#include <fmt/format_header_only.h>
#include <iostream>
#include <optional>
#include <vector>
extern "C"
{
#include "tls/randombytes.h"
#include <sss/sss.h>
}
namespace ccf
{
// The SecretSharing class provides static functions to split a secret into
// shares and (re-)combine those shares into the original secret.
// The size of the secret to share is fixed (SECRET_TO_SHARE_LENGTH, 64
// bytes). It is up to the caller to either shrink the secret if it is too
// long. If the secret to split is shorter than SECRET_TO_SHARE_LENGTH bytes,
// the caller should ignore the extra bytes.
class SecretSharing
{
public:
static constexpr size_t SECRET_TO_SHARE_LENGTH = sss_MLEN;
static constexpr size_t SHARE_LENGTH = sss_SHARE_LEN;
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_SHARE_LENGTH>;
static std::vector<Share> split(
const SecretToSplit& secret_to_share, size_t n, size_t k)
{
if (n == 0 || n > MAX_NUMBER_SHARES)
{
throw std::logic_error(
fmt::format("n not in 1-{} range", MAX_NUMBER_SHARES));
}
if (k == 0 || k > n)
{
throw std::logic_error(fmt::format("k not in 1-n range (n: {})", n));
}
std::vector<Share> shares(n);
sss_create_shares(
reinterpret_cast<sss_Share*>(shares.data()),
secret_to_share.data(),
n,
k);
return shares;
}
static SecretToSplit combine(const std::vector<Share>& shares, size_t k)
{
if (k == 0 || k > shares.size())
{
throw std::logic_error(
fmt::format("k not in 1-n range (n: {})", shares.size()));
}
SecretToSplit restored_secret;
if (
sss_combine_shares(
restored_secret.data(), (sss_Share*)shares.data(), k) != 0)
{
throw std::logic_error(fmt::format(
"Share combination failed: {} shares may be corrupted", k));
}
return restored_secret;
}
};
}

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

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "entities.h"
#include <msgpack-c/msgpack.hpp>
#include <vector>
namespace ccf
{
using KeyShareIndex = ObjectId;
struct KeyShareInfo
{
std::vector<uint8_t> encrypted_ledger_secret;
std::vector<std::vector<uint8_t>> encrypted_shares;
MSGPACK_DEFINE(encrypted_ledger_secret, encrypted_shares);
};
DECLARE_JSON_TYPE(KeyShareInfo)
DECLARE_JSON_REQUIRED_FIELDS(
KeyShareInfo, encrypted_ledger_secret, encrypted_shares)
using Shares = Store::Map<KeyShareIndex, KeyShareInfo>;
}

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

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "../secretshare.h"
#include <doctest/doctest.h>
#include <iomanip>
TEST_CASE("Simple test")
{
size_t n = 5;
size_t k = 3;
ccf::SecretSharing::SecretToSplit data_to_split;
INFO("Data to split must be have fixed length");
{
auto random =
tls::create_entropy()->random(ccf::SecretSharing::SECRET_TO_SHARE_LENGTH);
std::copy_n(
random.begin(),
ccf::SecretSharing::SECRET_TO_SHARE_LENGTH,
data_to_split.begin());
}
INFO("Split and combine shares");
{
auto shares = ccf::SecretSharing::split(data_to_split, n, k);
REQUIRE(shares.size() == n);
auto restored = ccf::SecretSharing::combine(shares, k);
REQUIRE(data_to_split == restored);
}
}
TEST_CASE("Edge cases")
{
size_t n = 3;
size_t k = 2;
ccf::SecretSharing::SecretToSplit data_to_split;
INFO("n = 0 and n too large");
{
REQUIRE_THROWS_AS(
ccf::SecretSharing::split(data_to_split, 0, 2), std::logic_error);
REQUIRE_THROWS_AS(
ccf::SecretSharing::split(
data_to_split, ccf::SecretSharing::MAX_NUMBER_SHARES + 1, k),
std::logic_error);
}
INFO("k = 0 and k too large");
{
REQUIRE_THROWS_AS(
ccf::SecretSharing::split(data_to_split, n, 0), std::logic_error);
REQUIRE_THROWS_AS(
ccf::SecretSharing::split(data_to_split, n, n + 1), std::logic_error);
}
}

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

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