From 8bc7b3de0c0990326f72933afe9ab44de9c137f8 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 27 Sep 2024 10:25:29 +0000 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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)