CCF
Loading...
Searching...
No Matches
share_manager.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
7#include "ccf/crypto/sha256.h"
9#include "ccf/ds/logger.h"
10#include "crypto/sharing.h"
11#include "kv/encryptor.h"
12#include "ledger_secrets.h"
13#include "network_state.h"
14#include "secret_share.h"
16
17#include <openssl/crypto.h>
18#include <vector>
19
20namespace ccf
21{
23 {
24 private:
25 static constexpr auto KZ_KEY_SIZE = ccf::crypto::GCM_DEFAULT_KEY_SIZE;
26 bool has_wrapped = false;
27 size_t num_shares;
28 size_t recovery_threshold;
29 std::vector<uint8_t> data; // Referred to as "kz" in TR
30 std::vector<ccf::crypto::sharing::Share> shares;
31
32 public:
33 LedgerSecretWrappingKey(size_t num_shares_, size_t recovery_threshold_) :
34 num_shares(num_shares_),
35 recovery_threshold(recovery_threshold_)
36 {
37 shares.resize(num_shares);
40 secret, shares, recovery_threshold);
41 data = secret.key(KZ_KEY_SIZE);
42 }
43
45 std::vector<ccf::crypto::sharing::Share>&& shares_,
46 size_t recovery_threshold_) :
47 recovery_threshold(recovery_threshold_)
48 {
49 shares = shares_;
52 secret, shares, recovery_threshold);
53 data = secret.key(KZ_KEY_SIZE);
54 }
55
57 std::vector<SecretSharing::Share>&& shares_, size_t recovery_threshold_) :
58 recovery_threshold(recovery_threshold_)
59 {
60 auto secret = SecretSharing::combine(shares_, shares_.size());
61 data.resize(secret.size());
62 std::copy_n(secret.begin(), secret.size(), data.begin());
63 OPENSSL_cleanse(secret.data(), secret.size());
64 }
65
67 {
68 OPENSSL_cleanse(data.data(), data.size());
69 }
70
71 size_t get_num_shares() const
72 {
73 return num_shares;
74 }
75
77 {
78 return recovery_threshold;
79 }
80
81 std::vector<std::vector<uint8_t>> get_shares() const
82 {
83 std::vector<std::vector<uint8_t>> shares_;
84 for (const ccf::crypto::sharing::Share& share : shares)
85 {
86 shares_.emplace_back(share.serialise());
87 }
88 return shares_;
89 }
90
91 template <typename T>
92 T get_raw_data() const
93 {
94 T ret;
95 std::copy_n(data.begin(), data.size(), ret.begin());
96 return ret;
97 }
98
99 std::vector<uint8_t> wrap(const LedgerSecretPtr& ledger_secret)
100 {
101 if (has_wrapped)
102 {
103 throw std::logic_error(
104 "Ledger secret wrapping key has already wrapped once");
105 }
106
107 ccf::crypto::GcmCipher encrypted_ls(ledger_secret->raw_key.size());
108
109 ccf::crypto::make_key_aes_gcm(data)->encrypt(
110 encrypted_ls.hdr.get_iv(), // iv is always 0 here as the share wrapping
111 // key is never re-used for encryption
112 ledger_secret->raw_key,
113 {},
114 encrypted_ls.cipher,
115 encrypted_ls.hdr.tag);
116
117 has_wrapped = true;
118
119 return encrypted_ls.serialise();
120 }
121
123 const std::vector<uint8_t>& wrapped_latest_ledger_secret)
124 {
125 ccf::crypto::GcmCipher encrypted_ls;
126 encrypted_ls.deserialise(wrapped_latest_ledger_secret);
127 std::vector<uint8_t> decrypted_ls;
128
129 if (!ccf::crypto::make_key_aes_gcm(data)->decrypt(
130 encrypted_ls.hdr.get_iv(),
131 encrypted_ls.hdr.tag,
132 encrypted_ls.cipher,
133 {},
134 decrypted_ls))
135 {
136 throw std::logic_error("Unwrapping latest ledger secret failed");
137 }
138
139 return std::make_shared<LedgerSecret>(std::move(decrypted_ls));
140 }
141 };
142
143 // During recovery, a list of EncryptedLedgerSecretInfo is constructed
144 // from the local hook on the encrypted ledger secrets table.
145 using RecoveredEncryptedLedgerSecrets = std::list<EncryptedLedgerSecretInfo>;
146
147 // The ShareManager class provides the interface between the ledger secrets
148 // object and the shares, ledger secrets and submitted shares KV tables. In
149 // particular, it is used to:
150 // - Issue new recovery shares whenever required (e.g. on startup, rekey and
151 // membership updates)
152 // - Re-assemble the ledger secrets on recovery, once a threshold of members
153 // have successfully submitted their shares
155 {
156 private:
157 std::shared_ptr<LedgerSecrets> ledger_secrets;
158
159 EncryptedSharesMap compute_encrypted_shares(
160 ccf::kv::Tx& tx, const LedgerSecretWrappingKey& ls_wrapping_key)
161 {
162 EncryptedSharesMap encrypted_shares;
163 auto shares = ls_wrapping_key.get_shares();
164
165 auto active_recovery_members_info =
167
168 size_t share_index = 0;
169 for (auto const& [member_id, enc_pub_key] : active_recovery_members_info)
170 {
171 auto member_enc_pubk = ccf::crypto::make_rsa_public_key(enc_pub_key);
172 auto raw_share = std::vector<uint8_t>(
173 shares[share_index].begin(), shares[share_index].end());
174 encrypted_shares[member_id] = member_enc_pubk->rsa_oaep_wrap(raw_share);
175 OPENSSL_cleanse(raw_share.data(), raw_share.size());
176 OPENSSL_cleanse(shares[share_index].data(), shares[share_index].size());
177 share_index++;
178 }
179
180 return encrypted_shares;
181 }
182
183 void shuffle_recovery_shares(
184 ccf::kv::Tx& tx, const LedgerSecretPtr& latest_ledger_secret)
185 {
186 auto active_recovery_members_info =
188 size_t recovery_threshold =
190
191 if (active_recovery_members_info.empty())
192 {
193 throw std::logic_error(
194 "There should be at least one active recovery member to issue "
195 "recovery shares");
196 }
197
198 if (recovery_threshold == 0)
199 {
200 throw std::logic_error(
201 "Recovery threshold should be set before recovery "
202 "shares are computed");
203 }
204
205 if (recovery_threshold > active_recovery_members_info.size())
206 {
207 throw std::logic_error(fmt::format(
208 "Recovery threshold {} should be equal to or less than the number of "
209 "active recovery members {}",
210 recovery_threshold,
211 active_recovery_members_info.size()));
212 }
213
214 const auto num_shares = active_recovery_members_info.size();
215 auto ls_wrapping_key =
216 LedgerSecretWrappingKey(num_shares, recovery_threshold);
217
218 auto wrapped_latest_ls = ls_wrapping_key.wrap(latest_ledger_secret);
219 auto recovery_shares = tx.rw<ccf::RecoveryShares>(Tables::SHARES);
220 recovery_shares->put(
221 {wrapped_latest_ls,
222 compute_encrypted_shares(tx, ls_wrapping_key),
223 latest_ledger_secret->previous_secret_stored_version});
224 }
225
226 void set_recovery_shares_info(
227 ccf::kv::Tx& tx,
228 const LedgerSecretPtr& latest_ledger_secret,
229 const std::optional<VersionedLedgerSecret>& previous_ledger_secret =
230 std::nullopt,
231 std::optional<ccf::kv::Version> latest_ls_version = std::nullopt)
232 {
233 // First, generate a fresh ledger secrets wrapping key and wrap the
234 // latest ledger secret with it. Then, encrypt the penultimate ledger
235 // secret with the latest ledger secret and split the ledger secret
236 // wrapping key, allocating a new share for each active recovery member.
237 // Finally, encrypt each share with the public key of each member and
238 // record it in the shares table.
239
240 shuffle_recovery_shares(tx, latest_ledger_secret);
241
242 auto encrypted_ls = tx.rw<ccf::EncryptedLedgerSecretsInfo>(
243 Tables::ENCRYPTED_PAST_LEDGER_SECRET);
244
245 std::vector<uint8_t> encrypted_previous_secret = {};
246 ccf::kv::Version version_previous_secret = ccf::kv::NoVersion;
247 if (previous_ledger_secret.has_value())
248 {
249 version_previous_secret = previous_ledger_secret->first;
250
251 ccf::crypto::GcmCipher encrypted_previous_ls(
252 previous_ledger_secret->second->raw_key.size());
253 encrypted_previous_ls.hdr.set_random_iv();
254
255 latest_ledger_secret->key->encrypt(
256 encrypted_previous_ls.hdr.get_iv(),
257 previous_ledger_secret->second->raw_key,
258 {},
259 encrypted_previous_ls.cipher,
260 encrypted_previous_ls.hdr.tag);
261
262 encrypted_previous_secret = encrypted_previous_ls.serialise();
263 encrypted_ls->put(
265 std::move(encrypted_previous_secret),
266 version_previous_secret,
267 encrypted_ls->get_version_of_previous_write()),
268 latest_ls_version});
269 }
270 else
271 {
272 encrypted_ls->put({std::nullopt, latest_ls_version});
273 }
274 }
275
276 std::vector<uint8_t> encrypt_submitted_share(
277 const std::vector<uint8_t>& submitted_share,
278 const LedgerSecretPtr& current_ledger_secret)
279 {
280 // Submitted recovery shares are encrypted with the latest ledger secret.
281 ccf::crypto::GcmCipher encrypted_submitted_share(submitted_share.size());
282
283 encrypted_submitted_share.hdr.set_random_iv();
284
285 current_ledger_secret->key->encrypt(
286 encrypted_submitted_share.hdr.get_iv(),
287 submitted_share,
288 {},
289 encrypted_submitted_share.cipher,
290 encrypted_submitted_share.hdr.tag);
291
292 return encrypted_submitted_share.serialise();
293 }
294
295 std::vector<uint8_t> decrypt_submitted_share(
296 const std::vector<uint8_t>& encrypted_submitted_share,
297 LedgerSecretPtr&& current_ledger_secret)
298 {
299 ccf::crypto::GcmCipher encrypted_share;
300 encrypted_share.deserialise(encrypted_submitted_share);
301 std::vector<uint8_t> decrypted_share;
302
303 current_ledger_secret->key->decrypt(
304 encrypted_share.hdr.get_iv(),
305 encrypted_share.hdr.tag,
306 encrypted_share.cipher,
307 {},
308 decrypted_share);
309
310 return decrypted_share;
311 }
312
313 LedgerSecretWrappingKey combine_from_encrypted_submitted_shares(
314 ccf::kv::Tx& tx)
315 {
316 auto encrypted_submitted_shares = tx.rw<ccf::EncryptedSubmittedShares>(
317 Tables::ENCRYPTED_SUBMITTED_SHARES);
318 auto config = tx.rw<ccf::Configuration>(Tables::CONFIGURATION);
319
320 std::vector<ccf::crypto::sharing::Share> new_shares = {};
321 std::vector<SecretSharing::Share> old_shares = {};
322 // Defensively allow shares in both formats for the time being, even if we
323 // get a mix, and so long as we have enough of one or the other, attempt
324 // to reassemble the secret. We only try with the most numerous kind of
325 // share, we won't try with the minority even if it meets the threshold
326 // too.
327 encrypted_submitted_shares->foreach(
328 [&new_shares, &old_shares, &tx, this](
329 const MemberId, const EncryptedSubmittedShare& encrypted_share) {
330 auto decrypted_share = decrypt_submitted_share(
331 encrypted_share, ledger_secrets->get_latest(tx).second);
332 switch (decrypted_share.size())
333 {
335 {
336 new_shares.emplace_back(decrypted_share);
337 break;
338 }
340 {
342 std::copy_n(
343 decrypted_share.begin(),
345 share.begin());
346 old_shares.emplace_back(std::move(share));
347 break;
348 }
349 default:
350 {
351 OPENSSL_cleanse(decrypted_share.data(), decrypted_share.size());
352 throw std::logic_error(fmt::format(
353 "Error combining recovery shares: decrypted share of {} bytes "
354 "is neither a new-style share of {} bytes nor an old-style "
355 "share of {} bytes",
356 decrypted_share.size(),
359 }
360 }
361 OPENSSL_cleanse(decrypted_share.data(), decrypted_share.size());
362 return true;
363 });
364
365 auto num_shares = std::max(old_shares.size(), new_shares.size());
366
367 auto recovery_threshold = config->get()->recovery_threshold;
368 if (recovery_threshold > num_shares)
369 {
370 throw std::logic_error(fmt::format(
371 "Error combining recovery shares: only {} recovery shares were "
372 "submitted but recovery threshold is {}",
373 num_shares,
374 recovery_threshold));
375 }
376
377 if (new_shares.size() > old_shares.size())
378 {
380 std::move(new_shares), recovery_threshold);
381 }
382 else
383 {
385 std::move(old_shares), recovery_threshold);
386 }
387 }
388
389 public:
390 ShareManager(const std::shared_ptr<LedgerSecrets>& ledger_secrets_) :
391 ledger_secrets(ledger_secrets_)
392 {}
393
401 {
402 auto [latest, penultimate] =
403 ledger_secrets->get_latest_and_penultimate(tx);
404
405 set_recovery_shares_info(tx, latest.second, penultimate, latest.first);
406 }
407
420 ccf::kv::Tx& tx, LedgerSecretPtr new_ledger_secret)
421 {
422 set_recovery_shares_info(
423 tx, new_ledger_secret, ledger_secrets->get_latest(tx));
424 }
425
433 {
434 shuffle_recovery_shares(tx, ledger_secrets->get_latest(tx).second);
435 }
436
437 static std::optional<EncryptedShare> get_encrypted_share(
438 ccf::kv::ReadOnlyTx& tx, const MemberId& member_id)
439 {
440 auto recovery_shares_info =
441 tx.ro<ccf::RecoveryShares>(Tables::SHARES)->get();
442 if (!recovery_shares_info.has_value())
443 {
444 throw std::logic_error(
445 "Failed to retrieve current recovery shares info");
446 }
447
448 auto search = recovery_shares_info->encrypted_shares.find(member_id);
449 if (search == recovery_shares_info->encrypted_shares.end())
450 {
451 return std::nullopt;
452 }
453
454 return search->second;
455 }
456
458 ccf::kv::Tx& tx,
459 const RecoveredEncryptedLedgerSecrets& recovery_ledger_secrets)
460 {
461 // First, re-assemble the ledger secret wrapping key from the submitted
462 // encrypted shares. Then, unwrap the latest ledger secret and use it to
463 // decrypt the sequence of recovered ledger secrets, from the last one.
464
465 if (recovery_ledger_secrets.empty())
466 {
467 throw std::logic_error("No recovery ledger secrets");
468 }
469
470 auto recovery_shares_info =
471 tx.ro<ccf::RecoveryShares>(Tables::SHARES)->get();
472 if (!recovery_shares_info.has_value())
473 {
474 throw std::logic_error(
475 "Failed to retrieve current recovery shares info");
476 }
477
478 auto restored_ls = combine_from_encrypted_submitted_shares(tx).unwrap(
479 recovery_shares_info->wrapped_latest_ledger_secret);
480
482 "Recovering {} encrypted ledger secrets",
483 recovery_ledger_secrets.size());
484
485 auto& current_ledger_secret_version =
486 recovery_ledger_secrets.back().next_version;
487 if (!current_ledger_secret_version.has_value())
488 {
489 // This should always be set by the recovery hook, which sets this to
490 // the version at which it is called if unset in the store
491 throw std::logic_error("Current ledger secret version should be set");
492 }
493
494 auto encrypted_previous_ledger_secret =
496 Tables::ENCRYPTED_PAST_LEDGER_SECRET);
497
498 LedgerSecretsMap restored_ledger_secrets = {};
499 auto s = restored_ledger_secrets.emplace(
500 current_ledger_secret_version.value(),
501 std::make_shared<LedgerSecret>(
502 std::move(restored_ls->raw_key),
503 encrypted_previous_ledger_secret->get_version_of_previous_write()));
504 auto latest_ls = s.first->second;
505
506 for (auto it = recovery_ledger_secrets.rbegin();
507 it != recovery_ledger_secrets.rend();
508 it++)
509 {
511 "Recovering encrypted ledger secret valid at seqno {}",
512 it->previous_ledger_secret->version);
513
514 if (!it->previous_ledger_secret.has_value())
515 {
516 // Very first entry does not encrypt any other ledger secret
517 break;
518 }
519
520 if (
521 restored_ledger_secrets.find(it->previous_ledger_secret->version) !=
522 restored_ledger_secrets.end())
523 {
524 // Already decrypted this ledger secret
526 "Skipping, already decrypted ledger secret with version {}",
527 it->previous_ledger_secret->version);
528 continue;
529 }
530
531 auto decrypted_ls_raw = decrypt_previous_ledger_secret_raw(
532 latest_ls, it->previous_ledger_secret->encrypted_data);
533
534 auto secret = restored_ledger_secrets.emplace(
535 it->previous_ledger_secret->version,
536 std::make_shared<LedgerSecret>(
537 std::move(decrypted_ls_raw),
538 it->previous_ledger_secret->previous_secret_stored_version));
539 latest_ls = secret.first->second;
540 }
541
542 return restored_ledger_secrets;
543 }
544
546 ccf::kv::Tx& tx,
547 MemberId member_id,
548 const std::vector<uint8_t>& submitted_recovery_share)
549 {
550 auto service = tx.rw<ccf::Service>(Tables::SERVICE);
551 auto encrypted_submitted_shares = tx.rw<ccf::EncryptedSubmittedShares>(
552 Tables::ENCRYPTED_SUBMITTED_SHARES);
553 auto active_service = service->get();
554 if (!active_service.has_value())
555 {
556 throw std::logic_error("Failed to get active service");
557 }
558
559 encrypted_submitted_shares->put(
560 member_id,
561 encrypt_submitted_share(
562 submitted_recovery_share, ledger_secrets->get_latest(tx).second));
563
564 return encrypted_submitted_shares->size();
565 }
566
568 {
569 auto encrypted_submitted_shares = tx.rw<ccf::EncryptedSubmittedShares>(
570 Tables::ENCRYPTED_SUBMITTED_SHARES);
571 encrypted_submitted_shares->clear();
572 }
573 };
574}
static std::map< MemberId, ccf::crypto::Pem > get_active_recovery_members(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:79
static size_t get_recovery_threshold(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:583
Definition share_manager.h:23
LedgerSecretPtr unwrap(const std::vector< uint8_t > &wrapped_latest_ledger_secret)
Definition share_manager.h:122
LedgerSecretWrappingKey(std::vector< ccf::crypto::sharing::Share > &&shares_, size_t recovery_threshold_)
Definition share_manager.h:44
LedgerSecretWrappingKey(size_t num_shares_, size_t recovery_threshold_)
Definition share_manager.h:33
std::vector< std::vector< uint8_t > > get_shares() const
Definition share_manager.h:81
~LedgerSecretWrappingKey()
Definition share_manager.h:66
T get_raw_data() const
Definition share_manager.h:92
size_t get_num_shares() const
Definition share_manager.h:71
size_t get_recovery_threshold() const
Definition share_manager.h:76
LedgerSecretWrappingKey(std::vector< SecretSharing::Share > &&shares_, size_t recovery_threshold_)
Definition share_manager.h:56
std::vector< uint8_t > wrap(const LedgerSecretPtr &ledger_secret)
Definition share_manager.h:99
std::array< uint8_t, SHARE_LENGTH > Share
Definition secret_share.h:43
static SplitSecret combine(std::vector< Share > &shares, size_t k)
Definition secret_share.h:74
static constexpr size_t SHARE_LENGTH
Definition secret_share.h:40
Definition share_manager.h:155
LedgerSecretsMap restore_recovery_shares_info(ccf::kv::Tx &tx, const RecoveredEncryptedLedgerSecrets &recovery_ledger_secrets)
Definition share_manager.h:457
void shuffle_recovery_shares(ccf::kv::Tx &tx)
Definition share_manager.h:432
static std::optional< EncryptedShare > get_encrypted_share(ccf::kv::ReadOnlyTx &tx, const MemberId &member_id)
Definition share_manager.h:437
void issue_recovery_shares(ccf::kv::Tx &tx, LedgerSecretPtr new_ledger_secret)
Definition share_manager.h:419
size_t submit_recovery_share(ccf::kv::Tx &tx, MemberId member_id, const std::vector< uint8_t > &submitted_recovery_share)
Definition share_manager.h:545
static void clear_submitted_recovery_shares(ccf::kv::Tx &tx)
Definition share_manager.h:567
ShareManager(const std::shared_ptr< LedgerSecrets > &ledger_secrets_)
Definition share_manager.h:390
void issue_recovery_shares(ccf::kv::Tx &tx)
Definition share_manager.h:400
Definition tx.h:161
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:170
Definition tx.h:202
M::Handle * rw(M &m)
Definition tx.h:213
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_DEBUG_FMT
Definition logger.h:380
void sample_secret_and_shares(Share &raw_secret, const std::span< Share > &shares, size_t threshold)
Definition sharing.cpp:130
void recover_unauthenticated_secret(Share &raw_secret, const std::span< Share const > &shares, size_t threshold)
Definition sharing.cpp:165
std::unique_ptr< KeyAesGcm > make_key_aes_gcm(std::span< const uint8_t > rawKey)
Free function implementation.
Definition symmetric_key.cpp:97
RSAPublicKeyPtr make_rsa_public_key(const Pem &pem)
Definition rsa_key_pair.cpp:13
constexpr size_t GCM_DEFAULT_KEY_SIZE
Definition symmetric_key.h:12
uint64_t Version
Definition version.h:8
Definition app_interface.h:15
ServiceValue< ServiceInfo > Service
Definition service.h:55
std::list< EncryptedLedgerSecretInfo > RecoveredEncryptedLedgerSecrets
Definition share_manager.h:145
ServiceMap< MemberId, EncryptedSubmittedShare > EncryptedSubmittedShares
Definition submitted_shares.h:19
std::map< ccf::kv::Version, LedgerSecretPtr > LedgerSecretsMap
Definition ledger_secrets.h:19
std::map< MemberId, EncryptedShare > EncryptedSharesMap
Definition shares.h:14
ServiceValue< EncryptedLedgerSecretInfo > EncryptedLedgerSecretsInfo
Definition shares.h:118
std::shared_ptr< LedgerSecret > LedgerSecretPtr
Definition ledger_secret.h:75
std::vector< uint8_t > EncryptedSubmittedShare
Definition submitted_shares.h:17
std::vector< uint8_t > decrypt_previous_ledger_secret_raw(const LedgerSecretPtr &ledger_secret, const std::vector< uint8_t > &encrypted_previous_secret_raw)
Definition ledger_secret.h:83
Definition shares.h:37
void set_random_iv(EntropyPtr entropy=ccf::crypto::get_entropy())
Definition symmetric_key.h:47
Definition symmetric_key.h:57
void deserialise(const std::vector< uint8_t > &serial)
Definition symmetric_key.cpp:88
std::vector< uint8_t > serialise()
Definition symmetric_key.cpp:74
StandardGcmHeader hdr
Definition symmetric_key.h:58
std::vector< uint8_t > cipher
Definition symmetric_key.h:59
uint8_t tag[GCM_SIZE_TAG]
Definition symmetric_key.h:18
std::span< const uint8_t > get_iv() const
Definition symmetric_key.cpp:32
Definition sharing.h:28
static constexpr size_t serialised_size
Definition sharing.h:32
HashBytes key(size_t key_size) const
Definition sharing.h:43
Definition kv_types.h:82