Set keyId when signing HTTP requests from C++ (#2101)

This commit is contained in:
Eddy Ashton 2021-01-22 09:21:35 +00:00 коммит произвёл GitHub
Родитель 550fb77d6a
Коммит 941d945360
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 239 добавлений и 161 удалений

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

@ -294,7 +294,7 @@ add_executable(
use_client_mbedtls(scenario_perf_client)
target_link_libraries(
scenario_perf_client PRIVATE ${CMAKE_THREAD_LIBS_INIT} secp256k1.host
http_parser.host
http_parser.host ccfcrypto.host
)
install(TARGETS scenario_perf_client DESTINATION bin)

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

@ -14,7 +14,9 @@ add_client_exe(
small_bank_client
SRCS ${CMAKE_CURRENT_LIST_DIR}/clients/small_bank_client.cpp
)
target_link_libraries(small_bank_client PRIVATE secp256k1.host http_parser.host)
target_link_libraries(
small_bank_client PRIVATE secp256k1.host http_parser.host ccfcrypto.host
)
# SmallBank application
add_ccf_app(smallbank SRCS ${CMAKE_CURRENT_LIST_DIR}/app/smallbank.cpp)

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

@ -40,6 +40,7 @@ protected:
ws::ResponseParser ws_parser;
std::optional<std::string> prefix;
tls::KeyPairPtr key_pair = nullptr;
std::string key_id = "Invalid";
bool is_ws = false;
size_t next_send_id = 0;
@ -80,7 +81,7 @@ protected:
if (key_pair != nullptr)
{
http::sign_request(r, key_pair);
http::sign_request(r, key_pair, key_id);
}
return r.build_request();
@ -133,14 +134,17 @@ public:
const std::string& host,
const std::string& port,
std::shared_ptr<tls::CA> node_ca = nullptr,
std::shared_ptr<tls::Cert> cert = nullptr) :
std::shared_ptr<tls::Cert> cert = nullptr,
const std::string& key_id_ = "Invalid") :
TlsClient(host, port, node_ca, cert),
key_id(key_id_),
parser(*this),
ws_parser(*this)
{}
HttpRpcTlsClient(const HttpRpcTlsClient& c) :
TlsClient(c),
key_id(c.key_id),
parser(*this),
ws_parser(*this)
{}

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

@ -23,7 +23,7 @@
class TlsClient
{
private:
protected:
std::string host;
std::string port;
std::shared_ptr<tls::CA> node_ca;

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

@ -75,15 +75,6 @@ namespace ccf
tx.get_read_only_view<CertDigests>(Tables::USER_DIGESTS);
auto user_id = digests_view->get(signed_request->key_id);
// This should be removed once
// https://github.com/microsoft/CCF/issues/2018 is completed
if (!user_id.has_value())
{
auto user_certs_view =
tx.get_read_only_view<CertDERs>(Tables::USER_CERT_DERS);
user_id = user_certs_view->get(ctx->session->caller_cert);
}
if (user_id.has_value())
{
Users users_table(Tables::USERS);
@ -185,15 +176,6 @@ namespace ccf
tx.get_read_only_view<CertDigests>(Tables::MEMBER_DIGESTS);
auto member_id = digests_view->get(signed_request->key_id);
// This should be removed once
// https://github.com/microsoft/CCF/issues/2018 is completed
if (!member_id.has_value())
{
auto member_certs_view =
tx.get_read_only_view<CertDERs>(Tables::MEMBER_CERT_DERS);
member_id = member_certs_view->get(ctx->session->caller_cert);
}
if (member_id.has_value())
{
Members members_table(Tables::MEMBERS);

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

@ -67,12 +67,6 @@ namespace http
return ret;
}
struct SigningDetails
{
std::vector<uint8_t> to_sign;
std::vector<uint8_t> signature;
};
inline void add_digest_header(http::Request& request)
{
// Ensure digest is present and up-to-date
@ -93,8 +87,8 @@ namespace http
inline void sign_request(
http::Request& request,
const tls::KeyPairPtr& kp,
const std::vector<std::string_view>& headers_to_sign,
SigningDetails* details = nullptr)
const std::string& key_id,
const std::vector<std::string_view>& headers_to_sign)
{
add_digest_header(request);
@ -112,35 +106,29 @@ namespace http
const auto signature = kp->sign(to_sign.value());
// https://github.com/microsoft/CCF/issues/2018
auto auth_value = fmt::format(
"Signature "
"keyId=\"ignored\",algorithm=\"{}\",headers=\"{}\",signature="
"keyId=\"{}\",algorithm=\"{}\",headers=\"{}\",signature="
"\"{}\"",
key_id,
auth::SIGN_ALGORITHM_HS_2019,
fmt::format("{}", fmt::join(headers_to_sign, " ")),
tls::b64_from_raw(signature.data(), signature.size()));
request.set_header(headers::AUTHORIZATION, auth_value);
if (details != nullptr)
{
details->to_sign = to_sign.value();
details->signature = signature;
}
}
inline void sign_request(
http::Request& request,
const tls::KeyPairPtr& kp,
SigningDetails* details = nullptr)
const std::string& key_id)
{
std::vector<std::string_view> headers_to_sign;
headers_to_sign.emplace_back(auth::SIGN_HEADER_REQUEST_TARGET);
headers_to_sign.emplace_back(headers::DIGEST);
headers_to_sign.emplace_back(headers::CONTENT_LENGTH);
sign_request(request, kp, headers_to_sign, details);
sign_request(request, kp, key_id, headers_to_sign);
}
// Implements verification of "Signature" scheme from

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

@ -455,6 +455,7 @@ DOCTEST_TEST_CASE("Signatures")
// Produce signed requests with some formatting variations, ensure we can
// parse them
auto kp = tls::make_key_pair();
const std::string key_id = "UniqueIdentifierForThisKeypair";
http::Request request("/foo", HTTP_POST);
request.set_query_param("param", "value");
@ -486,7 +487,7 @@ DOCTEST_TEST_CASE("Signatures")
headers_to_sign.emplace_back(http::auth::SIGN_HEADER_REQUEST_TARGET);
headers_to_sign.emplace_back(http::headers::DIGEST);
http::sign_request(request, kp, headers_to_sign);
http::sign_request(request, kp, key_id, headers_to_sign);
const auto serial_request = request.build_request();
@ -496,6 +497,7 @@ DOCTEST_TEST_CASE("Signatures")
p.execute(serial_request.data(), serial_request.size());
DOCTEST_REQUIRE(sp.signed_reqs.size() == 1);
const auto& sr = sp.signed_reqs.back();
DOCTEST_REQUIRE(sr.key_id == key_id);
sp.signed_reqs.pop();
}
@ -512,7 +514,7 @@ DOCTEST_TEST_CASE("Signatures")
std::sort(headers_to_sign.begin(), headers_to_sign.end());
while (true)
{
http::sign_request(request, kp, headers_to_sign);
http::sign_request(request, kp, key_id, headers_to_sign);
const auto serial_request = request.build_request();
@ -522,6 +524,7 @@ DOCTEST_TEST_CASE("Signatures")
p.execute(serial_request.data(), serial_request.size());
DOCTEST_REQUIRE(sp.signed_reqs.size() == 1);
const auto& sr = sp.signed_reqs.back();
DOCTEST_REQUIRE(sr.key_id == key_id);
sp.signed_reqs.pop();
const bool was_last_permutation =
@ -542,7 +545,7 @@ DOCTEST_TEST_CASE("Signatures")
headers_to_sign.emplace_back(header_it.first);
}
http::sign_request(request, kp, headers_to_sign);
http::sign_request(request, kp, key_id, headers_to_sign);
const auto& headers = request.get_headers();
const auto auth_it = headers.find(http::headers::AUTHORIZATION);

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

@ -23,21 +23,24 @@ namespace ccf
std::shared_ptr<kv::Consensus> consensus;
std::shared_ptr<enclave::RPCSessions> rpcsessions;
std::shared_ptr<enclave::RPCMap> rpc_map;
tls::KeyPairPtr node_sign_kp;
tls::Pem node_cert;
public:
JwtKeyAutoRefresh(
size_t refresh_interval_s,
NetworkState& network,
std::shared_ptr<kv::Consensus> consensus,
std::shared_ptr<enclave::RPCSessions> rpcsessions,
std::shared_ptr<enclave::RPCMap> rpc_map,
const std::shared_ptr<kv::Consensus>& consensus,
const std::shared_ptr<enclave::RPCSessions>& rpcsessions,
const std::shared_ptr<enclave::RPCMap>& rpc_map,
const tls::KeyPairPtr& node_sign_kp,
tls::Pem node_cert) :
refresh_interval_s(refresh_interval_s),
network(network),
consensus(consensus),
rpcsessions(rpcsessions),
rpc_map(rpc_map),
node_sign_kp(node_sign_kp),
node_cert(node_cert)
{}
@ -111,9 +114,13 @@ namespace ccf
request.set_header(
http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
request.set_body(&body);
// Need a custom authentication policy that accepts only node certs.
// See https://github.com/microsoft/CCF/issues/1904
// http::sign_request(request, node_sign_kp);
crypto::Sha256Hash hash;
const auto contents = node_cert.contents();
tls::do_hash(contents.data(), contents.size(), hash.h, MBEDTLS_MD_SHA256);
const std::string key_id = fmt::format("{:02x}", fmt::join(hash.h, ""));
http::sign_request(request, node_sign_kp, key_id);
auto packed = request.build_request();
auto node_session = std::make_shared<enclave::SessionContext>(

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

@ -659,6 +659,7 @@ namespace ccf
consensus,
rpcsessions,
rpc_map,
node_sign_kp,
node_cert);
jwt_key_auto_refresh->start();
@ -1401,7 +1402,13 @@ namespace ccf
http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
request.set_body(&body);
http::sign_request(request, node_sign_kp);
crypto::Sha256Hash hash;
const auto contents = node_cert.contents();
tls::do_hash(contents.data(), contents.size(), hash.h, MBEDTLS_MD_SHA256);
const std::string key_id = fmt::format("{:02x}", fmt::join(hash.h, ""));
http::sign_request(request, node_sign_kp, key_id);
return request.build_request();
}

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

@ -399,6 +399,7 @@ auto create_simple_request(
}
http::Request create_signed_request(
const tls::Pem& caller_cert,
const http::Request& r = create_simple_request(),
const std::vector<uint8_t>* body = nullptr)
{
@ -406,8 +407,12 @@ http::Request create_signed_request(
s.set_body(body);
http::SigningDetails details;
http::sign_request(s, kp, &details);
crypto::Sha256Hash hash;
const auto contents = caller_cert.contents();
tls::do_hash(contents.data(), contents.size(), hash.h, MBEDTLS_MD_SHA256);
const std::string key_id = fmt::format("{:02x}", fmt::join(hash.h, ""));
http::sign_request(s, kp, key_id);
return s;
}
@ -567,7 +572,7 @@ TEST_CASE("process with signatures")
SUBCASE("endpoint does not require signature")
{
const auto simple_call = create_simple_request();
const auto signed_call = create_signed_request(simple_call);
const auto signed_call = create_signed_request(user_caller, simple_call);
const auto serialized_simple_call = simple_call.build_request();
const auto serialized_signed_call = signed_call.build_request();
@ -594,7 +599,7 @@ TEST_CASE("process with signatures")
SUBCASE("endpoint requires signature")
{
const auto simple_call = create_simple_request("empty_function_signed");
const auto signed_call = create_signed_request(simple_call);
const auto signed_call = create_signed_request(user_caller, simple_call);
const auto serialized_simple_call = simple_call.build_request();
const auto serialized_signed_call = signed_call.build_request();
@ -785,9 +790,8 @@ TEST_CASE("MinimalEndpointFunction")
const nlohmann::json j_body = {{"data", {"nested", "Some string"}},
{"other", "Another string"}};
const auto serialized_body = serdes::pack(j_body, pack_type);
auto signed_call = create_signed_request(echo_call, &serialized_body);
const auto serialized_call = signed_call.build_request();
echo_call.set_body(serialized_body.data(), serialized_body.size());
const auto serialized_call = echo_call.build_request();
auto rpc_ctx = enclave::make_rpc_context(user_session, serialized_call);
auto response = parse_response(frontend.process(rpc_ctx).value());
@ -817,10 +821,8 @@ TEST_CASE("MinimalEndpointFunction")
{
INFO("Calling get_caller");
auto get_caller = create_simple_request("get_caller", pack_type);
const auto signed_call = create_signed_request(get_caller);
const auto serialized_call = signed_call.build_request();
const auto get_caller = create_simple_request("get_caller", pack_type);
const auto serialized_call = get_caller.build_request();
auto rpc_ctx = enclave::make_rpc_context(user_session, serialized_call);
auto response = parse_response(frontend.process(rpc_ctx).value());
@ -834,9 +836,7 @@ TEST_CASE("MinimalEndpointFunction")
{
INFO("Calling failable, without failing");
auto dont_fail = create_simple_request("failable");
const auto signed_call = create_signed_request(dont_fail);
const auto serialized_call = signed_call.build_request();
const auto serialized_call = dont_fail.build_request();
auto rpc_ctx = enclave::make_rpc_context(user_session, serialized_call);
auto response = parse_response(frontend.process(rpc_ctx).value());
@ -856,9 +856,8 @@ TEST_CASE("MinimalEndpointFunction")
const nlohmann::json j_body = {
{"error", {{"code", err}, {"message", msg}}}};
const auto serialized_body = serdes::pack(j_body, default_pack);
const auto signed_call = create_signed_request(fail, &serialized_body);
const auto serialized_call = signed_call.build_request();
fail.set_body(serialized_body.data(), serialized_body.size());
const auto serialized_call = fail.build_request();
auto rpc_ctx = enclave::make_rpc_context(user_session, serialized_call);
auto response = parse_response(frontend.process(rpc_ctx).value());
@ -1132,7 +1131,7 @@ TEST_CASE("Signed read requests can be executed on backup")
auto backup_consensus = std::make_shared<kv::BackupStubConsensus>();
network.tables->set_consensus(backup_consensus);
auto signed_call = create_signed_request();
auto signed_call = create_signed_request(user_caller);
auto serialized_signed_call = signed_call.build_request();
auto rpc_ctx =
enclave::make_rpc_context(user_session, serialized_signed_call);
@ -1266,7 +1265,7 @@ TEST_CASE("Forwarding" * doctest::test_suite("forwarding"))
INFO("Client signature on forwarded RPC is recorded by primary");
REQUIRE(channel_stub->is_empty());
auto signed_call = create_signed_request();
auto signed_call = create_signed_request(user_caller);
auto serialized_signed_call = signed_call.build_request();
auto signed_ctx =
enclave::make_rpc_context(user_session, serialized_signed_call);

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

@ -108,15 +108,21 @@ std::vector<uint8_t> create_signed_request(
const json& params,
const string& method_name,
const tls::KeyPairPtr& kp_,
const tls::Pem& caller,
llhttp_method verb = HTTP_POST)
{
http::Request r(method_name, verb);
const auto body = params.is_null() ? std::vector<uint8_t>() :
serdes::pack(params, default_pack);
r.set_body(&body);
http::sign_request(r, kp_);
crypto::Sha256Hash hash;
const auto contents = caller.contents();
tls::do_hash(contents.data(), contents.size(), hash.h, MBEDTLS_MD_SHA256);
const std::string key_id = fmt::format("{:02x}", fmt::join(hash.h, ""));
http::sign_request(r, kp_, key_id);
return r.build_request();
}
@ -199,7 +205,7 @@ auto activate(
StateDigest params;
params.state_digest = ack.state_digest;
const auto ack_req = create_signed_request(params, "ack", kp);
const auto ack_req = create_signed_request(params, "ack", kp, caller);
return frontend_process(frontend, ack_req, caller);
}
@ -376,7 +382,8 @@ DOCTEST_TEST_CASE("Proposer ballot")
)xxx");
proposal.parameter["cert"] = proposed_member;
proposal.parameter["encryption_pub_key"] = dummy_enc_pubk;
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, proposer_cert);
const auto r = frontend_process(frontend, propose, proposer_cert);
// the proposal should be accepted, but not succeed immediately
@ -390,7 +397,10 @@ DOCTEST_TEST_CASE("Proposer ballot")
DOCTEST_INFO("Second member votes for proposal");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
voter_cert);
const auto r = frontend_process(frontend, vote, voter_cert);
// The vote should not yet succeed
@ -425,7 +435,10 @@ DOCTEST_TEST_CASE("Proposer ballot")
DOCTEST_INFO("Proposer votes for");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
proposer_cert);
const auto r = frontend_process(frontend, vote, proposer_cert);
// The vote should now succeed
@ -471,7 +484,8 @@ DOCTEST_TEST_CASE("Reject duplicate vote")
)xxx");
proposal.parameter["cert"] = proposed_member;
proposal.parameter["encryption_pub_key"] = dummy_enc_pubk;
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, proposer_cert);
const auto r = frontend_process(frontend, propose, proposer_cert);
// the proposal should be accepted, but not succeed immediately
@ -485,7 +499,10 @@ DOCTEST_TEST_CASE("Reject duplicate vote")
DOCTEST_INFO("Proposer votes for");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
proposer_cert);
const auto r = frontend_process(frontend, vote, proposer_cert);
// The vote should now succeed
@ -496,7 +513,10 @@ DOCTEST_TEST_CASE("Reject duplicate vote")
DOCTEST_INFO("Proposer cannot vote again");
const auto vote = create_signed_request(
Vote{vote_against}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_against},
fmt::format("proposals/{}/votes", proposal_id),
kp,
proposer_cert);
check_error(
frontend_process(frontend, vote, proposer_cert), HTTP_STATUS_BAD_REQUEST);
}
@ -577,7 +597,8 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject")
proposal.parameter["cert"] = cert_pem;
proposal.parameter["encryption_pub_key"] = dummy_enc_pubk;
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, member_cert);
{
const auto r = frontend_process(frontend, propose, member_cert);
@ -592,7 +613,10 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject")
// vote for own proposal
Script vote_yes("return true");
const auto vote = create_signed_request(
Vote{vote_yes}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_yes},
fmt::format("proposals/{}/votes", proposal_id),
kp,
member_cert);
const auto r = frontend_process(frontend, vote, member_cert);
const auto result = parse_response_body<ProposalInfo>(r);
DOCTEST_CHECK(result.state == ProposalState::OPEN);
@ -620,7 +644,10 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject")
max_members));
const auto vote = create_signed_request(
Vote{vote_ballot}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_ballot},
fmt::format("proposals/{}/votes", proposal_id),
kp,
voter_a_cert);
{
const auto r = frontend_process(frontend, vote, voter_a_cert);
@ -705,7 +732,7 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject")
StateDigest params;
params.state_digest = ack0.state_digest;
const auto send_stale_sig_req =
create_signed_request(params, "ack", new_member->kp);
create_signed_request(params, "ack", new_member->kp, new_member->cert);
check_error(
frontend_process(frontend, send_stale_sig_req, new_member->cert),
HTTP_STATUS_BAD_REQUEST);
@ -713,7 +740,7 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject")
// (5) sign new state digest and send it
params.state_digest = ack1.state_digest;
const auto send_good_sig_req =
create_signed_request(params, "ack", new_member->kp);
create_signed_request(params, "ack", new_member->kp, new_member->cert);
const auto good_response =
frontend_process(frontend, send_good_sig_req, new_member->cert);
DOCTEST_CHECK(good_response.status == HTTP_STATUS_NO_CONTENT);
@ -781,7 +808,7 @@ DOCTEST_TEST_CASE("Accept node")
return Calls:call("trust_node", node_id)
)xxx");
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", new_kp);
Propose::In{proposal, node_id}, "proposals", new_kp, member_0_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_0_cert));
@ -795,7 +822,8 @@ DOCTEST_TEST_CASE("Accept node")
const auto vote = create_signed_request(
Vote{vote_yes},
fmt::format("proposals/{}/votes", trust_node_proposal_id),
new_kp);
new_kp,
member_0_cert);
const auto r = frontend_process(frontend, vote, member_0_cert);
const auto result = parse_response_body<ProposalInfo>(r);
DOCTEST_CHECK(result.state == ProposalState::OPEN);
@ -810,7 +838,8 @@ DOCTEST_TEST_CASE("Accept node")
const auto vote = create_signed_request(
Vote{vote_ballot},
fmt::format("proposals/{}/votes", trust_node_proposal_id),
kp);
kp,
member_1_cert);
check_result_state(
frontend_process(frontend, vote, member_1_cert), ProposalState::ACCEPTED);
@ -833,7 +862,7 @@ DOCTEST_TEST_CASE("Accept node")
return Calls:call("retire_node", node_id)
)xxx");
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", new_kp);
Propose::In{proposal, node_id}, "proposals", new_kp, member_0_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_0_cert));
@ -847,7 +876,8 @@ DOCTEST_TEST_CASE("Accept node")
const auto vote = create_signed_request(
Vote{vote_yes},
fmt::format("proposals/{}/votes", retire_node_proposal_id),
new_kp);
new_kp,
member_0_cert);
const auto r = frontend_process(frontend, vote, member_0_cert);
const auto result = parse_response_body<ProposalInfo>(r);
DOCTEST_CHECK(result.state == ProposalState::OPEN);
@ -859,7 +889,8 @@ DOCTEST_TEST_CASE("Accept node")
const auto vote = create_signed_request(
Vote{vote_ballot},
fmt::format("proposals/{}/votes", retire_node_proposal_id),
kp);
kp,
member_1_cert);
check_result_state(
frontend_process(frontend, vote, member_1_cert), ProposalState::ACCEPTED);
}
@ -880,7 +911,7 @@ DOCTEST_TEST_CASE("Accept node")
return Calls:call("trust_node", node_id)
)xxx");
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", new_kp);
Propose::In{proposal, node_id}, "proposals", new_kp, member_0_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_0_cert));
@ -888,11 +919,15 @@ DOCTEST_TEST_CASE("Accept node")
auto vote = create_signed_request(
Vote{vote_ballot},
fmt::format("proposals/{}/votes", r.proposal_id),
new_kp);
new_kp,
member_0_cert);
frontend_process(frontend, vote, member_0_cert);
vote = create_signed_request(
Vote{vote_ballot}, fmt::format("proposals/{}/votes", r.proposal_id), kp);
Vote{vote_ballot},
fmt::format("proposals/{}/votes", r.proposal_id),
kp,
member_1_cert);
check_result_state(
frontend_process(frontend, vote, member_1_cert), ProposalState::FAILED);
}
@ -904,7 +939,7 @@ DOCTEST_TEST_CASE("Accept node")
return Calls:call("retire_node", node_id)
)xxx");
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", new_kp);
Propose::In{proposal, node_id}, "proposals", new_kp, member_0_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_0_cert));
@ -912,11 +947,15 @@ DOCTEST_TEST_CASE("Accept node")
auto vote = create_signed_request(
Vote{vote_ballot},
fmt::format("proposals/{}/votes", r.proposal_id),
new_kp);
new_kp,
member_0_cert);
frontend_process(frontend, vote, member_0_cert);
vote = create_signed_request(
Vote{vote_ballot}, fmt::format("proposals/{}/votes", r.proposal_id), kp);
Vote{vote_ballot},
fmt::format("proposals/{}/votes", r.proposal_id),
kp,
member_1_cert);
check_result_state(
frontend_process(frontend, vote, member_1_cert), ProposalState::FAILED);
}
@ -950,7 +989,8 @@ ProposalInfo test_raw_writes(
const auto proposal_id = 0ul;
{
const uint8_t proposer_id = 0;
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, member_certs[0]);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_certs[0]));
@ -967,7 +1007,10 @@ ProposalInfo test_raw_writes(
{
const Script vote("return false");
const auto vote_serialized = create_signed_request(
Vote{vote}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote},
fmt::format("proposals/{}/votes", proposal_id),
kp,
member_certs[i]);
check_result_state(
frontend_process(frontend, vote_serialized, member_certs[i]),
@ -980,7 +1023,10 @@ ProposalInfo test_raw_writes(
{
const Script vote("return true");
const auto vote_serialized = create_signed_request(
Vote{vote}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote},
fmt::format("proposals/{}/votes", proposal_id),
kp,
member_certs[i]);
if (info.state == ProposalState::OPEN)
{
info = parse_response_body<ProposalInfo>(
@ -1131,8 +1177,8 @@ DOCTEST_TEST_CASE("Remove proposal")
}
{
const auto propose =
create_signed_request(Propose::In{proposal_script, 0}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal_script, 0}, "proposals", kp, member_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_cert));
@ -1153,7 +1199,10 @@ DOCTEST_TEST_CASE("Remove proposal")
DOCTEST_SUBCASE("Attempt withdraw proposal with non existing id")
{
const auto withdraw = create_signed_request(
nullptr, fmt::format("proposals/{}/withdraw", wrong_proposal_id), kp);
nullptr,
fmt::format("proposals/{}/withdraw", wrong_proposal_id),
kp,
member_cert);
check_error(
frontend_process(frontend, withdraw, member_cert),
@ -1163,7 +1212,10 @@ DOCTEST_TEST_CASE("Remove proposal")
DOCTEST_SUBCASE("Attempt withdraw proposal that you didn't propose")
{
const auto withdraw = create_signed_request(
nullptr, fmt::format("proposals/{}/withdraw", proposal_id), caller.kp);
nullptr,
fmt::format("proposals/{}/withdraw", proposal_id),
caller.kp,
cert);
check_error(
frontend_process(frontend, withdraw, cert), HTTP_STATUS_FORBIDDEN);
@ -1172,7 +1224,10 @@ DOCTEST_TEST_CASE("Remove proposal")
DOCTEST_SUBCASE("Successfully withdraw proposal")
{
const auto withdraw = create_signed_request(
nullptr, fmt::format("proposals/{}/withdraw", proposal_id), kp);
nullptr,
fmt::format("proposals/{}/withdraw", proposal_id),
kp,
member_cert);
check_result_state(
frontend_process(frontend, withdraw, member_cert),
@ -1215,8 +1270,8 @@ DOCTEST_TEST_CASE("Vetoed proposal gets rejected")
return Calls:call("new_user", user_cert)
)xxx");
const auto propose =
create_signed_request(Propose::In{proposal, user_cert}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal, user_cert}, "proposals", kp, voter_a_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, voter_a_cert));
@ -1227,7 +1282,10 @@ DOCTEST_TEST_CASE("Vetoed proposal gets rejected")
DOCTEST_INFO("Member vetoes proposal");
const auto vote = create_signed_request(
Vote{vote_against}, fmt::format("proposals/{}/votes", r.proposal_id), kp);
Vote{vote_against},
fmt::format("proposals/{}/votes", r.proposal_id),
kp,
voter_b_cert);
const auto r = frontend_process(frontend, vote, voter_b_cert);
check_result_state(r, ProposalState::REJECTED);
@ -1271,8 +1329,8 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls")
)xxx");
const auto user_cert = kp->self_sign("CN=new user");
const auto propose =
create_signed_request(Propose::In{proposal, user_cert}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal, user_cert}, "proposals", kp, member_cert);
auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_cert));
@ -1281,7 +1339,10 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls")
// vote for own proposal
Script vote_yes("return true");
const auto vote = create_signed_request(
Vote{vote_yes}, fmt::format("proposals/{}/votes", r.proposal_id), kp);
Vote{vote_yes},
fmt::format("proposals/{}/votes", r.proposal_id),
kp,
member_cert);
r = parse_response_body<ProposalInfo>(
frontend_process(frontend, vote, member_cert));
@ -1306,8 +1367,8 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls")
return Calls:call("remove_user", user_id)
)xxx");
const auto propose =
create_signed_request(Propose::In{proposal, 0}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal, 0}, "proposals", kp, member_cert);
auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, member_cert));
@ -1316,7 +1377,10 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls")
// vote for own proposal
Script vote_yes("return true");
const auto vote = create_signed_request(
Vote{vote_yes}, fmt::format("proposals/{}/votes", r.proposal_id), kp);
Vote{vote_yes},
fmt::format("proposals/{}/votes", r.proposal_id),
kp,
member_cert);
r = parse_response_body<ProposalInfo>(
frontend_process(frontend, vote, member_cert));
@ -1395,7 +1459,8 @@ DOCTEST_TEST_CASE(
proposal.parameter["cert"] = proposed_member;
proposal.parameter["encryption_pub_key"] = dummy_enc_pubk;
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, members[proposer_id]);
const auto r = parse_response_body<Propose::Out>(frontend_process(
frontend,
propose,
@ -1410,7 +1475,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("First member votes");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
members[proposer_id]);
const auto r = frontend_process(frontend, vote, members[proposer_id]);
check_result_state(r, ProposalState::OPEN);
@ -1420,7 +1488,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("Operator votes, but without effect");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
operator_cert);
const auto r = frontend_process(frontend, vote, operator_cert);
check_result_state(r, ProposalState::OPEN);
@ -1430,7 +1501,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("Second member votes for proposal, which passes");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
members[voter_id]);
const auto r = frontend_process(frontend, vote, members[voter_id]);
check_result_state(r, ProposalState::ACCEPTED);
@ -1526,8 +1600,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
return Calls:call("trust_node", node_id)
)xxx");
const auto propose =
create_signed_request(Propose::In{proposal, node_id}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", kp, operator_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, operator_cert));
@ -1561,7 +1635,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
proposal.parameter["cert"] = new_operator_cert;
proposal.parameter["member_data"] = operator_member_data();
const auto propose = create_signed_request(proposal, "proposals", kp);
const auto propose =
create_signed_request(proposal, "proposals", kp, operator_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, operator_cert));
@ -1576,8 +1651,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
StateDigest params;
params.state_digest = ack.state_digest;
const auto ack_req =
create_signed_request(params, "ack", new_operator_kp);
const auto ack_req = create_signed_request(
params, "ack", new_operator_kp, new_operator_cert);
const auto resp = frontend_process(frontend, ack_req, new_operator_cert);
}
}
@ -1588,8 +1663,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
proposal.script = fmt::format(
R"xxx(return Calls:call("retire_member", {}))xxx", operator_id);
const auto propose =
create_signed_request(proposal, "proposals", new_operator_kp);
const auto propose = create_signed_request(
proposal, "proposals", new_operator_kp, new_operator_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, new_operator_cert));
@ -1613,8 +1688,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
proposal.parameter["member_data"] =
nullptr; // blank member_data => not an operator
const auto propose =
create_signed_request(proposal, "proposals", new_operator_kp);
const auto propose = create_signed_request(
proposal, "proposals", new_operator_kp, new_operator_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, new_operator_cert));
@ -1630,8 +1705,8 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator"))
proposal.script = fmt::format(
R"xxx(return Calls:call("retire_member", {}))xxx", normal_member_id);
const auto propose =
create_signed_request(proposal, "proposals", new_operator_kp);
const auto propose = create_signed_request(
proposal, "proposals", new_operator_kp, new_operator_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, new_operator_cert));
@ -1705,8 +1780,8 @@ DOCTEST_TEST_CASE(
return Calls:call("trust_node", node_id)
)xxx");
const auto propose =
create_signed_request(Propose::In{proposal, node_id}, "proposals", kp);
const auto propose = create_signed_request(
Propose::In{proposal, node_id}, "proposals", kp, proposer_cert);
const auto r = parse_response_body<Propose::Out>(
frontend_process(frontend, propose, proposer_cert));
@ -1718,7 +1793,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("Member votes against");
const auto vote = create_signed_request(
Vote{vote_against}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_against},
fmt::format("proposals/{}/votes", proposal_id),
kp,
proposer_cert);
const auto r = frontend_process(frontend, vote, proposer_cert);
check_result_state(r, ProposalState::OPEN);
@ -1731,7 +1809,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("First member votes for proposal");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
members[first_voter_id]);
const auto r = frontend_process(frontend, vote, members[first_voter_id]);
check_result_state(r, ProposalState::OPEN);
@ -1741,7 +1822,10 @@ DOCTEST_TEST_CASE(
DOCTEST_INFO("Second member votes for proposal");
const auto vote = create_signed_request(
Vote{vote_for}, fmt::format("proposals/{}/votes", proposal_id), kp);
Vote{vote_for},
fmt::format("proposals/{}/votes", proposal_id),
kp,
members[second_voter_id]);
const auto r = frontend_process(frontend, vote, members[second_voter_id]);
check_result_state(r, ProposalState::ACCEPTED);
@ -1841,7 +1925,7 @@ DOCTEST_TEST_CASE("User data")
)xxx",
user_id);
const auto proposal_serialized =
create_signed_request(proposal, "proposals", kp);
create_signed_request(proposal, "proposals", kp, member_cert);
const auto propose_response = parse_response_body<Propose::Out>(
frontend_process(frontend, proposal_serialized, member_cert));
@ -1853,7 +1937,8 @@ DOCTEST_TEST_CASE("User data")
const auto vote = create_signed_request(
Vote{vote_yes},
fmt::format("proposals/{}/votes", propose_response.proposal_id),
kp);
kp,
member_cert);
const auto r = frontend_process(frontend, vote, member_cert);
const auto result = parse_response_body<ProposalInfo>(r);
DOCTEST_CHECK(result.state == ProposalState::ACCEPTED);
@ -1878,7 +1963,7 @@ DOCTEST_TEST_CASE("User data")
proposal.parameter["id"] = user_id;
proposal.parameter["data"] = user_data_string;
const auto proposal_serialized =
create_signed_request(proposal, "proposals", kp);
create_signed_request(proposal, "proposals", kp, member_cert);
const auto propose_response = parse_response_body<Propose::Out>(
frontend_process(frontend, proposal_serialized, member_cert));
DOCTEST_CHECK(propose_response.state == ProposalState::OPEN);
@ -1889,7 +1974,8 @@ DOCTEST_TEST_CASE("User data")
const auto vote = create_signed_request(
Vote{vote_yes},
fmt::format("proposals/{}/votes", propose_response.proposal_id),
kp);
kp,
member_cert);
const auto r = frontend_process(frontend, vote, member_cert);
const auto result = parse_response_body<ProposalInfo>(r);
DOCTEST_CHECK(result.state == ProposalState::ACCEPTED);

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

@ -207,27 +207,8 @@ namespace client
private:
tls::Pem key = {};
std::shared_ptr<tls::Cert> tls_cert;
// Create tls_cert if it doesn't exist, and return it
bool get_cert()
{
if (tls_cert == nullptr)
{
const auto raw_cert = files::slurp(options.cert_file);
const auto raw_key = files::slurp(options.key_file);
const auto ca = files::slurp(options.ca_file);
key = tls::Pem(raw_key);
tls_cert = std::make_shared<tls::Cert>(
std::make_shared<tls::CA>(ca), raw_cert, key);
return true;
}
return false;
}
std::string key_id = "Invalid";
std::shared_ptr<tls::Cert> tls_cert = nullptr;
// Process reply to an RPC. Records time reply was received. Calls
// check_response for derived-overridable validation
@ -308,23 +289,42 @@ namespace client
bool force_unsigned = false, bool upgrade = false)
{
// Create a cert if this is our first rpc_connection
const bool is_first = get_cert();
const bool is_first_time = tls_cert == nullptr;
if (is_first_time)
{
const auto raw_cert = files::slurp(options.cert_file);
const auto raw_key = files::slurp(options.key_file);
const auto ca = files::slurp(options.ca_file);
key = tls::Pem(raw_key);
crypto::Sha256Hash hash;
tls::do_hash(
raw_cert.data(), raw_cert.size(), hash.h, MBEDTLS_MD_SHA256);
key_id = fmt::format("{:02x}", fmt::join(hash.h, ""));
tls_cert = std::make_shared<tls::Cert>(
std::make_shared<tls::CA>(ca), raw_cert, key);
}
auto conn = std::make_shared<RpcTlsClient>(
options.server_address.hostname,
options.server_address.port,
nullptr,
tls_cert);
tls_cert,
key_id);
if (options.sign && !force_unsigned)
{
LOG_INFO_FMT("Creating key pair");
conn->create_key_pair(key);
}
conn->set_prefix("app");
// Report ciphersuite of first client (assume it is the same for each)
if (is_first)
if (is_first_time)
{
LOG_DEBUG_FMT(
"Connected to server via TLS ({})", conn->get_ciphersuite_name());