Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- In the C++ API, the method `get_txid()` on `ccf::kv::ReadOnlyStore` has been renamed to `current_txid()`. This may affect historical query code which works directly with the returned `StorePtr` (#7477).
- The C++ API for installing endpoints with local commit handlers has changed. These handlers should now be added to an `Endpoint` with `.set_locally_committed_function(handler)`, and the `make_[read_only_]endpoint_with_local_commit_handler` methods on `EndpointRegistry` have been removed (#7487).
- The format of CCF's stdout logging has changed. Each line previously tried to align host logs with enclave logs containing a timestamp offset. Since enclave logs no longer exist, this timestamp is never present, so the padding whitespace has been removed (#7491).
- Introduced `ccf::historical::verify_self_issued_receipt` to verify COSE CCF receipts against current service identity (#7494).

## [7.0.0-dev5]

Expand Down
40 changes: 16 additions & 24 deletions doc/schemas/app_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1227,30 +1227,6 @@
}
}
},
"/app/log/public/cbor_merkle_proof": {
"get": {
"operationId": "GetAppLogPublicCborMerkleProof",
"responses": {
"204": {
"description": "Default response description"
},
"default": {
"$ref": "#/components/responses/default"
}
},
"security": [
{
"jwt": []
},
{
"user_cose_sign1": []
}
],
"x-ccf-forwarding": {
"$ref": "#/components/x-ccf-forwarding/never"
}
}
},
"/app/log/public/cose_endorsements": {
"get": {
"operationId": "GetAppLogPublicCoseEndorsements",
Expand Down Expand Up @@ -1466,6 +1442,22 @@
}
}
},
"/app/log/public/verify_cose_receipt": {
"get": {
"operationId": "GetAppLogPublicVerifyCoseReceipt",
"responses": {
"204": {
"description": "Default response description"
},
"default": {
"$ref": "#/components/responses/default"
}
},
"x-ccf-forwarding": {
"$ref": "#/components/x-ccf-forwarding/never"
}
}
},
"/app/log/request_query": {
"get": {
"operationId": "GetAppLogRequestQuery",
Expand Down
9 changes: 8 additions & 1 deletion include/ccf/historical_queries_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ namespace ccf::historical
ccf::kv::ReadOnlyTx& tx,
ccf::historical::StatePtr& state,
AbstractStateCache& state_cache);
}

// Verifies CCF COSE receipt using the *current network* identity's
// certificate.
void verify_self_issued_receipt(
const std::vector<uint8_t>& cose_receipt,
std::shared_ptr<NetworkIdentitySubsystemInterface>
network_identity_subsystem);
}
89 changes: 56 additions & 33 deletions samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2013,39 +2013,6 @@ namespace loggingapp
.set_auto_schema<void, std::string>()
.install();

auto get_cbor_merkle_proof =
[](
ccf::endpoints::ReadOnlyEndpointContext& ctx,
ccf::historical::StatePtr historical_state) {
auto historical_tx = historical_state->store->create_read_only_tx();

assert(historical_state->receipt);
auto cbor_proof =
describe_merkle_proof_v1(*historical_state->receipt);
if (!cbor_proof.has_value())
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_NOT_FOUND,
ccf::errors::ResourceNotFound,
"No merkle proof available for this transaction");
return;
}
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_body(std::move(cbor_proof.value()));
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::CBOR);
};
make_read_only_endpoint(
"/log/public/cbor_merkle_proof",
HTTP_GET,
ccf::historical::read_only_adapter_v4(
get_cbor_merkle_proof, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, void>()
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();

auto get_cose_endorsements =
[](
ccf::endpoints::ReadOnlyEndpointContext& ctx,
Expand Down Expand Up @@ -2162,6 +2129,62 @@ namespace loggingapp
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();

auto verify_cose_receipt =
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
const auto* const expected =
ccf::http::headervalues::contenttype::COSE;
const auto actual =
ctx.rpc_ctx->get_request_header(ccf::http::headers::CONTENT_TYPE)
.value_or("");
if (expected != actual)
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
ccf::errors::InvalidHeaderValue,
fmt::format(
"Expected content-type '{}'. Got '{}'.", expected, actual));
return;
}

const std::vector<uint8_t>& receipt = ctx.rpc_ctx->get_request_body();

auto network_identity_subsystem =
context.get_subsystem<ccf::NetworkIdentitySubsystemInterface>();
if (network_identity_subsystem == nullptr)
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Network identity subsystem not available");
return;
}

try
{
ccf::historical::verify_self_issued_receipt(
receipt, network_identity_subsystem);
}
catch (const std::exception& e)
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::InvalidInput,
fmt::format("COSE receipt verification failed: {}", e.what()));
return;
}

ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
};

make_read_only_endpoint(
"/log/public/verify_cose_receipt",
HTTP_GET,
verify_cose_receipt,
ccf::no_auth_required)
.set_auto_schema<void, void>()
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();

auto get_cose_signatures_config =
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
auto subsystem =
Expand Down
1 change: 1 addition & 0 deletions src/crypto/openssl/cose_sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace ccf::crypto
static constexpr int64_t COSE_PHEADER_KEY_CWT = 15;
// Standardised: verifiable data structure.
static constexpr int64_t COSE_PHEADER_KEY_VDS = 395;
static constexpr int64_t COSE_PHEADER_VDS_MERKLE_TREE = 2;
// Standardised: issued at CWT claim. Value is **PLAIN INTEGER**, as per
// https://www.rfc-editor.org/rfc/rfc8392#section-2. Quote:
/* The "NumericDate" term in this specification has the same meaning and
Expand Down
39 changes: 39 additions & 0 deletions src/crypto/test/cose.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "ccf/crypto/cose.h"

#include "ccf/ds/hex.h"
#include "crypto/openssl/cose_sign.h"
#include "crypto/openssl/cose_verifier.h"
#include "node/cose_common.h"

#include <cstdint>
#include <doctest/doctest.h>
Expand Down Expand Up @@ -274,4 +276,41 @@ TEST_CASE("Check unprotected header")
err = QCBORDecode_Finish(&ctx);
}
}
}

TEST_CASE("Decode CCF COSE receipt")
{
const std::string receipt_hex =
"d284588ca50138220458403464393230653531646339303636373336653433333738636131"
"34323863656165306435343335326634306535316232306564633863366237633536316430"
"3519018b020fa3061a692875730173736572766963652e6578616d706c652e636f6d02706c"
"65646765722e7369676e6174757265666363662e7631a1647478696464322e3137a119018c"
"a1208158b7a201835820e2a97fad0c69119d6e216158b762b19277a579d7a89047d98aa37f"
"152f194a92784863653a322e31363a38633765646230386135323963613237326166623062"
"31653664613939306233636137336665313064336535663462356633663231613561346638"
"37663637635820000000000000000000000000000000000000000000000000000000000000"
"0000028182f55820d774c9dfeec96478a0797f8ce3d78464767833d052fb78d72b2b8eeda5"
"21215af658604568ff2c93350fa181bf02186b26d3f04728a61fd2ef2c9388a55268ed8bf7"
"88a6bd06bfa195c78676bebeef5560a87980e8dd13725a87ef0b00ac0b78ff07ab7eb4646a"
"4a54b421456d14e90b7dea1f0b32044bf93116d85ef0834f493681d5";

const auto receipt_bytes = ccf::ds::from_hex(receipt_hex);

auto receipt =
ccf::cose::decode_ccf_receipt(receipt_bytes, /*recompute_root*/ true);

REQUIRE(receipt.phdr.alg == -35);
REQUIRE(
ccf::ds::to_hex(receipt.phdr.kid) ==
"34643932306535316463393036363733366534333337386361313432386365616530643534"
"333532663430653531623230656463386336623763353631643035");
REQUIRE(receipt.phdr.cwt.iat == 1764259187);
REQUIRE(receipt.phdr.cwt.iss == "service.example.com");
REQUIRE(receipt.phdr.cwt.sub == "ledger.signature");
REQUIRE(receipt.phdr.ccf.txid == "2.17");
REQUIRE(receipt.phdr.vds == 2);

REQUIRE(
ccf::ds::to_hex(receipt.merkle_root) ==
"209f5aefb0f45d7647c917337044c44a1b848fe833fa2869d016bea797d79a9e");
}
Loading
Loading