Skip to content
Draft
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
5 changes: 5 additions & 0 deletions doc/host_config_schema/cchost_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@
"subject": {
"type": "string",
"description": "Subject, set in CWT_Claims of COSE ledger signatures. Can only be set once on service start."
},
"cose_only_ledger": {
"type": "boolean",
"default": false,
"description": "Only COSE-sign Merkle tree root"
}
}
},
Expand Down
4 changes: 3 additions & 1 deletion include/ccf/node/cose_signatures_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ namespace ccf
{
std::string issuer;
std::string subject;
bool cose_only_ledger = false;

bool operator==(const COSESignaturesConfig& other) const = default;
};

DECLARE_JSON_TYPE(COSESignaturesConfig);
DECLARE_JSON_REQUIRED_FIELDS(COSESignaturesConfig, issuer, subject);
DECLARE_JSON_REQUIRED_FIELDS(
COSESignaturesConfig, issuer, subject, cose_only_ledger);
}
7 changes: 5 additions & 2 deletions samples/config/start_config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"network": {
"node_to_node_interface": { "bind_address": "127.0.0.1:8081" },
"node_to_node_interface": {
"bind_address": "127.0.0.1:8081"
},
"rpc_interfaces": {
"primary_rpc_interface": {
"bind_address": "127.0.0.1:8080",
Expand Down Expand Up @@ -49,7 +51,8 @@
"service_subject_name": "CN=A Sample CCF Service",
"cose_signatures": {
"issuer": "service.example.com",
"subject": "ledger.signature"
"subject": "ledger.signature",
"cose_only_ledger": true
}
}
},
Expand Down
40 changes: 40 additions & 0 deletions src/endpoints/common_endpoint_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ccf/http_query.h"
#include "ccf/json_handler.h"
#include "ccf/node_context.h"
#include "ccf/receipt.h"
#include "ccf/service/tables/code_id.h"
#include "ccf/service/tables/host_data.h"
#include "ccf/service/tables/snp_measurements.h"
Expand Down Expand Up @@ -285,6 +286,45 @@ namespace ccf
"A signed statement from the service over a transaction entry in the "
"ledger")
.install();

auto get_cose_receipt =
[](
auto& ctx,
ccf::historical::StatePtr
historical_state) { // NOLINT(performance-unnecessary-value-param)
assert(historical_state->receipt);
auto cose_receipt =
ccf::describe_cose_receipt_v1(*historical_state->receipt);
if (!cose_receipt.has_value())
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_NOT_FOUND,
ccf::errors::ResourceNotFound,
"No COSE receipt available for this transaction.");
return;
}

ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::COSE);
ctx.rpc_ctx->set_response_body(*cose_receipt);
};

make_read_only_endpoint(
"/receipt/cose",
HTTP_GET,
ccf::historical::read_only_adapter_v4(
get_cose_receipt, context, is_tx_committed, txid_from_query_string),
no_auth_required)
.set_auto_schema<void, void>()
.add_query_parameter<ccf::TxID>(tx_id_param_key)
.set_openapi_summary("COSE receipt for a transaction")
.set_openapi_description(
"A COSE Sign1 envelope containing a signed statement from the "
"service over a transaction entry in the ledger, with a Merkle "
"proof in the unprotected header.")
.install();
}

void CommonEndpointRegistry::api_endpoint(
Expand Down
4 changes: 2 additions & 2 deletions src/kv/deserialise.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ namespace ccf::kv
}
auto success = ApplyResult::PASS;

auto search = changes.find(ccf::Tables::SIGNATURES);
auto search = changes.find(ccf::Tables::COSE_SIGNATURES);
if (search != changes.end())
{
switch (changes.size())
Expand All @@ -146,7 +146,7 @@ namespace ccf::kv
if (
changes.find(ccf::Tables::SERIALISED_MERKLE_TREE) !=
changes.end() &&
changes.find(ccf::Tables::COSE_SIGNATURES) != changes.end())
changes.find(ccf::Tables::SIGNATURES) != changes.end())
{
break;
}
Expand Down
14 changes: 14 additions & 0 deletions src/kv/test/kv_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,7 @@ TEST_CASE("Deserialise return status")
store.set_encryptor(encryptor);

ccf::Signatures signatures(ccf::Tables::SIGNATURES);
ccf::CoseSignatures cose_signatures(ccf::Tables::COSE_SIGNATURES);
ccf::SerialisedMerkleTree serialised_tree(
ccf::Tables::SERIALISED_MERKLE_TREE);

Expand Down Expand Up @@ -2292,9 +2293,12 @@ TEST_CASE("Deserialise return status")
{
auto tx = store.create_reserved_tx(store.next_txid());
auto sig_handle = tx.rw(signatures);
auto cose_sig_handle = tx.rw(cose_signatures);
auto tree_handle = tx.rw(serialised_tree);
ccf::PrimarySignature sigv(ccf::kv::test::PrimaryNodeId, 2);
sig_handle->put(sigv);
ccf::CoseSignature cose_sigv;
cose_sig_handle->put(cose_sigv);
tree_handle->put({});
auto [success_, data_, claims_digest, commit_evidence_digest, hooks] =
tx.commit_reserved();
Expand All @@ -2310,9 +2314,12 @@ TEST_CASE("Deserialise return status")
{
auto tx = store.create_reserved_tx(store.next_txid());
auto sig_handle = tx.rw(signatures);
auto cose_sig_handle = tx.rw(cose_signatures);
auto data_handle = tx.rw(data);
ccf::PrimarySignature sigv(ccf::kv::test::PrimaryNodeId, 2);
sig_handle->put(sigv);
ccf::CoseSignature cose_sigv;
cose_sig_handle->put(cose_sigv);
data_handle->put(43, 43);
auto [success_, data_, claims_digest, commit_evidence_digest, hooks] =
tx.commit_reserved();
Expand Down Expand Up @@ -3363,6 +3370,7 @@ TEST_CASE("Ledger entry chunk request")
store.set_consensus(consensus);

ccf::Signatures signatures(ccf::Tables::SIGNATURES);
ccf::CoseSignatures cose_signatures(ccf::Tables::COSE_SIGNATURES);
ccf::SerialisedMerkleTree serialised_tree(
ccf::Tables::SERIALISED_MERKLE_TREE);

Expand Down Expand Up @@ -3442,9 +3450,12 @@ TEST_CASE("Ledger entry chunk request")
auto txid = store.next_txid();
auto tx = store.create_reserved_tx(txid);
auto sig_handle = tx.rw(signatures);
auto cose_sig_handle = tx.rw(cose_signatures);
auto tree_handle = tx.rw(serialised_tree);
ccf::PrimarySignature sigv(ccf::kv::test::PrimaryNodeId, txid.seqno);
sig_handle->put(sigv);
ccf::CoseSignature cose_sigv;
cose_sig_handle->put(cose_sigv);
tree_handle->put({});
auto [success_, data_, claims_digest, commit_evidence_digest, hooks] =
tx.commit_reserved();
Expand Down Expand Up @@ -3518,9 +3529,12 @@ TEST_CASE("Ledger entry chunk request")

// Add the signature
auto sig_handle = tx.rw(signatures);
auto cose_sig_handle = tx.rw(cose_signatures);
auto tree_handle = tx.rw(serialised_tree);
ccf::PrimarySignature sigv(ccf::kv::test::PrimaryNodeId, txid.seqno);
sig_handle->put(sigv);
ccf::CoseSignature cose_sigv;
cose_sig_handle->put(cose_sigv);
tree_handle->put({});
auto [success_, data_, claims_digest, commit_evidence_digest, hooks] =
tx.commit_reserved();
Expand Down
14 changes: 8 additions & 6 deletions src/node/gov/handlers/acks.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "ccf/base_endpoint_registry.h"
#include "node/gov/api_version.h"
#include "node/gov/handlers/helpers.h"
#include "node/history.h"
#include "node/share_manager.h"
#include "service/internal_tables_access.h"

Expand Down Expand Up @@ -124,11 +125,11 @@ namespace ccf::gov::endpoints
ack = ack_opt.value();
}

// Get signature, containing merkle root state digest
auto sigs_handle =
ctx.tx.template ro<ccf::Signatures>(Tables::SIGNATURES);
auto sig = sigs_handle->get();
if (!sig.has_value())
// Get merkle root state digest from serialised merkle tree
auto tree_handle = ctx.tx.template ro<ccf::SerialisedMerkleTree>(
Tables::SERIALISED_MERKLE_TREE);
auto tree = tree_handle->get();
if (!tree.has_value())
{
detail::set_gov_error(
ctx.rpc_ctx,
Expand All @@ -137,9 +138,10 @@ namespace ccf::gov::endpoints
"Service has no signatures to ack yet - try again soon.");
return;
}
ccf::MerkleTreeHistory history(tree.value());

// Write ack back to the KV
ack.state_digest = sig->root.hex_str();
ack.state_digest = history.get_root().hex_str();
acks_handle->put(member_id, ack);

auto body = nlohmann::json::object();
Expand Down
68 changes: 56 additions & 12 deletions src/node/historical_queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "consensus/ledger_enclave_types.h"
#include "ds/ccf_assert.h"
#include "kv/store.h"
#include "node/cose_common.h"
#include "node/encryptor.h"
#include "node/history.h"
#include "node/ledger_secrets.h"
Expand Down Expand Up @@ -505,12 +506,11 @@ 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);
if (!sig.has_value())
const auto cose_sig = get_cose_signature(sig_details->store);
if (!cose_sig.has_value())
{
return false;
}
const auto cose_sig = get_cose_signature(sig_details->store);
const auto serialised_tree = get_tree(sig_details->store);
if (!serialised_tree.has_value())
{
Expand All @@ -535,15 +535,36 @@ namespace ccf::historical
auto details = search_rit->second;
if (details != nullptr && details->store != nullptr)
{
auto sig = get_signature(sig_details->store);
std::optional<std::vector<uint8_t>> sig_bytes{std::nullopt};
std::optional<ccf::crypto::Pem> sig_cert{std::nullopt};
ccf::NodeId sig_node{};
if (sig.has_value())
{
sig_bytes = sig->sig;
sig_node = sig->node;
sig_cert = sig->cert;
}

auto proof = tree.get_proof(seqno);
details->transaction_id = {sig->view, seqno};
auto cose_receipt =
ccf::cose::decode_ccf_receipt(cose_sig.value(), false);
auto parsed_txid =
ccf::TxID::from_str(cose_receipt.phdr.ccf.txid);
if (!parsed_txid.has_value())
{
throw std::logic_error(fmt::format(
"Cannot parse CCF TxID: {}", cose_receipt.phdr.ccf.txid));
}

details->transaction_id = {parsed_txid->view, seqno};
details->receipt = std::make_shared<TxReceiptImpl>(
sig->sig,
sig_bytes,
cose_sig,
proof.get_root(),
proof.get_path(),
sig->node,
sig->cert,
sig_node,
sig_cert,
details->entry_digest,
details->get_commit_evidence(),
details->claims_digest);
Expand Down Expand Up @@ -821,13 +842,36 @@ namespace ccf::historical
// the state to do so already, and it's simpler than constructing
// 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);
if (sig.has_value())
if (cose_sig.has_value())
{
details->transaction_id = {sig->view, sig->seqno};
details->receipt = std::make_shared<TxReceiptImpl>(
sig->sig, cose_sig, sig->root.h, nullptr, sig->node, sig->cert);
auto receipt = ccf::cose::decode_ccf_receipt(cose_sig.value(), false);
const auto& txid = receipt.phdr.ccf.txid;
auto parsed_txid = ccf::TxID::from_str(txid);

if (!parsed_txid.has_value())
{
throw std::logic_error(
fmt::format("Cannot parse CCF TxID: {}", txid));
}
details->transaction_id = parsed_txid.value();

const auto sig = get_signature(details->store);
if (sig.has_value())
{
details->receipt = std::make_shared<TxReceiptImpl>(
sig->sig, cose_sig, sig->root.h, nullptr, sig->node, sig->cert);
}
else
{
details->receipt = std::make_shared<TxReceiptImpl>(
std::nullopt,
cose_sig,
std::nullopt,
nullptr,
ccf::NodeId{},
std::nullopt);
}
}
}

Expand Down
Loading
Loading