Skip to content

Commit 96c71e6

Browse files
committed
Merge remote-tracking branch 'origin/main' into final_tidy
2 parents 27c9785 + 1d929c0 commit 96c71e6

File tree

11 files changed

+704
-140
lines changed

11 files changed

+704
-140
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2121
- 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).
2222
- 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).
2323
- 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).
24+
- Introduced `ccf::historical::verify_self_issued_receipt` to verify COSE CCF receipts against current service identity (#7494).
2425

2526
## [7.0.0-dev5]
2627

doc/schemas/app_openapi.json

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,30 +1227,6 @@
12271227
}
12281228
}
12291229
},
1230-
"/app/log/public/cbor_merkle_proof": {
1231-
"get": {
1232-
"operationId": "GetAppLogPublicCborMerkleProof",
1233-
"responses": {
1234-
"204": {
1235-
"description": "Default response description"
1236-
},
1237-
"default": {
1238-
"$ref": "#/components/responses/default"
1239-
}
1240-
},
1241-
"security": [
1242-
{
1243-
"jwt": []
1244-
},
1245-
{
1246-
"user_cose_sign1": []
1247-
}
1248-
],
1249-
"x-ccf-forwarding": {
1250-
"$ref": "#/components/x-ccf-forwarding/never"
1251-
}
1252-
}
1253-
},
12541230
"/app/log/public/cose_endorsements": {
12551231
"get": {
12561232
"operationId": "GetAppLogPublicCoseEndorsements",
@@ -1466,6 +1442,22 @@
14661442
}
14671443
}
14681444
},
1445+
"/app/log/public/verify_cose_receipt": {
1446+
"get": {
1447+
"operationId": "GetAppLogPublicVerifyCoseReceipt",
1448+
"responses": {
1449+
"204": {
1450+
"description": "Default response description"
1451+
},
1452+
"default": {
1453+
"$ref": "#/components/responses/default"
1454+
}
1455+
},
1456+
"x-ccf-forwarding": {
1457+
"$ref": "#/components/x-ccf-forwarding/never"
1458+
}
1459+
}
1460+
},
14691461
"/app/log/request_query": {
14701462
"get": {
14711463
"operationId": "GetAppLogRequestQuery",

include/ccf/historical_queries_utils.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,11 @@ namespace ccf::historical
4040
ccf::kv::ReadOnlyTx& tx,
4141
ccf::historical::StatePtr& state,
4242
AbstractStateCache& state_cache);
43-
}
43+
44+
// Verifies CCF COSE receipt using the *current network* identity's
45+
// certificate.
46+
void verify_self_issued_receipt(
47+
const std::vector<uint8_t>& cose_receipt,
48+
std::shared_ptr<NetworkIdentitySubsystemInterface>
49+
network_identity_subsystem);
50+
}

samples/apps/logging/logging.cpp

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,39 +2013,6 @@ namespace loggingapp
20132013
.set_auto_schema<void, std::string>()
20142014
.install();
20152015

2016-
auto get_cbor_merkle_proof =
2017-
[](
2018-
ccf::endpoints::ReadOnlyEndpointContext& ctx,
2019-
ccf::historical::StatePtr historical_state) {
2020-
auto historical_tx = historical_state->store->create_read_only_tx();
2021-
2022-
assert(historical_state->receipt);
2023-
auto cbor_proof =
2024-
describe_merkle_proof_v1(*historical_state->receipt);
2025-
if (!cbor_proof.has_value())
2026-
{
2027-
ctx.rpc_ctx->set_error(
2028-
HTTP_STATUS_NOT_FOUND,
2029-
ccf::errors::ResourceNotFound,
2030-
"No merkle proof available for this transaction");
2031-
return;
2032-
}
2033-
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
2034-
ctx.rpc_ctx->set_response_body(std::move(cbor_proof.value()));
2035-
ctx.rpc_ctx->set_response_header(
2036-
ccf::http::headers::CONTENT_TYPE,
2037-
ccf::http::headervalues::contenttype::CBOR);
2038-
};
2039-
make_read_only_endpoint(
2040-
"/log/public/cbor_merkle_proof",
2041-
HTTP_GET,
2042-
ccf::historical::read_only_adapter_v4(
2043-
get_cbor_merkle_proof, context, is_tx_committed),
2044-
auth_policies)
2045-
.set_auto_schema<void, void>()
2046-
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
2047-
.install();
2048-
20492016
auto get_cose_endorsements =
20502017
[](
20512018
ccf::endpoints::ReadOnlyEndpointContext& ctx,
@@ -2162,6 +2129,62 @@ namespace loggingapp
21622129
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
21632130
.install();
21642131

2132+
auto verify_cose_receipt =
2133+
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
2134+
const auto* const expected =
2135+
ccf::http::headervalues::contenttype::COSE;
2136+
const auto actual =
2137+
ctx.rpc_ctx->get_request_header(ccf::http::headers::CONTENT_TYPE)
2138+
.value_or("");
2139+
if (expected != actual)
2140+
{
2141+
ctx.rpc_ctx->set_error(
2142+
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
2143+
ccf::errors::InvalidHeaderValue,
2144+
fmt::format(
2145+
"Expected content-type '{}'. Got '{}'.", expected, actual));
2146+
return;
2147+
}
2148+
2149+
const std::vector<uint8_t>& receipt = ctx.rpc_ctx->get_request_body();
2150+
2151+
auto network_identity_subsystem =
2152+
context.get_subsystem<ccf::NetworkIdentitySubsystemInterface>();
2153+
if (network_identity_subsystem == nullptr)
2154+
{
2155+
ctx.rpc_ctx->set_error(
2156+
HTTP_STATUS_INTERNAL_SERVER_ERROR,
2157+
ccf::errors::InternalError,
2158+
"Network identity subsystem not available");
2159+
return;
2160+
}
2161+
2162+
try
2163+
{
2164+
ccf::historical::verify_self_issued_receipt(
2165+
receipt, network_identity_subsystem);
2166+
}
2167+
catch (const std::exception& e)
2168+
{
2169+
ctx.rpc_ctx->set_error(
2170+
HTTP_STATUS_BAD_REQUEST,
2171+
ccf::errors::InvalidInput,
2172+
fmt::format("COSE receipt verification failed: {}", e.what()));
2173+
return;
2174+
}
2175+
2176+
ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
2177+
};
2178+
2179+
make_read_only_endpoint(
2180+
"/log/public/verify_cose_receipt",
2181+
HTTP_GET,
2182+
verify_cose_receipt,
2183+
ccf::no_auth_required)
2184+
.set_auto_schema<void, void>()
2185+
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
2186+
.install();
2187+
21652188
auto get_cose_signatures_config =
21662189
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
21672190
auto subsystem =

src/crypto/openssl/cose_sign.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace ccf::crypto
2020
static constexpr int64_t COSE_PHEADER_KEY_CWT = 15;
2121
// Standardised: verifiable data structure.
2222
static constexpr int64_t COSE_PHEADER_KEY_VDS = 395;
23+
static constexpr int64_t COSE_PHEADER_VDS_CCF_LEDGER_SHA256 = 2;
2324
// Standardised: issued at CWT claim. Value is **PLAIN INTEGER**, as per
2425
// https://www.rfc-editor.org/rfc/rfc8392#section-2. Quote:
2526
/* The "NumericDate" term in this specification has the same meaning and

src/crypto/test/cose.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
44
#include "ccf/crypto/cose.h"
55

6+
#include "ccf/ds/hex.h"
67
#include "crypto/openssl/cose_sign.h"
78
#include "crypto/openssl/cose_verifier.h"
9+
#include "node/cose_common.h"
810

911
#include <cstdint>
1012
#include <doctest/doctest.h>
@@ -274,4 +276,41 @@ TEST_CASE("Check unprotected header")
274276
err = QCBORDecode_Finish(&ctx);
275277
}
276278
}
279+
}
280+
281+
TEST_CASE("Decode CCF COSE receipt")
282+
{
283+
const std::string receipt_hex =
284+
"d284588ca50138220458403464393230653531646339303636373336653433333738636131"
285+
"34323863656165306435343335326634306535316232306564633863366237633536316430"
286+
"3519018b020fa3061a692875730173736572766963652e6578616d706c652e636f6d02706c"
287+
"65646765722e7369676e6174757265666363662e7631a1647478696464322e3137a119018c"
288+
"a1208158b7a201835820e2a97fad0c69119d6e216158b762b19277a579d7a89047d98aa37f"
289+
"152f194a92784863653a322e31363a38633765646230386135323963613237326166623062"
290+
"31653664613939306233636137336665313064336535663462356633663231613561346638"
291+
"37663637635820000000000000000000000000000000000000000000000000000000000000"
292+
"0000028182f55820d774c9dfeec96478a0797f8ce3d78464767833d052fb78d72b2b8eeda5"
293+
"21215af658604568ff2c93350fa181bf02186b26d3f04728a61fd2ef2c9388a55268ed8bf7"
294+
"88a6bd06bfa195c78676bebeef5560a87980e8dd13725a87ef0b00ac0b78ff07ab7eb4646a"
295+
"4a54b421456d14e90b7dea1f0b32044bf93116d85ef0834f493681d5";
296+
297+
const auto receipt_bytes = ccf::ds::from_hex(receipt_hex);
298+
299+
auto receipt =
300+
ccf::cose::decode_ccf_receipt(receipt_bytes, /*recompute_root*/ true);
301+
302+
REQUIRE(receipt.phdr.alg == -35);
303+
REQUIRE(
304+
ccf::ds::to_hex(receipt.phdr.kid) ==
305+
"34643932306535316463393036363733366534333337386361313432386365616530643534"
306+
"333532663430653531623230656463386336623763353631643035");
307+
REQUIRE(receipt.phdr.cwt.iat == 1764259187);
308+
REQUIRE(receipt.phdr.cwt.iss == "service.example.com");
309+
REQUIRE(receipt.phdr.cwt.sub == "ledger.signature");
310+
REQUIRE(receipt.phdr.ccf.txid == "2.17");
311+
REQUIRE(receipt.phdr.vds == 2);
312+
313+
REQUIRE(
314+
ccf::ds::to_hex(receipt.merkle_root) ==
315+
"209f5aefb0f45d7647c917337044c44a1b848fe833fa2869d016bea797d79a9e");
277316
}

0 commit comments

Comments
 (0)