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