From 4dfe62e589a073bd3cbf1e9433f1194e3ed6b480 Mon Sep 17 00:00:00 2001 From: Julien Maffre <42961061+jumaffre@users.noreply.github.com> Date: Tue, 18 Feb 2020 09:28:05 +0000 Subject: [PATCH] Secret Sharing C++ API and first integration in CCF (#846) --- CMakeLists.txt | 6 ++ src/crypto/symmkey.h | 6 +- src/node/entities.h | 1 + src/node/genesisgen.h | 25 +++++ src/node/ledgersecrets.h | 3 +- src/node/members.h | 2 +- src/node/networktables.h | 4 + src/node/nodestate.h | 192 +++++++++++++++++++++------------- src/node/rpc/memberfrontend.h | 2 + src/node/rpc/nodecalltypes.h | 1 + src/node/rpc/serialization.h | 3 +- src/node/secretshare.h | 84 +++++++++++++++ src/node/shares.h | 27 +++++ src/node/test/secretshare.cpp | 58 ++++++++++ src/node/values.h | 1 + 15 files changed, 338 insertions(+), 77 deletions(-) create mode 100644 src/node/secretshare.h create mode 100644 src/node/shares.h create mode 100644 src/node/test/secretshare.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d937b01f7..c91078443c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/crypto/symmkey.h b/src/crypto/symmkey.h index 970acd175e..9734d4a0e3 100644 --- a/src/crypto/symmkey.h +++ b/src/crypto/symmkey.h @@ -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 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 diff --git a/src/node/entities.h b/src/node/entities.h index 71a2f746d8..caf3df5fad 100644 --- a/src/node/entities.h +++ b/src/node/entities.h @@ -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; diff --git a/src/node/genesisgen.h b/src/node/genesisgen.h index 9108d92eb5..5007543039 100644 --- a/src/node/genesisgen.h +++ b/src/node/genesisgen.h @@ -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> 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); + } }; } \ No newline at end of file diff --git a/src/node/ledgersecrets.h b/src/node/ledgersecrets.h index dc7bdb1fd7..12f42d91c8 100644 --- a/src/node/ledgersecrets.h +++ b/src/node/ledgersecrets.h @@ -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); } } diff --git a/src/node/members.h b/src/node/members.h index 2fcba662a8..44bf89eb65 100644 --- a/src/node/members.h +++ b/src/node/members.h @@ -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" diff --git a/src/node/networktables.h b/src/node/networktables.h index 14cc8134e6..7b36690614 100644 --- a/src/node/networktables.h +++ b/src/node/networktables.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(Tables::MEMBER_CLIENT_SIGNATURES)), + shares( + tables->create(Tables::SHARES, kv::SecurityDomain::PUBLIC)), users(tables->create(Tables::USERS)), user_certs(tables->create(Tables::USER_CERTS)), user_client_signatures( diff --git a/src/node/nodestate.h b/src/node/nodestate.h index ef6ef95328..93235dafeb 100644 --- a/src/node/nodestate.h +++ b/src/node/nodestate.h @@ -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 #include #include -extern "C" -{ -#include "tls/randombytes.h" - -#include -} - #include #include #include @@ -252,69 +246,6 @@ namespace ccf sm.advance(State::initialized); } - std::vector serialize_create_request( - const CreateNew::In& args, const std::vector& quote) - { - jsonrpc::ProcedureCall 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(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& 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& quote) - { - send_create_request(serialize_create_request(args, quote)); - return true; - } - // // funcs in state "initialized" // @@ -343,6 +274,9 @@ namespace ccf std::make_unique("CN=CCF Network"); network.ledger_secrets = std::make_shared(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( "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& 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> encrypted_shares; + for (auto const& s : shares) + { + encrypted_shares.emplace_back(s.begin(), s.end()); + } + + return {encrypted_ls.serialise(), encrypted_shares}; + } + + std::vector serialize_create_request( + const CreateNew::In& args, + const std::vector& quote, + const KeyShareInfo& genesis_key_share_info) + { + jsonrpc::ProcedureCall 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(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& 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& 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()) diff --git a/src/node/rpc/memberfrontend.h b/src/node/rpc/memberfrontend.h index ed9ac1c698..6dceeda0af 100644 --- a/src/node/rpc/memberfrontend.h +++ b/src/node/rpc/memberfrontend.h @@ -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; }; diff --git a/src/node/rpc/nodecalltypes.h b/src/node/rpc/nodecalltypes.h index 0aa7889b92..60fa15d9be 100644 --- a/src/node/rpc/nodecalltypes.h +++ b/src/node/rpc/nodecalltypes.h @@ -61,6 +61,7 @@ namespace ccf std::vector public_encryption_key; std::vector code_digest; NodeInfoNetwork node_info_network; + KeyShareInfo genesis_key_share_info; }; }; diff --git a/src/node/rpc/serialization.h b/src/node/rpc/serialization.h index 56d50acc28..8deff0ca48 100644 --- a/src/node/rpc/serialization.h +++ b/src/node/rpc/serialization.h @@ -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) diff --git a/src/node/secretshare.h b/src/node/secretshare.h new file mode 100644 index 0000000000..36ef6c20f3 --- /dev/null +++ b/src/node/secretshare.h @@ -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 +#include +#include +#include +#include + +extern "C" +{ +#include "tls/randombytes.h" + +#include +} + +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; + using SecretToSplit = std::array; + + static std::vector 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 shares(n); + + sss_create_shares( + reinterpret_cast(shares.data()), + secret_to_share.data(), + n, + k); + + return shares; + } + + static SecretToSplit combine(const std::vector& 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; + } + }; +} \ No newline at end of file diff --git a/src/node/shares.h b/src/node/shares.h new file mode 100644 index 0000000000..a7968c5bac --- /dev/null +++ b/src/node/shares.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include "entities.h" + +#include +#include + +namespace ccf +{ + using KeyShareIndex = ObjectId; + + struct KeyShareInfo + { + std::vector encrypted_ledger_secret; + std::vector> 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; +} \ No newline at end of file diff --git a/src/node/test/secretshare.cpp b/src/node/test/secretshare.cpp new file mode 100644 index 0000000000..bc17cfa3e2 --- /dev/null +++ b/src/node/test/secretshare.cpp @@ -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 +#include + +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); + } +} \ No newline at end of file diff --git a/src/node/values.h b/src/node/values.h index bd84b75dca..f5aa1451a7 100644 --- a/src/node/values.h +++ b/src/node/values.h @@ -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 };