зеркало из https://github.com/microsoft/CCF.git
Members key share retrieval, decryption and submission (#932)
This commit is contained in:
Родитель
66dc54915f
Коммит
7cb881bf7a
|
@ -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
|
||||
};
|
||||
|
|
214
src/tls/25519.h
214
src/tls/25519.h
|
@ -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
|
Загрузка…
Ссылка в новой задаче