From b08724a000b3f2c0c3d3fe04f836e7367130ca09 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Sep 2024 18:18:49 +0100 Subject: [PATCH] COSE signatures over merkle root in the ledger (#6453) --- doc/audit/builtin_maps.rst | 13 +++ include/ccf/crypto/cose_verifier.h | 2 + python/src/ccf/cose.py | 15 ++++ python/src/ccf/ledger.py | 27 ++++++ src/crypto/openssl/cose_sign.cpp | 112 +++++++++++++++++++---- src/crypto/openssl/cose_sign.h | 56 +++++++++++- src/crypto/openssl/cose_verifier.cpp | 122 +++++++++++++++++++++++++ src/crypto/openssl/cose_verifier.h | 3 + src/crypto/test/crypto.cpp | 104 ++++++++++++++------- src/kv/deserialise.h | 31 ++++--- src/kv/kv_types.h | 6 +- src/node/history.h | 129 ++++++++++++++++++++++----- src/node/identity.h | 16 +++- src/node/node_state.h | 6 ++ src/node/test/historical_queries.cpp | 4 + src/node/test/history.cpp | 46 +++++++--- src/node/test/history_bench.cpp | 8 +- src/node/test/receipt.cpp | 1 + src/node/test/snapshot.cpp | 5 ++ src/node/test/snapshotter.cpp | 10 +-- src/service/tables/signatures.h | 7 ++ 21 files changed, 612 insertions(+), 111 deletions(-) diff --git a/doc/audit/builtin_maps.rst b/doc/audit/builtin_maps.rst index 615038354d32..fec70a37e334 100644 --- a/doc/audit/builtin_maps.rst +++ b/doc/audit/builtin_maps.rst @@ -471,6 +471,19 @@ Signatures emitted by the primary node at regular interval, over the root of the :project: CCF :members: +``cose_signatures`` +~~~~~~~~~~~~~~ + +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`` ~~~~~~~~~~~~~~~~~~~ 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/python/src/ccf/cose.py b/python/src/ccf/cose.py index 0ee965737e60..6e339b86c4a0 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): + 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..48c1a207e08d 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) + 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""" diff --git a/src/crypto/openssl/cose_sign.cpp b/src/crypto/openssl/cose_sign.cpp index 6ff3d49cfa7e..e3719b8a55b0 100644 --- a/src/crypto/openssl/cose_sign.cpp +++ b/src/crypto/openssl/cose_sign.cpp @@ -6,15 +6,14 @@ #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. + 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 ccf::crypto::COSEProtectedHeaders& protected_headers, + const std::vector& protected_headers, std::span payload) { size_t result = @@ -28,8 +27,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 +37,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 +45,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 +67,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 +82,85 @@ namespace 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::SECP256R1: + return T_COSE_ALGORITHM_ES256; + case ccf::crypto::CurveID::SECP384R1: + return T_COSE_ALGORITHM_ES384; + default: + return std::nullopt; + } + } + + COSEParametersFactory cose_params_int_int(int64_t key, int64_t 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); + }, + args_size); + } + + COSEParametersFactory cose_params_int_string( + int64_t key, const std::string& value) + { + 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()); + }, + args_size); + } + + COSEParametersFactory cose_params_string_int( + const std::string& key, int64_t 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()); + QCBOREncode_AddInt64(ctx, value); + }, + args_size); + } + + COSEParametersFactory cose_params_string_string( + const std::string& key, const std::string& value) + { + 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()); + QCBOREncode_AddSZString(ctx, value.data()); + }, + args_size); + } + + COSEParametersFactory cose_params_int_bytes( + int64_t key, const std::vector& value) + { + 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) { + QCBOREncode_AddBytesToMapN(ctx, key, buf); + }, + args_size); + } + std::vector cose_sign1( - EVP_PKEY* key, - const COSEProtectedHeaders& protected_headers, + KeyPair_OpenSSL& key, + const std::vector& protected_headers, std::span payload) { const auto buf_size = estimate_buffer_size(protected_headers, payload); @@ -95,11 +170,18 @@ 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); + 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 f41dc3388fcd..ddf930a865c7 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -2,19 +2,69 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "crypto/openssl/key_pair.h" + #include #include #include +#include #include namespace ccf::crypto { + // Standardised field: algorithm used to sign + static constexpr int64_t COSE_PHEADER_KEY_ALG = 1; + // Standardised: hash of the signing key + static constexpr int64_t COSE_PHEADER_KEY_ID = 4; + // 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"; + + 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_int(int64_t key, int64_t value); + + COSEParametersFactory cose_params_int_string( + int64_t key, const std::string& value); + + COSEParametersFactory cose_params_string_int( + const std::string& key, int64_t value); + + COSEParametersFactory cose_params_string_string( + const std::string& key, const std::string& value); + + COSEParametersFactory cose_params_int_bytes( + int64_t key, const std::vector& value); + struct COSESignError : public std::runtime_error { COSESignError(const std::string& msg) : std::runtime_error(msg) {} }; - using COSEProtectedHeaders = std::unordered_map; + 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 @@ -24,7 +74,7 @@ namespace ccf::crypto https://www.iana.org/assignments/cose/cose.xhtml#header-parameters. */ std::vector cose_sign1( - EVP_PKEY* key, - const COSEProtectedHeaders& protected_headers, + KeyPair_OpenSSL& key, + const std::vector& protected_headers, std::span payload); } diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index bde1348863f5..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; @@ -91,6 +169,50 @@ namespace ccf::crypto return false; } + bool COSEVerifier_OpenSSL::verify_detached( + std::span buf, std::span payload) const + { + 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; + + 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/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index e1f31d5737de..c5c7b1c0afbf 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -226,7 +226,10 @@ t_cose_err_t verify_detached( } void require_match_headers( - const std::unordered_map& headers, + 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()}; @@ -244,39 +247,56 @@ 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[5 + 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_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_INT64; + + 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[++curr_id].uLabelType = QCBOR_TYPE_NONE; + header_items[5].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(header_items[1].val.int64, *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(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(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); @@ -1224,8 +1244,11 @@ 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_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")}; auto cose_sign = cose_sign1(*kp, protected_headers, payload); if constexpr (false) // enable to see the whole cose_sign as byte string @@ -1242,17 +1265,30 @@ TEST_CASE("COSE sign & verify") std::cout << std::endl; } - require_match_headers(protected_headers, cose_sign); + require_match_headers( + {35, 53}, + {36, "thirsty six"}, + {"hungry seven", 47}, + {"string key", "string value"}, + cose_sign); + + auto cose_verifier = + ccf::crypto::make_cose_verifier_from_key(kp->public_key_pem()); - REQUIRE_EQ(verify_detached(*kp, cose_sign, payload), T_COSE_SUCCESS); + 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, {}, {}); - require_match_headers({}, cose_sign); - REQUIRE_EQ(verify_detached(*kp, cose_sign, {}), T_COSE_SUCCESS); + require_match_headers( + {35, std::nullopt}, + {36, std::nullopt}, + {"hungry seven", std::nullopt}, + {"string key", std::nullopt}, + cose_sign); + + REQUIRE(cose_verifier->verify_detached(cose_sign, {})); } diff --git a/src/kv/deserialise.h b/src/kv/deserialise.h index f3c4e8855cd0..dd62c127aa42 100644 --- a/src/kv/deserialise.h +++ b/src/kv/deserialise.h @@ -123,21 +123,32 @@ namespace ccf::kv auto search = changes.find(ccf::Tables::SIGNATURES); if (search != changes.end()) { - // Transactions containing a signature must only contain the signature - // and the serialised Merkle tree and must be verified - if ( - changes.size() > 2 || - changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) == 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) { - 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 fbad8485a76a..0be236c901e2 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" @@ -402,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; @@ -428,6 +428,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 89f2fee6c816..893a9b16d2ef 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -2,10 +2,14 @@ // 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" #include "ds/thread_messaging.h" #include "endian.h" #include "kv/kv_types.h" @@ -105,10 +109,14 @@ 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); PrimarySignature sig_value(id, txid.version); signatures->put(sig_value); + cose_signatures->put(ccf::CoseSignature{}); serialised_tree->put({}); return sig.commit_reserved(); } @@ -152,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; } @@ -195,6 +203,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)); @@ -312,7 +326,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: @@ -321,13 +336,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_) {} @@ -336,13 +353,17 @@ 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(); 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 = node_kp.sign_hash(root_hash.data(), root_hash.size()); PrimarySignature sig_value( id, @@ -353,7 +374,26 @@ namespace ccf primary_sig, 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 = { + // 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), + // TxID + ccf::crypto::cose_params_string_string( + ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str())}; + auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash); + signatures->put(sig_value); + cose_signatures->put(cose_sign); serialised_tree->put(history.serialise_tree(txid.version - 1)); return sig.commit_reserved(); } @@ -496,7 +536,10 @@ namespace ccf NodeId id; T replicated_state_tree; - ccf::crypto::KeyPair& kp; + 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; @@ -513,13 +556,13 @@ namespace ccf HashedTxHistory( ccf::kv::Store& store_, const NodeId& id_, - ccf::crypto::KeyPair& kp_, + ccf::crypto::KeyPair& node_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_), sig_tx_interval(sig_tx_interval_), sig_ms_interval(sig_ms_interval_) { @@ -529,6 +572,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 @@ -659,7 +708,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; } @@ -667,16 +716,15 @@ 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; } - 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(); @@ -685,20 +733,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; + } + + const auto raw_cert = service_info->cert.raw(); + const std::vector root_hash{ + root.h.data(), root.h.data() + root.h.size()}; + + return cose_verifier_cached(raw_cert)->verify_detached( + cose_sig.value(), root_hash); } std::vector serialise_tree(size_t to) override @@ -783,10 +849,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, kp, endorsed_cert.value()), + txid, store, *this, id, node_kp, *service_kp, endorsed_cert.value()), true); } @@ -839,6 +911,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; 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 ed85d2cf819f..5671f313a0e3 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -505,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, @@ -540,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}; } @@ -654,6 +658,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()) { 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 b512e6b69864..e9d03376ef0a 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -74,16 +74,20 @@ 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); 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); @@ -91,12 +95,14 @@ 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); 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); ccf::Nodes nodes(ccf::Tables::NODES); + ccf::Service service(ccf::Tables::SERVICE); ccf::Signatures signatures(ccf::Tables::SIGNATURES); std::shared_ptr consensus = @@ -107,14 +113,19 @@ 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); 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); + + 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); } @@ -139,29 +150,35 @@ 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); 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); 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); 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); ccf::Nodes nodes(ccf::Tables::NODES); + ccf::Service service(ccf::Tables::SERVICE); std::shared_ptr consensus = std::make_shared(&backup_store); @@ -170,14 +187,19 @@ 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); 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); + + 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); } diff --git a/src/node/test/history_bench.cpp b/src/node/test/history_bench.cpp index 8fad0325b23f..a340280bc125 100644 --- a/src/node/test/history_bench.cpp +++ b/src/node/test/history_bench.cpp @@ -72,7 +72,7 @@ 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(); std::shared_ptr consensus = std::make_shared(); @@ -80,7 +80,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); store.set_history(history); std::vector> txs; @@ -111,7 +111,7 @@ 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(); std::shared_ptr consensus = std::make_shared(); @@ -119,7 +119,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); store.set_history(history); std::vector> txs; diff --git a/src/node/test/receipt.cpp b/src/node/test/receipt.cpp index 063ee06ceb6f..1ab323db5eb6 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 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); } diff --git a/src/node/test/snapshotter.cpp b/src/node/test/snapshotter.cpp index 914fdd3a3957..56fe1fe45c1f 100644 --- a/src/node/test/snapshotter.cpp +++ b/src/node/test/snapshotter.cpp @@ -21,7 +21,7 @@ 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(); using StringString = ccf::kv::Map; using rb_msg = std::pair; @@ -142,7 +142,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); network.tables->set_history(history); network.tables->initialise_term(2); network.tables->set_consensus(consensus); @@ -305,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, *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); @@ -436,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, *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); @@ -502,7 +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, *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(); diff --git a/src/service/tables/signatures.h b/src/service/tables/signatures.h index 665fe56cee78..efc300541d0b 100644 --- a/src/service/tables/signatures.h +++ b/src/service/tables/signatures.h @@ -61,9 +61,16 @@ namespace ccf using SerialisedMerkleTree = ccf::kv::RawCopySerialisedValue>; + using CoseSignature = std::vector; + + // 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