зеркало из https://github.com/microsoft/CCF.git
Secret Sharing C++ API and first integration in CCF (#846)
This commit is contained in:
Родитель
8c560a2ce3
Коммит
4dfe62e589
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче