CCF
Loading...
Searching...
No Matches
acks.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
8#include "node/history.h"
11
12namespace ccf::gov::endpoints
13{
14 inline void init_ack_handlers(
16 NetworkState& /*network*/,
17 ShareManager& share_manager)
18 {
19 auto get_state_digest = [&](auto& ctx, ApiVersion api_version) {
20 switch (api_version)
21 {
23 case ApiVersion::v1:
24 default:
25 {
26 // Get memberId from path parameter
27 std::string error;
28 std::string member_id_str;
30 ctx.rpc_ctx->get_request_path_params(),
31 "memberId",
32 member_id_str,
33 error))
34 {
35 detail::set_gov_error(
36 ctx.rpc_ctx,
37 HTTP_STATUS_BAD_REQUEST,
38 ccf::errors::InvalidResourceName,
39 std::move(error));
40 return;
41 }
42
43 // Read member's ack from KV
44 ccf::MemberId member_id(member_id_str);
45 auto acks_handle =
46 ctx.tx.template ro<ccf::MemberAcks>(Tables::MEMBER_ACKS);
47 auto ack = acks_handle->get(member_id);
48 if (!ack.has_value())
49 {
50 detail::set_gov_error(
51 ctx.rpc_ctx,
52 HTTP_STATUS_NOT_FOUND,
53 ccf::errors::ResourceNotFound,
54 fmt::format("No ACK record exists for member {}.", member_id));
55 return;
56 }
57
58 auto response_body = nlohmann::json::object();
59 response_body["memberId"] = member_id_str;
60 response_body["stateDigest"] = ack->state_digest;
61 ctx.rpc_ctx->set_response_json(response_body, HTTP_STATUS_OK);
62 return;
63 }
64 }
65 };
66 registry
68 "/members/state-digests/{memberId}",
69 HTTP_GET,
70 api_version_adapter(get_state_digest),
71 no_auth_required)
73 .install();
74
75 auto update_state_digest = [&](auto& ctx, ApiVersion api_version) {
76 switch (api_version)
77 {
79 case ApiVersion::v1:
80 default:
81 {
82 // Get memberId from path parameter
83 std::string error;
84 std::string member_id_str;
86 ctx.rpc_ctx->get_request_path_params(),
87 "memberId",
88 member_id_str,
89 error))
90 {
91 detail::set_gov_error(
92 ctx.rpc_ctx,
93 HTTP_STATUS_BAD_REQUEST,
94 ccf::errors::InvalidResourceName,
95 std::move(error));
96 return;
97 }
98
99 // Confirm this matches memberId from signature
100 ccf::MemberId member_id(member_id_str);
101 const auto& cose_ident =
102 ctx.template get_caller<ccf::MemberCOSESign1AuthnIdentity>();
103 if (cose_ident.member_id != member_id)
104 {
105 detail::set_gov_error(
106 ctx.rpc_ctx,
107 HTTP_STATUS_BAD_REQUEST,
108 ccf::errors::InvalidAuthenticationInfo,
109 fmt::format(
110 "Member ID from path parameter ({}) does not match "
111 "member ID from body signature ({}).",
112 member_id,
113 cose_ident.member_id));
114 return;
115 }
116
117 ccf::MemberAck ack;
118
119 // Get previous ack, if it exists
120 auto acks_handle =
121 ctx.tx.template rw<ccf::MemberAcks>(Tables::MEMBER_ACKS);
122 auto ack_opt = acks_handle->get(member_id);
123 if (ack_opt.has_value())
124 {
125 ack = ack_opt.value();
126 }
127
128 // Get merkle root state digest from serialised merkle tree
129 auto tree_handle = ctx.tx.template ro<ccf::SerialisedMerkleTree>(
130 Tables::SERIALISED_MERKLE_TREE);
131 auto tree = tree_handle->get();
132 if (!tree.has_value())
133 {
134 detail::set_gov_error(
135 ctx.rpc_ctx,
136 HTTP_STATUS_INTERNAL_SERVER_ERROR,
137 ccf::errors::InternalError,
138 "Service has no signatures to ack yet - try again soon.");
139 return;
140 }
141 ccf::MerkleTreeHistory history(tree.value());
142
143 // Write ack back to the KV
144 ack.state_digest = history.get_root().hex_str();
145 acks_handle->put(member_id, ack);
146
147 auto body = nlohmann::json::object();
148 body["memberId"] = member_id_str;
149 body["stateDigest"] = ack.state_digest;
150 ctx.rpc_ctx->set_response_json(body, HTTP_STATUS_OK);
151 return;
152 }
153 }
154 };
155 registry
157 "/members/state-digests/{memberId}:update",
158 HTTP_POST,
159 api_version_adapter(update_state_digest),
160 detail::member_sig_only_policies("state_digest"))
161 .set_openapi_hidden(true)
162 .install();
163
164 auto ack_state_digest = [&](auto& ctx, ApiVersion api_version) {
165 switch (api_version)
166 {
168 case ApiVersion::v1:
169 default:
170 {
171 // Get memberId from path parameter
172 std::string error;
173 std::string member_id_str;
175 ctx.rpc_ctx->get_request_path_params(),
176 "memberId",
177 member_id_str,
178 error))
179 {
180 detail::set_gov_error(
181 ctx.rpc_ctx,
182 HTTP_STATUS_BAD_REQUEST,
183 ccf::errors::InvalidResourceName,
184 std::move(error));
185 return;
186 }
187
188 // Confirm this matches memberId from signature
189 ccf::MemberId member_id(member_id_str);
190 const auto& cose_ident =
191 ctx.template get_caller<ccf::MemberCOSESign1AuthnIdentity>();
192 if (cose_ident.member_id != member_id)
193 {
194 detail::set_gov_error(
195 ctx.rpc_ctx,
196 HTTP_STATUS_BAD_REQUEST,
197 ccf::errors::InvalidAuthenticationInfo,
198 fmt::format(
199 "Member ID from path parameter ({}) does not match "
200 "member ID from body signature ({}).",
201 member_id,
202 cose_ident.member_id));
203 return;
204 }
205
206 // Check an expected digest for this member is in the KV
207 auto acks_handle =
208 ctx.tx.template rw<ccf::MemberAcks>(Tables::MEMBER_ACKS);
209 auto ack = acks_handle->get(member_id);
210 if (!ack.has_value())
211 {
212 detail::set_gov_error(
213 ctx.rpc_ctx,
214 HTTP_STATUS_FORBIDDEN,
215 ccf::errors::AuthorizationFailed,
216 fmt::format("No ACK record exists for member {}.", member_id));
217 return;
218 }
219
220 // Check signed digest matches expected digest in KV
221 const auto expected_digest = ack->state_digest;
222 const auto signed_body = nlohmann::json::parse(cose_ident.content);
223 const auto actual_digest =
224 signed_body["stateDigest"].template get<std::string>();
225 if (expected_digest != actual_digest)
226 {
227 detail::set_gov_error(
228 ctx.rpc_ctx,
229 HTTP_STATUS_BAD_REQUEST,
230 ccf::errors::StateDigestMismatch,
231 fmt::format(
232 "Submitted state digest is not valid.\n"
233 "Expected\n"
234 " {}\n"
235 "Received\n"
236 " {}",
237 expected_digest,
238 actual_digest));
239 return;
240 }
241
242 // Ensure old HTTP signed req is nulled
243 ack->signed_req = std::nullopt;
244
245 // Insert new signature
246 ack->cose_sign1_req = std::vector<uint8_t>(
247 cose_ident.envelope.begin(), cose_ident.envelope.end());
248
249 // Store signed ACK in KV
250 acks_handle->put(member_id, ack.value());
251
252 // Update member details
253 {
254 // Update member status to ACTIVE
255 bool newly_active = false;
256 try
257 {
258 newly_active =
259 InternalTablesAccess::activate_member(ctx.tx, member_id);
260 }
261 catch (const std::logic_error& e)
262 {
263 detail::set_gov_error(
264 ctx.rpc_ctx,
265 HTTP_STATUS_INTERNAL_SERVER_ERROR,
266 ccf::errors::InternalError,
267 fmt::format("Error activating member: {}", e.what()));
268 return;
269 }
270
271 // If this is a newly-active recovery participant/owner in an open
272 // service, allocate them a recovery share immediately
273 if (
274 newly_active &&
276 ctx.tx, member_id))
277 {
278 auto service_status =
280 if (!service_status.has_value())
281 {
282 detail::set_gov_error(
283 ctx.rpc_ctx,
284 HTTP_STATUS_INTERNAL_SERVER_ERROR,
285 ccf::errors::InternalError,
286 "No service currently available.");
287 return;
288 }
289
290 if (service_status.value() == ServiceStatus::OPEN)
291 {
292 try
293 {
294 share_manager.shuffle_recovery_shares(ctx.tx);
295 }
296 catch (const std::logic_error& e)
297 {
298 detail::set_gov_error(
299 ctx.rpc_ctx,
300 HTTP_STATUS_INTERNAL_SERVER_ERROR,
301 ccf::errors::InternalError,
302 fmt::format(
303 "Error issuing new recovery shares: {}", e.what()));
304 return;
305 }
306 }
307 }
308 }
309
310 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
311 return;
312 break;
313 }
314 }
315 };
316 registry
318 "/members/state-digests/{memberId}:ack",
319 HTTP_POST,
320 api_version_adapter(ack_state_digest),
321 {std::make_shared<MemberCOSESign1AuthnPolicy>("ack")})
322 .set_openapi_hidden(true)
323 .install();
324 }
325}
Definition base_endpoint_registry.h:121
static std::optional< ServiceStatus > get_service_status(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:741
static bool activate_member(ccf::kv::Tx &tx, const MemberId &member_id)
Definition internal_tables_access.h:266
static bool is_recovery_participant_or_owner(ccf::kv::ReadOnlyTx &tx, const MemberId &member_id)
Definition internal_tables_access.h:79
Definition history.h:436
ccf::crypto::Sha256Hash get_root() const
Definition history.h:462
Definition share_manager.h:169
std::string hex_str() const
Definition sha256_hash.cpp:57
virtual Endpoint make_endpoint(const std::string &method, RESTVerb verb, const EndpointFunction &f, const AuthnPolicies &ap)
Definition endpoint_registry.cpp:282
virtual Endpoint make_read_only_endpoint(const std::string &method, RESTVerb verb, const ReadOnlyEndpointFunction &f, const AuthnPolicies &ap)
Definition endpoint_registry.cpp:313
bool get_path_param(const ccf::PathParams &params, const std::string &param_name, T &value, std::string &error)
Definition endpoint_registry.h:75
AuthnPolicies member_sig_only_policies(const std::string &gov_msg_type)
Definition helpers.h:11
Definition api_version.h:11
ApiVersion
Definition api_version.h:13
auto api_version_adapter(Fn &&f, ApiVersion min_accepted=ApiVersion::MIN)
Definition api_version.h:101
void init_ack_handlers(ccf::BaseEndpointRegistry &registry, NetworkState &, ShareManager &share_manager)
Definition acks.h:14
@ error
Definition tls_session.h:23
Value & value()
Definition entity_id.h:67
Definition members.h:128
Definition network_state.h:12
std::string state_digest
Next state digest the member is expected to sign.
Definition members.h:116
Endpoint & set_openapi_hidden(bool hidden)
Definition endpoint.cpp:10
void install()
Definition endpoint.cpp:135