From 8bc7b3de0c0990326f72933afe9ab44de9c137f8 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 27 Sep 2024 10:25:29 +0000 Subject: [PATCH 01/43] WIP COSE back-endorsements interface --- include/ccf/historical_queries_utils.h | 11 +++++++++++ src/node/historical_queries_utils.cpp | 10 ++++++++++ src/node/tx_receipt_impl.h | 1 + src/service/internal_tables_access.h | 1 + src/service/tables/previous_service_identity.h | 2 ++ 5 files changed, 25 insertions(+) diff --git a/include/ccf/historical_queries_utils.h b/include/ccf/historical_queries_utils.h index 825ead71ee3a..8dcb39e817ae 100644 --- a/include/ccf/historical_queries_utils.h +++ b/include/ccf/historical_queries_utils.h @@ -25,4 +25,15 @@ namespace ccf::historical AbstractStateCache& state_cache, std::shared_ptr network_identity_subsystem); + + // TODO interface + // Same as above? True if complete no matter if endorsement was produced, + // false if in progress + bool populate_cose_service_endorsements( + ccf::kv::ReadOnlyTx& tx, // TODO check needed + ccf::historical::StatePtr& state, // TODO check needed + AbstractStateCache& state_cache, // TODO check needed + std::shared_ptr + network_identity_subsystem // TODO check needed + ); } \ No newline at end of file diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index eaa0281e1537..c777ba56f244 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -145,5 +145,15 @@ namespace ccf return true; } + + bool populate_cose_service_endorsements( + ccf::kv::ReadOnlyTx& tx, + ccf::historical::StatePtr& state, + AbstractStateCache& state_cache, + std::shared_ptr + network_identity_subsystem) + { + return true; + } } } \ No newline at end of file diff --git a/src/node/tx_receipt_impl.h b/src/node/tx_receipt_impl.h index 73c83c7f3bd4..4b35a24b2fec 100644 --- a/src/node/tx_receipt_impl.h +++ b/src/node/tx_receipt_impl.h @@ -22,6 +22,7 @@ namespace ccf ccf::ClaimsDigest claims_digest = {}; std::optional> service_endorsements = std::nullopt; + // TODO COSE endorsements (optional) TxReceiptImpl( const std::vector& signature_, diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 660c797278cb..2a4316134bcd 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -334,6 +334,7 @@ namespace ccf auto previous_service_identity = tx.wo( ccf::Tables::PREVIOUS_SERVICE_IDENTITY); previous_service_identity->put(prev_service_info->cert); + // TODO. Save COSE endorsement here // Record number of recoveries for service. If the value does // not exist in the table (i.e. pre 2.x ledger), assume it is the first diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index f4ae2b7315b4..5e95ce716f68 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -16,5 +16,7 @@ namespace ccf { static constexpr auto PREVIOUS_SERVICE_IDENTITY = "public:ccf.gov.service.previous_service_identity"; + static constexpr auto PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT = + "public:ccf.gov.service.previous_service_identity_endorsement"; } } \ No newline at end of file From a7772341bf4501920af7ffef0d8bd65471e607ce Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 30 Sep 2024 14:02:20 +0000 Subject: [PATCH 02/43] Store the endorsement in KV --- include/ccf/historical_queries_utils.h | 10 ++++----- src/crypto/openssl/cose_sign.cpp | 5 +++-- src/crypto/openssl/cose_sign.h | 5 +++-- src/node/rpc/node_frontend.h | 7 +++++- src/node/rpc/test/frontend_test.cpp | 5 ++++- src/node/rpc/test/node_frontend_test.cpp | 10 +++++++-- src/node/tx_receipt_impl.h | 2 +- src/service/internal_tables_access.h | 22 ++++++++++++++++++- .../tables/previous_service_identity.h | 3 +++ 9 files changed, 54 insertions(+), 15 deletions(-) diff --git a/include/ccf/historical_queries_utils.h b/include/ccf/historical_queries_utils.h index 8dcb39e817ae..97f26e9d0f7c 100644 --- a/include/ccf/historical_queries_utils.h +++ b/include/ccf/historical_queries_utils.h @@ -26,14 +26,14 @@ namespace ccf::historical std::shared_ptr network_identity_subsystem); - // TODO interface + // TO DO interface // Same as above? True if complete no matter if endorsement was produced, // false if in progress bool populate_cose_service_endorsements( - ccf::kv::ReadOnlyTx& tx, // TODO check needed - ccf::historical::StatePtr& state, // TODO check needed - AbstractStateCache& state_cache, // TODO check needed + ccf::kv::ReadOnlyTx& tx, // TO DO check needed + ccf::historical::StatePtr& state, // TO DO check needed + AbstractStateCache& state_cache, // TO DO check needed std::shared_ptr - network_identity_subsystem // TODO check needed + network_identity_subsystem // TO DO check needed ); } \ No newline at end of file diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 0097f013e729..1d3a6ecd576e 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -79,7 +79,8 @@ namespace namespace ccf::crypto { - std::optional key_to_cose_alg_id(ccf::crypto::PublicKey_OpenSSL& key) + std::optional key_to_cose_alg_id( + const ccf::crypto::PublicKey_OpenSSL& key) { const auto cid = key.get_curve_id(); switch (cid) @@ -156,7 +157,7 @@ namespace ccf::crypto } std::vector cose_sign1( - KeyPair_OpenSSL& key, + const KeyPair_OpenSSL& key, const std::vector& protected_headers, std::span payload) { diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index ddf930a865c7..13b2be900a6a 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -64,7 +64,8 @@ namespace ccf::crypto COSESignError(const std::string& msg) : std::runtime_error(msg) {} }; - std::optional key_to_cose_alg_id(ccf::crypto::PublicKey_OpenSSL& key); + std::optional key_to_cose_alg_id( + const ccf::crypto::PublicKey_OpenSSL& key); /* Sign a cose_sign1 payload with custom protected headers as strings, where - key: integer label to be assigned in a COSE value @@ -74,7 +75,7 @@ namespace ccf::crypto https://www.iana.org/assignments/cose/cose.xhtml#header-parameters. */ std::vector cose_sign1( - KeyPair_OpenSSL& key, + const KeyPair_OpenSSL& key, const std::vector& protected_headers, std::span payload); } diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 70c5b5a7eacb..32f8b97b96de 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1510,7 +1510,12 @@ namespace ccf } InternalTablesAccess::create_service( - ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering); + ctx.tx, + in.service_cert, + *this->network.identity->get_key_pair(), + in.create_txid, + in.service_data, + recovering); // Retire all nodes, in case there are any (i.e. post recovery) InternalTablesAccess::retire_active_nodes(ctx.tx); diff --git a/src/node/rpc/test/frontend_test.cpp b/src/node/rpc/test/frontend_test.cpp index 93792320c759..785450b61a6f 100644 --- a/src/node/rpc/test/frontend_test.cpp +++ b/src/node/rpc/test/frontend_test.cpp @@ -486,7 +486,10 @@ void prepare_callers(NetworkState& network) init_network(network); InternalTablesAccess::create_service( - tx, network.identity->cert, ccf::TxID{2, 1}); + tx, + network.identity->cert, + *network.identity->get_key_pair(), + ccf::TxID{2, 1}); user_id = InternalTablesAccess::add_user(tx, {user_caller}); member_id = InternalTablesAccess::add_member(tx, member_cert); invalid_member_id = InternalTablesAccess::add_member(tx, invalid_caller); diff --git a/src/node/rpc/test/node_frontend_test.cpp b/src/node/rpc/test/node_frontend_test.cpp index 79bdcd92bf7e..031d154984ad 100644 --- a/src/node/rpc/test/node_frontend_test.cpp +++ b/src/node/rpc/test/node_frontend_test.cpp @@ -98,7 +98,10 @@ TEST_CASE("Add a node to an opening service") } InternalTablesAccess::create_service( - gen_tx, network.identity->cert, ccf::TxID{2, 1}); + gen_tx, + network.identity->cert, + *network.identity->get_key_pair(), + ccf::TxID{2, 1}); REQUIRE(gen_tx.commit() == ccf::kv::CommitResult::SUCCESS); auto tx = network.tables->create_tx(); @@ -199,7 +202,10 @@ TEST_CASE("Add a node to an open service") up_to_ledger_secret_seqno, make_ledger_secret()); InternalTablesAccess::create_service( - gen_tx, network.identity->cert, ccf::TxID{2, 1}); + gen_tx, + network.identity->cert, + *network.identity->get_key_pair(), + ccf::TxID{2, 1}); InternalTablesAccess::init_configuration(gen_tx, {1}); InternalTablesAccess::activate_member( gen_tx, diff --git a/src/node/tx_receipt_impl.h b/src/node/tx_receipt_impl.h index 4b35a24b2fec..bdf392241c7d 100644 --- a/src/node/tx_receipt_impl.h +++ b/src/node/tx_receipt_impl.h @@ -22,7 +22,7 @@ namespace ccf ccf::ClaimsDigest claims_digest = {}; std::optional> service_endorsements = std::nullopt; - // TODO COSE endorsements (optional) + // TO DO COSE endorsements (optional) TxReceiptImpl( const std::vector& signature_, diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 2a4316134bcd..e3caf25349cd 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -11,6 +11,7 @@ #include "ccf/service/tables/snp_measurements.h" #include "ccf/service/tables/users.h" #include "ccf/tx.h" +#include "crypto/openssl/cose_sign.h" #include "node/ledger_secrets.h" #include "node/uvm_endorsements.h" #include "service/tables/governance_history.h" @@ -320,6 +321,7 @@ namespace ccf static void create_service( ccf::kv::Tx& tx, const ccf::crypto::Pem& service_cert, + const ccf::crypto::KeyPair_OpenSSL& service_key, ccf::TxID create_txid, nlohmann::json service_data = nullptr, bool recovering = false) @@ -331,10 +333,28 @@ namespace ccf if (service->has()) { const auto prev_service_info = service->get(); + auto previous_service_identity = tx.wo( ccf::Tables::PREVIOUS_SERVICE_IDENTITY); previous_service_identity->put(prev_service_info->cert); - // TODO. Save COSE endorsement here + + auto previous_identity_endorsement = + tx.wo( + ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT); + try + { + const auto prev_ident_endorsement = cose_sign1( + service_key, + {}, // TO DO headers, + prev_service_info->cert.raw()); + previous_identity_endorsement->put(prev_ident_endorsement); + } + catch (const ccf::crypto::COSESignError& e) + { + LOG_FAIL_FMT("Failed o sign previous service identity: {}", e.what()); + throw; // TO DO catch re-throw in frontent? Or change to log and still + // create service? + } // Record number of recoveries for service. If the value does // not exist in the table (i.e. pre 2.x ledger), assume it is the first diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 5e95ce716f68..188af0f66b09 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -12,6 +12,9 @@ namespace ccf { using PreviousServiceIdentity = ServiceValue; + using ServiceEndorsement = std::vector; + using PreviousServiceIdentityEndorsement = ServiceValue; + namespace Tables { static constexpr auto PREVIOUS_SERVICE_IDENTITY = From cb3ed93bb1cf200bcfe877e28dafe24e281c7eca Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 30 Sep 2024 15:13:03 +0000 Subject: [PATCH 03/43] Sign key instead of cert --- src/service/internal_tables_access.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e3caf25349cd..d0a96c8e6375 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -343,10 +343,12 @@ namespace ccf ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT); try { + const auto pubkey = ccf::crypto::public_key_pem_from_cert( + ccf::crypto::cert_pem_to_der(prev_service_info->cert)); const auto prev_ident_endorsement = cose_sign1( service_key, {}, // TO DO headers, - prev_service_info->cert.raw()); + pubkey.raw()); previous_identity_endorsement->put(prev_ident_endorsement); } catch (const ccf::crypto::COSESignError& e) From 117a7b653d9df73c7b85dee01a96fe6a296e4625 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 30 Sep 2024 18:05:52 +0000 Subject: [PATCH 04/43] Populate txreceiptimpl with cose SI endorsements chain --- include/ccf/historical_queries_utils.h | 22 +++-- src/node/historical_queries_utils.cpp | 89 ++++++++++++++++++- src/node/tx_receipt_impl.h | 10 ++- .../tables/previous_service_identity.h | 4 +- 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/include/ccf/historical_queries_utils.h b/include/ccf/historical_queries_utils.h index 97f26e9d0f7c..2443e0583b8c 100644 --- a/include/ccf/historical_queries_utils.h +++ b/include/ccf/historical_queries_utils.h @@ -26,14 +26,18 @@ namespace ccf::historical std::shared_ptr network_identity_subsystem); - // TO DO interface - // Same as above? True if complete no matter if endorsement was produced, - // false if in progress + // Modifies the receipt stored in state to include historical service + // endorsements, where required. If the state talks about a different service + // identity, which is known to be a predecessor of this service (via disaster + // recoveries), then an endorsement chain of all service identities preceding + // the current one will be created. This may need to use the state_cache to + // request additional historical entries to construct this endorsement, and + // may read from the current/latest state via tx. Returns true if the + // operation is complete, though it may still have failed to produce an + // endorsement. Returns false if additional entries have been requested, in + // which case the caller should retry later. bool populate_cose_service_endorsements( - ccf::kv::ReadOnlyTx& tx, // TO DO check needed - ccf::historical::StatePtr& state, // TO DO check needed - AbstractStateCache& state_cache, // TO DO check needed - std::shared_ptr - network_identity_subsystem // TO DO check needed - ); + ccf::kv::ReadOnlyTx& tx, + ccf::historical::StatePtr& state, + AbstractStateCache& state_cache); } \ No newline at end of file diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index c777ba56f244..87f002bc70be 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -8,12 +8,17 @@ #include "kv/kv_types.h" #include "node/identity.h" #include "node/tx_receipt_impl.h" +#include "service/tables/previous_service_identity.h" namespace ccf { static std::map> service_endorsement_cache; + // valid_from -> [valid_to, endorsement] + static std::map>> + cose_endorsements; + namespace historical { std::optional find_previous_service_identity( @@ -149,10 +154,88 @@ namespace ccf bool populate_cose_service_endorsements( ccf::kv::ReadOnlyTx& tx, ccf::historical::StatePtr& state, - AbstractStateCache& state_cache, - std::shared_ptr - network_identity_subsystem) + AbstractStateCache& state_cache) { + const auto service_info = tx.template ro(Tables::SERVICE)->get(); + const auto service_start = service_info->current_service_create_txid; + if (!service_start) + { + // TO DO log err + return true; + } + + const auto target_seq = state->transaction_id.seqno; + if (service_start->seqno <= target_seq) + { + return true; + } + + const auto prev_id_seq = service_info->previous_service_identity_version; + if (!prev_id_seq) + { + // TO DO log err + return true; + } + + if (cose_endorsements.empty()) + { + const auto endorsement = + tx.template ro( + Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) + ->get(); + CCF_ASSERT(endorsement.has_value, "Endorsed identity not found"); + + cose_endorsements.insert({*prev_id_seq, {target_seq, *endorsement}}); + } + + while (cose_endorsements.begin()->first > target_seq) + { + auto earlist_seqno = cose_endorsements.begin()->first; + auto hstate = state_cache.get_state_at(earlist_seqno, earlist_seqno); + if (!hstate) + { + return false; // retry later + } + auto htx = hstate->store->create_read_only_tx(); + const auto prev_service_info = + htx.template ro(Tables::SERVICE)->get(); + + if (!prev_service_info->previous_service_identity_version) + { + // TO DO log err + return true; + } + const auto endorsement = + htx + .template ro( + Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) + ->get(); + + if (!endorsement) + { + // TO DO log err: cose endorsement not present there + return true; + } + + cose_endorsements.insert( + {prev_service_info->previous_service_identity_version.value(), + {earlist_seqno, *endorsement}}); + } + + auto it = cose_endorsements.find(target_seq); + if (it->first != target_seq) + { + --it; + } + // TO DO check it + + std::vector> endorsements; + for (it; it != cose_endorsements.end(); ++it) + { + endorsements.push_back(it->second.second); + } + + state->receipt->cose_endorsements = endorsements; return true; } } diff --git a/src/node/tx_receipt_impl.h b/src/node/tx_receipt_impl.h index bdf392241c7d..d1c9edaa291a 100644 --- a/src/node/tx_receipt_impl.h +++ b/src/node/tx_receipt_impl.h @@ -22,7 +22,8 @@ namespace ccf ccf::ClaimsDigest claims_digest = {}; std::optional> service_endorsements = std::nullopt; - // TO DO COSE endorsements (optional) + std::optional>> cose_endorsements = + std::nullopt; TxReceiptImpl( const std::vector& signature_, @@ -38,7 +39,9 @@ namespace ccf // May not be set on historical transactions const ccf::ClaimsDigest& claims_digest_ = ccf::no_claims(), const std::optional>& - service_endorsements_ = std::nullopt) : + service_endorsements_ = std::nullopt, + const std::optional>>& + cose_endorsements_ = std::nullopt) : signature(signature_), cose_signature(cose_signature), root(root_), @@ -48,7 +51,8 @@ namespace ccf write_set_digest(write_set_digest_), commit_evidence(commit_evidence_), claims_digest(claims_digest_), - service_endorsements(service_endorsements_) + service_endorsements(service_endorsements_), + cose_endorsements(cose_endorsements_) {} }; diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 188af0f66b09..8670cf1f61b0 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -11,9 +11,7 @@ namespace ccf { using PreviousServiceIdentity = ServiceValue; - - using ServiceEndorsement = std::vector; - using PreviousServiceIdentityEndorsement = ServiceValue; + using PreviousServiceIdentityEndorsement = ServiceValue>; namespace Tables { From e84679eaa6adf4378793b7d55d70cca1f859272f Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 1 Oct 2024 15:19:58 +0000 Subject: [PATCH 05/43] Fix endorsement chain --- include/ccf/crypto/cose_verifier.h | 3 + src/crypto/openssl/cose_verifier.cpp | 90 +++++++++++++++++++++ src/node/historical_queries_adapter.cpp | 7 +- src/node/historical_queries_utils.cpp | 103 ++++++++++++++++-------- src/service/internal_tables_access.h | 12 ++- 5 files changed, 176 insertions(+), 39 deletions(-) diff --git a/include/ccf/crypto/cose_verifier.h b/include/ccf/crypto/cose_verifier.h index 5dd349eeca65..5430dfd3a7cc 100644 --- a/include/ccf/crypto/cose_verifier.h +++ b/include/ccf/crypto/cose_verifier.h @@ -27,4 +27,7 @@ namespace ccf::crypto COSEVerifierUniquePtr make_cose_verifier_from_cert( const std::vector& cert); COSEVerifierUniquePtr make_cose_verifier_from_key(const Pem& public_key); + + std::pair extract_cose_endorsement_validity( + std::span cose_msg); } diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index d6a4e126250c..23e6d1af1833 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -19,6 +19,11 @@ namespace { + static std::string qcbor_buf_to_string(const UsefulBufC& buf) + { + return std::string(reinterpret_cast(buf.ptr), buf.len); + } + static std::optional extract_algorithm_from_header( std::span cose_msg) { @@ -223,4 +228,89 @@ namespace ccf::crypto { return std::make_unique(public_key); } + + std::pair extract_cose_endorsement_validity( + std::span cose_msg) + { + UsefulBufC msg{cose_msg.data(), cose_msg.size()}; + QCBORError qcbor_result; + + QCBORDecodeContext ctx; + QCBORDecode_Init(&ctx, msg, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_EnterArray(&ctx, nullptr); + qcbor_result = QCBORDecode_GetError(&ctx); + if (qcbor_result != QCBOR_SUCCESS) + { + LOG_DEBUG_FMT("Failed to parse COSE_Sign1 outer array"); + return {}; + } + + const uint64_t tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); + if (tag != CBOR_TAG_COSE_SIGN1) + { + LOG_DEBUG_FMT("Failed to parse COSE_Sign1 tag"); + return {}; + } + + struct q_useful_buf_c protected_parameters; + QCBORDecode_EnterBstrWrapped( + &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters); + QCBORDecode_EnterMap(&ctx, NULL); + + enum + { + FROM_INDEX, + TILL_INDEX, + END_INDEX + }; + QCBORItem header_items[END_INDEX + 1]; + + header_items[FROM_INDEX].label.string = UsefulBufC{"from", 4}; + header_items[FROM_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[FROM_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; + + header_items[TILL_INDEX].label.string = UsefulBufC{"till", 4}; + header_items[TILL_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[TILL_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; + + header_items[END_INDEX].uLabelType = QCBOR_TYPE_NONE; + + QCBORDecode_GetItemsInMap(&ctx, header_items); + qcbor_result = QCBORDecode_GetError(&ctx); + if (qcbor_result != QCBOR_SUCCESS) + { + LOG_DEBUG_FMT("Failed to decode protected header"); + return {}; + } + + if (header_items[FROM_INDEX].uDataType == QCBOR_TYPE_NONE) + { + LOG_DEBUG_FMT("Failed to retrieve (missing) 'from' parameter"); + return {}; + } + + if (header_items[TILL_INDEX].uDataType == QCBOR_TYPE_NONE) + { + LOG_DEBUG_FMT("Failed to retrieve (missing) 'till' parameter"); + return {}; + } + + const auto from = qcbor_buf_to_string(header_items[FROM_INDEX].val.string); + const auto till = qcbor_buf_to_string(header_items[TILL_INDEX].val.string); + + // Complete decode to ensure well-formed CBOR. + + QCBORDecode_ExitMap(&ctx); + QCBORDecode_ExitBstrWrapped(&ctx); + + qcbor_result = QCBORDecode_GetError(&ctx); + if (qcbor_result != QCBOR_SUCCESS) + { + LOG_DEBUG_FMT("Failed to decode protected header: {}", qcbor_result); + return {}; + } + + return {from, till}; + } } diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index 2732c394d307..7d21d8d8983a 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -521,7 +521,12 @@ namespace ccf::historical if ( historical_state == nullptr || (!populate_service_endorsements( - args.tx, historical_state, state_cache, network_identity_subsystem))) + args.tx, + historical_state, + state_cache, + network_identity_subsystem)) || + !populate_cose_service_endorsements( + args.tx, historical_state, state_cache)) { auto reason = fmt::format( "Historical transaction {} is not currently available.", diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 87f002bc70be..7f44f02c45ed 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -3,6 +3,7 @@ #include "ccf/historical_queries_utils.h" +#include "ccf/crypto/cose_verifier.h" #include "ccf/rpc_context.h" #include "ccf/service/tables/service.h" #include "kv/kv_types.h" @@ -15,9 +16,13 @@ namespace ccf static std::map> service_endorsement_cache; - // valid_from -> [valid_to, endorsement] - static std::map>> - cose_endorsements; + struct BoundedEndorsement + { + ccf::SeqNo from{}; + ccf::SeqNo till{}; + std::vector endorsement{}; + }; + static std::vector cose_endorsements; namespace historical { @@ -160,20 +165,17 @@ namespace ccf const auto service_start = service_info->current_service_create_txid; if (!service_start) { - // TO DO log err + LOG_FAIL_FMT("Service start txid not found"); return true; } const auto target_seq = state->transaction_id.seqno; if (service_start->seqno <= target_seq) { - return true; - } - - const auto prev_id_seq = service_info->previous_service_identity_version; - if (!prev_id_seq) - { - // TO DO log err + LOG_TRACE_FMT( + "Target seqno {} belongs to current service started at {}", + target_seq, + service_start->seqno); return true; } @@ -183,28 +185,37 @@ namespace ccf tx.template ro( Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) ->get(); - CCF_ASSERT(endorsement.has_value, "Endorsed identity not found"); + if (!endorsement) + { + LOG_FAIL_FMT("Endorsed identity not found"); + return true; + } + + const auto [from, till] = + ccf::crypto::extract_cose_endorsement_validity(*endorsement); + + const auto from_id = TxID::from_str(from); + const auto till_id = TxID::from_str(till); - cose_endorsements.insert({*prev_id_seq, {target_seq, *endorsement}}); + if (!from_id || !till_id) + { + LOG_FAIL_FMT("Can't parse COSE endorsement validity"); + return true; + } + + cose_endorsements.push_back( + {from_id->seqno, till_id->seqno, *endorsement}); } - while (cose_endorsements.begin()->first > target_seq) + while (cose_endorsements.back().from > target_seq) { - auto earlist_seqno = cose_endorsements.begin()->first; - auto hstate = state_cache.get_state_at(earlist_seqno, earlist_seqno); + auto earliest_seqno = cose_endorsements.back().from; + auto hstate = state_cache.get_state_at(earliest_seqno, earliest_seqno); if (!hstate) { return false; // retry later } auto htx = hstate->store->create_read_only_tx(); - const auto prev_service_info = - htx.template ro(Tables::SERVICE)->get(); - - if (!prev_service_info->previous_service_identity_version) - { - // TO DO log err - return true; - } const auto endorsement = htx .template ro( @@ -213,26 +224,50 @@ namespace ccf if (!endorsement) { - // TO DO log err: cose endorsement not present there + LOG_DEBUG_FMT( + "No COSE endorsement for service identity befire {}, can't provide " + "a full endorsement chain", + earliest_seqno); return true; } - cose_endorsements.insert( - {prev_service_info->previous_service_identity_version.value(), - {earlist_seqno, *endorsement}}); + const auto [from, till] = + ccf::crypto::extract_cose_endorsement_validity(*endorsement); + + const auto from_id = TxID::from_str(from); + const auto till_id = TxID::from_str(till); + + if (!from_id || !till_id) + { + LOG_FAIL_FMT("Can't parse COSE endorsement validity"); + return true; + } + + cose_endorsements.push_back( + {from_id->seqno, till_id->seqno, *endorsement}); } - auto it = cose_endorsements.find(target_seq); - if (it->first != target_seq) + const auto final_endorsement = std::lower_bound( + cose_endorsements.begin(), + cose_endorsements.end(), + target_seq, + [](const auto& endorsement, const auto& seq) { + return endorsement.from <= seq; + }); + + if (final_endorsement == cose_endorsements.end()) { - --it; + LOG_FAIL_FMT( + "Error during COSE endorsement chain reconstruction, seqno {} not " + "found", + target_seq); + return true; } - // TO DO check it std::vector> endorsements; - for (it; it != cose_endorsements.end(); ++it) + for (auto it = cose_endorsements.begin(); it <= final_endorsement; ++it) { - endorsements.push_back(it->second.second); + endorsements.push_back(it->endorsement); } state->receipt->cose_endorsements = endorsements; diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index d0a96c8e6375..78f453c4610c 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -345,10 +345,14 @@ namespace ccf { const auto pubkey = ccf::crypto::public_key_pem_from_cert( ccf::crypto::cert_pem_to_der(prev_service_info->cert)); - const auto prev_ident_endorsement = cose_sign1( - service_key, - {}, // TO DO headers, - pubkey.raw()); + ; + const auto pheaders = { + ccf::crypto::cose_params_string_string( + "from", prev_service_info->current_service_create_txid->to_str()), + ccf::crypto::cose_params_string_string( + "till", create_txid.to_str())}; + const auto prev_ident_endorsement = + cose_sign1(service_key, pheaders, pubkey.raw()); previous_identity_endorsement->put(prev_ident_endorsement); } catch (const ccf::crypto::COSESignError& e) From 199c0e8e92d89df81b2e82fabc668c5c9215566c Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 1 Oct 2024 15:54:41 +0000 Subject: [PATCH 06/43] Err handling at create service --- src/node/rpc/node_frontend.h | 22 +++++++++++++++------- src/service/internal_tables_access.h | 9 +++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 32f8b97b96de..b985ae4f0f1f 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1509,13 +1509,21 @@ namespace ccf "Service is already created."); } - InternalTablesAccess::create_service( - ctx.tx, - in.service_cert, - *this->network.identity->get_key_pair(), - in.create_txid, - in.service_data, - recovering); + try + { + InternalTablesAccess::create_service( + ctx.tx, + in.service_cert, + *this->network.identity->get_key_pair(), + in.create_txid, + in.service_data, + recovering); + } + catch (const std::logic_error& e) + { + return make_error( + HTTP_STATUS_FORBIDDEN, ccf::errors::InternalError, e.what()); + } // Retire all nodes, in case there are any (i.e. post recovery) InternalTablesAccess::retire_active_nodes(ctx.tx); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 78f453c4610c..d61f8edd16c0 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -345,7 +345,7 @@ namespace ccf { const auto pubkey = ccf::crypto::public_key_pem_from_cert( ccf::crypto::cert_pem_to_der(prev_service_info->cert)); - ; + const auto pheaders = { ccf::crypto::cose_params_string_string( "from", prev_service_info->current_service_create_txid->to_str()), @@ -357,9 +357,10 @@ namespace ccf } catch (const ccf::crypto::COSESignError& e) { - LOG_FAIL_FMT("Failed o sign previous service identity: {}", e.what()); - throw; // TO DO catch re-throw in frontent? Or change to log and still - // create service? + LOG_FAIL_FMT( + "Failed to sign previous service identity: {}", e.what()); + throw std::logic_error(fmt::format( + "Failed to sign previous service identity: {}", e.what())); } // Record number of recoveries for service. If the value does From 66198546cb3bbd3407fdb5ceb7c365ac36d8b550 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 2 Oct 2024 12:10:20 +0000 Subject: [PATCH 07/43] Fix iterators corner case --- src/node/historical_queries_utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 7f44f02c45ed..a89c2005f69d 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -247,11 +247,11 @@ namespace ccf {from_id->seqno, till_id->seqno, *endorsement}); } - const auto final_endorsement = std::lower_bound( + const auto final_endorsement = std::upper_bound( cose_endorsements.begin(), cose_endorsements.end(), target_seq, - [](const auto& endorsement, const auto& seq) { + [](const auto& seq, const auto& endorsement) { return endorsement.from <= seq; }); From 25a9a606051317780900e911296a3e32dd8de88f Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 3 Oct 2024 17:34:35 +0000 Subject: [PATCH 08/43] Sign raw key not pem --- src/service/internal_tables_access.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index d61f8edd16c0..4a9ac8e635ec 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -343,7 +343,7 @@ namespace ccf ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT); try { - const auto pubkey = ccf::crypto::public_key_pem_from_cert( + const auto pubkey = ccf::crypto::public_key_der_from_cert( ccf::crypto::cert_pem_to_der(prev_service_info->cert)); const auto pheaders = { @@ -351,8 +351,10 @@ namespace ccf "from", prev_service_info->current_service_create_txid->to_str()), ccf::crypto::cose_params_string_string( "till", create_txid.to_str())}; + const auto prev_ident_endorsement = - cose_sign1(service_key, pheaders, pubkey.raw()); + cose_sign1(service_key, pheaders, pubkey); + previous_identity_endorsement->put(prev_ident_endorsement); } catch (const ccf::crypto::COSESignError& e) From fd8aeab778c4ffa27899cf3277ca766f42d40565 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 4 Oct 2024 12:25:54 +0000 Subject: [PATCH 09/43] Do endorsements when opening service, not creating --- src/crypto/openssl/cose_sign.cpp | 29 ++++++- src/crypto/openssl/cose_sign.h | 6 +- src/node/historical_queries_utils.cpp | 85 +++++++++---------- src/node/node_state.h | 7 +- src/node/rpc/node_frontend.h | 1 - src/node/rpc/test/frontend_test.cpp | 5 +- src/node/rpc/test/node_frontend_test.cpp | 12 +-- src/service/internal_tables_access.h | 84 ++++++++++++------ .../tables/previous_service_identity.h | 16 +++- 9 files changed, 153 insertions(+), 92 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 1d3a6ecd576e..2bbb074b4504 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -156,10 +156,25 @@ namespace ccf::crypto args_size); } + COSEParametersFactory cose_params_string_bytes( + const std::string& key, const std::vector& value) + { + const size_t args_size = key.size() + value.size() + + extra_size_for_seq_tag + extra_size_for_seq_tag; + q_useful_buf_c buf{value.data(), value.size()}; + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddSZString(ctx, key.data()); + QCBOREncode_AddBytes(ctx, buf); + }, + args_size); + } + std::vector cose_sign1( const KeyPair_OpenSSL& key, const std::vector& protected_headers, - std::span payload) + std::span payload, + bool detached_payload) { const auto buf_size = estimate_buffer_size(protected_headers, payload); std::vector underlying_buffer(buf_size); @@ -189,8 +204,16 @@ namespace ccf::crypto encode_parameters_custom(&sign_ctx, &cbor_encode, protected_headers); - // Mark empty payload manually. - QCBOREncode_AddNULL(&cbor_encode); + if (detached_payload) + { + // Mark empty payload explicitly. + QCBOREncode_AddNULL(&cbor_encode); + } + else + { + UsefulBufC payload_buffer{underlying_buffer.data(), buf_size}; + QCBOREncode_AddBytes(&cbor_encode, payload_buffer); + } // If payload is empty - we still want to sign. Putting NULL_Q_USEFUL_BUF_C, // however, makes t_cose think that the payload is included into the diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index 13b2be900a6a..5803556d7068 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -59,6 +59,9 @@ namespace ccf::crypto COSEParametersFactory cose_params_int_bytes( int64_t key, const std::vector& value); + COSEParametersFactory cose_params_string_bytes( + const std::string& key, const std::vector& value); + struct COSESignError : public std::runtime_error { COSESignError(const std::string& msg) : std::runtime_error(msg) {} @@ -77,5 +80,6 @@ namespace ccf::crypto std::vector cose_sign1( const KeyPair_OpenSSL& key, const std::vector& protected_headers, - std::span payload); + std::span payload, + bool detached_payload = true); } diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index a89c2005f69d..b448e85e233d 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -16,13 +16,7 @@ namespace ccf static std::map> service_endorsement_cache; - struct BoundedEndorsement - { - ccf::SeqNo from{}; - ccf::SeqNo till{}; - std::vector endorsement{}; - }; - static std::vector cose_endorsements; + static std::vector cose_endorsements; namespace historical { @@ -120,6 +114,8 @@ namespace ccf auto hpubkey = ccf::crypto::public_key_pem_from_cert( ccf::crypto::cert_pem_to_der(opt_psi->cert)); + std::cout << "PATTERN classic endorsement" << std::endl; + auto eit = service_endorsement_cache.find(hpubkey); if (eit != service_endorsement_cache.end()) { @@ -185,36 +181,28 @@ namespace ccf tx.template ro( Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) ->get(); - if (!endorsement) - { - LOG_FAIL_FMT("Endorsed identity not found"); - return true; - } - - const auto [from, till] = - ccf::crypto::extract_cose_endorsement_validity(*endorsement); - - const auto from_id = TxID::from_str(from); - const auto till_id = TxID::from_str(till); - if (!from_id || !till_id) + if (!endorsement) { - LOG_FAIL_FMT("Can't parse COSE endorsement validity"); + LOG_FAIL_FMT("COSE endorsement for current service not found"); return true; } - cose_endorsements.push_back( - {from_id->seqno, till_id->seqno, *endorsement}); + cose_endorsements.push_back(*endorsement); } - while (cose_endorsements.back().from > target_seq) + while (cose_endorsements.back().previous_version && + cose_endorsements.back().endorsed_range->first.seqno > target_seq) { - auto earliest_seqno = cose_endorsements.back().from; - auto hstate = state_cache.get_state_at(earliest_seqno, earliest_seqno); + const auto prev_endorsement_seqno = + cose_endorsements.back().previous_version.value(); + const auto hstate = state_cache.get_state_at( + prev_endorsement_seqno, prev_endorsement_seqno); if (!hstate) { - return false; // retry later + return false; // fetching, retry later } + auto htx = hstate->store->create_read_only_tx(); const auto endorsement = htx @@ -224,39 +212,41 @@ namespace ccf if (!endorsement) { - LOG_DEBUG_FMT( - "No COSE endorsement for service identity befire {}, can't provide " - "a full endorsement chain", - earliest_seqno); - return true; - } - - const auto [from, till] = - ccf::crypto::extract_cose_endorsement_validity(*endorsement); + fmt::println( + "PATTERN no cose endorsement before {}", + cose_endorsements.back().endorsed_range->first.seqno); - const auto from_id = TxID::from_str(from); - const auto till_id = TxID::from_str(till); - - if (!from_id || !till_id) - { - LOG_FAIL_FMT("Can't parse COSE endorsement validity"); + LOG_FAIL_FMT( + "Invalid previous COSE endorsement pointer to seqno {}", + prev_endorsement_seqno); return true; } - cose_endorsements.push_back( - {from_id->seqno, till_id->seqno, *endorsement}); + cose_endorsements.push_back(*endorsement); } + const bool last_is_self_endorsement = + !cose_endorsements.back().previous_version; + + const auto search_to = last_is_self_endorsement ? + cose_endorsements.end() : + cose_endorsements.end() - 1; + const auto final_endorsement = std::upper_bound( cose_endorsements.begin(), - cose_endorsements.end(), + search_to, target_seq, [](const auto& seq, const auto& endorsement) { - return endorsement.from <= seq; + return endorsement.endorsed_range->first.seqno <= seq; }); if (final_endorsement == cose_endorsements.end()) { + fmt::println( + "PATTERN loop error earliest is {} target is {}", + search_to->endorsed_range->first.seqno, + target_seq); + LOG_FAIL_FMT( "Error during COSE endorsement chain reconstruction, seqno {} not " "found", @@ -267,10 +257,15 @@ namespace ccf std::vector> endorsements; for (auto it = cose_endorsements.begin(); it <= final_endorsement; ++it) { + fmt::println( + "PATTERN Adding endorsement from {} to {}", + it->endorsed_range->first.seqno, + it->endorsed_range->second.seqno); endorsements.push_back(it->endorsement); } state->receipt->cose_endorsements = endorsements; + std::cout << "PATTERN good!!" << std::endl; return true; } } diff --git a/src/node/node_state.h b/src/node/node_state.h index 02ea36454e9d..6b97f9939e94 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -1176,7 +1176,10 @@ namespace ccf // previous ledger secrets have been recovered share_manager.issue_recovery_shares(tx); - if (!InternalTablesAccess::open_service(tx)) + if ( + !InternalTablesAccess::open_service(tx) || + !InternalTablesAccess::endorse_previous_identity( + tx, *network.identity->get_key_pair())) { throw std::logic_error("Service could not be opened"); } @@ -1501,6 +1504,8 @@ namespace ccf } InternalTablesAccess::open_service(tx); + InternalTablesAccess::endorse_previous_identity( + tx, *network.identity->get_key_pair()); trigger_snapshot(tx); return; } diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index b985ae4f0f1f..48bd4eaec6e8 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1514,7 +1514,6 @@ namespace ccf InternalTablesAccess::create_service( ctx.tx, in.service_cert, - *this->network.identity->get_key_pair(), in.create_txid, in.service_data, recovering); diff --git a/src/node/rpc/test/frontend_test.cpp b/src/node/rpc/test/frontend_test.cpp index 785450b61a6f..93792320c759 100644 --- a/src/node/rpc/test/frontend_test.cpp +++ b/src/node/rpc/test/frontend_test.cpp @@ -486,10 +486,7 @@ void prepare_callers(NetworkState& network) init_network(network); InternalTablesAccess::create_service( - tx, - network.identity->cert, - *network.identity->get_key_pair(), - ccf::TxID{2, 1}); + tx, network.identity->cert, ccf::TxID{2, 1}); user_id = InternalTablesAccess::add_user(tx, {user_caller}); member_id = InternalTablesAccess::add_member(tx, member_cert); invalid_member_id = InternalTablesAccess::add_member(tx, invalid_caller); diff --git a/src/node/rpc/test/node_frontend_test.cpp b/src/node/rpc/test/node_frontend_test.cpp index 031d154984ad..76e977fffbba 100644 --- a/src/node/rpc/test/node_frontend_test.cpp +++ b/src/node/rpc/test/node_frontend_test.cpp @@ -98,10 +98,7 @@ TEST_CASE("Add a node to an opening service") } InternalTablesAccess::create_service( - gen_tx, - network.identity->cert, - *network.identity->get_key_pair(), - ccf::TxID{2, 1}); + gen_tx, network.identity->cert, ccf::TxID{2, 1}); REQUIRE(gen_tx.commit() == ccf::kv::CommitResult::SUCCESS); auto tx = network.tables->create_tx(); @@ -202,10 +199,7 @@ TEST_CASE("Add a node to an open service") up_to_ledger_secret_seqno, make_ledger_secret()); InternalTablesAccess::create_service( - gen_tx, - network.identity->cert, - *network.identity->get_key_pair(), - ccf::TxID{2, 1}); + gen_tx, network.identity->cert, ccf::TxID{2, 1}); InternalTablesAccess::init_configuration(gen_tx, {1}); InternalTablesAccess::activate_member( gen_tx, @@ -213,6 +207,8 @@ TEST_CASE("Add a node to an open service") gen_tx, {member_cert, ccf::crypto::make_rsa_key_pair()->public_key_pem()})); REQUIRE(InternalTablesAccess::open_service(gen_tx)); + REQUIRE(InternalTablesAccess::endorse_previous_identity( + gen_tx, *network.identity->get_key_pair())); REQUIRE(gen_tx.commit() == ccf::kv::CommitResult::SUCCESS); // Node certificate diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 4a9ac8e635ec..a45243163817 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -321,7 +321,6 @@ namespace ccf static void create_service( ccf::kv::Tx& tx, const ccf::crypto::Pem& service_cert, - const ccf::crypto::KeyPair_OpenSSL& service_key, ccf::TxID create_txid, nlohmann::json service_data = nullptr, bool recovering = false) @@ -338,33 +337,6 @@ namespace ccf ccf::Tables::PREVIOUS_SERVICE_IDENTITY); previous_service_identity->put(prev_service_info->cert); - auto previous_identity_endorsement = - tx.wo( - ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT); - try - { - const auto pubkey = ccf::crypto::public_key_der_from_cert( - ccf::crypto::cert_pem_to_der(prev_service_info->cert)); - - const auto pheaders = { - ccf::crypto::cose_params_string_string( - "from", prev_service_info->current_service_create_txid->to_str()), - ccf::crypto::cose_params_string_string( - "till", create_txid.to_str())}; - - const auto prev_ident_endorsement = - cose_sign1(service_key, pheaders, pubkey); - - previous_identity_endorsement->put(prev_ident_endorsement); - } - catch (const ccf::crypto::COSESignError& e) - { - LOG_FAIL_FMT( - "Failed to sign previous service identity: {}", e.what()); - throw std::logic_error(fmt::format( - "Failed to sign previous service identity: {}", e.what())); - } - // Record number of recoveries for service. If the value does // not exist in the table (i.e. pre 2.x ledger), assume it is the first // recovery. @@ -387,6 +359,62 @@ namespace ccf return service.has_value() && service->cert == expected_service_cert; } + static bool endorse_previous_identity( + ccf::kv::Tx& tx, const ccf::crypto::KeyPair_OpenSSL& service_key) + { + auto service = tx.ro(Tables::SERVICE); + auto active_service = service->get(); + + auto previous_identity_endorsement = + tx.rw( + ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT); + + ccf::CoseEndorsement endorsement{}; + std::vector pheaders{}; + std::vector key_to_endorse{}; + + endorsement.endorsing_key = service_key.public_key_der(); + + if (previous_identity_endorsement->has()) + { + const auto prev_endorsement = previous_identity_endorsement->get(); + const auto from = prev_endorsement->endorsed_range->second; + const auto till = active_service->current_service_create_txid.value(); + pheaders.push_back( + ccf::crypto::cose_params_string_string("from", from.to_str())); + pheaders.push_back( + ccf::crypto::cose_params_string_string("till", till.to_str())); + endorsement.endorsed_range = {from, till}; + + endorsement.previous_version = + previous_identity_endorsement->get_version_of_previous_write(); + + key_to_endorse = prev_endorsement->endorsing_key; + } + else + { + key_to_endorse = endorsement.endorsing_key; // self-sign + } + + try + { + endorsement.endorsement = cose_sign1( + service_key, + pheaders, + key_to_endorse, + false // detached payload + ); + } + catch (const ccf::crypto::COSESignError& e) + { + LOG_FAIL_FMT("Failed to sign previous service identity: {}", e.what()); + return false; + } + + previous_identity_endorsement->put(endorsement); + return true; + } + static bool open_service(ccf::kv::Tx& tx) { auto service = tx.rw(Tables::SERVICE); diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 8670cf1f61b0..33e4a11d12a5 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -11,7 +11,21 @@ namespace ccf { using PreviousServiceIdentity = ServiceValue; - using PreviousServiceIdentityEndorsement = ServiceValue>; + + struct CoseEndorsement + { + std::vector endorsement{}; + std::vector endorsing_key{}; + std::optional previous_version{}; + std::optional> endorsed_range{}; + }; + + DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CoseEndorsement); + DECLARE_JSON_REQUIRED_FIELDS(CoseEndorsement, endorsement, endorsing_key); + DECLARE_JSON_OPTIONAL_FIELDS( + CoseEndorsement, previous_version, endorsed_range); + + using PreviousServiceIdentityEndorsement = ServiceValue; namespace Tables { From 4611b0998d39ffa4c0c001927543402c0eeacec2 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 4 Oct 2024 14:01:25 +0000 Subject: [PATCH 10/43] Polish receipts --- src/node/historical_queries_utils.cpp | 241 ++++++++++++++++---------- 1 file changed, 154 insertions(+), 87 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index b448e85e233d..9a495c10a7a9 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -11,13 +11,150 @@ #include "node/tx_receipt_impl.h" #include "service/tables/previous_service_identity.h" +namespace +{ + static std::vector cose_endorsements_cache = {}; + + bool is_self_endorsement(const ccf::CoseEndorsement& endorsement) + { + return endorsement.previous_version.has_value(); + } + + void validate_fetched_endorsement( + const std::optional& endorsement) + { + if (!endorsement) + { + throw std::logic_error("COSE endorsement for current service not found"); + } + + if (!is_self_endorsement(*endorsement)) + { + const auto [from, till] = ccf::crypto::extract_cose_endorsement_validity( + endorsement->endorsement); + const auto from_txid = ccf::TxID::from_str(from); + const auto till_txid = ccf::TxID::from_str(till); + if ( + !from_txid || !till_txid || !endorsement->endorsed_range || + endorsement->endorsed_range->first != *from_txid || + endorsement->endorsed_range->second != *till_txid) + { + throw std::logic_error("COSE endorsement fetched but range is invalid"); + } + } + } + + void ensure_first_fetch(ccf::kv::ReadOnlyTx& tx) + { + if (cose_endorsements_cache.empty()) [[unlikely]] + { + const auto endorsement = + tx.template ro( + ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) + ->get(); + validate_fetched_endorsement(endorsement); + cose_endorsements_cache.push_back(*endorsement); + } + } + + bool keep_fetching(ccf::SeqNo target_seq) + { + return !is_self_endorsement(cose_endorsements_cache.back()) && + cose_endorsements_cache.back().endorsed_range->second.seqno > target_seq; + } + + std::optional>> fetch_endorsements_for( + ccf::kv::ReadOnlyTx& tx, + ccf::historical::AbstractStateCache& state_cache, + ccf::SeqNo target_seq) + { + ensure_first_fetch(tx); + + while (keep_fetching(target_seq)) + { + const auto prev_endorsement_seqno = + cose_endorsements_cache.back().previous_version.value(); + const auto hstate = state_cache.get_state_at( + prev_endorsement_seqno, prev_endorsement_seqno); + + if (!hstate) + { + return std::nullopt; // fetching, try later + } + + auto htx = hstate->store->create_read_only_tx(); + const auto endorsement = + htx + .template ro( + ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) + ->get(); + + validate_fetched_endorsement(endorsement); + cose_endorsements_cache.push_back(*endorsement); + } + + if (cose_endorsements_cache.size() == 1) + { + // Only current service self-endorsement was found, meaning no historical + // TXs for previous epochs were COSE-endorsed. + return std::nullopt; + } + + const auto search_to = is_self_endorsement(cose_endorsements_cache.back()) ? + cose_endorsements_cache.end() : + cose_endorsements_cache.end() - 1; + + if (search_to->endorsed_range->first.seqno > target_seq) + { + // COSE-endorsements are fetched for newer epochs, but target_seq is far + // behind and was never endorsed. + return std::nullopt; + } + + const auto final_endorsement = std::upper_bound( + cose_endorsements_cache.begin(), + search_to, + target_seq, + [](const auto& seq, const auto& endorsement) { + return endorsement.endorsed_range->first.seqno <= seq; + }); + + if (final_endorsement == search_to) + { + fmt::println( + "PATTERN loop error earliest is {} target is {}", + search_to->endorsed_range->first.seqno, + target_seq); + + LOG_FAIL_FMT( + "Error during COSE endorsement chain reconstruction, seqno {} not " + "found", + target_seq); + + throw std::logic_error(fmt::format( + "Error during COSE endorsement chain reconstruction for seqno {}", + target_seq)); + } + + std::vector> endorsements; + endorsements.reserve( + std::distance(cose_endorsements_cache.begin(), final_endorsement) + 1); + + std::transform( + cose_endorsements_cache.begin(), + final_endorsement, + std::back_inserter(endorsements), + [](const auto& e) { return e.endorsement; }); + + return endorsements; + } +} + namespace ccf { static std::map> service_endorsement_cache; - static std::vector cose_endorsements; - namespace historical { std::optional find_previous_service_identity( @@ -100,9 +237,9 @@ namespace ccf {&network_identity->cert}, {}, /* ignore_time */ true)) { // The current service identity does not endorse the node - // certificate in the receipt, so we search for the the most recent - // write to the service info table before the historical transaction - // ID to get the historical service identity. + // certificate in the receipt, so we search for the the most + // recent write to the service info table before the historical + // transaction ID to get the historical service identity. auto opt_psi = find_previous_service_identity(tx, state, state_cache); @@ -175,97 +312,27 @@ namespace ccf return true; } - if (cose_endorsements.empty()) + try { - const auto endorsement = - tx.template ro( - Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) - ->get(); - - if (!endorsement) + auto endorsements = + fetch_endorsements_for(tx, state_cache, state->transaction_id.seqno); + if (!endorsements) { - LOG_FAIL_FMT("COSE endorsement for current service not found"); - return true; + return false; // False means try later due to historical state + // fetching. } - - cose_endorsements.push_back(*endorsement); + state->receipt->cose_endorsements = endorsements; } - - while (cose_endorsements.back().previous_version && - cose_endorsements.back().endorsed_range->first.seqno > target_seq) + catch (const std::logic_error& e) { - const auto prev_endorsement_seqno = - cose_endorsements.back().previous_version.value(); - const auto hstate = state_cache.get_state_at( - prev_endorsement_seqno, prev_endorsement_seqno); - if (!hstate) - { - return false; // fetching, retry later - } - - auto htx = hstate->store->create_read_only_tx(); - const auto endorsement = - htx - .template ro( - Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT) - ->get(); + LOG_FAIL_FMT("Failed to fetch endorsements: {}", e.what()); - if (!endorsement) - { - fmt::println( - "PATTERN no cose endorsement before {}", - cose_endorsements.back().endorsed_range->first.seqno); - - LOG_FAIL_FMT( - "Invalid previous COSE endorsement pointer to seqno {}", - prev_endorsement_seqno); - return true; - } - - cose_endorsements.push_back(*endorsement); - } + assert(false); // In debug, fail fast and debug. - const bool last_is_self_endorsement = - !cose_endorsements.back().previous_version; - - const auto search_to = last_is_self_endorsement ? - cose_endorsements.end() : - cose_endorsements.end() - 1; - - const auto final_endorsement = std::upper_bound( - cose_endorsements.begin(), - search_to, - target_seq, - [](const auto& seq, const auto& endorsement) { - return endorsement.endorsed_range->first.seqno <= seq; - }); - - if (final_endorsement == cose_endorsements.end()) - { - fmt::println( - "PATTERN loop error earliest is {} target is {}", - search_to->endorsed_range->first.seqno, - target_seq); - - LOG_FAIL_FMT( - "Error during COSE endorsement chain reconstruction, seqno {} not " - "found", - target_seq); - return true; - } - - std::vector> endorsements; - for (auto it = cose_endorsements.begin(); it <= final_endorsement; ++it) - { - fmt::println( - "PATTERN Adding endorsement from {} to {}", - it->endorsed_range->first.seqno, - it->endorsed_range->second.seqno); - endorsements.push_back(it->endorsement); + return true; // In release, return true to avoid being stuck waiting for + // endorsements that can't be done because of an error. } - state->receipt->cose_endorsements = endorsements; - std::cout << "PATTERN good!!" << std::endl; return true; } } From 90b71756a2c619f1750e3059cf1a5b7ddcabc413 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 4 Oct 2024 14:51:01 +0000 Subject: [PATCH 11/43] Fixups + logging cose endpoint --- CHANGELOG.md | 1 + include/ccf/receipt.h | 10 ++++--- samples/apps/logging/logging.cpp | 38 +++++++++++++++++++++++++ src/crypto/openssl/cose_sign.cpp | 2 +- src/node/historical_queries_adapter.cpp | 6 ++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeab73b6ac8d..b82f93eeeff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Provided API for getting COSE signatures and Merkle proofs (#6477). - Exposed COSE signature in historical API via `TxReceiptImpl`. - Introduced `ccf::describe_merkle_proof_v1(receipt)` for Merkle proof construction in CBOR format. + - Introduced `ccf::describe_cose_endorsements_v1(receipt)` for COSE-endorsements chain of previous service identities. - Added COSE signatures over the Merkle root to the KV (#6449). - Signing is done with service key (different from raw signatures, which remain unchanged and are still signed by the node key). - New signature reside in `public:ccf.internal.cose_signatures`. diff --git a/include/ccf/receipt.h b/include/ccf/receipt.h index 5115f9b1468a..f82c181fd225 100644 --- a/include/ccf/receipt.h +++ b/include/ccf/receipt.h @@ -146,6 +146,8 @@ namespace ccf }; std::optional> describe_merkle_proof_v1( const TxReceiptImpl& in); + std::optional>> + describe_cose_endorsements_v1(const TxReceiptImpl& in); // Manual JSON serializers are specified for these types as they are not // trivial POD structs @@ -170,10 +172,10 @@ namespace ccf void add_schema_components( T& helper, nlohmann::json& schema, const ProofReceipt::Components* comp) { - helper.template add_schema_component(); - helper.template add_schema_component(); + helper.template add_schema_component< + decltype(ProofReceipt::Components::write_set_digest)>(); + helper.template add_schema_component< + decltype(ProofReceipt::Components::claims_digest)>(); fill_json_schema(schema, comp); } diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 6531ac02a6e5..7d313175990a 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -1961,6 +1961,44 @@ namespace loggingapp .set_auto_schema() .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) .install(); + + auto get_cose_endorsements = + [this]( + ccf::endpoints::ReadOnlyEndpointContext& ctx, + ccf::historical::StatePtr historical_state) { + auto historical_tx = historical_state->store->create_read_only_tx(); + + assert(historical_state->receipt); + auto endorsements = + describe_cose_endorsements_v1(*historical_state->receipt); + if (!endorsements.has_value()) + { + ctx.rpc_ctx->set_error( + HTTP_STATUS_NOT_FOUND, + ccf::errors::ResourceNotFound, + "No COSE endorsements available for this transaction"); + return; + } + auto response = nlohmann::json::array(); + for (const auto& endorsement : *endorsements) + { + response.push_back(endorsement); + } + ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); + ctx.rpc_ctx->set_response_header( + ccf::http::headers::CONTENT_TYPE, + ccf::http::headervalues::contenttype::JSON); + ctx.rpc_ctx->set_response_body(response.dump()); + }; + make_read_only_endpoint( + "/log/public/cose_endorsements", + HTTP_GET, + ccf::historical::read_only_adapter_v4( + get_cbor_merkle_proof, context, is_tx_committed), + auth_policies) + .set_auto_schema() + .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) + .install(); } }; } diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 2bbb074b4504..3f4254aae112 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -211,7 +211,7 @@ namespace ccf::crypto } else { - UsefulBufC payload_buffer{underlying_buffer.data(), buf_size}; + UsefulBufC payload_buffer{payload.data(), payload.size()}; QCBOREncode_AddBytes(&cbor_encode, payload_buffer); } diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index ee5e8ab3b422..a499c4e735de 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -248,6 +248,12 @@ namespace ccf underlying_buffer.shrink_to_fit(); return underlying_buffer; } + + std::optional>> + describe_cose_endorsements_v1(const TxReceiptImpl& receipt) + { + return receipt.cose_endorsements; + } } namespace ccf::historical From 0f3562931ed68a21bfe285067246e0e7a3a25e71 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 12:39:14 +0000 Subject: [PATCH 12/43] Fixup result to avoid being stuck when no endorsements --- src/node/historical_queries_utils.cpp | 48 +++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 9a495c10a7a9..9db4b9c869d9 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -13,11 +13,18 @@ namespace { + using Endorsements = std::vector>; + struct FetchResult + { + std::optional endorsements{std::nullopt}; + bool retry{false}; + }; + static std::vector cose_endorsements_cache = {}; bool is_self_endorsement(const ccf::CoseEndorsement& endorsement) { - return endorsement.previous_version.has_value(); + return !endorsement.previous_version.has_value(); } void validate_fetched_endorsement( @@ -25,7 +32,7 @@ namespace { if (!endorsement) { - throw std::logic_error("COSE endorsement for current service not found"); + throw std::logic_error("Fetched COSE endorsement is invalid"); } if (!is_self_endorsement(*endorsement)) @@ -63,7 +70,7 @@ namespace cose_endorsements_cache.back().endorsed_range->second.seqno > target_seq; } - std::optional>> fetch_endorsements_for( + FetchResult fetch_endorsements_for( ccf::kv::ReadOnlyTx& tx, ccf::historical::AbstractStateCache& state_cache, ccf::SeqNo target_seq) @@ -79,7 +86,7 @@ namespace if (!hstate) { - return std::nullopt; // fetching, try later + return {.endorsements = std::nullopt, .retry = true}; } auto htx = hstate->store->create_read_only_tx(); @@ -97,7 +104,7 @@ namespace { // Only current service self-endorsement was found, meaning no historical // TXs for previous epochs were COSE-endorsed. - return std::nullopt; + return {.endorsements = std::nullopt, .retry = false}; } const auto search_to = is_self_endorsement(cose_endorsements_cache.back()) ? @@ -108,7 +115,7 @@ namespace { // COSE-endorsements are fetched for newer epochs, but target_seq is far // behind and was never endorsed. - return std::nullopt; + return {.endorsements = std::nullopt, .retry = false}; } const auto final_endorsement = std::upper_bound( @@ -121,11 +128,6 @@ namespace if (final_endorsement == search_to) { - fmt::println( - "PATTERN loop error earliest is {} target is {}", - search_to->endorsed_range->first.seqno, - target_seq); - LOG_FAIL_FMT( "Error during COSE endorsement chain reconstruction, seqno {} not " "found", @@ -136,9 +138,9 @@ namespace target_seq)); } - std::vector> endorsements; - endorsements.reserve( - std::distance(cose_endorsements_cache.begin(), final_endorsement) + 1); + const auto endorsements_count = + std::distance(cose_endorsements_cache.begin(), final_endorsement) + 1; + Endorsements endorsements(endorsements_count); std::transform( cose_endorsements_cache.begin(), @@ -146,7 +148,7 @@ namespace std::back_inserter(endorsements), [](const auto& e) { return e.endorsement; }); - return endorsements; + return {.endorsements = std::move(endorsements), .retry = false}; } } @@ -251,8 +253,6 @@ namespace ccf auto hpubkey = ccf::crypto::public_key_pem_from_cert( ccf::crypto::cert_pem_to_der(opt_psi->cert)); - std::cout << "PATTERN classic endorsement" << std::endl; - auto eit = service_endorsement_cache.find(hpubkey); if (eit != service_endorsement_cache.end()) { @@ -314,23 +314,23 @@ namespace ccf try { - auto endorsements = + auto result = fetch_endorsements_for(tx, state_cache, state->transaction_id.seqno); - if (!endorsements) + if (!result.endorsements) { - return false; // False means try later due to historical state - // fetching. + const bool final_result = !result.retry; + return final_result; } - state->receipt->cose_endorsements = endorsements; + state->receipt->cose_endorsements = result.endorsements.value(); } catch (const std::logic_error& e) { LOG_FAIL_FMT("Failed to fetch endorsements: {}", e.what()); - assert(false); // In debug, fail fast and debug. + assert(false); // In debug, fail fast. return true; // In release, return true to avoid being stuck waiting for - // endorsements that can't be done because of an error. + // endorsements that can't be provided because of an error. } return true; From 043bf58dec8a18f0ff9991d325d1984e4fb97138 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 13:26:19 +0000 Subject: [PATCH 13/43] Fixup prev identity ptr --- include/ccf/receipt.h | 8 ++--- src/node/historical_queries_utils.cpp | 12 +++---- src/service/internal_tables_access.h | 31 ++++++++++++++----- .../tables/previous_service_identity.h | 19 ++++++++++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/include/ccf/receipt.h b/include/ccf/receipt.h index f82c181fd225..50bf425d3bc8 100644 --- a/include/ccf/receipt.h +++ b/include/ccf/receipt.h @@ -172,10 +172,10 @@ namespace ccf void add_schema_components( T& helper, nlohmann::json& schema, const ProofReceipt::Components* comp) { - helper.template add_schema_component< - decltype(ProofReceipt::Components::write_set_digest)>(); - helper.template add_schema_component< - decltype(ProofReceipt::Components::claims_digest)>(); + helper.template add_schema_component(); + helper.template add_schema_component(); fill_json_schema(schema, comp); } diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 9db4b9c869d9..4f6e1e5c2b36 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -42,9 +42,9 @@ namespace const auto from_txid = ccf::TxID::from_str(from); const auto till_txid = ccf::TxID::from_str(till); if ( - !from_txid || !till_txid || !endorsement->endorsed_range || - endorsement->endorsed_range->first != *from_txid || - endorsement->endorsed_range->second != *till_txid) + !till_txid || !endorsement->endorsed_till || + endorsement->endorsed_from != *from_txid || + endorsement->endorsed_till != *till_txid) { throw std::logic_error("COSE endorsement fetched but range is invalid"); } @@ -67,7 +67,7 @@ namespace bool keep_fetching(ccf::SeqNo target_seq) { return !is_self_endorsement(cose_endorsements_cache.back()) && - cose_endorsements_cache.back().endorsed_range->second.seqno > target_seq; + cose_endorsements_cache.back().endorsed_till->seqno > target_seq; } FetchResult fetch_endorsements_for( @@ -111,7 +111,7 @@ namespace cose_endorsements_cache.end() : cose_endorsements_cache.end() - 1; - if (search_to->endorsed_range->first.seqno > target_seq) + if (search_to->endorsed_from.seqno > target_seq) { // COSE-endorsements are fetched for newer epochs, but target_seq is far // behind and was never endorsed. @@ -123,7 +123,7 @@ namespace search_to, target_seq, [](const auto& seq, const auto& endorsement) { - return endorsement.endorsed_range->first.seqno <= seq; + return endorsement.endorsed_from.seqno <= seq; }); if (final_endorsement == search_to) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index a45243163817..e8785b3bd2fd 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -378,13 +378,14 @@ namespace ccf if (previous_identity_endorsement->has()) { const auto prev_endorsement = previous_identity_endorsement->get(); - const auto from = prev_endorsement->endorsed_range->second; - const auto till = active_service->current_service_create_txid.value(); - pheaders.push_back( - ccf::crypto::cose_params_string_string("from", from.to_str())); - pheaders.push_back( - ccf::crypto::cose_params_string_string("till", till.to_str())); - endorsement.endorsed_range = {from, till}; + + endorsement.endorsed_from = + prev_endorsement->endorsed_till.has_value() ? + prev_endorsement->endorsed_till.value() : + prev_endorsement->endorsed_from; + + endorsement.endorsed_till = + active_service->current_service_create_txid.value(); endorsement.previous_version = previous_identity_endorsement->get_version_of_previous_write(); @@ -393,7 +394,21 @@ namespace ccf } else { - key_to_endorse = endorsement.endorsing_key; // self-sign + // There's no till for the a self-endorsement, leave it open-ranged and + // sign the current service key. + + endorsement.endorsed_from = + active_service->current_service_create_txid.value(); + + key_to_endorse = endorsement.endorsing_key; + } + + pheaders.push_back(ccf::crypto::cose_params_string_string( + "from", endorsement.endorsed_from.to_str())); + if (endorsement.endorsed_till) + { + pheaders.push_back(ccf::crypto::cose_params_string_string( + "till", endorsement.endorsed_till->to_str())); } try diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 33e4a11d12a5..ec272fcdc679 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -14,16 +14,29 @@ namespace ccf struct CoseEndorsement { + /// COSE-sign of the a previous service identity's public key. std::vector endorsement{}; + + /// Service key at the moment of endorsing. std::vector endorsing_key{}; + + /// The transaction ID when the *endorsing* service was created. + ccf::TxID endorsed_from{}; + + /// Pointer to the previous CoseEndorsement entry. Only present for previous + /// service endorsements, self-endorsed services must not have this set. std::optional previous_version{}; - std::optional> endorsed_range{}; + + /// Exclusive upper bound of the endorsement validity range. Self-endorsed + /// services must not have this value set. + std::optional endorsed_till{}; }; DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CoseEndorsement); - DECLARE_JSON_REQUIRED_FIELDS(CoseEndorsement, endorsement, endorsing_key); + DECLARE_JSON_REQUIRED_FIELDS( + CoseEndorsement, endorsement, endorsed_from, endorsing_key); DECLARE_JSON_OPTIONAL_FIELDS( - CoseEndorsement, previous_version, endorsed_range); + CoseEndorsement, previous_version, endorsed_till); using PreviousServiceIdentityEndorsement = ServiceValue; From 0f1deb6e637f1edb831d282dc2728e7006a3d119 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 13:34:16 +0000 Subject: [PATCH 14/43] till-from in fetch check and format --- src/node/historical_queries_utils.cpp | 2 +- src/node/rpc/node_frontend.h | 16 ++-------------- src/service/internal_tables_access.h | 5 ++--- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 4f6e1e5c2b36..fc3705b51a24 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -67,7 +67,7 @@ namespace bool keep_fetching(ccf::SeqNo target_seq) { return !is_self_endorsement(cose_endorsements_cache.back()) && - cose_endorsements_cache.back().endorsed_till->seqno > target_seq; + cose_endorsements_cache.back().endorsed_from.seqno > target_seq; } FetchResult fetch_endorsements_for( diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 48bd4eaec6e8..70c5b5a7eacb 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1509,20 +1509,8 @@ namespace ccf "Service is already created."); } - try - { - InternalTablesAccess::create_service( - ctx.tx, - in.service_cert, - in.create_txid, - in.service_data, - recovering); - } - catch (const std::logic_error& e) - { - return make_error( - HTTP_STATUS_FORBIDDEN, ccf::errors::InternalError, e.what()); - } + InternalTablesAccess::create_service( + ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering); // Retire all nodes, in case there are any (i.e. post recovery) InternalTablesAccess::retire_active_nodes(ctx.tx); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e8785b3bd2fd..8abbbbe25959 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -332,7 +332,6 @@ namespace ccf if (service->has()) { const auto prev_service_info = service->get(); - auto previous_service_identity = tx.wo( ccf::Tables::PREVIOUS_SERVICE_IDENTITY); previous_service_identity->put(prev_service_info->cert); @@ -394,8 +393,8 @@ namespace ccf } else { - // There's no till for the a self-endorsement, leave it open-ranged and - // sign the current service key. + // There's no 'till' for the a self-endorsement, leave it open-ranged + // and sign the current service key. endorsement.endorsed_from = active_service->current_service_create_txid.value(); From 3e909bbf39c69171dbd24e632ec974f55d45fe1c Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 14:31:26 +0000 Subject: [PATCH 15/43] cose endorsement endpoint fix path --- doc/schemas/app_openapi.json | 24 ++++++++++++++++++++++++ samples/apps/logging/logging.cpp | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/schemas/app_openapi.json b/doc/schemas/app_openapi.json index 8da41e8821d2..f588be501296 100644 --- a/doc/schemas/app_openapi.json +++ b/doc/schemas/app_openapi.json @@ -1210,6 +1210,30 @@ } } }, + "/app/log/public/cose_endorsements": { + "get": { + "operationId": "GetAppLogPublicCoseEndorsements", + "responses": { + "204": { + "description": "Default response description" + }, + "default": { + "$ref": "#/components/responses/default" + } + }, + "security": [ + { + "jwt": [] + }, + { + "user_cose_sign1": [] + } + ], + "x-ccf-forwarding": { + "$ref": "#/components/x-ccf-forwarding/never" + } + } + }, "/app/log/public/count": { "get": { "operationId": "GetAppLogPublicCount", diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 7d313175990a..e88bd2bf1ade 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -1994,7 +1994,7 @@ namespace loggingapp "/log/public/cose_endorsements", HTTP_GET, ccf::historical::read_only_adapter_v4( - get_cbor_merkle_proof, context, is_tx_committed), + get_cose_endorsements, context, is_tx_committed), auth_policies) .set_auto_schema() .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) From 200b9b011d121e1f9efc66483c35be0003b4144d Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 22:26:41 +0000 Subject: [PATCH 16/43] Fixup ids of seek and testcover --- python/src/ccf/cose.py | 14 ++--- python/src/ccf/ledger.py | 5 +- src/node/historical_queries_utils.cpp | 12 ++-- tests/recovery.py | 84 +++++++++++++++++++++------ 4 files changed, 83 insertions(+), 32 deletions(-) diff --git a/python/src/ccf/cose.py b/python/src/ccf/cose.py index 6e339b86c4a0..48b3898140a9 100644 --- a/python/src/ccf/cose.py +++ b/python/src/ccf/cose.py @@ -174,16 +174,14 @@ def create_cose_sign1_finish( return msg.encode(sign=False) -def validate_cose_sign1(payload: bytes, cert_pem: Pem, cose_sign1: bytes): - cert = load_pem_x509_certificate(cert_pem.encode("ascii"), default_backend()) - if not isinstance(cert.public_key(), EllipticCurvePublicKey): - raise NotImplementedError("unsupported key type") - - key = cert.public_key() - cose_key = from_cryptography_eckey_obj(key) +def validate_cose_sign1(pubkey, cose_sign1, payload=None): + cose_key = from_cryptography_eckey_obj(pubkey) msg = Sign1Message.decode(cose_sign1) msg.key = cose_key - msg.payload = payload + + if payload: + # Detached payload + msg.payload = payload if not msg.verify_signature(): raise ValueError("signature is invalid") diff --git a/python/src/ccf/ledger.py b/python/src/ccf/ledger.py index 48c1a207e08d..2b35b754e4e9 100644 --- a/python/src/ccf/ledger.py +++ b/python/src/ccf/ledger.py @@ -571,8 +571,11 @@ def _verify_root_signature(self, tx_info: TxBundleInfo): def _verify_root_cose_signature(self, root, cose_sign1): try: + cert = load_pem_x509_certificate( + self.service_cert.encode("ascii"), default_backend() + ) validate_cose_sign1( - payload=root, cert_pem=self.service_cert, cose_sign1=cose_sign1 + cose_sign1=cose_sign1, pubkey=cert.public_key(), payload=root ) except Exception as exc: raise InvalidRootCoseSignatureException( diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index fc3705b51a24..2bb1f77c9ada 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -108,8 +108,8 @@ namespace } const auto search_to = is_self_endorsement(cose_endorsements_cache.back()) ? - cose_endorsements_cache.end() : - cose_endorsements_cache.end() - 1; + cose_endorsements_cache.end() - 1 : + cose_endorsements_cache.end(); if (search_to->endorsed_from.seqno > target_seq) { @@ -133,18 +133,18 @@ namespace "found", target_seq); + assert(false); // In debug, fail fast. + throw std::logic_error(fmt::format( "Error during COSE endorsement chain reconstruction for seqno {}", target_seq)); } - const auto endorsements_count = - std::distance(cose_endorsements_cache.begin(), final_endorsement) + 1; - Endorsements endorsements(endorsements_count); + Endorsements endorsements; std::transform( cose_endorsements_cache.begin(), - final_endorsement, + final_endorsement + 1, // Inclusive std::back_inserter(endorsements), [](const auto& e) { return e.endorsement; }); diff --git a/tests/recovery.py b/tests/recovery.py index c21a4a70cf5f..f0441c64a41e 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -20,7 +20,13 @@ import ccf.tx_id import tempfile import http +import base64 import shutil +from cryptography.x509 import load_pem_x509_certificate +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from ccf.cose import validate_cose_sign1 +from pycose.messages import Sign1Message # type: ignore from loguru import logger as LOG @@ -47,6 +53,26 @@ def get_and_verify_historical_receipt(network, ref_msg): return ref_msg +def query_endorsements_chain(node, txid): + for _ in range(0, 10): + with node.client("user0") as cli: + response = cli.get( + "/log/public/cose_endorsements", + headers={infra.clients.CCF_TX_ID_HEADER: str(txid)}, + ) + if response.status_code != http.HTTPStatus.ACCEPTED: + return response + time.sleep(0.1) + return response + + +def verify_endorsements_chain(endorsements, pubkey): + for endorsement in endorsements: + validate_cose_sign1(cose_sign1=endorsement, pubkey=pubkey) + next_key_bytes = Sign1Message.decode(endorsement).payload + pubkey = serialization.load_der_public_key(next_key_bytes, default_backend()) + + @reqs.description("Recover a service") @reqs.recover(number_txs=2) def test_recover_service(network, args, from_snapshot=True, no_ledger=False): @@ -299,7 +325,7 @@ def test_recover_service_with_wrong_identity(network, args): cli.get("/node/commit").body.json()["transaction_id"] ) - # Check receipts for transactions after multiple recoveries + # Check receipts for transactions after multiple recoveries. This test relies on previous recoveries and is therefore prone to failures if surrounding test calls change. txids = [ # Last TX before previous recovery shifted_tx(previous_service_created_tx_id, -2, -1), @@ -325,6 +351,30 @@ def test_recover_service_with_wrong_identity(network, args): # try again with a flag to force skip leaf components verification. verify_receipt(receipt, recovered_network.cert, is_signature_tx=True) + with primary.client() as cli: + service_cert = cli.get("/node/network").body.json()["service_certificate"] + cert = load_pem_x509_certificate( + service_cert.encode("ascii"), default_backend() + ) + + for tx in txids[0:1]: + response = query_endorsements_chain(primary, tx) + assert response.status_code == http.HTTPStatus.OK, response + endorsements = [base64.b64decode(x) for x in response.body.json()] + assert len(endorsements) == 2 # 2 recoveries behind + verify_endorsements_chain(endorsements, cert.public_key()) + + for tx in txids[1:4]: + response = query_endorsements_chain(primary, tx) + assert response.status_code == http.HTTPStatus.OK, response + endorsements = [base64.b64decode(x) for x in response.body.json()] + assert len(endorsements) == 1 # 1 recovery behind + verify_endorsements_chain(endorsements, cert.public_key()) + + for tx in txids[4:]: + response = query_endorsements_chain(primary, tx) + assert response.status_code == http.HTTPStatus.NOT_FOUND, response + return recovered_network @@ -943,21 +993,21 @@ def add(parser): # can be dictated by the test. In particular, the signature interval is large # enough to create in-progress ledger files that do not end on a signature. The # test is also in control of the ledger chunking. - cr.add( - "recovery_corrupt_ledger", - run_corrupted_ledger, - package="samples/apps/logging/liblogging", - nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery - sig_ms_interval=1000, - ledger_chunk_bytes="1GB", - snapshot_tx_interval=1000000, - ) - - cr.add( - "recovery_snapshot_alone", - run_recover_snapshot_alone, - package="samples/apps/logging/liblogging", - nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery - ) + # cr.add( + # "recovery_corrupt_ledger", + # run_corrupted_ledger, + # package="samples/apps/logging/liblogging", + # nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery + # sig_ms_interval=1000, + # ledger_chunk_bytes="1GB", + # snapshot_tx_interval=1000000, + # ) + + # cr.add( + # "recovery_snapshot_alone", + # run_recover_snapshot_alone, + # package="samples/apps/logging/liblogging", + # nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery + # ) cr.run() From a646ac1f590ceb3a8e939d9ec224b8790ac78163 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sat, 5 Oct 2024 22:28:25 +0000 Subject: [PATCH 17/43] More asserts --- src/node/historical_queries_utils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 2bb1f77c9ada..43313a71f0f1 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -299,6 +299,8 @@ namespace ccf if (!service_start) { LOG_FAIL_FMT("Service start txid not found"); + assert(false); // In debug, fail fast. + return true; } From 61e6bbae017a51d2c1b1dc9efd5be6d3074055e1 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Sun, 6 Oct 2024 00:05:20 +0000 Subject: [PATCH 18/43] Enable disabled test --- tests/recovery.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/recovery.py b/tests/recovery.py index f0441c64a41e..3370e45b3127 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -993,21 +993,21 @@ def add(parser): # can be dictated by the test. In particular, the signature interval is large # enough to create in-progress ledger files that do not end on a signature. The # test is also in control of the ledger chunking. - # cr.add( - # "recovery_corrupt_ledger", - # run_corrupted_ledger, - # package="samples/apps/logging/liblogging", - # nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery - # sig_ms_interval=1000, - # ledger_chunk_bytes="1GB", - # snapshot_tx_interval=1000000, - # ) - - # cr.add( - # "recovery_snapshot_alone", - # run_recover_snapshot_alone, - # package="samples/apps/logging/liblogging", - # nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery - # ) + cr.add( + "recovery_corrupt_ledger", + run_corrupted_ledger, + package="samples/apps/logging/liblogging", + nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery + sig_ms_interval=1000, + ledger_chunk_bytes="1GB", + snapshot_tx_interval=1000000, + ) + + cr.add( + "recovery_snapshot_alone", + run_recover_snapshot_alone, + package="samples/apps/logging/liblogging", + nodes=infra.e2e_args.min_nodes(cr.args, f=0), # 1 node suffices for recovery + ) cr.run() From b99468115ec033dc160d544272f15aded7eb53c0 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 11:35:48 +0000 Subject: [PATCH 19/43] Trace logs --- src/node/historical_queries_utils.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 43313a71f0f1..ebb7f131a233 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -102,8 +102,9 @@ namespace if (cose_endorsements_cache.size() == 1) { - // Only current service self-endorsement was found, meaning no historical - // TXs for previous epochs were COSE-endorsed. + LOG_TRACE_FMT( + "Only current service self-endorsement was found, no historical TXs " + "for previous epochs were COSE-endorsed."); return {.endorsements = std::nullopt, .retry = false}; } @@ -113,8 +114,11 @@ namespace if (search_to->endorsed_from.seqno > target_seq) { - // COSE-endorsements are fetched for newer epochs, but target_seq is far - // behind and was never endorsed. + LOG_TRACE_FMT( + "COSE-endorsements are fetched for newer epochs, but target_seq {} is " + "far behind and was never endorsed.", + target_seq); + return {.endorsements = std::nullopt, .retry = false}; } From d5731c3dd3a8fc952a80a95cf64b50cdb4636591 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 12:36:36 +0000 Subject: [PATCH 20/43] Convert new table to internal and document --- doc/audit/builtin_maps.rst | 10 +++++++++- src/service/network_tables.h | 3 +++ src/service/tables/previous_service_identity.h | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index fec70a37e334..4c7be3d22559 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -509,4 +509,12 @@ Evidence inserted in the ledger by a primary producing a snapshot to establish p Used to persist submitted shares during a recovery. -While the contents themselves are encrypted, the table is public so as to be accessible by nodes bootstrapping a recovery service. \ No newline at end of file +While the contents themselves are encrypted, the table is public so as to be accessible by nodes bootstrapping a recovery service. + + +``previous_service_identity_endorsement`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Key** Name of a network interface (string). + +**Value** Endorsed COSE sign1 for the interface, represented as a DER-encoded string. \ No newline at end of file diff --git a/src/service/network_tables.h b/src/service/network_tables.h index 3e1ce975c767..6ada64dcd645 100644 --- a/src/service/network_tables.h +++ b/src/service/network_tables.h @@ -178,6 +178,9 @@ namespace ccf const Service service = {Tables::SERVICE}; const PreviousServiceIdentity previous_service_identity = { Tables::PREVIOUS_SERVICE_IDENTITY}; + const PreviousServiceIdentityEndorsement + previous_service_identity_endorsement = { + Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT}; const Configuration config = {Tables::CONFIGURATION}; const Constitution constitution = {Tables::CONSTITUTION}; diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index ec272fcdc679..954e75afc7c7 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -45,6 +45,6 @@ namespace ccf static constexpr auto PREVIOUS_SERVICE_IDENTITY = "public:ccf.gov.service.previous_service_identity"; static constexpr auto PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT = - "public:ccf.gov.service.previous_service_identity_endorsement"; + "public:ccf.internal.previous_service_identity_endorsement"; } } \ No newline at end of file From 68f140a7cf69b5aa5a6a5bd1553e743c46d2a199 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 13:29:11 +0000 Subject: [PATCH 21/43] Format --- tests/recovery.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/recovery.py b/tests/recovery.py index 3370e45b3127..2c7959703eef 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -325,7 +325,9 @@ def test_recover_service_with_wrong_identity(network, args): cli.get("/node/commit").body.json()["transaction_id"] ) - # Check receipts for transactions after multiple recoveries. This test relies on previous recoveries and is therefore prone to failures if surrounding test calls change. + # Check receipts for transactions after multiple recoveries. This test + # relies on previous recoveries and is therefore prone to failures if + # surrounding test calls change. txids = [ # Last TX before previous recovery shifted_tx(previous_service_created_tx_id, -2, -1), From babc0822c975f33baf82c1c931cd586a5ef690eb Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 16:32:08 +0000 Subject: [PATCH 22/43] Type alias + error handling --- include/ccf/receipt.h | 7 ++++-- src/node/historical_queries_adapter.cpp | 4 ++-- src/node/historical_queries_utils.cpp | 29 ++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/include/ccf/receipt.h b/include/ccf/receipt.h index 50bf425d3bc8..96a58eeee79c 100644 --- a/include/ccf/receipt.h +++ b/include/ccf/receipt.h @@ -146,8 +146,11 @@ namespace ccf }; std::optional> describe_merkle_proof_v1( const TxReceiptImpl& in); - std::optional>> - describe_cose_endorsements_v1(const TxReceiptImpl& in); + + using SerialisedCoseEndorsement = std::vector; + using SerialisedCoseEndorsements = std::vector; + std::optional describe_cose_endorsements_v1( + const TxReceiptImpl& in); // Manual JSON serializers are specified for these types as they are not // trivial POD structs diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index a499c4e735de..cb0886b1e4f9 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -249,8 +249,8 @@ namespace ccf return underlying_buffer; } - std::optional>> - describe_cose_endorsements_v1(const TxReceiptImpl& receipt) + std::optional describe_cose_endorsements_v1( + const TxReceiptImpl& receipt) { return receipt.cose_endorsements; } diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index ebb7f131a233..8e39505e4091 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -39,14 +39,37 @@ namespace { const auto [from, till] = ccf::crypto::extract_cose_endorsement_validity( endorsement->endorsement); + const auto from_txid = ccf::TxID::from_str(from); + if (!from_txid) + { + throw std::logic_error( + fmt::format("Can't parse COSE endorsement 'from' header: {}", from)); + } + const auto till_txid = ccf::TxID::from_str(till); + if (!till_txid) + { + throw std::logic_error( + fmt::format("Can't parse COSE endorsement 'till' header: ", till)); + } + + if (!endorsement->endorsed_till) + { + throw std::logic_error( + "COSE endorsement doesn't contain 'till' field in the table entry"); + } if ( - !till_txid || !endorsement->endorsed_till || endorsement->endorsed_from != *from_txid || - endorsement->endorsed_till != *till_txid) + *endorsement->endorsed_till != *till_txid) { - throw std::logic_error("COSE endorsement fetched but range is invalid"); + throw std::logic_error(fmt ::format( + "COSE endorsement fetched but range is invalid, from {} till {}, " + "header from: {}, header till: {}", + endorsement->endorsed_from.to_str(), + endorsement->endorsed_till->to_str(), + from, + till)); } } } From 3c0efba711d38e578cfd25013e887465d00423cc Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 16:47:23 +0000 Subject: [PATCH 23/43] Schema JSON for http sample --- samples/apps/logging/logging.cpp | 11 +++++++---- samples/apps/logging/logging_schema.h | 10 ++++++++++ tests/recovery.py | 8 ++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index e88bd2bf1ade..b3f8afc6da6f 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -1979,16 +1979,19 @@ namespace loggingapp "No COSE endorsements available for this transaction"); return; } - auto response = nlohmann::json::array(); + LoggingGetCoseEndorsements::Out response{ + .endorsements = ccf::SerialisedCoseEndorsements{}}; for (const auto& endorsement : *endorsements) { - response.push_back(endorsement); + response.endorsements->push_back(endorsement); } + + nlohmann::json j_response = response; ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); ctx.rpc_ctx->set_response_header( ccf::http::headers::CONTENT_TYPE, ccf::http::headervalues::contenttype::JSON); - ctx.rpc_ctx->set_response_body(response.dump()); + ctx.rpc_ctx->set_response_body(j_response.dump()); }; make_read_only_endpoint( "/log/public/cose_endorsements", @@ -1996,7 +1999,7 @@ namespace loggingapp ccf::historical::read_only_adapter_v4( get_cose_endorsements, context, is_tx_committed), auth_policies) - .set_auto_schema() + .set_auto_schema() .set_forwarding_required(ccf::endpoints::ForwardingRequired::Never) .install(); } diff --git a/samples/apps/logging/logging_schema.h b/samples/apps/logging/logging_schema.h index 5380eacb90b4..b908061bfc58 100644 --- a/samples/apps/logging/logging_schema.h +++ b/samples/apps/logging/logging_schema.h @@ -98,6 +98,16 @@ namespace loggingapp DECLARE_JSON_OPTIONAL_FIELDS_WITH_RENAMES( LoggingGetHistoricalRange::Out, next_link, "@nextLink"); + struct LoggingGetCoseEndorsements + { + struct Out + { + std::optional endorsements; + }; + }; + DECLARE_JSON_TYPE(LoggingGetCoseEndorsements::Out); + DECLARE_JSON_REQUIRED_FIELDS(LoggingGetCoseEndorsements::Out, endorsements); + // Public record/get // Manual schemas, verified then parsed in handler static const std::string j_record_public_in = R"!!!( diff --git a/tests/recovery.py b/tests/recovery.py index 2c7959703eef..4ec67d2d2bde 100644 --- a/tests/recovery.py +++ b/tests/recovery.py @@ -362,14 +362,18 @@ def test_recover_service_with_wrong_identity(network, args): for tx in txids[0:1]: response = query_endorsements_chain(primary, tx) assert response.status_code == http.HTTPStatus.OK, response - endorsements = [base64.b64decode(x) for x in response.body.json()] + endorsements = [ + base64.b64decode(x) for x in response.body.json()["endorsements"] + ] assert len(endorsements) == 2 # 2 recoveries behind verify_endorsements_chain(endorsements, cert.public_key()) for tx in txids[1:4]: response = query_endorsements_chain(primary, tx) assert response.status_code == http.HTTPStatus.OK, response - endorsements = [base64.b64decode(x) for x in response.body.json()] + endorsements = [ + base64.b64decode(x) for x in response.body.json()["endorsements"] + ] assert len(endorsements) == 1 # 1 recovery behind verify_endorsements_chain(endorsements, cert.public_key()) From f47e91dfc2c47df086ac2b1f0cc39f2d5fd49be4 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 17:03:07 +0000 Subject: [PATCH 24/43] Err handling for COSE unpack --- src/crypto/openssl/cose_verifier.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 23e6d1af1833..4696ce07cee5 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -254,10 +254,26 @@ namespace ccf::crypto } struct q_useful_buf_c protected_parameters; + QCBORDecode_EnterBstrWrapped( &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters); + + qcbor_result = QCBORDecode_GetError(&ctx); + if (qcbor_result != QCBOR_SUCCESS) + { + LOG_DEBUG_FMT("Failed to parse COSE_Sign1 as bstr"); + return {}; + } + QCBORDecode_EnterMap(&ctx, NULL); + qcbor_result = QCBORDecode_GetError(&ctx); + if (qcbor_result != QCBOR_SUCCESS) + { + LOG_DEBUG_FMT("Failed to parse COSE_Sign1 wrapped map"); + return {}; + } + enum { FROM_INDEX, From 5448ae25a995b4d4b1da40c98438807162598085 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 17:22:43 +0000 Subject: [PATCH 25/43] Attempting debug crash --- src/node/historical_queries_utils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 8e39505e4091..204a3b351744 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -112,6 +112,8 @@ namespace return {.endorsements = std::nullopt, .retry = true}; } + assert(hstate->store); // TO DO remove me + auto htx = hstate->store->create_read_only_tx(); const auto endorsement = htx From 816860df0a82c5bc2f9fbc66cbdf0b36a796e405 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 23:55:05 +0000 Subject: [PATCH 26/43] Inclusive TX range + validation --- src/crypto/openssl/cose_sign.h | 6 ++- src/crypto/openssl/cose_verifier.cpp | 8 +++- src/node/historical_queries_utils.cpp | 46 ++++++++++++++----- src/service/internal_tables_access.h | 35 ++++++++++---- .../tables/previous_service_identity.h | 12 ++--- 5 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index 5803556d7068..e121b0298521 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -19,7 +19,11 @@ namespace ccf::crypto // Standardised: verifiable data structure static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; // CCF-specific: last signed TxID - static constexpr const char* COSE_PHEADER_KEY_TXID = "ccf.txid"; + static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid"; + // CCF-specific: first TX in the range. + static const std::string COSE_PHEADER_KEY_RANGE_BEGIN = "ccf.range.begin"; + // CCF-specific: last TX included in the range. + static const std::string COSE_PHEADER_KEY_RANGE_END = "ccf.epoch.end"; class COSEParametersFactory { diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 4696ce07cee5..3830d8d99856 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -282,11 +282,15 @@ namespace ccf::crypto }; QCBORItem header_items[END_INDEX + 1]; - header_items[FROM_INDEX].label.string = UsefulBufC{"from", 4}; + header_items[FROM_INDEX].label.string = UsefulBufC{ + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN.data(), + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN.size()}; header_items[FROM_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; header_items[FROM_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; - header_items[TILL_INDEX].label.string = UsefulBufC{"till", 4}; + header_items[TILL_INDEX].label.string = UsefulBufC{ + ccf::crypto::COSE_PHEADER_KEY_RANGE_END.data(), + ccf::crypto::COSE_PHEADER_KEY_RANGE_END.size()}; header_items[TILL_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; header_items[TILL_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 204a3b351744..9d1c9c4d5db0 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -37,7 +37,7 @@ namespace if (!is_self_endorsement(*endorsement)) { - const auto [from, till] = ccf::crypto::extract_cose_endorsement_validity( + const auto [from, to] = ccf::crypto::extract_cose_endorsement_validity( endorsement->endorsement); const auto from_txid = ccf::TxID::from_str(from); @@ -47,33 +47,49 @@ namespace fmt::format("Can't parse COSE endorsement 'from' header: {}", from)); } - const auto till_txid = ccf::TxID::from_str(till); - if (!till_txid) + const auto to_txid = ccf::TxID::from_str(to); + if (!to_txid) { throw std::logic_error( - fmt::format("Can't parse COSE endorsement 'till' header: ", till)); + fmt::format("Can't parse COSE endorsement 'till' header: ", to)); } - if (!endorsement->endorsed_till) + if (!endorsement->endorsed_to) { throw std::logic_error( - "COSE endorsement doesn't contain 'till' field in the table entry"); + "COSE endorsement doesn't contain 'to' field in the table entry"); } if ( endorsement->endorsed_from != *from_txid || - *endorsement->endorsed_till != *till_txid) + *endorsement->endorsed_to != *to_txid) { throw std::logic_error(fmt ::format( - "COSE endorsement fetched but range is invalid, from {} till {}, " - "header from: {}, header till: {}", + "COSE endorsement fetched but range is invalid, from {} to {}, " + "header from: {}, header to: {}", endorsement->endorsed_from.to_str(), - endorsement->endorsed_till->to_str(), + endorsement->endorsed_to->to_str(), from, - till)); + to)); } } } + void validate_chain_integrity( + const ccf::CoseEndorsement& newer, const ccf::CoseEndorsement& older) + { + if ( + !is_self_endorsement(older) && + (newer.endorsed_from.view - 2 != older.endorsed_to->view || + newer.endorsed_from.seqno - 1 != older.endorsed_to->seqno)) + { + throw std::logic_error(fmt::format( + "COSE endorsement chain integrity is violated, older endorsement end " + "{} is not chained with newer endorsement start {}", + older.endorsed_to->to_str(), + newer.endorsed_from.to_str())); + } + } + void ensure_first_fetch(ccf::kv::ReadOnlyTx& tx) { if (cose_endorsements_cache.empty()) [[unlikely]] @@ -122,6 +138,8 @@ namespace ->get(); validate_fetched_endorsement(endorsement); + validate_chain_integrity( + cose_endorsements_cache.back(), endorsement.value()); cose_endorsements_cache.push_back(*endorsement); } @@ -356,7 +374,11 @@ namespace ccf } catch (const std::logic_error& e) { - LOG_FAIL_FMT("Failed to fetch endorsements: {}", e.what()); + LOG_FAIL_FMT("FAIL: Failed to fetch endorsements: {}", e.what()); + LOG_DEBUG_FMT("DEBUG: Failed to fetch endorsements: {}", e.what()); + LOG_INFO_FMT("INFO: Failed to fetch endorsements: {}", e.what()); + + std::cerr << "Failed to fetch endorsements: " << e.what() << std::endl; assert(false); // In debug, fail fast. diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 8abbbbe25959..c93e9f676112 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -22,6 +22,24 @@ namespace ccf { + + /* We can't query the past epochs' TXs if the service hasn't been opened + * yet. We do guess values based on epoch value and seqno changing rules. */ + static int RECOVERED_SERVICE_EPOCH_DIFF = 2; + static int RECOVERED_SERVICE_SEQNO_DIFF = 1; + ccf::TxID previous_tx_if_recovery(ccf::TxID txid) + { + return ccf::TxID{ + .view = txid.view - RECOVERED_SERVICE_EPOCH_DIFF, + .seqno = txid.seqno - RECOVERED_SERVICE_SEQNO_DIFF}; + } + ccf::TxID next_tx_if_recovery(ccf::TxID txid) + { + return ccf::TxID{ + .view = txid.view + RECOVERED_SERVICE_EPOCH_DIFF, + .seqno = txid.seqno + RECOVERED_SERVICE_SEQNO_DIFF}; + } + // This class provides functions for interacting with various internal // service-governance tables. Specifically, it aims to maintain some // invariants amongst these tables (eg - keys being present in multiple @@ -378,13 +396,12 @@ namespace ccf { const auto prev_endorsement = previous_identity_endorsement->get(); - endorsement.endorsed_from = - prev_endorsement->endorsed_till.has_value() ? - prev_endorsement->endorsed_till.value() : + endorsement.endorsed_from = prev_endorsement->endorsed_to.has_value() ? + next_tx_if_recovery(prev_endorsement->endorsed_to.value()) : prev_endorsement->endorsed_from; - endorsement.endorsed_till = - active_service->current_service_create_txid.value(); + endorsement.endorsed_to = previous_tx_if_recovery( + active_service->current_service_create_txid.value()); endorsement.previous_version = previous_identity_endorsement->get_version_of_previous_write(); @@ -403,11 +420,13 @@ namespace ccf } pheaders.push_back(ccf::crypto::cose_params_string_string( - "from", endorsement.endorsed_from.to_str())); - if (endorsement.endorsed_till) + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN, + endorsement.endorsed_from.to_str())); + if (endorsement.endorsed_to) { pheaders.push_back(ccf::crypto::cose_params_string_string( - "till", endorsement.endorsed_till->to_str())); + ccf::crypto::COSE_PHEADER_KEY_RANGE_END, + endorsement.endorsed_to->to_str())); } try diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 954e75afc7c7..2a1143a270f6 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -20,23 +20,23 @@ namespace ccf /// Service key at the moment of endorsing. std::vector endorsing_key{}; - /// The transaction ID when the *endorsing* service was created. + /// The transaction ID when the *endorsed* service was created. ccf::TxID endorsed_from{}; /// Pointer to the previous CoseEndorsement entry. Only present for previous /// service endorsements, self-endorsed services must not have this set. std::optional previous_version{}; - /// Exclusive upper bound of the endorsement validity range. Self-endorsed - /// services must not have this value set. - std::optional endorsed_till{}; + /// Last transaction ID that the endorsement is valid for. Only present for + /// previouse services endorsements, self-endorsed services must not have + /// this set. + std::optional endorsed_to{}; }; DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CoseEndorsement); DECLARE_JSON_REQUIRED_FIELDS( CoseEndorsement, endorsement, endorsed_from, endorsing_key); - DECLARE_JSON_OPTIONAL_FIELDS( - CoseEndorsement, previous_version, endorsed_till); + DECLARE_JSON_OPTIONAL_FIELDS(CoseEndorsement, previous_version, endorsed_to); using PreviousServiceIdentityEndorsement = ServiceValue; From 16a0cd5508d25786396670b1312f2fafb65f98d4 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Mon, 7 Oct 2024 23:56:10 +0000 Subject: [PATCH 27/43] Format --- src/service/internal_tables_access.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index c93e9f676112..9266298d4a83 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -22,7 +22,6 @@ namespace ccf { - /* We can't query the past epochs' TXs if the service hasn't been opened * yet. We do guess values based on epoch value and seqno changing rules. */ static int RECOVERED_SERVICE_EPOCH_DIFF = 2; From 930acd99dd20e4436842e65edee297eccb274ae2 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 11:18:01 +0000 Subject: [PATCH 28/43] Fix iterators bug --- src/node/historical_queries_utils.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 9d1c9c4d5db0..953a9ee12f76 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -128,8 +128,6 @@ namespace return {.endorsements = std::nullopt, .retry = true}; } - assert(hstate->store); // TO DO remove me - auto htx = hstate->store->create_read_only_tx(); const auto endorsement = htx @@ -151,11 +149,15 @@ namespace return {.endorsements = std::nullopt, .retry = false}; } - const auto search_to = is_self_endorsement(cose_endorsements_cache.back()) ? - cose_endorsements_cache.end() - 1 : - cose_endorsements_cache.end(); + auto last_valid_endorsement = cose_endorsements_cache.end() - 1; + if (is_self_endorsement(*last_valid_endorsement)) + { + --last_valid_endorsement; + } - if (search_to->endorsed_from.seqno > target_seq) + const auto search_to = last_valid_endorsement + 1; + + if (last_valid_endorsement->endorsed_from.seqno > target_seq) { LOG_TRACE_FMT( "COSE-endorsements are fetched for newer epochs, but target_seq {} is " @@ -375,10 +377,6 @@ namespace ccf catch (const std::logic_error& e) { LOG_FAIL_FMT("FAIL: Failed to fetch endorsements: {}", e.what()); - LOG_DEBUG_FMT("DEBUG: Failed to fetch endorsements: {}", e.what()); - LOG_INFO_FMT("INFO: Failed to fetch endorsements: {}", e.what()); - - std::cerr << "Failed to fetch endorsements: " << e.what() << std::endl; assert(false); // In debug, fail fast. From 6edf5b167d5ec685ffe4614ec035460d948c249a Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 11:45:06 +0000 Subject: [PATCH 29/43] Named struct to return validity --- include/ccf/crypto/cose_verifier.h | 7 ++++++- src/crypto/openssl/cose_verifier.cpp | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/include/ccf/crypto/cose_verifier.h b/include/ccf/crypto/cose_verifier.h index 5430dfd3a7cc..d0ccc323197b 100644 --- a/include/ccf/crypto/cose_verifier.h +++ b/include/ccf/crypto/cose_verifier.h @@ -28,6 +28,11 @@ namespace ccf::crypto const std::vector& cert); COSEVerifierUniquePtr make_cose_verifier_from_key(const Pem& public_key); - std::pair extract_cose_endorsement_validity( + struct COSEEndorsementValidity + { + std::string from{}; + std::string to{}; + }; + COSEEndorsementValidity extract_cose_endorsement_validity( std::span cose_msg); } diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 3830d8d99856..84812dd366ad 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -229,7 +229,7 @@ namespace ccf::crypto return std::make_unique(public_key); } - std::pair extract_cose_endorsement_validity( + COSEEndorsementValidity extract_cose_endorsement_validity( std::span cose_msg) { UsefulBufC msg{cose_msg.data(), cose_msg.size()}; @@ -277,7 +277,7 @@ namespace ccf::crypto enum { FROM_INDEX, - TILL_INDEX, + TO_INDEX, END_INDEX }; QCBORItem header_items[END_INDEX + 1]; @@ -288,11 +288,11 @@ namespace ccf::crypto header_items[FROM_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; header_items[FROM_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; - header_items[TILL_INDEX].label.string = UsefulBufC{ + header_items[TO_INDEX].label.string = UsefulBufC{ ccf::crypto::COSE_PHEADER_KEY_RANGE_END.data(), ccf::crypto::COSE_PHEADER_KEY_RANGE_END.size()}; - header_items[TILL_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; - header_items[TILL_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; + header_items[TO_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[TO_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING; header_items[END_INDEX].uLabelType = QCBOR_TYPE_NONE; @@ -306,18 +306,22 @@ namespace ccf::crypto if (header_items[FROM_INDEX].uDataType == QCBOR_TYPE_NONE) { - LOG_DEBUG_FMT("Failed to retrieve (missing) 'from' parameter"); + LOG_DEBUG_FMT( + "Failed to retrieve (missing) {} parameter", + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN); return {}; } - if (header_items[TILL_INDEX].uDataType == QCBOR_TYPE_NONE) + if (header_items[TO_INDEX].uDataType == QCBOR_TYPE_NONE) { - LOG_DEBUG_FMT("Failed to retrieve (missing) 'till' parameter"); + LOG_DEBUG_FMT( + "Failed to retrieve (missing) {} parameter", + ccf::crypto::COSE_PHEADER_KEY_RANGE_END); return {}; } const auto from = qcbor_buf_to_string(header_items[FROM_INDEX].val.string); - const auto till = qcbor_buf_to_string(header_items[TILL_INDEX].val.string); + const auto to = qcbor_buf_to_string(header_items[TO_INDEX].val.string); // Complete decode to ensure well-formed CBOR. @@ -331,6 +335,6 @@ namespace ccf::crypto return {}; } - return {from, till}; + return COSEEndorsementValidity{.from = from, .to = to}; } } From 37c26d54a07355d6f1869ee7ca03cebfebc77b63 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 12:31:13 +0000 Subject: [PATCH 30/43] Schema test fix --- doc/schemas/app_openapi.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/schemas/app_openapi.json b/doc/schemas/app_openapi.json index f588be501296..70942e22f07a 100644 --- a/doc/schemas/app_openapi.json +++ b/doc/schemas/app_openapi.json @@ -98,6 +98,17 @@ ], "type": "object" }, + "LoggingGetCoseEndorsements__Out": { + "properties": { + "endorsements": { + "$ref": "#/components/schemas/base64string_array" + } + }, + "required": [ + "endorsements" + ], + "type": "object" + }, "LoggingGetHistoricalRange__Entry": { "properties": { "id": { @@ -215,6 +226,16 @@ ], "type": "string" }, + "base64string": { + "format": "base64", + "type": "string" + }, + "base64string_array": { + "items": { + "$ref": "#/components/schemas/base64string" + }, + "type": "array" + }, "boolean": { "type": "boolean" }, @@ -1214,7 +1235,14 @@ "get": { "operationId": "GetAppLogPublicCoseEndorsements", "responses": { - "204": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoggingGetCoseEndorsements__Out" + } + } + }, "description": "Default response description" }, "default": { From 83a3381de94351c76adb9c7411b7bf3502fdccc9 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 12:33:32 +0000 Subject: [PATCH 31/43] Rename from->from_txid and to --- include/ccf/crypto/cose_verifier.h | 4 ++-- src/crypto/openssl/cose_verifier.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ccf/crypto/cose_verifier.h b/include/ccf/crypto/cose_verifier.h index d0ccc323197b..98e4bb71ac91 100644 --- a/include/ccf/crypto/cose_verifier.h +++ b/include/ccf/crypto/cose_verifier.h @@ -30,8 +30,8 @@ namespace ccf::crypto struct COSEEndorsementValidity { - std::string from{}; - std::string to{}; + std::string from_txid{}; + std::string to_txid{}; }; COSEEndorsementValidity extract_cose_endorsement_validity( std::span cose_msg); diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 84812dd366ad..838f1485c270 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -335,6 +335,6 @@ namespace ccf::crypto return {}; } - return COSEEndorsementValidity{.from = from, .to = to}; + return COSEEndorsementValidity{.from_txid = from, .to_txid = to}; } } From 2f8a0f795119ba9211a61bd2cf0c80aa3ecec196 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 12:35:21 +0000 Subject: [PATCH 32/43] Style --- src/node/historical_queries_utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 953a9ee12f76..e74e7894f843 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -44,14 +44,14 @@ namespace if (!from_txid) { throw std::logic_error( - fmt::format("Can't parse COSE endorsement 'from' header: {}", from)); + fmt::format("Cannot parse COSE endorsement 'from' header: {}", from)); } const auto to_txid = ccf::TxID::from_str(to); if (!to_txid) { throw std::logic_error( - fmt::format("Can't parse COSE endorsement 'till' header: ", to)); + fmt::format("Cannot parse COSE endorsement 'to' header: {}", to)); } if (!endorsement->endorsed_to) From 1f81bab65d216f9659f287a5ac5c897d3bac9a4a Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 12:51:20 +0000 Subject: [PATCH 33/43] Consistent naming --- src/crypto/openssl/cose_sign.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index e121b0298521..4cdf05aa0523 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -21,7 +21,7 @@ namespace ccf::crypto // CCF-specific: last signed TxID static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid"; // CCF-specific: first TX in the range. - static const std::string COSE_PHEADER_KEY_RANGE_BEGIN = "ccf.range.begin"; + static const std::string COSE_PHEADER_KEY_RANGE_BEGIN = "ccf.epoch.begin"; // CCF-specific: last TX included in the range. static const std::string COSE_PHEADER_KEY_RANGE_END = "ccf.epoch.end"; From 2b2de0b78524f12b2694e5a263469ba5ae1fb6a4 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 12:52:11 +0000 Subject: [PATCH 34/43] Fix doc --- doc/audit/builtin_maps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index 4c7be3d22559..b9270fda3b26 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -515,6 +515,6 @@ While the contents themselves are encrypted, the table is public so as to be acc ``previous_service_identity_endorsement`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Key** Name of a network interface (string). +**Key** Sentinel value 0, represented as a little-endian 64-bit unsigned integer. **Value** Endorsed COSE sign1 for the interface, represented as a DER-encoded string. \ No newline at end of file From ba6d13f422c0f10ce3752ce68a6e29a9c8dd2e88 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 13:07:59 +0000 Subject: [PATCH 35/43] View change from raft.h --- src/node/historical_queries_utils.cpp | 4 +++- src/service/internal_tables_access.h | 9 +++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index e74e7894f843..29bc96e19d64 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -6,6 +6,7 @@ #include "ccf/crypto/cose_verifier.h" #include "ccf/rpc_context.h" #include "ccf/service/tables/service.h" +#include "consensus/aft/raft_types.h" #include "kv/kv_types.h" #include "node/identity.h" #include "node/tx_receipt_impl.h" @@ -79,7 +80,8 @@ namespace { if ( !is_self_endorsement(older) && - (newer.endorsed_from.view - 2 != older.endorsed_to->view || + (newer.endorsed_from.view - aft::starting_view_change != + older.endorsed_to->view || newer.endorsed_from.seqno - 1 != older.endorsed_to->seqno)) { throw std::logic_error(fmt::format( diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 9266298d4a83..e85b36e57c6c 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -11,6 +11,7 @@ #include "ccf/service/tables/snp_measurements.h" #include "ccf/service/tables/users.h" #include "ccf/tx.h" +#include "consensus/aft/raft_types.h" #include "crypto/openssl/cose_sign.h" #include "node/ledger_secrets.h" #include "node/uvm_endorsements.h" @@ -24,19 +25,15 @@ namespace ccf { /* We can't query the past epochs' TXs if the service hasn't been opened * yet. We do guess values based on epoch value and seqno changing rules. */ - static int RECOVERED_SERVICE_EPOCH_DIFF = 2; - static int RECOVERED_SERVICE_SEQNO_DIFF = 1; ccf::TxID previous_tx_if_recovery(ccf::TxID txid) { return ccf::TxID{ - .view = txid.view - RECOVERED_SERVICE_EPOCH_DIFF, - .seqno = txid.seqno - RECOVERED_SERVICE_SEQNO_DIFF}; + .view = txid.view - aft::starting_view_change, .seqno = txid.seqno - 1}; } ccf::TxID next_tx_if_recovery(ccf::TxID txid) { return ccf::TxID{ - .view = txid.view + RECOVERED_SERVICE_EPOCH_DIFF, - .seqno = txid.seqno + RECOVERED_SERVICE_SEQNO_DIFF}; + .view = txid.view + aft::starting_view_change, .seqno = txid.seqno + 1}; } // This class provides functions for interacting with various internal From 00cdbd35651b0847e600140dea80d19ecbf77e67 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 13:36:53 +0000 Subject: [PATCH 36/43] Consistent epoch naming --- src/node/historical_queries_utils.cpp | 47 ++++++++++--------- src/service/internal_tables_access.h | 17 +++---- .../tables/previous_service_identity.h | 17 +++---- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 29bc96e19d64..8a025c06bdeb 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -44,31 +44,33 @@ namespace const auto from_txid = ccf::TxID::from_str(from); if (!from_txid) { - throw std::logic_error( - fmt::format("Cannot parse COSE endorsement 'from' header: {}", from)); + throw std::logic_error(fmt::format( + "Cannot parse COSE endorsement header: {}", + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN)); } const auto to_txid = ccf::TxID::from_str(to); if (!to_txid) { - throw std::logic_error( - fmt::format("Cannot parse COSE endorsement 'to' header: {}", to)); + throw std::logic_error(fmt::format( + "Cannot parse COSE endorsement header: {}", + ccf::crypto::COSE_PHEADER_KEY_RANGE_END)); } - if (!endorsement->endorsed_to) + if (!endorsement->endorsement_epoch_end) { throw std::logic_error( - "COSE endorsement doesn't contain 'to' field in the table entry"); + "COSE endorsement doesn't contain epoch end in the table entry"); } if ( - endorsement->endorsed_from != *from_txid || - *endorsement->endorsed_to != *to_txid) + endorsement->endorsement_epoch_begin != *from_txid || + *endorsement->endorsement_epoch_end != *to_txid) { throw std::logic_error(fmt ::format( - "COSE endorsement fetched but range is invalid, from {} to {}, " - "header from: {}, header to: {}", - endorsement->endorsed_from.to_str(), - endorsement->endorsed_to->to_str(), + "COSE endorsement fetched but range is invalid, epoch begin {}, " + "epoch end {}, header epoch begin: {}, header epoch end: {}", + endorsement->endorsement_epoch_begin.to_str(), + endorsement->endorsement_epoch_end->to_str(), from, to)); } @@ -80,15 +82,16 @@ namespace { if ( !is_self_endorsement(older) && - (newer.endorsed_from.view - aft::starting_view_change != - older.endorsed_to->view || - newer.endorsed_from.seqno - 1 != older.endorsed_to->seqno)) + (newer.endorsement_epoch_begin.view - aft::starting_view_change != + older.endorsement_epoch_end->view || + newer.endorsement_epoch_begin.seqno - 1 != + older.endorsement_epoch_end->seqno)) { throw std::logic_error(fmt::format( - "COSE endorsement chain integrity is violated, older endorsement end " - "{} is not chained with newer endorsement start {}", - older.endorsed_to->to_str(), - newer.endorsed_from.to_str())); + "COSE endorsement chain integrity is violated, previous endorsement " + "epoch end {} is not chained with newer endorsement epoch begin {}", + older.endorsement_epoch_end->to_str(), + newer.endorsement_epoch_begin.to_str())); } } @@ -108,7 +111,7 @@ namespace bool keep_fetching(ccf::SeqNo target_seq) { return !is_self_endorsement(cose_endorsements_cache.back()) && - cose_endorsements_cache.back().endorsed_from.seqno > target_seq; + cose_endorsements_cache.back().endorsement_epoch_begin.seqno > target_seq; } FetchResult fetch_endorsements_for( @@ -159,7 +162,7 @@ namespace const auto search_to = last_valid_endorsement + 1; - if (last_valid_endorsement->endorsed_from.seqno > target_seq) + if (last_valid_endorsement->endorsement_epoch_begin.seqno > target_seq) { LOG_TRACE_FMT( "COSE-endorsements are fetched for newer epochs, but target_seq {} is " @@ -174,7 +177,7 @@ namespace search_to, target_seq, [](const auto& seq, const auto& endorsement) { - return endorsement.endorsed_from.seqno <= seq; + return endorsement.endorsement_epoch_begin.seqno <= seq; }); if (final_endorsement == search_to) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e85b36e57c6c..5187532bb125 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -392,11 +392,12 @@ namespace ccf { const auto prev_endorsement = previous_identity_endorsement->get(); - endorsement.endorsed_from = prev_endorsement->endorsed_to.has_value() ? - next_tx_if_recovery(prev_endorsement->endorsed_to.value()) : - prev_endorsement->endorsed_from; + endorsement.endorsement_epoch_begin = + prev_endorsement->endorsement_epoch_end.has_value() ? + next_tx_if_recovery(prev_endorsement->endorsement_epoch_end.value()) : + prev_endorsement->endorsement_epoch_begin; - endorsement.endorsed_to = previous_tx_if_recovery( + endorsement.endorsement_epoch_end = previous_tx_if_recovery( active_service->current_service_create_txid.value()); endorsement.previous_version = @@ -409,7 +410,7 @@ namespace ccf // There's no 'till' for the a self-endorsement, leave it open-ranged // and sign the current service key. - endorsement.endorsed_from = + endorsement.endorsement_epoch_begin = active_service->current_service_create_txid.value(); key_to_endorse = endorsement.endorsing_key; @@ -417,12 +418,12 @@ namespace ccf pheaders.push_back(ccf::crypto::cose_params_string_string( ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN, - endorsement.endorsed_from.to_str())); - if (endorsement.endorsed_to) + endorsement.endorsement_epoch_begin.to_str())); + if (endorsement.endorsement_epoch_end) { pheaders.push_back(ccf::crypto::cose_params_string_string( ccf::crypto::COSE_PHEADER_KEY_RANGE_END, - endorsement.endorsed_to->to_str())); + endorsement.endorsement_epoch_end->to_str())); } try diff --git a/src/service/tables/previous_service_identity.h b/src/service/tables/previous_service_identity.h index 2a1143a270f6..9400053f3391 100644 --- a/src/service/tables/previous_service_identity.h +++ b/src/service/tables/previous_service_identity.h @@ -21,22 +21,23 @@ namespace ccf std::vector endorsing_key{}; /// The transaction ID when the *endorsed* service was created. - ccf::TxID endorsed_from{}; - - /// Pointer to the previous CoseEndorsement entry. Only present for previous - /// service endorsements, self-endorsed services must not have this set. - std::optional previous_version{}; + ccf::TxID endorsement_epoch_begin{}; /// Last transaction ID that the endorsement is valid for. Only present for /// previouse services endorsements, self-endorsed services must not have /// this set. - std::optional endorsed_to{}; + std::optional endorsement_epoch_end{}; + + /// Pointer to the previous CoseEndorsement entry. Only present for previous + /// service endorsements, self-endorsed services must not have this set. + std::optional previous_version{}; }; DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CoseEndorsement); DECLARE_JSON_REQUIRED_FIELDS( - CoseEndorsement, endorsement, endorsed_from, endorsing_key); - DECLARE_JSON_OPTIONAL_FIELDS(CoseEndorsement, previous_version, endorsed_to); + CoseEndorsement, endorsement, endorsement_epoch_begin, endorsing_key); + DECLARE_JSON_OPTIONAL_FIELDS( + CoseEndorsement, previous_version, endorsement_epoch_end); using PreviousServiceIdentityEndorsement = ServiceValue; From 399646b169d1e2f04c7b6eb71cd907977c2c63fe Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 14:38:21 +0000 Subject: [PATCH 37/43] Better error handling --- src/crypto/openssl/cose_verifier.cpp | 29 +++++++---------- src/node/historical_queries_adapter.cpp | 41 ++++++++++++++++--------- src/node/historical_queries_utils.cpp | 37 +++++----------------- 3 files changed, 46 insertions(+), 61 deletions(-) diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 838f1485c270..fbf5a8248852 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -242,15 +242,13 @@ namespace ccf::crypto qcbor_result = QCBORDecode_GetError(&ctx); if (qcbor_result != QCBOR_SUCCESS) { - LOG_DEBUG_FMT("Failed to parse COSE_Sign1 outer array"); - return {}; + throw std::logic_error("Failed to parse COSE_Sign1 outer array"); } const uint64_t tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); if (tag != CBOR_TAG_COSE_SIGN1) { - LOG_DEBUG_FMT("Failed to parse COSE_Sign1 tag"); - return {}; + throw std::logic_error("Failed to parse COSE_Sign1 tag"); } struct q_useful_buf_c protected_parameters; @@ -261,8 +259,7 @@ namespace ccf::crypto qcbor_result = QCBORDecode_GetError(&ctx); if (qcbor_result != QCBOR_SUCCESS) { - LOG_DEBUG_FMT("Failed to parse COSE_Sign1 as bstr"); - return {}; + throw std::logic_error("Failed to parse COSE_Sign1 as bstr"); } QCBORDecode_EnterMap(&ctx, NULL); @@ -270,8 +267,7 @@ namespace ccf::crypto qcbor_result = QCBORDecode_GetError(&ctx); if (qcbor_result != QCBOR_SUCCESS) { - LOG_DEBUG_FMT("Failed to parse COSE_Sign1 wrapped map"); - return {}; + throw std::logic_error("Failed to parse COSE_Sign1 wrapped map"); } enum @@ -300,24 +296,21 @@ namespace ccf::crypto qcbor_result = QCBORDecode_GetError(&ctx); if (qcbor_result != QCBOR_SUCCESS) { - LOG_DEBUG_FMT("Failed to decode protected header"); - return {}; + throw std::logic_error("Failed to decode protected header"); } if (header_items[FROM_INDEX].uDataType == QCBOR_TYPE_NONE) { - LOG_DEBUG_FMT( + throw std::logic_error(fmt::format( "Failed to retrieve (missing) {} parameter", - ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN); - return {}; + ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN)); } if (header_items[TO_INDEX].uDataType == QCBOR_TYPE_NONE) { - LOG_DEBUG_FMT( + throw std::logic_error(fmt::format( "Failed to retrieve (missing) {} parameter", - ccf::crypto::COSE_PHEADER_KEY_RANGE_END); - return {}; + ccf::crypto::COSE_PHEADER_KEY_RANGE_END)); } const auto from = qcbor_buf_to_string(header_items[FROM_INDEX].val.string); @@ -331,8 +324,8 @@ namespace ccf::crypto qcbor_result = QCBORDecode_GetError(&ctx); if (qcbor_result != QCBOR_SUCCESS) { - LOG_DEBUG_FMT("Failed to decode protected header: {}", qcbor_result); - return {}; + throw std::logic_error(fmt::format( + "Failed to decode protected header with error code: {}", qcbor_result)); } return COSEEndorsementValidity{.from_txid = from, .to_txid = to}; diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index cb0886b1e4f9..13754abce2ab 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -545,23 +545,36 @@ namespace ccf::historical // Get a state at the target version from the cache, if it is present auto historical_state = state_cache.get_state_at(historic_request_handle, target_tx_id.seqno); - if ( - historical_state == nullptr || - (!populate_service_endorsements( - args.tx, - historical_state, - state_cache, - network_identity_subsystem)) || - !populate_cose_service_endorsements( - args.tx, historical_state, state_cache)) + try + { + if ( + historical_state == nullptr || + (!populate_service_endorsements( + args.tx, + historical_state, + state_cache, + network_identity_subsystem)) || + !populate_cose_service_endorsements( + args.tx, historical_state, state_cache)) + { + auto reason = fmt::format( + "Historical transaction {} is not currently available.", + target_tx_id.to_str()); + ehandler( + HistoricalQueryErrorCode::TransactionPartiallyReady, + std::move(reason), + args); + return; + } + } + catch (const std::exception& e) { auto reason = fmt::format( - "Historical transaction {} is not currently available.", - target_tx_id.to_str()); + "Historical transaction {} failed with error: {}", + target_tx_id.to_str(), + e.what()); ehandler( - HistoricalQueryErrorCode::TransactionPartiallyReady, - std::move(reason), - args); + HistoricalQueryErrorCode::InternalError, std::move(reason), args); return; } diff --git a/src/node/historical_queries_utils.cpp b/src/node/historical_queries_utils.cpp index 8a025c06bdeb..63cac995f857 100644 --- a/src/node/historical_queries_utils.cpp +++ b/src/node/historical_queries_utils.cpp @@ -182,13 +182,6 @@ namespace if (final_endorsement == search_to) { - LOG_FAIL_FMT( - "Error during COSE endorsement chain reconstruction, seqno {} not " - "found", - target_seq); - - assert(false); // In debug, fail fast. - throw std::logic_error(fmt::format( "Error during COSE endorsement chain reconstruction for seqno {}", target_seq)); @@ -352,10 +345,8 @@ namespace ccf const auto service_start = service_info->current_service_create_txid; if (!service_start) { - LOG_FAIL_FMT("Service start txid not found"); - assert(false); // In debug, fail fast. - - return true; + throw std::logic_error( + "COSE endorsements fetch: current service create_txid not available"); } const auto target_seq = state->transaction_id.seqno; @@ -368,27 +359,15 @@ namespace ccf return true; } - try + const auto result = + fetch_endorsements_for(tx, state_cache, state->transaction_id.seqno); + if (!result.endorsements) { - auto result = - fetch_endorsements_for(tx, state_cache, state->transaction_id.seqno); - if (!result.endorsements) - { - const bool final_result = !result.retry; - return final_result; - } - state->receipt->cose_endorsements = result.endorsements.value(); - } - catch (const std::logic_error& e) - { - LOG_FAIL_FMT("FAIL: Failed to fetch endorsements: {}", e.what()); - - assert(false); // In debug, fail fast. - - return true; // In release, return true to avoid being stuck waiting for - // endorsements that can't be provided because of an error. + const bool final_result = !result.retry; + return final_result; } + state->receipt->cose_endorsements = result.endorsements.value(); return true; } } From 2f035e943fe3526c4d24154886c187dd990a2be6 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 16:23:31 +0000 Subject: [PATCH 38/43] Typo --- src/service/internal_tables_access.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 5187532bb125..82c31c11e567 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -407,8 +407,8 @@ namespace ccf } else { - // There's no 'till' for the a self-endorsement, leave it open-ranged - // and sign the current service key. + // There's no `epoch_end` for the a self-endorsement, leave it + // open-ranged and sign the current service key. endorsement.endorsement_epoch_begin = active_service->current_service_create_txid.value(); From b5a08f2e569f0ba632b9a73d0b44de385f6a3825 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 16:43:43 +0000 Subject: [PATCH 39/43] Changelog + release num --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7cbefe29b71..62738081816f 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). +## [6.0.0-pre2] + +### Added + +- Introduced `ccf::describe_cose_endorsements_v1(receipt)` for COSE-endorsements chain of previous service identities (#6500). + ## [6.0.0-dev1] [6.0.0-dev1]: https://github.com/microsoft/CCF/releases/tag/6.0.0-dev1 @@ -34,7 +40,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Provided API for getting COSE signatures and Merkle proofs (#6477). - Exposed COSE signature in historical API via `TxReceiptImpl`. - Introduced `ccf::describe_merkle_proof_v1(receipt)` for Merkle proof construction in CBOR format. - - Introduced `ccf::describe_cose_endorsements_v1(receipt)` for COSE-endorsements chain of previous service identities. - Added COSE signatures over the Merkle root to the KV (#6449). - Signing is done with service key (different from raw signatures, which remain unchanged and are still signed by the node key). - New signature reside in `public:ccf.internal.cose_signatures`. From 6d612d610ee6c2c798b952505a547da9fe71d7d6 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 8 Oct 2024 16:45:34 +0000 Subject: [PATCH 40/43] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62738081816f..efc7dbd84e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [6.0.0-pre2] +[6.0.0-pre2]: https://github.com/microsoft/CCF/releases/tag/6.0.0-pre2 + ### Added - Introduced `ccf::describe_cose_endorsements_v1(receipt)` for COSE-endorsements chain of previous service identities (#6500). From 3b12223db0f710d813780e3720580aec9581f530 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Wed, 9 Oct 2024 11:56:17 +0100 Subject: [PATCH 41/43] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc7dbd84e54..d7bc3558ac51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## [6.0.0-pre2] +## [6.0.0-dev2] [6.0.0-pre2]: https://github.com/microsoft/CCF/releases/tag/6.0.0-pre2 From 45ba87e18b34b16691ab12ebcfa8acc46c174858 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Wed, 9 Oct 2024 11:56:23 +0100 Subject: [PATCH 42/43] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bc3558ac51..69cf4507bd77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [6.0.0-dev2] -[6.0.0-pre2]: https://github.com/microsoft/CCF/releases/tag/6.0.0-pre2 +[6.0.0-dev2]: https://github.com/microsoft/CCF/releases/tag/6.0.0-dev2 ### Added From 1e20c588896cfec5e0e29803d66a3d42c4afdb16 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Wed, 9 Oct 2024 10:57:15 +0000 Subject: [PATCH 43/43] Update pyproject.toml --- python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 4d57fca837c9..01353f9d6e5b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ccf" -version = "6.0.0-dev1" +version = "6.0.0-dev2" authors = [ { name="CCF Team", email="CCF-Sec@microsoft.com" }, ]