From 937c62143bf000556a1441e948b2ba28053d43f3 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 27 Aug 2024 10:12:28 +0000 Subject: [PATCH 01/33] WIP --- src/kv/deserialise.h | 19 +++++++++++--- src/node/history.h | 18 ++++++++++++++ src/service/tables/signatures.h | 44 +++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index f3c4e8855cd0..55eb842edb2b 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -121,12 +121,15 @@ namespace ccf::kv auto success = ApplyResult::PASS; auto search = changes.find(ccf::Tables::SIGNATURES); + // TODO + if (search != changes.end()) { - // Transactions containing a signature must only contain the signature - // and the serialised Merkle tree and must be verified + // Transactions containing a signature must only contain the signature, + // COSE signature (optional), and the serialised Merkle tree and must be + // verified if ( - changes.size() > 2 || + changes.size() > 3 || changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end()) { LOG_FAIL_FMT("Failed to deserialise"); @@ -135,6 +138,16 @@ namespace ccf::kv return ApplyResult::FAIL; } + if ( + changes.size() == 3 && + changes.find(ccf::Tables::COSE_SIGNATURES) == changes.end()) + { + LOG_FAIL_FMT("Failed to deserialise"); + LOG_DEBUG_FMT( + "Unexpected contents in signature transaction {}", version); + return ApplyResult::FAIL; + } + if (history) { if (!history->verify(&term)) diff --git a/src/node/history.h b/src/node/history.h index 89f2fee6c816..b00fe2adc57a 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -5,7 +5,9 @@ #include "ccf/ds/logger.h" #include "ccf/pal/locking.h" #include "ccf/service/tables/nodes.h" +#include "crypto/openssl/cose_sign.h" #include "crypto/openssl/hash.h" +#include "crypto/openssl/key_pair.h" #include "ds/thread_messaging.h" #include "endian.h" #include "kv/kv_types.h" @@ -105,6 +107,7 @@ namespace ccf auto sig = store.create_reserved_tx(txid); auto signatures = sig.template wo(ccf::Tables::SIGNATURES); + // TODO auto serialised_tree = sig.template wo( ccf::Tables::SERIALISED_MERKLE_TREE); PrimarySignature sig_value(id, txid.version); @@ -336,6 +339,8 @@ namespace ccf auto sig = store.create_reserved_tx(txid); auto signatures = sig.template wo(ccf::Tables::SIGNATURES); + auto cose_signatures = + sig.template wo(ccf::Tables::COSE_SIGNATURES); auto serialised_tree = sig.template wo( ccf::Tables::SERIALISED_MERKLE_TREE); ccf::crypto::Sha256Hash root = history.get_replicated_state_root(); @@ -353,7 +358,20 @@ namespace ccf primary_sig, endorsed_cert); + const auto root_str = root.hex_str(); + std::vector root_bytes{ + (const uint8_t*)root_str.c_str(), + (const uint8_t*)root_str.c_str() + root_str.size()}; + + ccf::crypto::KeyPair_OpenSSL kp2( + kp.private_key_pem()); // why can't we expose EVP_KEY from key pair? + auto cose_sign = crypto::cose_sign1(kp2, {}, root_bytes); + + CoseSignature cose_sig_value( + id, txid.version, txid.term, root, cose_sign, endorsed_cert); + signatures->put(sig_value); + cose_signatures->put(cose_sig_value); serialised_tree->put(history.serialise_tree(txid.version - 1)); return sig.commit_reserved(); } diff --git a/src/service/tables/signatures.h b/src/service/tables/signatures.h index 665fe56cee78..ef39a7504ba0 100644 --- a/src/service/tables/signatures.h +++ b/src/service/tables/signatures.h @@ -61,9 +61,53 @@ namespace ccf using SerialisedMerkleTree = ccf::kv::RawCopySerialisedValue>; + struct CoseSignature : public NodeSignature + { + /// Sequence number of the signature transaction + ccf::SeqNo seqno = 0; + /// View of the signature transaction + ccf::View view = 0; + /// Root of the Merkle Tree as of seqno - 1 + ccf::crypto::Sha256Hash root; + /// Service-endorsed certificate of the node which produced the signature + ccf::crypto::Pem cert; + + CoseSignature() {} + + CoseSignature(const ccf::NodeId& node_, ccf::SeqNo seqno_) : + NodeSignature(node_), + seqno(seqno_) + {} + + CoseSignature(const ccf::crypto::Sha256Hash& root_) : root(root_) {} + + CoseSignature( + const ccf::NodeId& node_, + ccf::SeqNo seqno_, + ccf::View view_, + const ccf::crypto::Sha256Hash root_, + const std::vector& sig_, + const ccf::crypto::Pem& cert_) : + NodeSignature(sig_, node_, Nonce{}), + seqno(seqno_), + view(view_), + root(root_), + cert(cert_) + {} + }; + + DECLARE_JSON_TYPE_WITH_BASE_AND_OPTIONAL_FIELDS(CoseSignature, NodeSignature) + DECLARE_JSON_REQUIRED_FIELDS(CoseSignature, seqno, view, root) + DECLARE_JSON_OPTIONAL_FIELDS(CoseSignature, cert); + + // Most recent COSE signature is a single Value in the KV + using CoseSignatures = ServiceValue; + namespace Tables { static constexpr auto SIGNATURES = "public:ccf.internal.signatures"; + static constexpr auto COSE_SIGNATURES = + "public:ccf.internal.cose_signatures"; static constexpr auto SERIALISED_MERKLE_TREE = "public:ccf.internal.tree"; } } \ No newline at end of file From e3a71ad91f1e9ac0733a128adc19d78e8d86e33d Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 28 Aug 2024 10:59:48 +0000 Subject: [PATCH 02/33] Cose signature simplified. Check tests --- src/kv/deserialise.h | 1 - src/node/history.h | 8 ++++--- src/service/tables/signatures.h | 37 +++++---------------------------- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index 55eb842edb2b..274518d6febb 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -121,7 +121,6 @@ namespace ccf::kv auto success = ApplyResult::PASS; auto search = changes.find(ccf::Tables::SIGNATURES); - // TODO if (search != changes.end()) { diff --git a/src/node/history.h b/src/node/history.h index b00fe2adc57a..f76f84d573e7 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -107,11 +107,14 @@ namespace ccf auto sig = store.create_reserved_tx(txid); auto signatures = sig.template wo(ccf::Tables::SIGNATURES); - // TODO + auto cose_signatures = + sig.template wo(ccf::Tables::COSE_SIGNATURES); + auto serialised_tree = sig.template wo( ccf::Tables::SERIALISED_MERKLE_TREE); PrimarySignature sig_value(id, txid.version); signatures->put(sig_value); + cose_signatures->put(ccf::CoseSignature{}); serialised_tree->put({}); return sig.commit_reserved(); } @@ -367,8 +370,7 @@ namespace ccf kp.private_key_pem()); // why can't we expose EVP_KEY from key pair? auto cose_sign = crypto::cose_sign1(kp2, {}, root_bytes); - CoseSignature cose_sig_value( - id, txid.version, txid.term, root, cose_sign, endorsed_cert); + CoseSignature cose_sig_value(cose_sign); signatures->put(sig_value); cose_signatures->put(cose_sig_value); diff --git a/src/service/tables/signatures.h b/src/service/tables/signatures.h index ef39a7504ba0..c55175040018 100644 --- a/src/service/tables/signatures.h +++ b/src/service/tables/signatures.h @@ -61,44 +61,17 @@ namespace ccf using SerialisedMerkleTree = ccf::kv::RawCopySerialisedValue>; - struct CoseSignature : public NodeSignature + struct CoseSignature { - /// Sequence number of the signature transaction - ccf::SeqNo seqno = 0; - /// View of the signature transaction - ccf::View view = 0; - /// Root of the Merkle Tree as of seqno - 1 - ccf::crypto::Sha256Hash root; - /// Service-endorsed certificate of the node which produced the signature - ccf::crypto::Pem cert; + std::vector sig; CoseSignature() {} - CoseSignature(const ccf::NodeId& node_, ccf::SeqNo seqno_) : - NodeSignature(node_), - seqno(seqno_) - {} - - CoseSignature(const ccf::crypto::Sha256Hash& root_) : root(root_) {} - - CoseSignature( - const ccf::NodeId& node_, - ccf::SeqNo seqno_, - ccf::View view_, - const ccf::crypto::Sha256Hash root_, - const std::vector& sig_, - const ccf::crypto::Pem& cert_) : - NodeSignature(sig_, node_, Nonce{}), - seqno(seqno_), - view(view_), - root(root_), - cert(cert_) - {} + CoseSignature(const std::vector& sig_) : sig(sig_) {} }; - DECLARE_JSON_TYPE_WITH_BASE_AND_OPTIONAL_FIELDS(CoseSignature, NodeSignature) - DECLARE_JSON_REQUIRED_FIELDS(CoseSignature, seqno, view, root) - DECLARE_JSON_OPTIONAL_FIELDS(CoseSignature, cert); + DECLARE_JSON_TYPE(CoseSignature) + DECLARE_JSON_REQUIRED_FIELDS(CoseSignature, sig); // Most recent COSE signature is a single Value in the KV using CoseSignatures = ServiceValue; From f5da99be121b52dd6d2bac6925ae1e7ad5589f67 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 28 Aug 2024 12:35:15 +0000 Subject: [PATCH 03/33] Key fixup --- src/node/history.h | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/node/history.h b/src/node/history.h index f76f84d573e7..8deaee7f6fd9 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -350,7 +350,9 @@ namespace ccf std::vector primary_sig; - primary_sig = kp.sign_hash(root.h.data(), root.h.size()); + const std::vector root_hash{ + root.h.data(), root.h.data() + root.h.size()}; + primary_sig = kp.sign_hash(root_hash.data(), root_hash.size()); PrimarySignature sig_value( id, @@ -361,19 +363,15 @@ namespace ccf primary_sig, endorsed_cert); - const auto root_str = root.hex_str(); - std::vector root_bytes{ - (const uint8_t*)root_str.c_str(), - (const uint8_t*)root_str.c_str() + root_str.size()}; - - ccf::crypto::KeyPair_OpenSSL kp2( - kp.private_key_pem()); // why can't we expose EVP_KEY from key pair? - auto cose_sign = crypto::cose_sign1(kp2, {}, root_bytes); - - CoseSignature cose_sig_value(cose_sign); + const auto as_openssl = dynamic_cast(&kp); + if (as_openssl == nullptr) + { + throw std::logic_error("Can't COSE-sign the root with non-OpenSSL key"); + } + auto cose_sign = crypto::cose_sign1(*as_openssl, {}, root_hash); signatures->put(sig_value); - cose_signatures->put(cose_sig_value); + cose_signatures->put(CoseSignature{cose_sign}); serialised_tree->put(history.serialise_tree(txid.version - 1)); return sig.commit_reserved(); } From 400bba62553745f87ee080f1a27fb389c52f8a83 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 29 Aug 2024 09:33:57 +0000 Subject: [PATCH 04/33] Support variable COSE header types --- src/crypto/openssl/cose_sign.cpp | 58 +++++++++++++++++------ src/crypto/openssl/cose_sign.h | 45 ++++++++++++++++-- src/crypto/test/crypto.cpp | 79 ++++++++++++++++++++------------ src/kv/deserialise.h | 11 +---- 4 files changed, 138 insertions(+), 55 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 6ff3d49cfa7e..4568e22d6c36 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -6,15 +6,11 @@ #include "ccf/ds/logger.h" #include -#include namespace { - constexpr int64_t COSE_HEADER_PARAM_ALG = - 1; // Duplicate of t_cose::COSE_HEADER_PARAM_ALG to keep it compatible. - size_t estimate_buffer_size( - const ccf::crypto::COSEProtectedHeaders& protected_headers, + const std::vector& protected_headers, std::span payload) { size_t result = @@ -28,8 +24,8 @@ namespace protected_headers.begin(), protected_headers.end(), result, - [](auto result, const auto& kv) { - return result + sizeof(kv.first) + kv.second.size(); + [](auto result, const auto& factory) { + return result + factory.estimated_size(); }); return result + payload.size(); @@ -38,7 +34,7 @@ namespace void encode_protected_headers( t_cose_sign1_sign_ctx* ctx, QCBOREncodeContext* encode_ctx, - const ccf::crypto::COSEProtectedHeaders& protected_headers) + const std::vector& protected_headers) { QCBOREncode_BstrWrap(encode_ctx); QCBOREncode_OpenMap(encode_ctx); @@ -46,12 +42,12 @@ namespace // This's what the t_cose implementation of `encode_protected_parameters` // sets unconditionally. QCBOREncode_AddInt64ToMapN( - encode_ctx, COSE_HEADER_PARAM_ALG, ctx->cose_algorithm_id); + encode_ctx, ccf::crypto::COSE_PHEADER_KEY_ALG, ctx->cose_algorithm_id); // Caller-provided headers follow - for (const auto& [label, value] : protected_headers) + for (const auto& factory : protected_headers) { - QCBOREncode_AddSZStringToMapN(encode_ctx, label, value.c_str()); + factory.apply(encode_ctx); } QCBOREncode_CloseMap(encode_ctx); @@ -68,7 +64,7 @@ namespace void encode_parameters_custom( struct t_cose_sign1_sign_ctx* me, QCBOREncodeContext* cbor_encode, - const ccf::crypto::COSEProtectedHeaders& protected_headers) + const std::vector& protected_headers) { QCBOREncode_AddTag(cbor_encode, CBOR_TAG_COSE_SIGN1); QCBOREncode_OpenArray(cbor_encode); @@ -83,9 +79,45 @@ namespace namespace ccf::crypto { + COSEParametersFactory cose_params_int_string( + int64_t key, std::string_view value) + + { + const size_t args_size = sizeof(key) + value.size(); + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddSZStringToMapN(ctx, key, value.data()); + }, + args_size); + } + + COSEParametersFactory cose_params_string_int( + std::string_view key, int64_t value) + { + const size_t args_size = key.size() + sizeof(value); + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddSZString(ctx, key.data()); + QCBOREncode_AddInt64(ctx, value); + }, + args_size); + } + + COSEParametersFactory cose_params_string_string( + std::string_view key, std::string_view value) + { + const size_t args_size = key.size() + value.size(); + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddSZString(ctx, key.data()); + QCBOREncode_AddSZString(ctx, value.data()); + }, + args_size); + } + std::vector cose_sign1( EVP_PKEY* key, - const COSEProtectedHeaders& protected_headers, + const std::vector& protected_headers, std::span payload) { const auto buf_size = estimate_buffer_size(protected_headers, payload); diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index f41dc3388fcd..feca7052af3d 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -5,17 +5,56 @@ #include #include #include +#include #include namespace ccf::crypto { + // Algorithm used to sign, standardatised field + static constexpr int64_t COSE_PHEADER_KEY_ALG = 1; + // Verifiable data structure, standartised field + static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; + // CCF-specifix, last signed TxID + static constexpr const char* COSE_PHEADER_KEY_TXID = "ccf.txid"; + + class COSEParametersFactory + { + public: + template + COSEParametersFactory(Callable&& impl, size_t args_size) : + impl(std::forward(impl)), + args_size{args_size} + {} + + void apply(QCBOREncodeContext* ctx) const + { + impl(ctx); + } + + size_t estimated_size() const + { + return args_size; + } + + private: + std::function impl{}; + size_t args_size{}; + }; + + COSEParametersFactory cose_params_int_string( + int64_t key, std::string_view value); + + COSEParametersFactory cose_params_string_int( + std::string_view key, int64_t value); + + COSEParametersFactory cose_params_string_string( + std::string_view key, std::string_view value); + struct COSESignError : public std::runtime_error { COSESignError(const std::string& msg) : std::runtime_error(msg) {} }; - using COSEProtectedHeaders = std::unordered_map; - /* Sign a cose_sign1 payload with custom protected headers as strings, where - key: integer label to be assigned in a COSE value - value: string behind the label. @@ -25,6 +64,6 @@ namespace ccf::crypto */ std::vector cose_sign1( EVP_PKEY* key, - const COSEProtectedHeaders& protected_headers, + const std::vector& protected_headers, std::span payload); } diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index e1f31d5737de..551a702072f6 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -226,7 +226,9 @@ t_cose_err_t verify_detached( } void require_match_headers( - const std::unordered_map& headers, + std::pair> kv1, + std::pair> kv2, + std::pair> kv3, const std::vector& cose_sign) { UsefulBufC msg{cose_sign.data(), cose_sign.size()}; @@ -244,39 +246,47 @@ void require_match_headers( &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters); QCBORDecode_EnterMap(&ctx, NULL); - QCBORItem header_items[headers.size() + 2]; - size_t curr_id{0}; - for (const auto& kv : headers) - { - header_items[curr_id].label.int64 = kv.first; - header_items[curr_id].uLabelType = QCBOR_TYPE_INT64; - header_items[curr_id].uDataType = QCBOR_TYPE_TEXT_STRING; - - curr_id++; - } + QCBORItem header_items[4 + 1]; // Verify 'alg' is default-encoded. - header_items[curr_id].label.int64 = 1; - header_items[curr_id].uLabelType = QCBOR_TYPE_INT64; - header_items[curr_id].uDataType = QCBOR_TYPE_INT64; + header_items[0].label.int64 = 1; + header_items[0].uLabelType = QCBOR_TYPE_INT64; + header_items[0].uDataType = QCBOR_TYPE_INT64; + + header_items[1].label.int64 = kv1.first; + header_items[1].uLabelType = QCBOR_TYPE_INT64; + header_items[1].uDataType = QCBOR_TYPE_TEXT_STRING; + + header_items[2].label.string = {kv2.first.data(), kv2.first.size()}; + header_items[2].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[2].uDataType = QCBOR_TYPE_INT64; + + header_items[3].label.string = {kv3.first.data(), kv3.first.size()}; + header_items[3].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[3].uDataType = QCBOR_TYPE_TEXT_STRING; - header_items[++curr_id].uLabelType = QCBOR_TYPE_NONE; + header_items[4].uLabelType = QCBOR_TYPE_NONE; QCBORDecode_GetItemsInMap(&ctx, header_items); REQUIRE_EQ(QCBORDecode_GetError(&ctx), QCBOR_SUCCESS); - curr_id = 0; - for (const auto& kv : headers) - { - REQUIRE_NE(header_items[curr_id].uDataType, QCBOR_TYPE_NONE); - REQUIRE_EQ( - qcbor_buf_to_string(header_items[curr_id].val.string), kv.second); + // 'alg' + REQUIRE_NE(header_items[0].uDataType, QCBOR_TYPE_NONE); - curr_id++; - } + if (kv1.second) + REQUIRE_EQ(qcbor_buf_to_string(header_items[1].val.string), *kv1.second); + else + REQUIRE_EQ(header_items[1].uDataType, QCBOR_TYPE_NONE); - // 'alg' - REQUIRE_NE(header_items[curr_id].uDataType, QCBOR_TYPE_NONE); + if (kv2.second) + REQUIRE_EQ(header_items[2].val.int64, *kv2.second); + else + REQUIRE_EQ(header_items[2].uDataType, QCBOR_TYPE_NONE); + + if (kv3.second) + REQUIRE_EQ(qcbor_buf_to_string(header_items[3].val.string), *kv3.second); + else + REQUIRE_EQ(header_items[3].uDataType, QCBOR_TYPE_NONE); QCBORDecode_ExitMap(&ctx); QCBORDecode_ExitBstrWrapped(&ctx); @@ -1224,8 +1234,10 @@ TEST_CASE("COSE sign & verify") ccf::crypto::make_key_pair(CurveID::SECP384R1)); std::vector payload{1, 10, 42, 43, 44, 45, 100}; - const std::unordered_map protected_headers = { - {36, "thirsty six"}, {47, "hungry seven"}}; + const auto protected_headers = { + ccf::crypto::cose_params_int_string(36, "thirsty six"), + ccf::crypto::cose_params_string_int("hungry seven", 47), + ccf::crypto::cose_params_string_string("string key", "string value")}; auto cose_sign = cose_sign1(*kp, protected_headers, payload); if constexpr (false) // enable to see the whole cose_sign as byte string @@ -1242,7 +1254,11 @@ TEST_CASE("COSE sign & verify") std::cout << std::endl; } - require_match_headers(protected_headers, cose_sign); + require_match_headers( + {36, "thirsty six"}, + {"hungry seven", 47}, + {"string key", "string value"}, + cose_sign); REQUIRE_EQ(verify_detached(*kp, cose_sign, payload), T_COSE_SUCCESS); @@ -1253,6 +1269,11 @@ TEST_CASE("COSE sign & verify") // Empty headers and payload handled correctly cose_sign = cose_sign1(*kp, {}, {}); - require_match_headers({}, cose_sign); + require_match_headers( + {36, std::nullopt}, + {"hungry seven", std::nullopt}, + {"string key", std::nullopt}, + cose_sign); + REQUIRE_EQ(verify_detached(*kp, cose_sign, {}), T_COSE_SUCCESS); } diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index 274518d6febb..1e07c96267ba 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -129,16 +129,7 @@ namespace ccf::kv // verified if ( changes.size() > 3 || - changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end()) - { - LOG_FAIL_FMT("Failed to deserialise"); - LOG_DEBUG_FMT( - "Unexpected contents in signature transaction {}", version); - return ApplyResult::FAIL; - } - - if ( - changes.size() == 3 && + changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end() || changes.find(ccf::Tables::COSE_SIGNATURES) == changes.end()) { LOG_FAIL_FMT("Failed to deserialise"); From f51b3b1b68809001ee3a716d20742be89ecbfe0b Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 29 Aug 2024 09:50:21 +0000 Subject: [PATCH 05/33] Sign VDS + txid --- src/crypto/openssl/cose_sign.cpp | 12 +++++++++- src/crypto/openssl/cose_sign.h | 2 ++ src/crypto/test/crypto.cpp | 39 +++++++++++++++++++++----------- src/node/history.h | 15 +++++++++++- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 4568e22d6c36..60940c39a279 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -79,9 +79,19 @@ namespace namespace ccf::crypto { + + COSEParametersFactory cose_params_int_int(int64_t key, int64_t value) + { + const size_t args_size = sizeof(key) + sizeof(value); + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddInt64ToMapN(ctx, key, value); + }, + args_size); + } + COSEParametersFactory cose_params_int_string( int64_t key, std::string_view value) - { const size_t args_size = sizeof(key) + value.size(); return COSEParametersFactory( diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index feca7052af3d..d7abb4460bd6 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -41,6 +41,8 @@ namespace ccf::crypto size_t args_size{}; }; + COSEParametersFactory cose_params_int_int(int64_t key, int64_t value); + COSEParametersFactory cose_params_int_string( int64_t key, std::string_view value); diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index 551a702072f6..9bd6484c0938 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -226,9 +226,10 @@ t_cose_err_t verify_detached( } void require_match_headers( - std::pair> kv1, - std::pair> kv2, - std::pair> kv3, + std::pair> kv1, + std::pair> kv2, + std::pair> kv3, + std::pair> kv4, const std::vector& cose_sign) { UsefulBufC msg{cose_sign.data(), cose_sign.size()}; @@ -246,7 +247,7 @@ void require_match_headers( &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters); QCBORDecode_EnterMap(&ctx, NULL); - QCBORItem header_items[4 + 1]; + QCBORItem header_items[5 + 1]; // Verify 'alg' is default-encoded. header_items[0].label.int64 = 1; @@ -255,17 +256,21 @@ void require_match_headers( header_items[1].label.int64 = kv1.first; header_items[1].uLabelType = QCBOR_TYPE_INT64; - header_items[1].uDataType = QCBOR_TYPE_TEXT_STRING; + header_items[1].uDataType = QCBOR_TYPE_INT64; - header_items[2].label.string = {kv2.first.data(), kv2.first.size()}; - header_items[2].uLabelType = QCBOR_TYPE_TEXT_STRING; - header_items[2].uDataType = QCBOR_TYPE_INT64; + header_items[2].label.int64 = kv2.first; + header_items[2].uLabelType = QCBOR_TYPE_INT64; + header_items[2].uDataType = QCBOR_TYPE_TEXT_STRING; header_items[3].label.string = {kv3.first.data(), kv3.first.size()}; header_items[3].uLabelType = QCBOR_TYPE_TEXT_STRING; - header_items[3].uDataType = QCBOR_TYPE_TEXT_STRING; + header_items[3].uDataType = QCBOR_TYPE_INT64; - header_items[4].uLabelType = QCBOR_TYPE_NONE; + header_items[4].label.string = {kv4.first.data(), kv4.first.size()}; + header_items[4].uLabelType = QCBOR_TYPE_TEXT_STRING; + header_items[4].uDataType = QCBOR_TYPE_TEXT_STRING; + + header_items[5].uLabelType = QCBOR_TYPE_NONE; QCBORDecode_GetItemsInMap(&ctx, header_items); REQUIRE_EQ(QCBORDecode_GetError(&ctx), QCBOR_SUCCESS); @@ -274,20 +279,25 @@ void require_match_headers( REQUIRE_NE(header_items[0].uDataType, QCBOR_TYPE_NONE); if (kv1.second) - REQUIRE_EQ(qcbor_buf_to_string(header_items[1].val.string), *kv1.second); + REQUIRE_EQ(header_items[1].val.int64, *kv1.second); else REQUIRE_EQ(header_items[1].uDataType, QCBOR_TYPE_NONE); if (kv2.second) - REQUIRE_EQ(header_items[2].val.int64, *kv2.second); + REQUIRE_EQ(qcbor_buf_to_string(header_items[2].val.string), *kv2.second); else REQUIRE_EQ(header_items[2].uDataType, QCBOR_TYPE_NONE); if (kv3.second) - REQUIRE_EQ(qcbor_buf_to_string(header_items[3].val.string), *kv3.second); + REQUIRE_EQ(header_items[3].val.int64, *kv3.second); else REQUIRE_EQ(header_items[3].uDataType, QCBOR_TYPE_NONE); + if (kv4.second) + REQUIRE_EQ(qcbor_buf_to_string(header_items[4].val.string), *kv4.second); + else + REQUIRE_EQ(header_items[4].uDataType, QCBOR_TYPE_NONE); + QCBORDecode_ExitMap(&ctx); QCBORDecode_ExitBstrWrapped(&ctx); @@ -1235,6 +1245,7 @@ TEST_CASE("COSE sign & verify") std::vector payload{1, 10, 42, 43, 44, 45, 100}; const auto protected_headers = { + ccf::crypto::cose_params_int_int(35, 53), ccf::crypto::cose_params_int_string(36, "thirsty six"), ccf::crypto::cose_params_string_int("hungry seven", 47), ccf::crypto::cose_params_string_string("string key", "string value")}; @@ -1255,6 +1266,7 @@ TEST_CASE("COSE sign & verify") } require_match_headers( + {35, 53}, {36, "thirsty six"}, {"hungry seven", 47}, {"string key", "string value"}, @@ -1270,6 +1282,7 @@ TEST_CASE("COSE sign & verify") // Empty headers and payload handled correctly cose_sign = cose_sign1(*kp, {}, {}); require_match_headers( + {35, std::nullopt}, {36, std::nullopt}, {"hungry seven", std::nullopt}, {"string key", std::nullopt}, diff --git a/src/node/history.h b/src/node/history.h index 8deaee7f6fd9..50e05fd827bb 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -368,7 +368,20 @@ namespace ccf { throw std::logic_error("Can't COSE-sign the root with non-OpenSSL key"); } - auto cose_sign = crypto::cose_sign1(*as_openssl, {}, root_hash); + + constexpr int64_t vds_merkle_tree = 2; + const auto pheaders = { + // 1. VDS + ccf::crypto::cose_params_int_int( + ccf::crypto::COSE_PHEADER_KEY_VDS, vds_merkle_tree), + // 2. TxID + ccf::crypto::cose_params_string_string( + ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()) + // 3. Key digest + // TO + // DO + }; + auto cose_sign = crypto::cose_sign1(*as_openssl, pheaders, root_hash); signatures->put(sig_value); cose_signatures->put(CoseSignature{cose_sign}); From 14818e793bb7802e36f2ffd80865bd97930b1fee Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 29 Aug 2024 12:35:58 +0000 Subject: [PATCH 06/33] Change node key to service key for COSE --- src/crypto/openssl/cose_sign.cpp | 1 - src/node/history.h | 32 ++++++++++++++-------------- src/node/identity.h | 16 ++++++++++---- src/node/node_state.h | 3 +++ src/node/test/historical_queries.cpp | 7 ++++-- src/node/test/history.cpp | 24 ++++++++++++--------- src/node/test/history_bench.cpp | 12 +++++++---- src/node/test/receipt.cpp | 4 +++- src/node/test/snapshot.cpp | 9 ++++++-- src/node/test/snapshotter.cpp | 15 ++++++++----- 10 files changed, 78 insertions(+), 45 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 60940c39a279..6e96cb9b79a7 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -79,7 +79,6 @@ namespace namespace ccf::crypto { - COSEParametersFactory cose_params_int_int(int64_t key, int64_t value) { const size_t args_size = sizeof(key) + sizeof(value); diff --git a/src/node/history.h b/src/node/history.h index 50e05fd827bb..5d66224e1ed7 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -318,7 +318,8 @@ namespace ccf ccf::kv::Store& store; ccf::kv::TxHistory& history; NodeId id; - ccf::crypto::KeyPair& kp; + ccf::crypto::KeyPair& node_kp; + ccf::crypto::KeyPair_OpenSSL& service_kp; ccf::crypto::Pem& endorsed_cert; public: @@ -327,13 +328,15 @@ namespace ccf ccf::kv::Store& store_, ccf::kv::TxHistory& history_, const NodeId& id_, - ccf::crypto::KeyPair& kp_, + ccf::crypto::KeyPair& node_kp_, + ccf::crypto::KeyPair_OpenSSL& service_kp_, ccf::crypto::Pem& endorsed_cert_) : txid(txid_), store(store_), history(history_), id(id_), - kp(kp_), + node_kp(node_kp_), + service_kp(service_kp_), endorsed_cert(endorsed_cert_) {} @@ -352,7 +355,7 @@ namespace ccf const std::vector root_hash{ root.h.data(), root.h.data() + root.h.size()}; - primary_sig = kp.sign_hash(root_hash.data(), root_hash.size()); + primary_sig = node_kp.sign_hash(root_hash.data(), root_hash.size()); PrimarySignature sig_value( id, @@ -363,12 +366,6 @@ namespace ccf primary_sig, endorsed_cert); - const auto as_openssl = dynamic_cast(&kp); - if (as_openssl == nullptr) - { - throw std::logic_error("Can't COSE-sign the root with non-OpenSSL key"); - } - constexpr int64_t vds_merkle_tree = 2; const auto pheaders = { // 1. VDS @@ -381,7 +378,7 @@ namespace ccf // TO // DO }; - auto cose_sign = crypto::cose_sign1(*as_openssl, pheaders, root_hash); + auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); signatures->put(sig_value); cose_signatures->put(CoseSignature{cose_sign}); @@ -527,7 +524,8 @@ namespace ccf NodeId id; T replicated_state_tree; - ccf::crypto::KeyPair& kp; + ccf::crypto::KeyPair& node_kp; + ccf::crypto::KeyPair_OpenSSL& service_kp; std::optional<::threading::TaskQueue::TimerEntry> emit_signature_timer_entry = std::nullopt; @@ -544,13 +542,15 @@ namespace ccf HashedTxHistory( ccf::kv::Store& store_, const NodeId& id_, - ccf::crypto::KeyPair& kp_, + ccf::crypto::KeyPair& node_kp_, + ccf::crypto::KeyPair_OpenSSL& service_kp_, size_t sig_tx_interval_ = 0, size_t sig_ms_interval_ = 0, bool signature_timer = false) : store(store_), id(id_), - kp(kp_), + node_kp(node_kp_), + service_kp(service_kp_), sig_tx_interval(sig_tx_interval_), sig_ms_interval(sig_ms_interval_) { @@ -698,7 +698,7 @@ namespace ccf ccf::kv::TxHistory::Result result = ccf::kv::TxHistory::Result::OK; sig.node = id; - sig.sig = kp.sign_hash(sig.root.h.data(), sig.root.h.size()); + sig.sig = node_kp.sign_hash(sig.root.h.data(), sig.root.h.size()); return result; } @@ -817,7 +817,7 @@ namespace ccf store.commit( txid, std::make_unique>( - txid, store, *this, id, kp, endorsed_cert.value()), + txid, store, *this, id, node_kp, service_kp, endorsed_cert.value()), true); } diff --git a/src/node/identity.h b/src/node/identity.h index e6754298e39e..1c231b51924c 100644 --- a/src/node/identity.h +++ b/src/node/identity.h @@ -24,6 +24,17 @@ namespace ccf ccf::crypto::Pem cert; std::optional type = IdentityType::REPLICATED; std::string subject_name = "CN=CCF Service"; + std::shared_ptr kp{}; + + std::shared_ptr get_key_pair() + { + if (!kp) + { + kp = std::make_shared(priv_key); + } + + return kp; + } bool operator==(const NetworkIdentity& other) const { @@ -86,11 +97,8 @@ namespace ccf virtual ccf::crypto::Pem issue_certificate( const std::string& valid_from, size_t validity_period_days) override { - auto identity_key_pair = - std::make_shared(priv_key); - return ccf::crypto::create_self_signed_cert( - identity_key_pair, + get_key_pair(), subject_name, {} /* SAN */, valid_from, diff --git a/src/node/node_state.h b/src/node/node_state.h index 485eb21d086e..9c4bab935e02 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -191,6 +191,7 @@ namespace ccf *snapshot_store.get(), self, *node_sign_kp, + *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* No signature timer on snapshot_history */); @@ -1245,6 +1246,7 @@ namespace ccf *recovery_store.get(), self, *node_sign_kp, + *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* No signature timer on recovery_history */); @@ -2404,6 +2406,7 @@ namespace ccf *network.tables.get(), self, *node_sign_kp, + *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* start timed signatures after first tx */); diff --git a/src/node/test/historical_queries.cpp b/src/node/test/historical_queries.cpp index 8ae0c8203d4d..f59fbb8eb102 100644 --- a/src/node/test/historical_queries.cpp +++ b/src/node/test/historical_queries.cpp @@ -40,6 +40,7 @@ struct TestState std::shared_ptr kv_store = nullptr; std::shared_ptr ledger_secrets = nullptr; ccf::crypto::KeyPairPtr node_kp = nullptr; + std::shared_ptr service_kp = nullptr; }; TestState create_and_init_state(bool initialise_ledger_rekey = true) @@ -52,11 +53,13 @@ TestState create_and_init_state(bool initialise_ledger_rekey = true) ts.kv_store->set_encryptor(encryptor); ts.node_kp = ccf::crypto::make_key_pair(); + ts.service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); // Make history to produce signatures const ccf::NodeId node_id = std::string("node_id"); - auto h = - std::make_shared(*ts.kv_store, node_id, *ts.node_kp); + auto h = std::make_shared( + *ts.kv_store, node_id, *ts.node_kp, *ts.service_kp); h->set_endorsed_certificate({}); ts.kv_store->set_history(h); ts.kv_store->initialise_term(2); diff --git a/src/node/test/history.cpp b/src/node/test/history.cpp index b512e6b69864..121f4fde1543 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -74,15 +74,17 @@ TEST_CASE("Check signature verification") { auto encryptor = std::make_shared(); - auto kp = ccf::crypto::make_key_pair(); - const auto self_signed = kp->self_sign("CN=Node", valid_from, valid_to); + auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); + const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; primary_store.set_encryptor(encryptor); constexpr auto store_term = 2; std::shared_ptr primary_history = std::make_shared( - primary_store, ccf::kv::test::PrimaryNodeId, *kp); + primary_store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); primary_history->set_endorsed_certificate(self_signed); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -91,7 +93,7 @@ TEST_CASE("Check signature verification") backup_store.set_encryptor(encryptor); std::shared_ptr backup_history = std::make_shared( - backup_store, ccf::kv::test::FirstBackupNodeId, *kp); + backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp, *service_kp); backup_history->set_endorsed_certificate(self_signed); backup_store.set_history(backup_history); backup_store.initialise_term(store_term); @@ -112,7 +114,7 @@ TEST_CASE("Check signature verification") auto txs = primary_store.create_tx(); auto tx = txs.rw(nodes); ccf::NodeInfo ni; - ni.encryption_pub_key = kp->public_key_pem(); + ni.encryption_pub_key = node_kp->public_key_pem(); ni.cert = self_signed; tx->put(ccf::kv::test::PrimaryNodeId, ni); REQUIRE(txs.commit() == ccf::kv::CommitResult::SUCCESS); @@ -139,15 +141,17 @@ TEST_CASE("Check signing works across rollback") { auto encryptor = std::make_shared(); - auto kp = ccf::crypto::make_key_pair(); - const auto self_signed = kp->self_sign("CN=Node", valid_from, valid_to); + auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); + const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; primary_store.set_encryptor(encryptor); constexpr auto store_term = 2; std::shared_ptr primary_history = std::make_shared( - primary_store, ccf::kv::test::PrimaryNodeId, *kp); + primary_store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); primary_history->set_endorsed_certificate(self_signed); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -155,7 +159,7 @@ TEST_CASE("Check signing works across rollback") ccf::kv::Store backup_store; std::shared_ptr backup_history = std::make_shared( - backup_store, ccf::kv::test::FirstBackupNodeId, *kp); + backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp, *service_kp); backup_history->set_endorsed_certificate(self_signed); backup_store.set_history(backup_history); backup_store.set_encryptor(encryptor); @@ -175,7 +179,7 @@ TEST_CASE("Check signing works across rollback") auto txs = primary_store.create_tx(); auto tx = txs.rw(nodes); ccf::NodeInfo ni; - ni.encryption_pub_key = kp->public_key_pem(); + ni.encryption_pub_key = node_kp->public_key_pem(); ni.cert = self_signed; tx->put(ccf::kv::test::PrimaryNodeId, ni); REQUIRE(txs.commit() == ccf::kv::CommitResult::SUCCESS); diff --git a/src/node/test/history_bench.cpp b/src/node/test/history_bench.cpp index 8fad0325b23f..d437c63fed9c 100644 --- a/src/node/test/history_bench.cpp +++ b/src/node/test/history_bench.cpp @@ -72,7 +72,9 @@ static void append(picobench::state& s) ::srand(42); ccf::kv::Store store; - auto kp = ccf::crypto::make_key_pair(); + auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); std::shared_ptr consensus = std::make_shared(); @@ -80,7 +82,7 @@ static void append(picobench::state& s) std::shared_ptr history = std::make_shared( - store, ccf::kv::test::PrimaryNodeId, *kp); + store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); store.set_history(history); std::vector> txs; @@ -111,7 +113,9 @@ static void append_compact(picobench::state& s) ::srand(42); ccf::kv::Store store; - auto kp = ccf::crypto::make_key_pair(); + auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); std::shared_ptr consensus = std::make_shared(); @@ -119,7 +123,7 @@ static void append_compact(picobench::state& s) std::shared_ptr history = std::make_shared( - store, ccf::kv::test::PrimaryNodeId, *kp); + store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); store.set_history(history); std::vector> txs; diff --git a/src/node/test/receipt.cpp b/src/node/test/receipt.cpp index 063ee06ceb6f..f572ba37a749 100644 --- a/src/node/test/receipt.cpp +++ b/src/node/test/receipt.cpp @@ -6,6 +6,7 @@ #include "ccf/crypto/key_pair.h" #include "ccf/service/tables/nodes.h" #include "crypto/openssl/hash.h" +#include "crypto/openssl/key_pair.h" #include "ds/x509_time_fmt.h" #include @@ -68,7 +69,8 @@ void populate_receipt(std::shared_ptr receipt) const auto num_endorsements = rand() % 3; for (auto i = 0; i < num_endorsements; ++i) { - auto service_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); auto service_cert = service_kp->self_sign("CN=service", valid_from, valid_to); const auto csr = node_kp->create_csr(fmt::format("CN=Test{}", i)); diff --git a/src/node/test/snapshot.cpp b/src/node/test/snapshot.cpp index fa3b6a5d571f..b834e73ae8c4 100644 --- a/src/node/test/snapshot.cpp +++ b/src/node/test/snapshot.cpp @@ -27,9 +27,11 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) ccf::NodeId source_node_id = ccf::kv::test::PrimaryNodeId; auto source_node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); auto source_history = std::make_shared( - source_store, source_node_id, *source_node_kp); + source_store, source_node_id, *source_node_kp, *service_kp); source_history->set_endorsed_certificate({}); source_store.set_history(source_history); source_store.initialise_term(2); @@ -96,7 +98,10 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) auto target_node_kp = ccf::crypto::make_key_pair(); auto target_history = std::make_shared( - target_store, ccf::kv::test::PrimaryNodeId, *target_node_kp); + target_store, + ccf::kv::test::PrimaryNodeId, + *target_node_kp, + *service_kp); target_history->set_endorsed_certificate({}); target_store.set_history(target_history); } diff --git a/src/node/test/snapshotter.cpp b/src/node/test/snapshotter.cpp index 914fdd3a3957..d6429e0bbe83 100644 --- a/src/node/test/snapshotter.cpp +++ b/src/node/test/snapshotter.cpp @@ -21,7 +21,9 @@ std::unique_ptr threading::ThreadMessaging::singleton = nullptr; constexpr auto buffer_size = 1024 * 16; -auto kp = ccf::crypto::make_key_pair(); +auto node_kp = ccf::crypto::make_key_pair(); +auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); using StringString = ccf::kv::Map; using rb_msg = std::pair; @@ -142,7 +144,7 @@ TEST_CASE("Regular snapshotting") auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -305,7 +307,7 @@ TEST_CASE("Rollback before snapshot is committed") ccf::NetworkState network; auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -436,7 +438,7 @@ TEST_CASE("Rekey ledger while snapshot is in progress") auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -502,7 +504,10 @@ TEST_CASE("Rekey ledger while snapshot is in progress") // Snapshot can be deserialised to backup store ccf::NetworkState backup_network; auto backup_history = std::make_shared( - *backup_network.tables.get(), ccf::kv::test::FirstBackupNodeId, *kp); + *backup_network.tables.get(), + ccf::kv::test::FirstBackupNodeId, + *node_kp, + *service_kp); backup_network.tables->set_history(backup_history); auto tx = network.tables->create_read_only_tx(); From 2bed88bc37b3939e003d6fbdcf3d52ce3629deb9 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 30 Aug 2024 09:47:19 +0000 Subject: [PATCH 07/33] Change key setting in history --- src/kv/kv_types.h | 3 +++ src/node/history.h | 24 ++++++++++++++++++++---- src/node/node_state.h | 9 ++++++--- src/node/test/historical_queries.cpp | 7 ++----- src/node/test/history.cpp | 12 ++++-------- src/node/test/history_bench.cpp | 8 ++------ src/node/test/receipt.cpp | 3 +-- src/node/test/snapshot.cpp | 9 ++------- src/node/test/snapshotter.cpp | 13 ++++--------- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/kv/kv_types.h b/src/kv/kv_types.h index fbad8485a76a..89f0e560486f 100644 --- a/src/kv/kv_types.h +++ b/src/kv/kv_types.h @@ -11,6 +11,7 @@ #include "ccf/kv/hooks.h" #include "ccf/kv/version.h" #include "ccf/tx_id.h" +#include "crypto/openssl/key_pair.h" #include "enclave/consensus_type.h" #include "enclave/reconfiguration_type.h" #include "serialiser_declare.h" @@ -428,6 +429,8 @@ namespace ccf::kv virtual std::vector serialise_tree(size_t to) = 0; virtual void set_endorsed_certificate(const ccf::crypto::Pem& cert) = 0; virtual void start_signature_emit_timer() = 0; + virtual void set_service_kp( + std::shared_ptr) = 0; }; class Consensus : public ConfigurableConsensus diff --git a/src/node/history.h b/src/node/history.h index 5d66224e1ed7..9df04e402d34 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -201,6 +201,12 @@ namespace ccf void start_signature_emit_timer() override {} + void set_service_kp( + std::shared_ptr service_kp_) override + { + std::ignore = std::move(service_kp_); + } + ccf::crypto::Sha256Hash get_replicated_state_root() override { return ccf::crypto::Sha256Hash(std::to_string(version)); @@ -525,7 +531,7 @@ namespace ccf T replicated_state_tree; ccf::crypto::KeyPair& node_kp; - ccf::crypto::KeyPair_OpenSSL& service_kp; + std::shared_ptr service_kp{}; std::optional<::threading::TaskQueue::TimerEntry> emit_signature_timer_entry = std::nullopt; @@ -543,14 +549,12 @@ namespace ccf ccf::kv::Store& store_, const NodeId& id_, ccf::crypto::KeyPair& node_kp_, - ccf::crypto::KeyPair_OpenSSL& service_kp_, size_t sig_tx_interval_ = 0, size_t sig_ms_interval_ = 0, bool signature_timer = false) : store(store_), id(id_), node_kp(node_kp_), - service_kp(service_kp_), sig_tx_interval(sig_tx_interval_), sig_ms_interval(sig_ms_interval_) { @@ -560,6 +564,12 @@ namespace ccf } } + void set_service_kp( + std::shared_ptr service_kp_) override + { + service_kp = std::move(service_kp_); + } + void start_signature_emit_timer() override { struct EmitSigMsg @@ -814,10 +824,16 @@ namespace ccf LOG_DEBUG_FMT("Signed at {} in view: {}", txid.version, txid.term); + if (!service_kp) + { + throw std::logic_error( + fmt::format("No service key has been set yet to sign")); + } + store.commit( txid, std::make_unique>( - txid, store, *this, id, node_kp, service_kp, endorsed_cert.value()), + txid, store, *this, id, node_kp, *service_kp, endorsed_cert.value()), true); } diff --git a/src/node/node_state.h b/src/node/node_state.h index 9c4bab935e02..944c7e6f8d79 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -191,7 +191,6 @@ namespace ccf *snapshot_store.get(), self, *node_sign_kp, - *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* No signature timer on snapshot_history */); @@ -506,6 +505,8 @@ namespace ccf network.ledger_secrets->init(); + history->set_service_kp(network.identity->get_key_pair()); + setup_consensus( ServiceStatus::OPENING, ReconfigurationType::ONE_TRANSACTION, @@ -541,6 +542,8 @@ namespace ccf config.startup_host_time, config.initial_service_certificate_validity_days); + history->set_service_kp(network.identity->get_key_pair()); + LOG_INFO_FMT("Created recovery node {}", self); return {self_signed_node_cert, network.identity->cert}; } @@ -644,6 +647,8 @@ namespace ccf network.ledger_secrets->init_from_map( std::move(resp.network_info->ledger_secrets)); + history->set_service_kp(network.identity->get_key_pair()); + ccf::crypto::Pem n2n_channels_cert; if (!resp.network_info->endorsed_certificate.has_value()) { @@ -1246,7 +1251,6 @@ namespace ccf *recovery_store.get(), self, *node_sign_kp, - *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* No signature timer on recovery_history */); @@ -2406,7 +2410,6 @@ namespace ccf *network.tables.get(), self, *node_sign_kp, - *network.identity->get_key_pair(), sig_tx_interval, sig_ms_interval, false /* start timed signatures after first tx */); diff --git a/src/node/test/historical_queries.cpp b/src/node/test/historical_queries.cpp index f59fbb8eb102..8ae0c8203d4d 100644 --- a/src/node/test/historical_queries.cpp +++ b/src/node/test/historical_queries.cpp @@ -40,7 +40,6 @@ struct TestState std::shared_ptr kv_store = nullptr; std::shared_ptr ledger_secrets = nullptr; ccf::crypto::KeyPairPtr node_kp = nullptr; - std::shared_ptr service_kp = nullptr; }; TestState create_and_init_state(bool initialise_ledger_rekey = true) @@ -53,13 +52,11 @@ TestState create_and_init_state(bool initialise_ledger_rekey = true) ts.kv_store->set_encryptor(encryptor); ts.node_kp = ccf::crypto::make_key_pair(); - ts.service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); // Make history to produce signatures const ccf::NodeId node_id = std::string("node_id"); - auto h = std::make_shared( - *ts.kv_store, node_id, *ts.node_kp, *ts.service_kp); + auto h = + std::make_shared(*ts.kv_store, node_id, *ts.node_kp); h->set_endorsed_certificate({}); ts.kv_store->set_history(h); ts.kv_store->initialise_term(2); diff --git a/src/node/test/history.cpp b/src/node/test/history.cpp index 121f4fde1543..2e3904966adf 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -75,8 +75,6 @@ TEST_CASE("Check signature verification") auto encryptor = std::make_shared(); auto node_kp = ccf::crypto::make_key_pair(); - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; @@ -84,7 +82,7 @@ TEST_CASE("Check signature verification") constexpr auto store_term = 2; std::shared_ptr primary_history = std::make_shared( - primary_store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -93,7 +91,7 @@ TEST_CASE("Check signature verification") backup_store.set_encryptor(encryptor); std::shared_ptr backup_history = std::make_shared( - backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp, *service_kp); + backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); backup_store.set_history(backup_history); backup_store.initialise_term(store_term); @@ -142,8 +140,6 @@ TEST_CASE("Check signing works across rollback") auto encryptor = std::make_shared(); auto node_kp = ccf::crypto::make_key_pair(); - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; @@ -151,7 +147,7 @@ TEST_CASE("Check signing works across rollback") constexpr auto store_term = 2; std::shared_ptr primary_history = std::make_shared( - primary_store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -159,7 +155,7 @@ TEST_CASE("Check signing works across rollback") ccf::kv::Store backup_store; std::shared_ptr backup_history = std::make_shared( - backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp, *service_kp); + backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); backup_store.set_history(backup_history); backup_store.set_encryptor(encryptor); diff --git a/src/node/test/history_bench.cpp b/src/node/test/history_bench.cpp index d437c63fed9c..a340280bc125 100644 --- a/src/node/test/history_bench.cpp +++ b/src/node/test/history_bench.cpp @@ -73,8 +73,6 @@ static void append(picobench::state& s) ccf::kv::Store store; auto node_kp = ccf::crypto::make_key_pair(); - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); std::shared_ptr consensus = std::make_shared(); @@ -82,7 +80,7 @@ static void append(picobench::state& s) std::shared_ptr history = std::make_shared( - store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + store, ccf::kv::test::PrimaryNodeId, *node_kp); store.set_history(history); std::vector> txs; @@ -114,8 +112,6 @@ static void append_compact(picobench::state& s) ccf::kv::Store store; auto node_kp = ccf::crypto::make_key_pair(); - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); std::shared_ptr consensus = std::make_shared(); @@ -123,7 +119,7 @@ static void append_compact(picobench::state& s) std::shared_ptr history = std::make_shared( - store, ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + store, ccf::kv::test::PrimaryNodeId, *node_kp); store.set_history(history); std::vector> txs; diff --git a/src/node/test/receipt.cpp b/src/node/test/receipt.cpp index f572ba37a749..1ab323db5eb6 100644 --- a/src/node/test/receipt.cpp +++ b/src/node/test/receipt.cpp @@ -69,8 +69,7 @@ void populate_receipt(std::shared_ptr receipt) const auto num_endorsements = rand() % 3; for (auto i = 0; i < num_endorsements; ++i) { - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); + auto service_kp = ccf::crypto::make_key_pair(); auto service_cert = service_kp->self_sign("CN=service", valid_from, valid_to); const auto csr = node_kp->create_csr(fmt::format("CN=Test{}", i)); diff --git a/src/node/test/snapshot.cpp b/src/node/test/snapshot.cpp index b834e73ae8c4..fa3b6a5d571f 100644 --- a/src/node/test/snapshot.cpp +++ b/src/node/test/snapshot.cpp @@ -27,11 +27,9 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) ccf::NodeId source_node_id = ccf::kv::test::PrimaryNodeId; auto source_node_kp = ccf::crypto::make_key_pair(); - auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); auto source_history = std::make_shared( - source_store, source_node_id, *source_node_kp, *service_kp); + source_store, source_node_id, *source_node_kp); source_history->set_endorsed_certificate({}); source_store.set_history(source_history); source_store.initialise_term(2); @@ -98,10 +96,7 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) auto target_node_kp = ccf::crypto::make_key_pair(); auto target_history = std::make_shared( - target_store, - ccf::kv::test::PrimaryNodeId, - *target_node_kp, - *service_kp); + target_store, ccf::kv::test::PrimaryNodeId, *target_node_kp); target_history->set_endorsed_certificate({}); target_store.set_history(target_history); } diff --git a/src/node/test/snapshotter.cpp b/src/node/test/snapshotter.cpp index d6429e0bbe83..56fe1fe45c1f 100644 --- a/src/node/test/snapshotter.cpp +++ b/src/node/test/snapshotter.cpp @@ -22,8 +22,6 @@ std::unique_ptr constexpr auto buffer_size = 1024 * 16; auto node_kp = ccf::crypto::make_key_pair(); -auto service_kp = std::dynamic_pointer_cast( - ccf::crypto::make_key_pair()); using StringString = ccf::kv::Map; using rb_msg = std::pair; @@ -144,7 +142,7 @@ TEST_CASE("Regular snapshotting") auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -307,7 +305,7 @@ TEST_CASE("Rollback before snapshot is committed") ccf::NetworkState network; auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -438,7 +436,7 @@ TEST_CASE("Rekey ledger while snapshot is in progress") auto consensus = std::make_shared(); auto history = std::make_shared( - *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp, *service_kp); + *network.tables.get(), ccf::kv::test::PrimaryNodeId, *node_kp); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -504,10 +502,7 @@ TEST_CASE("Rekey ledger while snapshot is in progress") // Snapshot can be deserialised to backup store ccf::NetworkState backup_network; auto backup_history = std::make_shared( - *backup_network.tables.get(), - ccf::kv::test::FirstBackupNodeId, - *node_kp, - *service_kp); + *backup_network.tables.get(), ccf::kv::test::FirstBackupNodeId, *node_kp); backup_network.tables->set_history(backup_history); auto tx = network.tables->create_read_only_tx(); From baabe3dabdc72dea1e23fe9c1fb8b1acd0e86183 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 30 Aug 2024 09:56:21 +0000 Subject: [PATCH 08/33] Doc rst WIP --- doc/audit/builtin_maps.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index 615038354d32..45ebb93ec885 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -399,6 +399,8 @@ Service constitution: JavaScript module, exporting ``validate()``, ``resolve()`` **Value** JavaScript module, represented as a string. + + ``history`` ~~~~~~~~~~~ @@ -471,6 +473,11 @@ Signatures emitted by the primary node at regular interval, over the root of the :project: CCF :members: +``cose_signatures`` +~~~~~~~~~~~~~~ + +TO DO + ``recovery_shares`` ~~~~~~~~~~~~~~~~~~~ From 35bacc0c6555f347675a454007a164f4871ca38b Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 30 Aug 2024 10:22:00 +0000 Subject: [PATCH 09/33] Fixup keys in tests --- src/node/test/historical_queries.cpp | 4 ++++ src/node/test/history.cpp | 10 ++++++++++ src/node/test/snapshot.cpp | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/src/node/test/historical_queries.cpp b/src/node/test/historical_queries.cpp index 8ae0c8203d4d..501ebe1e1a52 100644 --- a/src/node/test/historical_queries.cpp +++ b/src/node/test/historical_queries.cpp @@ -40,6 +40,7 @@ struct TestState std::shared_ptr kv_store = nullptr; std::shared_ptr ledger_secrets = nullptr; ccf::crypto::KeyPairPtr node_kp = nullptr; + std::shared_ptr service_kp = nullptr; }; TestState create_and_init_state(bool initialise_ledger_rekey = true) @@ -52,12 +53,15 @@ TestState create_and_init_state(bool initialise_ledger_rekey = true) ts.kv_store->set_encryptor(encryptor); ts.node_kp = ccf::crypto::make_key_pair(); + ts.service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); // Make history to produce signatures const ccf::NodeId node_id = std::string("node_id"); auto h = std::make_shared(*ts.kv_store, node_id, *ts.node_kp); h->set_endorsed_certificate({}); + h->set_service_kp(ts.service_kp); ts.kv_store->set_history(h); ts.kv_store->initialise_term(2); diff --git a/src/node/test/history.cpp b/src/node/test/history.cpp index 2e3904966adf..9a834a5424fb 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -75,6 +75,9 @@ TEST_CASE("Check signature verification") auto encryptor = std::make_shared(); auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); + const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; @@ -84,6 +87,7 @@ TEST_CASE("Check signature verification") std::make_shared( primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); + primary_history->set_service_kp(service_kp); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -93,6 +97,7 @@ TEST_CASE("Check signature verification") std::make_shared( backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); + backup_history->set_service_kp(service_kp); backup_store.set_history(backup_history); backup_store.initialise_term(store_term); @@ -140,6 +145,9 @@ TEST_CASE("Check signing works across rollback") auto encryptor = std::make_shared(); auto node_kp = ccf::crypto::make_key_pair(); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); + const auto self_signed = node_kp->self_sign("CN=Node", valid_from, valid_to); ccf::kv::Store primary_store; @@ -149,6 +157,7 @@ TEST_CASE("Check signing works across rollback") std::make_shared( primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); + primary_history->set_service_kp(service_kp); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -157,6 +166,7 @@ TEST_CASE("Check signing works across rollback") std::make_shared( backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); + backup_history->set_service_kp(service_kp); backup_store.set_history(backup_history); backup_store.set_encryptor(encryptor); backup_store.initialise_term(store_term); diff --git a/src/node/test/snapshot.cpp b/src/node/test/snapshot.cpp index fa3b6a5d571f..225b4bc65fe2 100644 --- a/src/node/test/snapshot.cpp +++ b/src/node/test/snapshot.cpp @@ -25,12 +25,16 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) source_store.set_encryptor(encryptor); source_store.set_consensus(source_consensus); + auto service_kp = std::dynamic_pointer_cast( + ccf::crypto::make_key_pair()); + ccf::NodeId source_node_id = ccf::kv::test::PrimaryNodeId; auto source_node_kp = ccf::crypto::make_key_pair(); auto source_history = std::make_shared( source_store, source_node_id, *source_node_kp); source_history->set_endorsed_certificate({}); + source_history->set_service_kp(service_kp); source_store.set_history(source_history); source_store.initialise_term(2); @@ -98,6 +102,7 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) auto target_history = std::make_shared( target_store, ccf::kv::test::PrimaryNodeId, *target_node_kp); target_history->set_endorsed_certificate({}); + target_history->set_service_kp(service_kp); target_store.set_history(target_history); } From 6de93148ab52c6636a80baa2112cc377f7803255 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 30 Aug 2024 10:35:09 +0000 Subject: [PATCH 10/33] Fixup change deser --- src/kv/deserialise.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index 1e07c96267ba..274518d6febb 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -129,7 +129,16 @@ namespace ccf::kv // verified if ( changes.size() > 3 || - changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end() || + changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end()) + { + LOG_FAIL_FMT("Failed to deserialise"); + LOG_DEBUG_FMT( + "Unexpected contents in signature transaction {}", version); + return ApplyResult::FAIL; + } + + if ( + changes.size() == 3 && changes.find(ccf::Tables::COSE_SIGNATURES) == changes.end()) { LOG_FAIL_FMT("Failed to deserialise"); From 49537db7d825e03da166ac84b4921553f26d5d51 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 5 Sep 2024 12:12:04 +0000 Subject: [PATCH 11/33] Verify COSE in history->verify --- include/ccf/crypto/cose_verifier.h | 2 ++ src/crypto/openssl/cose_verifier.cpp | 33 ++++++++++++++++++++ src/crypto/openssl/cose_verifier.h | 3 ++ src/kv/deserialise.h | 2 +- src/kv/kv_types.h | 5 ++-- src/node/history.h | 45 ++++++++++++++++++++-------- 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/include/ccf/crypto/cose_verifier.h b/include/ccf/crypto/cose_verifier.h index e4d403661912..5dd349eeca65 100644 --- a/include/ccf/crypto/cose_verifier.h +++ b/include/ccf/crypto/cose_verifier.h @@ -17,6 +17,8 @@ namespace ccf::crypto virtual bool verify( const std::span& buf, std::span& authned_content) const = 0; + virtual bool verify_detached( + std::span buf, std::span payload) const = 0; virtual ~COSEVerifier() = default; }; diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index bde1348863f5..4eff801df279 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -91,6 +91,39 @@ namespace ccf::crypto return false; } + bool COSEVerifier_OpenSSL::verify_detached( + std::span buf, std::span payload) const + { + EVP_PKEY* evp_key = *public_key; + + t_cose_key cose_key; + cose_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL; + cose_key.k.key_ptr = evp_key; + + t_cose_sign1_verify_ctx verify_ctx; + t_cose_sign1_verify_init(&verify_ctx, T_COSE_OPT_TAG_REQUIRED); + t_cose_sign1_set_verification_key(&verify_ctx, cose_key); + + q_useful_buf_c buf_; + buf_.ptr = buf.data(); + buf_.len = buf.size(); + + q_useful_buf_c payload_; + payload_.ptr = payload.data(); + payload_.len = payload.size(); + + t_cose_err_t error = t_cose_sign1_verify_detached( + &verify_ctx, buf_, NULL_Q_USEFUL_BUF_C, payload_, nullptr); + + if (error == T_COSE_SUCCESS) + { + return true; + } + + LOG_DEBUG_FMT("COSE Sign1 verification failed with error {}", error); + return false; + } + COSEVerifierUniquePtr make_cose_verifier_from_cert( const std::vector& cert) { diff --git a/src/crypto/openssl/cose_verifier.h b/src/crypto/openssl/cose_verifier.h index 6bd61dca037e..80fa685573ec 100644 --- a/src/crypto/openssl/cose_verifier.h +++ b/src/crypto/openssl/cose_verifier.h @@ -23,6 +23,9 @@ namespace ccf::crypto virtual bool verify( const std::span& buf, std::span& authned_content) const override; + virtual bool verify_detached( + std::span buf, + std::span payload) const override; }; class COSECertVerifier_OpenSSL : public COSEVerifier_OpenSSL diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index 274518d6febb..0174aa79ad68 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -149,7 +149,7 @@ namespace ccf::kv if (history) { - if (!history->verify(&term)) + if (!history->verify_root_signatures()) { LOG_FAIL_FMT("Failed to deserialise"); LOG_DEBUG_FMT( diff --git a/src/kv/kv_types.h b/src/kv/kv_types.h index 89f0e560486f..e804f0dfb117 100644 --- a/src/kv/kv_types.h +++ b/src/kv/kv_types.h @@ -403,8 +403,7 @@ namespace ccf::kv ccf::PrimarySignature& signature, Term* term, ccf::kv::Configuration::Nodes& nodes) = 0; - virtual bool verify( - Term* term = nullptr, ccf::PrimarySignature* sig = nullptr) = 0; + virtual bool verify_root_signatures() = 0; virtual void try_emit_signature() = 0; virtual void emit_signature() = 0; virtual ccf::crypto::Sha256Hash get_replicated_state_root() = 0; @@ -579,7 +578,7 @@ namespace ccf::kv class AbstractSnapshotter { public: - virtual ~AbstractSnapshotter(){}; + virtual ~AbstractSnapshotter() {}; virtual bool record_committable(ccf::kv::Version v) = 0; virtual void commit(ccf::kv::Version v, bool generate_snapshot) = 0; diff --git a/src/node/history.h b/src/node/history.h index 9df04e402d34..a995d723225a 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -2,9 +2,11 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/crypto/cose_verifier.h" #include "ccf/ds/logger.h" #include "ccf/pal/locking.h" #include "ccf/service/tables/nodes.h" +#include "ccf/service/tables/service.h" #include "crypto/openssl/cose_sign.h" #include "crypto/openssl/hash.h" #include "crypto/openssl/key_pair.h" @@ -158,7 +160,7 @@ namespace ccf return ccf::kv::TxHistory::Result::OK; } - bool verify(ccf::kv::Term*, ccf::PrimarySignature*) override + bool verify_root_signatures() override { return true; } @@ -700,7 +702,7 @@ namespace ccf ccf::kv::Term* term, ccf::kv::Configuration::Nodes& config) override { - if (!verify(term, &sig)) + if (!verify_root_signatures()) { return ccf::kv::TxHistory::Result::FAIL; } @@ -713,11 +715,10 @@ namespace ccf return result; } - bool verify( - ccf::kv::Term* term = nullptr, - PrimarySignature* signature = nullptr) override + bool verify_root_signatures() override { auto tx = store.create_read_only_tx(); + auto signatures = tx.template ro(ccf::Tables::SIGNATURES); auto sig = signatures->get(); @@ -726,20 +727,38 @@ namespace ccf LOG_FAIL_FMT("No signature found in signatures map"); return false; } - auto& sig_value = sig.value(); - if (term) + + auto root = get_replicated_state_root(); + log_hash(root, VERIFY); + if (!verify_node_signature(tx, sig->node, sig->sig, root)) { - *term = sig_value.view; + return false; } - if (signature) + auto cose_signatures = + tx.template ro(ccf::Tables::COSE_SIGNATURES); + auto cose_sig = cose_signatures->get(); + + if (!cose_sig.has_value()) { - *signature = sig_value; + return true; } - auto root = get_replicated_state_root(); - log_hash(root, VERIFY); - return verify_node_signature(tx, sig_value.node, sig_value.sig, root); + auto service = tx.template ro(Tables::SERVICE); + auto service_info = service->get(); + + if (!service_info.has_value()) + { + LOG_FAIL_FMT("No service key found to verify the signature"); + return false; + } + + auto verifier = + ccf::crypto::make_cose_verifier_from_cert(service_info->cert.raw()); + const std::vector root_hash{ + root.h.data(), root.h.data() + root.h.size()}; + + return verifier->verify_detached(cose_sig->sig, root_hash); } std::vector serialise_tree(size_t to) override From d89b7c51f86d4fa687f5f31913cf3a863b1b5f74 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 5 Sep 2024 12:53:03 +0000 Subject: [PATCH 12/33] Format checks --- src/kv/kv_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kv/kv_types.h b/src/kv/kv_types.h index e804f0dfb117..0be236c901e2 100644 --- a/src/kv/kv_types.h +++ b/src/kv/kv_types.h @@ -578,7 +578,7 @@ namespace ccf::kv class AbstractSnapshotter { public: - virtual ~AbstractSnapshotter() {}; + virtual ~AbstractSnapshotter(){}; virtual bool record_committable(ccf::kv::Version v) = 0; virtual void commit(ccf::kv::Version v, bool generate_snapshot) = 0; From 246e3d577d30393507f1266e3bec5080a4803f3d Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Thu, 5 Sep 2024 19:21:32 +0000 Subject: [PATCH 13/33] Optimise key creation --- src/crypto/test/crypto.cpp | 12 ++++++----- src/kv/deserialise.h | 41 +++++++++++++++++++------------------- src/node/history.h | 12 +++++------ 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index 9bd6484c0938..c5c7b1c0afbf 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -1272,12 +1272,14 @@ TEST_CASE("COSE sign & verify") {"string key", "string value"}, cose_sign); - REQUIRE_EQ(verify_detached(*kp, cose_sign, payload), T_COSE_SUCCESS); + auto cose_verifier = + ccf::crypto::make_cose_verifier_from_key(kp->public_key_pem()); + + REQUIRE(cose_verifier->verify_detached(cose_sign, payload)); // Wrong payload, must not pass verification. - REQUIRE_EQ( - verify_detached(*kp, cose_sign, std::vector{1, 2, 3}), - T_COSE_ERR_SIG_VERIFY); + REQUIRE_FALSE( + cose_verifier->verify_detached(cose_sign, std::vector{1, 2, 3})); // Empty headers and payload handled correctly cose_sign = cose_sign1(*kp, {}, {}); @@ -1288,5 +1290,5 @@ TEST_CASE("COSE sign & verify") {"string key", std::nullopt}, cose_sign); - REQUIRE_EQ(verify_detached(*kp, cose_sign, {}), T_COSE_SUCCESS); + REQUIRE(cose_verifier->verify_detached(cose_sign, {})); } diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index 0174aa79ad68..dd62c127aa42 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -121,30 +121,29 @@ namespace ccf::kv auto success = ApplyResult::PASS; auto search = changes.find(ccf::Tables::SIGNATURES); - if (search != changes.end()) { - // Transactions containing a signature must only contain the signature, - // COSE signature (optional), and the serialised Merkle tree and must be - // verified - if ( - changes.size() > 3 || - changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == changes.end()) - { - LOG_FAIL_FMT("Failed to deserialise"); - LOG_DEBUG_FMT( - "Unexpected contents in signature transaction {}", version); - return ApplyResult::FAIL; - } - - if ( - changes.size() == 3 && - changes.find(ccf::Tables::COSE_SIGNATURES) == changes.end()) + switch (changes.size()) { - LOG_FAIL_FMT("Failed to deserialise"); - LOG_DEBUG_FMT( - "Unexpected contents in signature transaction {}", version); - return ApplyResult::FAIL; + case 2: + if ( + changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) != + changes.end()) + { + break; + } + case 3: + if ( + changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) != + changes.end() && + changes.find(ccf::Tables::COSE_SIGNATURES) != changes.end()) + { + break; + } + default: + LOG_DEBUG_FMT( + "Unexpected contents in signature transaction {}", version); + return ApplyResult::FAIL; } if (history) diff --git a/src/node/history.h b/src/node/history.h index a995d723225a..61fce2e25dbf 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -534,6 +534,7 @@ namespace ccf ccf::crypto::KeyPair& node_kp; std::shared_ptr service_kp{}; + ccf::crypto::COSEVerifierUniquePtr cose_verifier{}; std::optional<::threading::TaskQueue::TimerEntry> emit_signature_timer_entry = std::nullopt; @@ -570,6 +571,8 @@ namespace ccf std::shared_ptr service_kp_) override { service_kp = std::move(service_kp_); + cose_verifier = + ccf::crypto::make_cose_verifier_from_key(service_kp->public_key_pem()); } void start_signature_emit_timer() override @@ -744,21 +747,16 @@ namespace ccf return true; } - auto service = tx.template ro(Tables::SERVICE); - auto service_info = service->get(); - - if (!service_info.has_value()) + if (!cose_verifier) { LOG_FAIL_FMT("No service key found to verify the signature"); return false; } - auto verifier = - ccf::crypto::make_cose_verifier_from_cert(service_info->cert.raw()); const std::vector root_hash{ root.h.data(), root.h.data() + root.h.size()}; - return verifier->verify_detached(cose_sig->sig, root_hash); + return cose_verifier->verify_detached(cose_sig->sig, root_hash); } std::vector serialise_tree(size_t to) override From ec538fab14d97f79be8c328d6afe1e03e9cd581c Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 6 Sep 2024 10:47:10 +0000 Subject: [PATCH 14/33] Rollback public key caching --- src/node/history.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/node/history.h b/src/node/history.h index 61fce2e25dbf..37627686a123 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -534,7 +534,6 @@ namespace ccf ccf::crypto::KeyPair& node_kp; std::shared_ptr service_kp{}; - ccf::crypto::COSEVerifierUniquePtr cose_verifier{}; std::optional<::threading::TaskQueue::TimerEntry> emit_signature_timer_entry = std::nullopt; @@ -571,8 +570,6 @@ namespace ccf std::shared_ptr service_kp_) override { service_kp = std::move(service_kp_); - cose_verifier = - ccf::crypto::make_cose_verifier_from_key(service_kp->public_key_pem()); } void start_signature_emit_timer() override @@ -747,16 +744,22 @@ namespace ccf return true; } - if (!cose_verifier) + auto service = tx.template ro(Tables::SERVICE); + auto service_info = service->get(); + + if (!service_info.has_value()) { LOG_FAIL_FMT("No service key found to verify the signature"); return false; } + // TO DO try use cache + auto verifier = + ccf::crypto::make_cose_verifier_from_cert(service_info->cert.raw()); const std::vector root_hash{ root.h.data(), root.h.data() + root.h.size()}; - return cose_verifier->verify_detached(cose_sig->sig, root_hash); + return verifier->verify_detached(cose_sig->sig, root_hash); } std::vector serialise_tree(size_t to) override From c04640384225c520a02b044e1e70ec8444afa9a6 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Fri, 6 Sep 2024 16:15:45 +0000 Subject: [PATCH 15/33] FIx history test (mock service key) --- src/node/test/history.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/node/test/history.cpp b/src/node/test/history.cpp index 9a834a5424fb..e9d03376ef0a 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -102,6 +102,7 @@ TEST_CASE("Check signature verification") backup_store.initialise_term(store_term); ccf::Nodes nodes(ccf::Tables::NODES); + ccf::Service service(ccf::Tables::SERVICE); ccf::Signatures signatures(ccf::Tables::SIGNATURES); std::shared_ptr consensus = @@ -112,7 +113,7 @@ TEST_CASE("Check signature verification") std::make_shared(nullptr); backup_store.set_consensus(null_consensus); - INFO("Write certificate"); + INFO("Write certificates"); { auto txs = primary_store.create_tx(); auto tx = txs.rw(nodes); @@ -120,6 +121,11 @@ TEST_CASE("Check signature verification") ni.encryption_pub_key = node_kp->public_key_pem(); ni.cert = self_signed; tx->put(ccf::kv::test::PrimaryNodeId, ni); + + auto stx = txs.rw(service); + auto service_info = ccf::ServiceInfo{ + .cert = service_kp->self_sign("CN=Service", valid_from, valid_to)}; + stx->put(service_info); REQUIRE(txs.commit() == ccf::kv::CommitResult::SUCCESS); } @@ -172,6 +178,7 @@ TEST_CASE("Check signing works across rollback") backup_store.initialise_term(store_term); ccf::Nodes nodes(ccf::Tables::NODES); + ccf::Service service(ccf::Tables::SERVICE); std::shared_ptr consensus = std::make_shared(&backup_store); @@ -180,7 +187,7 @@ TEST_CASE("Check signing works across rollback") std::make_shared(nullptr); backup_store.set_consensus(null_consensus); - INFO("Write certificate"); + INFO("Write certificates"); { auto txs = primary_store.create_tx(); auto tx = txs.rw(nodes); @@ -188,6 +195,11 @@ TEST_CASE("Check signing works across rollback") ni.encryption_pub_key = node_kp->public_key_pem(); ni.cert = self_signed; tx->put(ccf::kv::test::PrimaryNodeId, ni); + + auto stx = txs.rw(service); + auto service_info = ccf::ServiceInfo{ + .cert = service_kp->self_sign("CN=Service", valid_from, valid_to)}; + stx->put(service_info); REQUIRE(txs.commit() == ccf::kv::CommitResult::SUCCESS); } From 7ef7f27f84b158ed8cba84d56037b5b194aca759 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 10:42:04 +0000 Subject: [PATCH 16/33] Change default curve to es384 --- src/crypto/openssl/cose_sign.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 6e96cb9b79a7..465515267aa7 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -136,7 +136,7 @@ namespace ccf::crypto QCBOREncode_Init(&cbor_encode, signed_cose_buffer); t_cose_sign1_sign_ctx sign_ctx; - t_cose_sign1_sign_init(&sign_ctx, 0, T_COSE_ALGORITHM_ES256); + t_cose_sign1_sign_init(&sign_ctx, 0, T_COSE_ALGORITHM_ES384); t_cose_key signing_key; signing_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL; From 5272c147e4329a020d34b86cfdcbfa38f6898310 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 10:43:17 +0000 Subject: [PATCH 17/33] Add cose sig verification to python ledger checker --- python/src/ccf/cose.py | 15 +++++++++++++++ python/src/ccf/ledger.py | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/python/src/ccf/cose.py b/python/src/ccf/cose.py index 0ee965737e60..fd01c4816a36 100644 --- a/python/src/ccf/cose.py +++ b/python/src/ccf/cose.py @@ -174,6 +174,21 @@ def create_cose_sign1_finish( return msg.encode(sign=False) +def validate_cose_sign1(payload: bytes, cert_pem: Pem, cose_sign1: bytes) -> bool: + 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) + msg = Sign1Message.decode(cose_sign1) + msg.key = cose_key + msg.payload = payload + + if not msg.verify_signature(): + raise ValueError("signature is invalid") + + _SIGN_DESCRIPTION = """Create and sign a COSE Sign1 message for CCF governance Note that this tool writes binary COSE Sign1 to standard output. diff --git a/python/src/ccf/ledger.py b/python/src/ccf/ledger.py index 78841fdbcb3b..3e4c5cacd525 100644 --- a/python/src/ccf/ledger.py +++ b/python/src/ccf/ledger.py @@ -20,6 +20,7 @@ from ccf.merkletree import MerkleTree from ccf.tx_id import TxID +from ccf.cose import validate_cose_sign1 import ccf.receipt from hashlib import sha256 import functools @@ -31,6 +32,7 @@ # Public table names as defined in CCF SIGNATURE_TX_TABLE_NAME = "public:ccf.internal.signatures" +COSE_SIGNATURE_TX_TABLE_NAME = "public:ccf.internal.cose_signatures" NODES_TABLE_NAME = "public:ccf.gov.nodes.info" ENDORSED_NODE_CERTIFICATES_TABLE_NAME = "public:ccf.gov.nodes.endorsed_certificates" SERVICE_INFO_TABLE_NAME = "public:ccf.gov.service.info" @@ -389,6 +391,7 @@ def __init__(self, accept_deprecated_entry_types: bool = True): self.last_verified_view = 0 self.service_status = None + self.service_cert = None def last_verified_txid(self) -> TxID: return TxID(self.last_verified_view, self.last_verified_seqno) @@ -509,6 +512,14 @@ def add_transaction(self, transaction): else: assert self.service_status is None, self.service_status self.service_status = updated_status + self.service_cert = updated_service_json["cert"] + + if COSE_SIGNATURE_TX_TABLE_NAME in tables: + cose_signature_table = tables[COSE_SIGNATURE_TX_TABLE_NAME] + cose_signature = cose_signature_table.get(WELL_KNOWN_SINGLETON_TABLE_KEY) + signature = json.loads(cose_signature) + cose_sign1 = base64.b64decode(signature["sig"]) + self._verify_root_cose_signature(self.merkle.get_merkle_root(), cose_sign1) # Checks complete, add this transaction to tree self.merkle.add_leaf(transaction.get_tx_digest(), False) @@ -558,6 +569,18 @@ def _verify_root_signature(self, tx_info: TxBundleInfo): + f"\nRoot: {tx_info.existing_root.hex()}" ) from InvalidSignature + def _verify_root_cose_signature(self, root, cose_sign1): + try: + validate_cose_sign1( + payload=root, cert_pem=self.service_cert, cose_sign1=cose_sign1 + ) + except Exception as exc: + raise InvalidRootCoseSignatureException( + "Signature verification failed:" + + f"\nCertificate: {self.service_cert}" + + f"\nRoot: {root}" + ) from exc + def _verify_merkle_root(self, merkletree: MerkleTree, existing_root: bytes): """Verify item 3, by comparing the roots from the merkle tree that's maintained by this class and from the one extracted from the ledger""" root = merkletree.get_merkle_root() @@ -1061,6 +1084,10 @@ class InvalidRootSignatureException(Exception): """Signature of the MerkleRoot doesn't match with the signature that's reported in the signature's table""" +class InvalidRootCoseSignatureException(Exception): + """COSE signature of the MerkleRoot doesn't pass COSE verification""" + + class CommitIdRangeException(Exception): """Missing ledger chunk in the ledger directory""" From 69fa97dd1ac630fc1d7ebfabac6117626099188b Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 12:11:54 +0000 Subject: [PATCH 18/33] Fix linter --- python/src/ccf/cose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/ccf/cose.py b/python/src/ccf/cose.py index fd01c4816a36..6e339b86c4a0 100644 --- a/python/src/ccf/cose.py +++ b/python/src/ccf/cose.py @@ -174,7 +174,7 @@ def create_cose_sign1_finish( return msg.encode(sign=False) -def validate_cose_sign1(payload: bytes, cert_pem: Pem, cose_sign1: bytes) -> bool: +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") From 45ec6182b0cf6d9b3f1eb6e81f2c9aa26e42a5c1 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 14:17:57 +0000 Subject: [PATCH 19/33] Use correct alg id in signing --- src/crypto/openssl/cose_sign.cpp | 21 +++++++++++++++++++-- src/crypto/openssl/cose_sign.h | 4 +++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 465515267aa7..cc15946f7831 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -75,6 +75,22 @@ namespace // Explicitly leave unprotected headers empty to be an empty map. QCBOREncode_CloseMap(cbor_encode); } + + int get_algorithm_id(ccf::crypto::KeyPair_OpenSSL& key) + { + const auto cid = key.get_curve_id(); + switch (cid) + { + case ccf::crypto::CurveID::SECP256K1: + case ccf::crypto::CurveID::SECP256R1: + return T_COSE_ALGORITHM_ES256; + case ccf::crypto::CurveID::SECP384R1: + return T_COSE_ALGORITHM_ES384; + default: + throw ccf::crypto::COSESignError( + fmt::format("COSE sign: unsupported curve {}", cid)); + } + } } namespace ccf::crypto @@ -125,7 +141,7 @@ namespace ccf::crypto } std::vector cose_sign1( - EVP_PKEY* key, + KeyPair_OpenSSL& key, const std::vector& protected_headers, std::span payload) { @@ -136,7 +152,8 @@ namespace ccf::crypto QCBOREncode_Init(&cbor_encode, signed_cose_buffer); t_cose_sign1_sign_ctx sign_ctx; - t_cose_sign1_sign_init(&sign_ctx, 0, T_COSE_ALGORITHM_ES384); + const int algorithm_id = get_algorithm_id(key); + t_cose_sign1_sign_init(&sign_ctx, 0, algorithm_id); t_cose_key signing_key; signing_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL; diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index d7abb4460bd6..e8b0a1a2497f 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -2,6 +2,8 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "crypto/openssl/key_pair.h" + #include #include #include @@ -65,7 +67,7 @@ namespace ccf::crypto https://www.iana.org/assignments/cose/cose.xhtml#header-parameters. */ std::vector cose_sign1( - EVP_PKEY* key, + KeyPair_OpenSSL& key, const std::vector& protected_headers, std::span payload); } From 105518028e1614a6df928a304d6897d824398352 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 14:18:24 +0000 Subject: [PATCH 20/33] Cache verifier --- src/node/history.h | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/node/history.h b/src/node/history.h index 37627686a123..52311e64f548 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -534,6 +534,8 @@ namespace ccf ccf::crypto::KeyPair& node_kp; std::shared_ptr service_kp{}; + ccf::crypto::COSEVerifierUniquePtr cose_verifier{}; + std::vector cose_cert_cached{}; std::optional<::threading::TaskQueue::TimerEntry> emit_signature_timer_entry = std::nullopt; @@ -753,13 +755,12 @@ namespace ccf return false; } - // TO DO try use cache - auto verifier = - ccf::crypto::make_cose_verifier_from_cert(service_info->cert.raw()); + const auto raw_cert = service_info->cert.raw(); const std::vector root_hash{ root.h.data(), root.h.data() + root.h.size()}; - return verifier->verify_detached(cose_sig->sig, root_hash); + return cose_verifier_cached(raw_cert)->verify_detached( + cose_sig->sig, root_hash); } std::vector serialise_tree(size_t to) override @@ -906,6 +907,19 @@ namespace ccf { endorsed_cert = cert; } + + private: + ccf::crypto::COSEVerifierUniquePtr& cose_verifier_cached( + const std::vector& cert) + { + if (cert != cose_cert_cached) + { + cose_cert_cached = cert; + cose_verifier = + ccf::crypto::make_cose_verifier_from_cert(cose_cert_cached); + } + return cose_verifier; + } }; using MerkleTxHistory = HashedTxHistory; From cbd46f23aa7aa224e5f99f5b96ffb8d558ffb61e Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 18:21:09 +0000 Subject: [PATCH 21/33] Improve alg. verification in cpp code --- src/crypto/openssl/cose_sign.cpp | 26 +++++--- src/crypto/openssl/cose_sign.h | 2 + src/crypto/openssl/cose_verifier.cpp | 89 ++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index cc15946f7831..3c78ec570990 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -75,26 +75,28 @@ namespace // Explicitly leave unprotected headers empty to be an empty map. QCBOREncode_CloseMap(cbor_encode); } +} - int get_algorithm_id(ccf::crypto::KeyPair_OpenSSL& key) +namespace ccf::crypto +{ + + std::optional key_to_cose_alg_id(ccf::crypto::PublicKey_OpenSSL& key) { const auto cid = key.get_curve_id(); switch (cid) { case ccf::crypto::CurveID::SECP256K1: case ccf::crypto::CurveID::SECP256R1: + std::cout << "Return ES256" << std::endl; return T_COSE_ALGORITHM_ES256; case ccf::crypto::CurveID::SECP384R1: + std::cout << "Return ES384" << std::endl; return T_COSE_ALGORITHM_ES384; default: - throw ccf::crypto::COSESignError( - fmt::format("COSE sign: unsupported curve {}", cid)); + return std::nullopt; } } -} -namespace ccf::crypto -{ COSEParametersFactory cose_params_int_int(int64_t key, int64_t value) { const size_t args_size = sizeof(key) + sizeof(value); @@ -152,12 +154,18 @@ namespace ccf::crypto QCBOREncode_Init(&cbor_encode, signed_cose_buffer); t_cose_sign1_sign_ctx sign_ctx; - const int algorithm_id = get_algorithm_id(key); - t_cose_sign1_sign_init(&sign_ctx, 0, algorithm_id); + const auto algorithm_id = key_to_cose_alg_id(key); + if (!algorithm_id.has_value()) + { + throw ccf::crypto::COSESignError(fmt::format("Unsupported key type")); + } + + t_cose_sign1_sign_init(&sign_ctx, 0, *algorithm_id); + EVP_PKEY* evp_key = key; t_cose_key signing_key; signing_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL; - signing_key.k.key_ptr = key; + signing_key.k.key_ptr = evp_key; t_cose_sign1_set_signing_key(&sign_ctx, signing_key, NULL_Q_USEFUL_BUF_C); diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index e8b0a1a2497f..fa6304c90595 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -59,6 +59,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); + /* Sign a cose_sign1 payload with custom protected headers as strings, where - key: integer label to be assigned in a COSE value - value: string behind the label. diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 4eff801df279..d6a4e126250c 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -5,6 +5,7 @@ #include "ccf/crypto/public_key.h" #include "ccf/ds/logger.h" +#include "crypto/openssl/cose_sign.h" #include "crypto/openssl/openssl_wrappers.h" #include "crypto/openssl/rsa_key_pair.h" #include "x509_time.h" @@ -13,8 +14,85 @@ #include #include #include +#include #include +namespace +{ + static std::optional extract_algorithm_from_header( + 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 std::nullopt; + } + + const uint64_t tag = QCBORDecode_GetNthTagOfLast(&ctx, 0); + if (tag != CBOR_TAG_COSE_SIGN1) + { + LOG_DEBUG_FMT("Failed to parse COSE_Sign1 tag"); + return std::nullopt; + } + + struct q_useful_buf_c protected_parameters; + QCBORDecode_EnterBstrWrapped( + &ctx, QCBOR_TAG_REQUIREMENT_NOT_A_TAG, &protected_parameters); + QCBORDecode_EnterMap(&ctx, NULL); + + enum + { + ALG_INDEX, + END_INDEX + }; + QCBORItem header_items[END_INDEX + 1]; + + header_items[ALG_INDEX].label.int64 = ccf::crypto::COSE_PHEADER_KEY_ALG; + header_items[ALG_INDEX].uLabelType = QCBOR_TYPE_INT64; + header_items[ALG_INDEX].uDataType = QCBOR_TYPE_INT64; + + 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 std::nullopt; + } + + if (header_items[ALG_INDEX].uDataType == QCBOR_TYPE_NONE) + { + LOG_DEBUG_FMT("Failed to retrieve (missing) 'alg' parameter"); + return std::nullopt; + } + + const int alg = header_items[ALG_INDEX].val.int64; + + // 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 std::nullopt; + } + + return alg; + } +} + namespace ccf::crypto { using namespace OpenSSL; @@ -96,6 +174,17 @@ namespace ccf::crypto { EVP_PKEY* evp_key = *public_key; + const auto alg_header = extract_algorithm_from_header(buf); + const auto alg_key = ccf::crypto::key_to_cose_alg_id(*public_key); + if (!alg_header || !alg_key || alg_key != alg_header) + { + LOG_DEBUG_FMT( + "COSE Sign1 verification: incompatible key IDS ({} vs {})", + alg_header, + alg_key); + return false; + } + t_cose_key cose_key; cose_key.crypto_lib = T_COSE_CRYPTO_LIB_OPENSSL; cose_key.k.key_ptr = evp_key; From 08a800ca2308096a6474558e4a5936b9b2749c08 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 18:28:16 +0000 Subject: [PATCH 22/33] Format fix --- src/crypto/openssl/cose_sign.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 3c78ec570990..aaaf863e76be 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -79,7 +79,6 @@ namespace namespace ccf::crypto { - std::optional key_to_cose_alg_id(ccf::crypto::PublicKey_OpenSSL& key) { const auto cid = key.get_curve_id(); From d35115e673e854952b9330adb2b5125586ab825b Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 21:36:29 +0000 Subject: [PATCH 23/33] Pass kid as key hash --- src/crypto/openssl/cose_sign.cpp | 12 ++++++++++++ src/crypto/openssl/cose_sign.h | 11 ++++++++--- src/node/history.h | 18 +++++++++++------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index aaaf863e76be..a0a074723627 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -141,6 +141,18 @@ namespace ccf::crypto args_size); } + COSEParametersFactory cose_params_int_bytes( + int64_t key, std::span value) + { + const size_t args_size = sizeof(key) + value.size(); + q_useful_buf_c buf{value.data(), value.size()}; + return COSEParametersFactory( + [=](QCBOREncodeContext* ctx) { + QCBOREncode_AddBytesToMapN(ctx, key, buf); + }, + args_size); + } + std::vector cose_sign1( KeyPair_OpenSSL& key, const std::vector& protected_headers, diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index fa6304c90595..b2fffa5be2cb 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -12,11 +12,13 @@ namespace ccf::crypto { - // Algorithm used to sign, standardatised field + // Standartised field: algorithm used to sign static constexpr int64_t COSE_PHEADER_KEY_ALG = 1; - // Verifiable data structure, standartised field + // Standartised: hash of the signing key + static constexpr int64_t COSE_PHEADER_KEY_ID = 4; + // Standartised: verifiable data structure static constexpr int64_t COSE_PHEADER_KEY_VDS = 395; - // CCF-specifix, last signed TxID + // CCF-specific: last signed TxID static constexpr const char* COSE_PHEADER_KEY_TXID = "ccf.txid"; class COSEParametersFactory @@ -54,6 +56,9 @@ namespace ccf::crypto COSEParametersFactory cose_params_string_string( std::string_view key, std::string_view value); + COSEParametersFactory cose_params_int_bytes( + int64_t key, std::span value); + struct COSESignError : public std::runtime_error { COSESignError(const std::string& msg) : std::runtime_error(msg) {} diff --git a/src/node/history.h b/src/node/history.h index 52311e64f548..6aad45c0e253 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -375,17 +375,21 @@ namespace ccf endorsed_cert); constexpr int64_t vds_merkle_tree = 2; + + const auto& service_key_der = service_kp.public_key_der(); + std::vector kid(SHA256_DIGEST_LENGTH); + SHA256(service_key_der.data(), service_key_der.size(), kid.data()); + const auto pheaders = { - // 1. VDS + // Key digest + ccf::crypto::cose_params_int_bytes( + ccf::crypto::COSE_PHEADER_KEY_ID, kid), + // VDS ccf::crypto::cose_params_int_int( ccf::crypto::COSE_PHEADER_KEY_VDS, vds_merkle_tree), - // 2. TxID + // TxID ccf::crypto::cose_params_string_string( - ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()) - // 3. Key digest - // TO - // DO - }; + ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str())}; auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); signatures->put(sig_value); From 34f21f33995d7bfc0fa9ab1d5e26ffa3f98beb19 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 21:38:39 +0000 Subject: [PATCH 24/33] Update doc --- doc/audit/builtin_maps.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index 45ebb93ec885..399d88315efb 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -476,7 +476,15 @@ Signatures emitted by the primary node at regular interval, over the root of the ``cose_signatures`` ~~~~~~~~~~~~~~ -TO DO +COSE signatures emitted by the primary node over the root of the Merkle Tree at that sequence number. + +**Key** Sentinel value 0, represented as a little-endian 64-bit unsigned integer. + +**Value** + +.. doxygenstruct:: ccf::CoseSignature + :project: CCF + :members: ``recovery_shares`` ~~~~~~~~~~~~~~~~~~~ From 270a646aee1f6f71071390a3f70161d146329fd4 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 21:39:32 +0000 Subject: [PATCH 25/33] Redundant spaces --- doc/audit/builtin_maps.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index 399d88315efb..fec70a37e334 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -399,8 +399,6 @@ Service constitution: JavaScript module, exporting ``validate()``, ``resolve()`` **Value** JavaScript module, represented as a string. - - ``history`` ~~~~~~~~~~~ From 63a30b41b337c1ff3dce64d6f11df4bf33d1e9d0 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Tue, 10 Sep 2024 21:55:56 +0000 Subject: [PATCH 26/33] Long test (removeme) --- .github/workflows/ci.yml | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5a28e8fd59c..78375e79f0aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,3 +93,98 @@ jobs: build/workspace/*/err if-no-files-found: ignore if: success() || failure() + + scan_build: + name: "Scan build" + runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] + container: + image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Run scan" + run: | + set -x + mkdir build + cd build + ../scripts/scan-build.sh + + long-asan: + name: ASAN + runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] + container: + image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Install deps" + run: | + sudo apt-get -y update + sudo apt install ansible -y + cd getting_started/setup_vm + ansible-playbook ccf-extended-testing.yml + + - name: "Build" + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + mkdir build + cd build + cmake -GNinja -DCOMPILE_TARGET=virtual -DCMAKE_BUILD_TYPE=Debug -DLONG_TESTS=ON -DLVI_MITIGATIONS=OFF -DSAN=ON .. + ninja + + - name: "Test" + run: | + set +x + cd build + ./tests.sh --output-on-failure --timeout 1600 -LE "benchmark" + + - name: "Upload logs" + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: logs-asan + path: | + build/workspace/*/*.config.json + build/workspace/*/out + build/workspace/*/err + if-no-files-found: ignore + + long-tsan: + name: TSAN + runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] + container: + image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: "Build" + run: | + git config --global --add safe.directory /__w/CCF/CCF + mkdir build + cd build + cmake -GNinja -DCOMPILE_TARGET=virtual -DCMAKE_BUILD_TYPE=Debug -DLONG_TESTS=ON -DLVI_MITIGATIONS=OFF -DTSAN=ON -DWORKER_THREADS=2 .. + ninja + + - name: "Test" + run: | + set +x + cd build + ./tests.sh --output-on-failure --timeout 1600 -LE "benchmark" + + - name: "Upload logs" + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: logs-tsan + path: | + build/workspace/*/*.config.json + build/workspace/*/out + build/workspace/*/err + if-no-files-found: ignore From 3b9f98641e741c5659157ef8fd5893c9edfc333f Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 12:46:06 +0000 Subject: [PATCH 27/33] Typos and logs --- src/crypto/openssl/cose_sign.cpp | 2 -- src/crypto/openssl/cose_sign.h | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index a0a074723627..2c541c6acedc 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -86,10 +86,8 @@ namespace ccf::crypto { case ccf::crypto::CurveID::SECP256K1: case ccf::crypto::CurveID::SECP256R1: - std::cout << "Return ES256" << std::endl; return T_COSE_ALGORITHM_ES256; case ccf::crypto::CurveID::SECP384R1: - std::cout << "Return ES384" << std::endl; return T_COSE_ALGORITHM_ES384; default: return std::nullopt; diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index b2fffa5be2cb..ff97d4b6d9ee 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -12,11 +12,11 @@ namespace ccf::crypto { - // Standartised field: algorithm used to sign + // Standardised field: algorithm used to sign static constexpr int64_t COSE_PHEADER_KEY_ALG = 1; - // Standartised: hash of the signing key + // Standardised: hash of the signing key static constexpr int64_t COSE_PHEADER_KEY_ID = 4; - // Standartised: verifiable data structure + // 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"; From bd948180b43ad56f2979467b6306667528ead596 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 13:10:54 +0000 Subject: [PATCH 28/33] FIx ASAN --- src/crypto/openssl/cose_sign.cpp | 8 ++++---- src/crypto/openssl/cose_sign.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 2c541c6acedc..71feccba6aa1 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -105,7 +105,7 @@ namespace ccf::crypto } COSEParametersFactory cose_params_int_string( - int64_t key, std::string_view value) + int64_t key, const std::string& value) { const size_t args_size = sizeof(key) + value.size(); return COSEParametersFactory( @@ -116,7 +116,7 @@ namespace ccf::crypto } COSEParametersFactory cose_params_string_int( - std::string_view key, int64_t value) + const std::string& key, int64_t value) { const size_t args_size = key.size() + sizeof(value); return COSEParametersFactory( @@ -128,7 +128,7 @@ namespace ccf::crypto } COSEParametersFactory cose_params_string_string( - std::string_view key, std::string_view value) + const std::string& key, const std::string& value) { const size_t args_size = key.size() + value.size(); return COSEParametersFactory( @@ -140,7 +140,7 @@ namespace ccf::crypto } COSEParametersFactory cose_params_int_bytes( - int64_t key, std::span value) + int64_t key, const std::vector& value) { const size_t args_size = sizeof(key) + value.size(); q_useful_buf_c buf{value.data(), value.size()}; diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index ff97d4b6d9ee..ddf930a865c7 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -48,16 +48,16 @@ namespace ccf::crypto COSEParametersFactory cose_params_int_int(int64_t key, int64_t value); COSEParametersFactory cose_params_int_string( - int64_t key, std::string_view value); + int64_t key, const std::string& value); COSEParametersFactory cose_params_string_int( - std::string_view key, int64_t value); + const std::string& key, int64_t value); COSEParametersFactory cose_params_string_string( - std::string_view key, std::string_view value); + const std::string& key, const std::string& value); COSEParametersFactory cose_params_int_bytes( - int64_t key, std::span value); + int64_t key, const std::vector& value); struct COSESignError : public std::runtime_error { From da541bb898a4407233e2fd9681eb5d167e52c938 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 13:18:45 +0000 Subject: [PATCH 29/33] Remove SECP256K1 support --- src/crypto/openssl/cose_sign.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 71feccba6aa1..97af1ad31f22 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -84,7 +84,6 @@ namespace ccf::crypto const auto cid = key.get_curve_id(); switch (cid) { - case ccf::crypto::CurveID::SECP256K1: case ccf::crypto::CurveID::SECP256R1: return T_COSE_ALGORITHM_ES256; case ccf::crypto::CurveID::SECP384R1: From 77c11dcc33b84331981724fa232d7a1d72352bd4 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 14:06:52 +0000 Subject: [PATCH 30/33] COSE sig as bytes instead of JSON(bytes) --- python/src/ccf/ledger.py | 3 +-- src/node/history.h | 4 ++-- src/service/tables/signatures.h | 12 +----------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/python/src/ccf/ledger.py b/python/src/ccf/ledger.py index 3e4c5cacd525..be605233e7e2 100644 --- a/python/src/ccf/ledger.py +++ b/python/src/ccf/ledger.py @@ -517,8 +517,7 @@ def add_transaction(self, transaction): if COSE_SIGNATURE_TX_TABLE_NAME in tables: cose_signature_table = tables[COSE_SIGNATURE_TX_TABLE_NAME] cose_signature = cose_signature_table.get(WELL_KNOWN_SINGLETON_TABLE_KEY) - signature = json.loads(cose_signature) - cose_sign1 = base64.b64decode(signature["sig"]) + cose_sign1 = base64.b64decode(cose_signature) self._verify_root_cose_signature(self.merkle.get_merkle_root(), cose_sign1) # Checks complete, add this transaction to tree diff --git a/src/node/history.h b/src/node/history.h index 6aad45c0e253..893a9b16d2ef 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -393,7 +393,7 @@ namespace ccf auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); signatures->put(sig_value); - cose_signatures->put(CoseSignature{cose_sign}); + cose_signatures->put(cose_sign); serialised_tree->put(history.serialise_tree(txid.version - 1)); return sig.commit_reserved(); } @@ -764,7 +764,7 @@ namespace ccf root.h.data(), root.h.data() + root.h.size()}; return cose_verifier_cached(raw_cert)->verify_detached( - cose_sig->sig, root_hash); + cose_sig.value(), root_hash); } std::vector serialise_tree(size_t to) override diff --git a/src/service/tables/signatures.h b/src/service/tables/signatures.h index c55175040018..efc300541d0b 100644 --- a/src/service/tables/signatures.h +++ b/src/service/tables/signatures.h @@ -61,17 +61,7 @@ namespace ccf using SerialisedMerkleTree = ccf::kv::RawCopySerialisedValue>; - struct CoseSignature - { - std::vector sig; - - CoseSignature() {} - - CoseSignature(const std::vector& sig_) : sig(sig_) {} - }; - - DECLARE_JSON_TYPE(CoseSignature) - DECLARE_JSON_REQUIRED_FIELDS(CoseSignature, sig); + using CoseSignature = std::vector; // Most recent COSE signature is a single Value in the KV using CoseSignatures = ServiceValue; From bb3adc1d99d4f03769eb7c544c4950ee9969d1fd Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 14:33:06 +0000 Subject: [PATCH 31/33] Improved estimated arg size --- src/crypto/openssl/cose_sign.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 97af1ad31f22..e3719b8a55b0 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -9,6 +9,9 @@ namespace { + static constexpr size_t extra_size_for_int_tag = 1; // type + static constexpr size_t extra_size_for_seq_tag = 1 + 8; // type + size + size_t estimate_buffer_size( const std::vector& protected_headers, std::span payload) @@ -95,7 +98,8 @@ namespace ccf::crypto COSEParametersFactory cose_params_int_int(int64_t key, int64_t value) { - const size_t args_size = sizeof(key) + sizeof(value); + const size_t args_size = sizeof(key) + sizeof(value) + + extra_size_for_int_tag + extra_size_for_int_tag; return COSEParametersFactory( [=](QCBOREncodeContext* ctx) { QCBOREncode_AddInt64ToMapN(ctx, key, value); @@ -106,7 +110,8 @@ namespace ccf::crypto COSEParametersFactory cose_params_int_string( int64_t key, const std::string& value) { - const size_t args_size = sizeof(key) + value.size(); + const size_t args_size = sizeof(key) + value.size() + + extra_size_for_int_tag + extra_size_for_seq_tag; return COSEParametersFactory( [=](QCBOREncodeContext* ctx) { QCBOREncode_AddSZStringToMapN(ctx, key, value.data()); @@ -117,7 +122,8 @@ namespace ccf::crypto COSEParametersFactory cose_params_string_int( const std::string& key, int64_t value) { - const size_t args_size = key.size() + sizeof(value); + const size_t args_size = key.size() + sizeof(value) + + extra_size_for_seq_tag + extra_size_for_int_tag; return COSEParametersFactory( [=](QCBOREncodeContext* ctx) { QCBOREncode_AddSZString(ctx, key.data()); @@ -129,7 +135,8 @@ namespace ccf::crypto COSEParametersFactory cose_params_string_string( const std::string& key, const std::string& value) { - const size_t args_size = key.size() + value.size(); + const size_t args_size = key.size() + value.size() + + extra_size_for_seq_tag + extra_size_for_seq_tag; return COSEParametersFactory( [=](QCBOREncodeContext* ctx) { QCBOREncode_AddSZString(ctx, key.data()); @@ -141,7 +148,8 @@ namespace ccf::crypto COSEParametersFactory cose_params_int_bytes( int64_t key, const std::vector& value) { - const size_t args_size = sizeof(key) + value.size(); + const size_t args_size = sizeof(key) + value.size() + + +extra_size_for_int_tag + extra_size_for_seq_tag; q_useful_buf_c buf{value.data(), value.size()}; return COSEParametersFactory( [=](QCBOREncodeContext* ctx) { From a668cf89cab71c46dcda59c627386c969d4d601c Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 15:27:53 +0000 Subject: [PATCH 32/33] Revert "Long test (removeme)" This reverts commit 63a30b41b337c1ff3dce64d6f11df4bf33d1e9d0. --- .github/workflows/ci.yml | 95 ---------------------------------------- 1 file changed, 95 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78375e79f0aa..e5a28e8fd59c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,98 +93,3 @@ jobs: build/workspace/*/err if-no-files-found: ignore if: success() || failure() - - scan_build: - name: "Scan build" - runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] - container: - image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: "Run scan" - run: | - set -x - mkdir build - cd build - ../scripts/scan-build.sh - - long-asan: - name: ASAN - runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] - container: - image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: "Install deps" - run: | - sudo apt-get -y update - sudo apt install ansible -y - cd getting_started/setup_vm - ansible-playbook ccf-extended-testing.yml - - - name: "Build" - run: | - git config --global --add safe.directory "$GITHUB_WORKSPACE" - mkdir build - cd build - cmake -GNinja -DCOMPILE_TARGET=virtual -DCMAKE_BUILD_TYPE=Debug -DLONG_TESTS=ON -DLVI_MITIGATIONS=OFF -DSAN=ON .. - ninja - - - name: "Test" - run: | - set +x - cd build - ./tests.sh --output-on-failure --timeout 1600 -LE "benchmark" - - - name: "Upload logs" - if: success() || failure() - uses: actions/upload-artifact@v4 - with: - name: logs-asan - path: | - build/workspace/*/*.config.json - build/workspace/*/out - build/workspace/*/err - if-no-files-found: ignore - - long-tsan: - name: TSAN - runs-on: [self-hosted, 1ES.Pool=gha-virtual-ccf-sub] - container: - image: ghcr.io/microsoft/ccf/ci/default:build-25-07-2024 - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: "Build" - run: | - git config --global --add safe.directory /__w/CCF/CCF - mkdir build - cd build - cmake -GNinja -DCOMPILE_TARGET=virtual -DCMAKE_BUILD_TYPE=Debug -DLONG_TESTS=ON -DLVI_MITIGATIONS=OFF -DTSAN=ON -DWORKER_THREADS=2 .. - ninja - - - name: "Test" - run: | - set +x - cd build - ./tests.sh --output-on-failure --timeout 1600 -LE "benchmark" - - - name: "Upload logs" - if: success() || failure() - uses: actions/upload-artifact@v4 - with: - name: logs-tsan - path: | - build/workspace/*/*.config.json - build/workspace/*/out - build/workspace/*/err - if-no-files-found: ignore From e90e33f02cf921368e6019d36f30e72dbceceab7 Mon Sep 17 00:00:00 2001 From: Max Tropets Date: Wed, 11 Sep 2024 16:23:17 +0000 Subject: [PATCH 33/33] Cose as JSON in python parser --- python/src/ccf/ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/src/ccf/ledger.py b/python/src/ccf/ledger.py index be605233e7e2..48c1a207e08d 100644 --- a/python/src/ccf/ledger.py +++ b/python/src/ccf/ledger.py @@ -517,7 +517,8 @@ def add_transaction(self, transaction): if COSE_SIGNATURE_TX_TABLE_NAME in tables: cose_signature_table = tables[COSE_SIGNATURE_TX_TABLE_NAME] cose_signature = cose_signature_table.get(WELL_KNOWN_SINGLETON_TABLE_KEY) - cose_sign1 = base64.b64decode(cose_signature) + signature = json.loads(cose_signature) + cose_sign1 = base64.b64decode(signature) self._verify_root_cose_signature(self.merkle.get_merkle_root(), cose_sign1) # Checks complete, add this transaction to tree