diff --git a/.gitignore b/.gitignore index a58d71ab334d..0856fc559874 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ env/ python/setup.py node_modules package-lock.json +.cache \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f97f1a71e8d..0e4f5c55c78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Governance proposal ids are now digests, hex-encoded as strings. + ## [0.17.1] ### Changed diff --git a/doc/governance/accept_recovery.rst b/doc/governance/accept_recovery.rst index 06beea621850..7db925b57239 100644 --- a/doc/governance/accept_recovery.rst +++ b/doc/governance/accept_recovery.rst @@ -23,21 +23,21 @@ A member proposes to recover the network and other members can vote on the propo $ scurl.sh https:///gov/proposals --cacert network_cert --key member1_privk --cert member1_cert --data-binary @accept_recovery.json -H "content-type: application/json" { - "proposal_id": 1, + "proposal_id": "1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377", "proposer_id": 0, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/1/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept.json -H "content-type: application/json" { - "proposal_id": 1, + "proposal_id": "1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377", "proposer_id": 0, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/1/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept.json -H "content-type: application/json" { - "proposal_id": 1, + "proposal_id": "1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377", "proposer_id": 0, "state": "ACCEPTED" } diff --git a/doc/governance/common_member_operations.rst b/doc/governance/common_member_operations.rst index 1d4699ffb07d..94223c8e8491 100644 --- a/doc/governance/common_member_operations.rst +++ b/doc/governance/common_member_operations.rst @@ -61,21 +61,21 @@ To limit the scope of key compromise, members of the consortium can refresh the $ scurl.sh https:///gov/proposals --cacert network_cert --key member1_privk --cert member1_cert --data-binary @rekey_ledger.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "2f739d154b8cddacd7fc6d03cc8d4d20626e477ec4b1af10a74c670bb38bed5e", "proposer_id": 1, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/4/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept_1.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/2f739d154b8cddacd7fc6d03cc8d4d20626e477ec4b1af10a74c670bb38bed5e/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept_1.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "2f739d154b8cddacd7fc6d03cc8d4d20626e477ec4b1af10a74c670bb38bed5e", "proposer_id": 1, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/4/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept_1.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/2f739d154b8cddacd7fc6d03cc8d4d20626e477ec4b1af10a74c670bb38bed5e/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept_1.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "2f739d154b8cddacd7fc6d03cc8d4d20626e477ec4b1af10a74c670bb38bed5e", "proposer_id": 1, "state": "ACCEPTED" } @@ -103,21 +103,21 @@ The number of member shares required to restore the private ledger (``recovery_t $ scurl.sh https:///gov/proposals --cacert network_cert --key member1_privk --cert member1_cert --data-binary @set_recovery_threshold.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "b9c08b3861395eca904d913427dcb436136e277cf4712eb14e9e9cddf9d231a8", "proposer_id": 1, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/5/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept_1.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/b9c08b3861395eca904d913427dcb436136e277cf4712eb14e9e9cddf9d231a8/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_accept_1.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "b9c08b3861395eca904d913427dcb436136e277cf4712eb14e9e9cddf9d231a8", "proposer_id": 1, "state": "OPEN" } - $ scurl.sh https:///gov/proposals/5/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept_1.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/b9c08b3861395eca904d913427dcb436136e277cf4712eb14e9e9cddf9d231a8/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept_1.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "b9c08b3861395eca904d913427dcb436136e277cf4712eb14e9e9cddf9d231a8", "proposer_id": 1, "state": "ACCEPTED" } diff --git a/doc/governance/open_network.rst b/doc/governance/open_network.rst index 9e997b6031d0..af6d6dda7887 100644 --- a/doc/governance/open_network.rst +++ b/doc/governance/open_network.rst @@ -22,7 +22,7 @@ Then, the certificates of trusted users should be registered in CCF via the memb $ scurl.sh https:///gov/proposals --cacert network_cert --key member0_privk --cert member0_cert --data-binary @add_user.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253", "proposer_id": 0, "state": "OPEN" } @@ -38,9 +38,9 @@ Other members are then allowed to vote for the proposal, using the proposal id r } } - $ scurl.sh https:///gov/proposals/5/votes --cacert network_cert --key member1_privk --cert member1_cert --data-binary @vote_accept.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253/votes --cacert network_cert --key member1_privk --cert member1_cert --data-binary @vote_accept.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253", "proposer_id": 0, "state": "OPEN" } @@ -52,9 +52,9 @@ Other members are then allowed to vote for the proposal, using the proposal id r } } - $ scurl.sh https:///gov/proposals/5/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_conditional.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_conditional.json -H "content-type: application/json" { - "proposal_id": 5, + "proposal_id": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253", "proposer_id": 0, "state": "ACCEPTED" } @@ -134,7 +134,7 @@ Once users are added to the opening network, members should create a proposal to $ scurl.sh https:///gov/proposals --cacert network_cert --key member0_privk --cert member0_cert --data-binary @open_network.json -H "content-type: application/json" { - "proposal_id": 10, + "proposal_id": "77374e16de0b2d61f58aec84d01e6218205d19c9401d2df127d893ce62576b81", "proposer_id": 0, "state": "OPEN" } diff --git a/doc/governance/proposals.rst b/doc/governance/proposals.rst index af84317fc333..d4a523c3e761 100644 --- a/doc/governance/proposals.rst +++ b/doc/governance/proposals.rst @@ -112,7 +112,7 @@ For example, ``member1`` may submit a proposal to add a new member (``member4``) $ scurl.sh https:///gov/proposals --cacert network_cert --key member1_privk --cert member1_cert --data-binary @add_member.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd", "proposer_id": 1, "state": "OPEN" } @@ -138,17 +138,17 @@ In this case, a new proposal with id ``4`` has successfully been created and the } # Member 2 rejects the proposal (votes in favour: 1/3) - $ scurl.sh https:///gov/proposals/4/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_reject.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd/votes --cacert network_cert --key member2_privk --cert member2_cert --data-binary @vote_reject.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd", "proposer_id": 1, "state": "OPEN" } # Member 3 accepts the proposal (votes in favour: 2/3) - $ scurl.sh https:///gov/proposals/4/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept.json -H "content-type: application/json" + $ scurl.sh https:///gov/proposals/d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd/votes --cacert network_cert --key member3_privk --cert member3_cert --data-binary @vote_accept.json -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd", "proposer_id": 1, "state": "ACCEPTED" } @@ -167,7 +167,7 @@ The details of pending proposals, including the proposer member id, proposal scr .. code-block:: bash # The full proposal state, including votes, can still be retrieved by any member - $ scurl.sh https:///gov/proposals/4 --cacert networkcert.pem --key member3_privk.pem --cert member3_cert.pem -H "content-type: application/json" -X GET + $ scurl.sh https:///gov/proposals/d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd --cacert networkcert.pem --key member3_privk.pem --cert member3_cert.pem -H "content-type: application/json" -X GET { "parameter": {...}, "proposer": 1, @@ -204,7 +204,7 @@ At any stage during the voting process, before the proposal is accepted, the pro $ scurl.sh https:///gov/proposals//withdraw --cacert networkcert.pem --key member1_privk.pem --cert member1_cert.pem -H "content-type: application/json" { - "proposal_id": 4, + "proposal_id": "d4ec2de82267f97d3d1b464020af0bd3241f1bedf769f0fee73cd00f08e9c7fd", "proposer_id": 1, "state": "WITHDRAWN" } diff --git a/doc/schemas/gov_openapi.json b/doc/schemas/gov_openapi.json index d8750aee0398..2b41c0d9aefc 100644 --- a/doc/schemas/gov_openapi.json +++ b/doc/schemas/gov_openapi.json @@ -173,7 +173,7 @@ "ProposalInfo": { "properties": { "proposal_id": { - "$ref": "#/components/schemas/uint64" + "$ref": "#/components/schemas/string" }, "proposer_id": { "$ref": "#/components/schemas/uint64" diff --git a/src/http/authentication/sig_auth.h b/src/http/authentication/sig_auth.h index e6db647a9de5..4b67f1eec060 100644 --- a/src/http/authentication/sig_auth.h +++ b/src/http/authentication/sig_auth.h @@ -153,6 +153,7 @@ namespace ccf tls::Pem member_cert; nlohmann::json member_data; SignedReq signed_request; + std::vector request_digest; }; class MemberSignatureAuthnPolicy : public AuthnPolicy @@ -187,15 +188,20 @@ namespace ccf "Members and member certs tables do not match"); } + std::vector digest; auto verifier = verifiers.get_verifier(member->cert); if (verifier->verify( - signed_request->req, signed_request->sig, signed_request->md)) + signed_request->req, + signed_request->sig, + signed_request->md, + digest)) { auto identity = std::make_unique(); identity->member_id = member_id.value(); identity->member_cert = member->cert; identity->member_data = member->member_data; identity->signed_request = signed_request.value(); + identity->request_digest = std::move(digest); return identity; } else diff --git a/src/node/proposals.h b/src/node/proposals.h index e2f95f316f14..f7b339a4f7fa 100644 --- a/src/node/proposals.h +++ b/src/node/proposals.h @@ -93,11 +93,12 @@ namespace ccf DECLARE_JSON_REQUIRED_FIELDS( Proposal, script, parameter, proposer, state, votes) - using Proposals = kv::Map; + using ProposalId = std::string; + using Proposals = kv::Map; struct ProposalInfo { - ObjectId proposal_id; + ProposalId proposal_id; MemberId proposer_id; ProposalState state; }; diff --git a/src/node/rpc/endpoint_registry.h b/src/node/rpc/endpoint_registry.h index bf94b8cd7e0f..a4fd6362e81f 100644 --- a/src/node/rpc/endpoint_registry.h +++ b/src/node/rpc/endpoint_registry.h @@ -505,6 +505,24 @@ namespace ccf return true; } + template <> + bool get_path_param( + const enclave::PathParams& params, + const std::string& param_name, + std::string& value, + std::string& error) + { + const auto it = params.find(param_name); + if (it == params.end()) + { + error = fmt::format("No parameter named '{}' in path", param_name); + return false; + } + + value = it->second; + return true; + } + protected: EndpointPtr default_endpoint; std::map> diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index aab974945327..ecff03a25d87 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -32,6 +32,8 @@ namespace ccf { + constexpr auto INVALID_PROPOSAL_ID = "INVALID"; + static oe_result_t oe_verify_attestation_certificate_with_evidence_cb( oe_claim_t* claims, size_t claims_length, void* arg) { @@ -356,7 +358,7 @@ namespace ccf bool set_jwt_public_signing_keys( kv::Tx& tx, - ObjectId proposal_id, + const ProposalId& proposal_id, std::string issuer, const JwtIssuerMetadata& issuer_metadata, const JsonWebKeySet& jwks) @@ -365,9 +367,9 @@ namespace ccf auto key_issuer = tx.get_view(this->network.jwt_public_signing_key_issuer); - auto log_prefix = proposal_id != INVALID_ID ? - fmt::format("Proposal {}", proposal_id) : - "JWT key auto-refresh"; + auto log_prefix = proposal_id == INVALID_PROPOSAL_ID ? + "JWT key auto-refresh" : + fmt::format("Proposal {}", proposal_id); // add keys if (jwks.keys.empty()) @@ -515,7 +517,7 @@ namespace ccf kv::Tx& tx, const CodeDigest& new_code_id, CodeIDs& code_id_table, - ObjectId proposal_id) + const ProposalId& proposal_id) { auto code_ids = tx.get_view(code_id_table); auto existing_code_id = code_ids->get(new_code_id); @@ -535,7 +537,7 @@ namespace ccf kv::Tx& tx, const CodeDigest& code_id, CodeIDs& code_id_table, - ObjectId proposal_id) + const ProposalId& proposal_id) { auto code_ids = tx.get_view(code_id_table); auto existing_code_id = code_ids->get(code_id); @@ -554,41 +556,41 @@ namespace ccf //! Table of functions that proposal scripts can propose to invoke const std::unordered_map< std::string, - std::function> + std::function> hardcoded_funcs = { // set the js application script {"set_js_app", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const std::string app = args; set_js_scripts(tx, lua::Interpreter().invoke(app)); return true; }}, // deploy the js application bundle {"deploy_js_app", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto parsed = args.get(); return deploy_js_app(tx, parsed.bundle); }}, // undeploy/remove the js application {"remove_js_app", - [this](ObjectId, kv::Tx& tx, const nlohmann::json&) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json&) { return remove_js_app(tx); }}, // add/update a module {"set_module", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto parsed = args.get(); return set_module(tx, parsed.name, parsed.module); }}, // remove a module {"remove_module", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto name = args.get(); return remove_module(tx, name); }}, // add a new member {"new_member", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto parsed = args.get(); GenesisGenerator g(this->network, tx); g.add_member(parsed); @@ -597,7 +599,7 @@ namespace ccf }}, // retire an existing member {"retire_member", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto member_id = args.get(); GenesisGenerator g(this->network, tx); @@ -629,7 +631,10 @@ namespace ccf return true; }}, {"set_member_data", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); auto members_view = tx.get_view(this->network.members); auto member_info = members_view->get(parsed.member_id); @@ -647,7 +652,7 @@ namespace ccf return true; }}, {"new_user", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto user_info = args.get(); GenesisGenerator g(this->network, tx); @@ -656,7 +661,10 @@ namespace ccf return true; }}, {"remove_user", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const UserId user_id = args; GenesisGenerator g(this->network, tx); @@ -670,7 +678,10 @@ namespace ccf return r; }}, {"set_user_data", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); auto users_view = tx.get_view(this->network.users); auto user_info = users_view->get(parsed.user_id); @@ -688,7 +699,10 @@ namespace ccf return true; }}, {"set_ca_cert", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); auto ca_certs = tx.get_view(this->network.ca_certs); std::vector cert_der; @@ -709,14 +723,17 @@ namespace ccf return true; }}, {"remove_ca_cert", - [this](ObjectId, kv::Tx& tx, const nlohmann::json& args) { + [this](const ProposalId&, kv::Tx& tx, const nlohmann::json& args) { const auto cert_name = args.get(); auto ca_certs = tx.get_view(this->network.ca_certs); ca_certs->remove(cert_name); return true; }}, {"set_jwt_issuer", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); auto issuers = tx.get_view(this->network.jwt_issuers); auto ca_certs = tx.get_read_only_view(this->network.ca_certs); @@ -783,7 +800,10 @@ namespace ccf return success; }}, {"remove_jwt_issuer", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); const auto issuer = parsed.issuer; auto issuers = tx.get_view(this->network.jwt_issuers); @@ -800,7 +820,10 @@ namespace ccf return true; }}, {"set_jwt_public_signing_keys", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto parsed = args.get(); auto issuers = tx.get_view(this->network.jwt_issuers); @@ -820,7 +843,10 @@ namespace ccf }}, // accept a node {"trust_node", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto node_id = args.get(); try { @@ -837,7 +863,10 @@ namespace ccf }}, // retire a node {"retire_node", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto id = args.get(); auto nodes = tx.get_view(this->network.nodes); auto node_info = nodes->get(id); @@ -860,7 +889,10 @@ namespace ccf }}, // accept new node code ID {"new_node_code", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { return this->add_new_code_id( tx, args.get(), @@ -869,7 +901,10 @@ namespace ccf }}, // retire node code ID {"retire_node_code", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { return this->retire_code_id( tx, args.get(), @@ -877,7 +912,8 @@ namespace ccf proposal_id); }}, {"accept_recovery", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json&) { + [this]( + const ProposalId& proposal_id, kv::Tx& tx, const nlohmann::json&) { if (node.is_part_of_public_network()) { const auto accept_recovery = node.accept_recovery(tx); @@ -895,7 +931,8 @@ namespace ccf } }}, {"open_network", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json&) { + [this]( + const ProposalId& proposal_id, kv::Tx& tx, const nlohmann::json&) { // On network open, the service checks that a sufficient number of // recovery members have become active. If so, recovery shares are // allocated to each recovery member @@ -926,7 +963,8 @@ namespace ccf return network_opened; }}, {"rekey_ledger", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json&) { + [this]( + const ProposalId& proposal_id, kv::Tx& tx, const nlohmann::json&) { const auto ledger_rekeyed = node.rekey_ledger(tx); if (!ledger_rekeyed) { @@ -935,7 +973,8 @@ namespace ccf return ledger_rekeyed; }}, {"update_recovery_shares", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json&) { + [this]( + const ProposalId& proposal_id, kv::Tx& tx, const nlohmann::json&) { try { share_manager.issue_shares(tx); @@ -951,7 +990,10 @@ namespace ccf return true; }}, {"set_recovery_threshold", - [this](ObjectId proposal_id, kv::Tx& tx, const nlohmann::json& args) { + [this]( + const ProposalId& proposal_id, + kv::Tx& tx, + const nlohmann::json& args) { const auto new_recovery_threshold = args.get(); GenesisGenerator g(this->network, tx); @@ -986,7 +1028,7 @@ namespace ccf }; ProposalInfo complete_proposal( - kv::Tx& tx, const ObjectId proposal_id, Proposal& proposal) + kv::Tx& tx, const ProposalId& proposal_id, Proposal& proposal) { if (proposal.state != ProposalState::OPEN) { @@ -1144,14 +1186,14 @@ namespace ccf } static ProposalInfo get_proposal_info( - ObjectId proposal_id, const Proposal& proposal) + const ProposalId& proposal_id, const Proposal& proposal) { return ProposalInfo{proposal_id, proposal.proposer, proposal.state}; } bool get_proposal_id_from_path( const enclave::PathParams& params, - ObjectId& proposal_id, + ProposalId& proposal_id, std::string& error) { return get_path_param(params, "proposal_id", proposal_id, error); @@ -1292,8 +1334,9 @@ namespace ccf } const auto in = params.get(); - const auto proposal_id = get_next_id( - ctx.tx.get_view(this->network.values), ValueIds::NEXT_PROPOSAL_ID); + const auto proposal_id = + fmt::format("{:02x}", fmt::join(caller_identity.request_digest, "")); + Proposal proposal(in.script, in.parameter, caller_identity.member_id); auto proposals = ctx.tx.get_view(this->network.proposals); @@ -1321,7 +1364,7 @@ namespace ccf "Member is not active."); } - ObjectId proposal_id; + ProposalId proposal_id; std::string error; if (!get_proposal_id_from_path( ctx.rpc_ctx->get_request_path_params(), proposal_id, error)) @@ -1362,7 +1405,7 @@ namespace ccf "Member is not active."); } - ObjectId proposal_id; + ProposalId proposal_id; std::string error; if (!get_proposal_id_from_path( ctx.rpc_ctx->get_request_path_params(), proposal_id, error)) @@ -1435,7 +1478,7 @@ namespace ccf "Member is not active."); } - ObjectId proposal_id; + ProposalId proposal_id; std::string error; if (!get_proposal_id_from_path( ctx.rpc_ctx->get_request_path_params(), proposal_id, error)) @@ -1505,7 +1548,7 @@ namespace ccf } std::string error; - ObjectId proposal_id; + ProposalId proposal_id; if (!get_proposal_id_from_path( ctx.rpc_ctx->get_request_path_params(), proposal_id, error)) { @@ -1967,7 +2010,11 @@ namespace ccf } if (!set_jwt_public_signing_keys( - ctx.tx, INVALID_ID, parsed.issuer, issuer_metadata, parsed.jwks)) + ctx.tx, + INVALID_PROPOSAL_ID, + parsed.issuer, + issuer_metadata, + parsed.jwks)) { LOG_FAIL_FMT( "JWT key auto-refresh: error while storing signing keys for issuer " diff --git a/src/node/rpc/test/member_voting_test.cpp b/src/node/rpc/test/member_voting_test.cpp index ce7a17fedacf..59ebe6ecb2c5 100644 --- a/src/node/rpc/test/member_voting_test.cpp +++ b/src/node/rpc/test/member_voting_test.cpp @@ -169,7 +169,9 @@ auto frontend_process( } auto get_proposal( - MemberRpcFrontend& frontend, size_t proposal_id, const tls::Pem& caller) + MemberRpcFrontend& frontend, + const ProposalId& proposal_id, + const tls::Pem& caller) { const auto getter = create_request(nullptr, fmt::format("proposals/{}", proposal_id), HTTP_GET); @@ -180,7 +182,7 @@ auto get_proposal( auto get_vote( MemberRpcFrontend& frontend, - size_t proposal_id, + ProposalId proposal_id, MemberId voter, const tls::Pem& caller) { @@ -366,7 +368,7 @@ DOCTEST_TEST_CASE("Proposer ballot") MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - size_t proposal_id; + ProposalId proposal_id; const ccf::Script vote_for("return true"); const ccf::Script vote_against("return false"); @@ -468,7 +470,7 @@ DOCTEST_TEST_CASE("Reject duplicate vote") MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - size_t proposal_id; + ProposalId proposal_id; const ccf::Script vote_for("return true"); const ccf::Script vote_against("return false"); @@ -573,7 +575,6 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject") auto i = 0ul; for (auto& new_member : new_members) { - const auto proposal_id = i; new_member.id = initial_members + i++; // new member certificate @@ -600,12 +601,13 @@ DOCTEST_TEST_CASE("Add new members until there are 7 then reject") const auto propose = create_signed_request(proposal, "proposals", kp, member_cert); + ProposalId proposal_id; { const auto r = frontend_process(frontend, propose, member_cert); const auto result = parse_response_body(r); // the proposal should be accepted, but not succeed immediately - DOCTEST_CHECK(result.proposal_id == proposal_id); + proposal_id = result.proposal_id; DOCTEST_CHECK(result.state == ProposalState::OPEN); } @@ -801,7 +803,7 @@ DOCTEST_TEST_CASE("Accept node") } // m0 proposes adding new node - ObjectId trust_node_proposal_id; + ProposalId trust_node_proposal_id; { Script proposal(R"xxx( local tables, node_id = ... @@ -855,7 +857,7 @@ DOCTEST_TEST_CASE("Accept node") } // m0 proposes retire node - ObjectId retire_node_proposal_id; + ProposalId retire_node_proposal_id; { Script proposal(R"xxx( local tables, node_id = ... @@ -986,7 +988,7 @@ ProposalInfo test_raw_writes( } // propose - const auto proposal_id = 0ul; + ProposalId proposal_id; { const uint8_t proposer_id = 0; const auto propose = @@ -997,7 +999,7 @@ ProposalInfo test_raw_writes( const auto expected_state = (n_members == 1) ? ProposalState::ACCEPTED : ProposalState::OPEN; DOCTEST_CHECK(r.state == expected_state); - DOCTEST_CHECK(r.proposal_id == proposal_id); + proposal_id = r.proposal_id; if (r.state == ProposalState::ACCEPTED) return r; } @@ -1162,7 +1164,7 @@ DOCTEST_TEST_CASE("Remove proposal") gen.finalize(); MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - auto proposal_id = 0; + ProposalId proposal_id; auto wrong_proposal_id = 1; ccf::Script proposal_script(R"xxx( local tables, param = ... @@ -1182,7 +1184,7 @@ DOCTEST_TEST_CASE("Remove proposal") const auto r = parse_response_body( frontend_process(frontend, propose, member_cert)); - DOCTEST_CHECK(r.proposal_id == proposal_id); + proposal_id = r.proposal_id; DOCTEST_CHECK(r.state == ProposalState::OPEN); } @@ -1294,7 +1296,7 @@ DOCTEST_TEST_CASE("Vetoed proposal gets rejected") { DOCTEST_INFO("Check proposal was rejected"); - const auto proposal = get_proposal(frontend, 0, voter_a_cert); + const auto proposal = get_proposal(frontend, r.proposal_id, voter_a_cert); DOCTEST_CHECK(proposal.state == ProposalState::REJECTED); } @@ -1347,7 +1349,6 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls") frontend_process(frontend, vote, member_cert)); DOCTEST_CHECK(r.state == ProposalState::ACCEPTED); - DOCTEST_CHECK(r.proposal_id == 0); auto tx1 = network.tables->create_tx(); const auto uid = tx1.get_view(network.values)->get(ValueIds::NEXT_USER_ID); @@ -1385,7 +1386,6 @@ DOCTEST_TEST_CASE("Add and remove user via proposed calls") frontend_process(frontend, vote, member_cert)); DOCTEST_CHECK(r.state == ProposalState::ACCEPTED); - DOCTEST_CHECK(r.proposal_id == 1); auto tx1 = network.tables->create_tx(); auto user = tx1.get_view(network.users)->get(0); @@ -1440,7 +1440,7 @@ DOCTEST_TEST_CASE( MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - size_t proposal_id; + ProposalId proposal_id; size_t proposer_id = 1; size_t voter_id = 2; @@ -1578,7 +1578,7 @@ DOCTEST_TEST_CASE("Passing operator change" * doctest::test_suite("operator")) MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - size_t proposal_id; + ProposalId proposal_id; const ccf::Script vote_for("return true"); const ccf::Script vote_against("return false"); @@ -1759,7 +1759,7 @@ DOCTEST_TEST_CASE( MemberRpcFrontend frontend(network, node, share_manager); frontend.open(); - size_t proposal_id; + ProposalId proposal_id; const ccf::Script vote_for("return true"); const ccf::Script vote_against("return false"); diff --git a/src/tls/verifier.h b/src/tls/verifier.h index 45a5dbbe65c4..8bd3472b1e49 100644 --- a/src/tls/verifier.h +++ b/src/tls/verifier.h @@ -121,6 +121,21 @@ namespace tls md_type); } + bool verify( + const std::vector& contents, + const std::vector& signature, + mbedtls_md_type_t md_type, + HashBytes& hash_bytes) const + { + return verify( + contents.data(), + contents.size(), + signature.data(), + signature.size(), + md_type, + hash_bytes); + } + bool verify( const uint8_t* contents, size_t contents_size, @@ -134,6 +149,18 @@ namespace tls return verify_hash(hash, sig, sig_size, md_type); } + bool verify( + const uint8_t* contents, + size_t contents_size, + const uint8_t* sig, + size_t sig_size, + mbedtls_md_type_t md_type, + HashBytes& hash_bytes) const + { + do_hash(cert->pk, contents, contents_size, hash_bytes, md_type); + return verify_hash(hash_bytes, sig, sig_size, md_type); + } + const mbedtls_x509_crt* raw() { return cert.get(); diff --git a/tests/infra/consortium.py b/tests/infra/consortium.py index efb55d62709d..ad0ba52e3ab7 100644 --- a/tests/infra/consortium.py +++ b/tests/infra/consortium.py @@ -269,7 +269,7 @@ def get_proposals(self, remote_node): for proposal_id, attr in r.body.json().items(): proposals.append( infra.proposal.Proposal( - proposal_id=int(proposal_id), + proposal_id=proposal_id, proposer_id=int(attr["proposer"]), state=infra.proposal.ProposalState(attr["state"]), )