Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COSE receipts: CPP API #6480

Merged
merged 18 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Provided API for getting COSE signatures and Merkle proofs (#6477).
- Exposed COSE signature in historical API via `TxReceiptImpl`.
- Introduced `ccf::describe_merkle_proof_v1(receipt)` for Merkle proof construction in CBOR format.
- Added COSE signatures over the Merkle root to the KV (#6449).
- Signing is done with service key (different from raw signatures, which remain unchanged and are still signed by the node key).
- New signature reside in `public:ccf.internal.cose_signatures`.
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,7 @@ if(BUILD_TESTS)
)
target_link_libraries(
historical_queries_test PRIVATE http_parser.host sss.host ccf_kv.host
ccf_endpoints.host
)
# Temporarily disabled flaky test
# https://github.com/microsoft/CCF/issues/4403 add_unit_test( indexing_test
Expand Down
10 changes: 10 additions & 0 deletions include/ccf/receipt.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ namespace ccf
nlohmann::json describe_receipt_v1(const TxReceiptImpl& receipt);
ReceiptPtr describe_receipt_v2(const TxReceiptImpl& receipt);

enum MerkleProofLabel : int64_t
{
// Values TBD:
// https://github.com/ietf-scitt/draft-birkholz-cose-cometre-ccf-profile
MERKLE_PROOF_LEAF_LABEL = 404,
MERKLE_PROOF_PATH_LABEL = 405
};
std::optional<std::vector<uint8_t>> describe_merkle_proof_v1(
const TxReceiptImpl& in);

// Manual JSON serializers are specified for these types as they are not
// trivial POD structs

Expand Down
19 changes: 12 additions & 7 deletions src/crypto/openssl/cose_sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ namespace
QCBOREncodeContext* cbor_encode,
const std::vector<ccf::crypto::COSEParametersFactory>& protected_headers)
{
QCBOREncode_AddTag(cbor_encode, CBOR_TAG_COSE_SIGN1);
QCBOREncode_OpenArray(cbor_encode);

encode_protected_headers(me, cbor_encode, protected_headers);

QCBOREncode_OpenMap(cbor_encode);
Expand Down Expand Up @@ -164,7 +161,8 @@ namespace ccf::crypto
std::span<const uint8_t> payload)
{
const auto buf_size = estimate_buffer_size(protected_headers, payload);
Q_USEFUL_BUF_MAKE_STACK_UB(signed_cose_buffer, buf_size);
std::vector<uint8_t> underlying_buffer(buf_size);
q_useful_buf signed_cose_buffer{underlying_buffer.data(), buf_size};

QCBOREncodeContext cbor_encode;
QCBOREncode_Init(&cbor_encode, signed_cose_buffer);
Expand All @@ -185,6 +183,9 @@ namespace ccf::crypto

t_cose_sign1_set_signing_key(&sign_ctx, signing_key, NULL_Q_USEFUL_BUF_C);

QCBOREncode_AddTag(&cbor_encode, CBOR_TAG_COSE_SIGN1);
QCBOREncode_OpenArray(&cbor_encode);

encode_parameters_custom(&sign_ctx, &cbor_encode, protected_headers);

// Mark empty payload manually.
Expand Down Expand Up @@ -216,8 +217,12 @@ namespace ccf::crypto
fmt::format("Can't finish QCBOR encoding with error code {}", err));
}

return {
static_cast<const uint8_t*>(signed_cose.ptr),
static_cast<const uint8_t*>(signed_cose.ptr) + signed_cose.len};
// Memory address is said to match:
// github.com/laurencelundblade/QCBOR/blob/v1.4.1/inc/qcbor/qcbor_encode.h#L2190-L2191
assert(signed_cose.ptr == underlying_buffer.data());

underlying_buffer.resize(signed_cose.len);
underlying_buffer.shrink_to_fit();
return underlying_buffer;
}
}
13 changes: 12 additions & 1 deletion src/node/historical_queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ namespace ccf::historical
return signatures->get();
}

static std::optional<ccf::CoseSignature> get_cose_signature(
const ccf::kv::StorePtr& sig_store)
{
auto tx = sig_store->create_read_only_tx();
auto signatures = tx.ro<ccf::CoseSignatures>(ccf::Tables::COSE_SIGNATURES);
return signatures->get();
}

static std::optional<std::vector<uint8_t>> get_tree(
const ccf::kv::StorePtr& sig_store)
{
Expand Down Expand Up @@ -430,6 +438,7 @@ namespace ccf::historical
// Iterate through earlier indices. If this signature covers them
// then create a receipt for them
const auto sig = get_signature(sig_details->store);
const auto cose_sig = get_cose_signature(sig_details->store);
ccf::MerkleTreeHistory tree(get_tree(sig_details->store).value());

// This is either pointing at the sig itself, or the closest larger
Expand All @@ -453,6 +462,7 @@ namespace ccf::historical
details->transaction_id = {sig->view, seqno};
details->receipt = std::make_shared<TxReceiptImpl>(
sig->sig,
cose_sig,
proof.get_root(),
proof.get_path(),
sig->node,
Expand Down Expand Up @@ -735,10 +745,11 @@ namespace ccf::historical
// the receipt _later_ for an already-fetched signature
// transaction.
const auto sig = get_signature(details->store);
const auto cose_sig = get_cose_signature(details->store);
assert(sig.has_value());
details->transaction_id = {sig->view, sig->seqno};
details->receipt = std::make_shared<TxReceiptImpl>(
sig->sig, sig->root.h, nullptr, sig->node, sig->cert);
sig->sig, cose_sig, sig->root.h, nullptr, sig->node, sig->cert);
}

auto request_it = requests.begin();
Expand Down
97 changes: 97 additions & 0 deletions src/node/historical_queries_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,51 @@
#include "node/rpc/network_identity_subsystem.h"
#include "node/tx_receipt_impl.h"

#include <t_cose/t_cose_sign1_sign.h>

namespace
{
void encode_leaf_cbor(
QCBOREncodeContext& ctx, const ccf::TxReceiptImpl& receipt)
{
QCBOREncode_OpenArrayInMapN(
&ctx, ccf::MerkleProofLabel::MERKLE_PROOF_LEAF_LABEL);

// 1 WSD
const auto& wsd = receipt.write_set_digest->h;
QCBOREncode_AddBytes(&ctx, {wsd.data(), wsd.size()});

// 2. CE
const auto& ce = receipt.commit_evidence.value();
QCBOREncode_AddSZString(&ctx, ce.data());

// 3. CD
const auto& cd = receipt.claims_digest.value().h;
QCBOREncode_AddBytes(&ctx, {cd.data(), cd.size()});

QCBOREncode_CloseArray(&ctx);
}

void encode_path_cbor(
QCBOREncodeContext& ctx, const ccf::HistoryTree::Path& path)
{
QCBOREncode_OpenArrayInMapN(
&ctx, ccf::MerkleProofLabel::MERKLE_PROOF_PATH_LABEL);
for (const auto& node : path)
{
const int64_t dir =
(node.direction == ccf::HistoryTree::Path::Direction::PATH_LEFT);
std::vector<uint8_t> hash{node.hash};

QCBOREncode_OpenArray(&ctx);
QCBOREncode_AddInt64(&ctx, dir);
QCBOREncode_AddBytes(&ctx, {hash.data(), hash.size()});
QCBOREncode_CloseArray(&ctx);
}
QCBOREncode_CloseArray(&ctx);
}
}

namespace ccf
{
nlohmann::json describe_receipt_v1(const TxReceiptImpl& receipt)
Expand Down Expand Up @@ -153,6 +198,58 @@ namespace ccf

return receipt;
}

std::optional<std::vector<uint8_t>> describe_merkle_proof_v1(
const TxReceiptImpl& receipt)
{
constexpr size_t buf_size = 2048;
std::vector<uint8_t> underlying_buffer(buf_size);
q_useful_buf buffer{underlying_buffer.data(), buf_size};

QCBOREncodeContext ctx;
QCBOREncode_Init(&ctx, buffer);

QCBOREncode_BstrWrap(&ctx);
QCBOREncode_OpenMap(&ctx);

if (!receipt.commit_evidence)
{
LOG_DEBUG_FMT("Merkle proof is missing commit evidence");
return std::nullopt;
}
if (!receipt.write_set_digest)
{
LOG_DEBUG_FMT("Merkle proof is missing write set digest");
return std::nullopt;
}
encode_leaf_cbor(ctx, receipt);

if (!receipt.path)
{
LOG_DEBUG_FMT("Merkle proof is missing path");
return std::nullopt;
}
encode_path_cbor(ctx, *receipt.path);

QCBOREncode_CloseMap(&ctx);
QCBOREncode_CloseBstrWrap2(&ctx, false, nullptr);

struct q_useful_buf_c result;
auto qerr = QCBOREncode_Finish(&ctx, &result);
if (qerr)
{
LOG_DEBUG_FMT("Failed to encode merkle proof: {}", qerr);
return std::nullopt;
}

// Memory address is said to match:
// github.com/laurencelundblade/QCBOR/blob/v1.4.1/inc/qcbor/qcbor_encode.h#L2190-L2191
assert(result.ptr == underlying_buffer.data());

underlying_buffer.resize(result.len);
underlying_buffer.shrink_to_fit();
return underlying_buffer;
}
}

namespace ccf::historical
Expand Down
1 change: 1 addition & 0 deletions src/node/snapshot_serdes.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ namespace ccf
cd.set(std::move(claims_digest));
ccf::TxReceiptImpl tx_receipt(
sig,
std::nullopt, // cose
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
proof.get_root(),
proof.get_path(),
node_id,
Expand Down
Loading