Skip to content

Commit 2f13c2f

Browse files
committed
WIP
1 parent 52751f1 commit 2f13c2f

File tree

9 files changed

+670
-64
lines changed

9 files changed

+670
-64
lines changed

CHANGELOG.md

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

1920
## [7.0.0-dev5]
2021

doc/schemas/app_openapi.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,30 @@
14661466
}
14671467
}
14681468
},
1469+
"/app/log/public/verify_cose_receipt": {
1470+
"get": {
1471+
"operationId": "GetAppLogPublicVerifyCoseReceipt",
1472+
"responses": {
1473+
"204": {
1474+
"description": "Default response description"
1475+
},
1476+
"default": {
1477+
"$ref": "#/components/responses/default"
1478+
}
1479+
},
1480+
"security": [
1481+
{
1482+
"jwt": []
1483+
},
1484+
{
1485+
"user_cose_sign1": []
1486+
}
1487+
],
1488+
"x-ccf-forwarding": {
1489+
"$ref": "#/components/x-ccf-forwarding/never"
1490+
}
1491+
}
1492+
},
14691493
"/app/log/request_query": {
14701494
"get": {
14711495
"operationId": "GetAppLogRequestQuery",

include/ccf/historical_queries_utils.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,10 @@ 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 certificate.
45+
void verify_cose_receipt(
46+
const std::vector<uint8_t>& cose_receipt,
47+
std::shared_ptr<NetworkIdentitySubsystemInterface>
48+
network_identity_subsystem);
49+
}

samples/apps/logging/logging.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,62 @@ namespace loggingapp
21622162
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
21632163
.install();
21642164

2165+
auto verify_cose_receipt =
2166+
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
2167+
const auto* const expected =
2168+
ccf::http::headervalues::contenttype::COSE;
2169+
const auto actual =
2170+
ctx.rpc_ctx->get_request_header(ccf::http::headers::CONTENT_TYPE)
2171+
.value_or("");
2172+
if (expected != actual)
2173+
{
2174+
ctx.rpc_ctx->set_error(
2175+
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
2176+
ccf::errors::InvalidHeaderValue,
2177+
fmt::format(
2178+
"Expected content-type '{}'. Got '{}'.", expected, actual));
2179+
return;
2180+
}
2181+
2182+
const std::vector<uint8_t>& receipt = ctx.rpc_ctx->get_request_body();
2183+
2184+
auto network_identity_subsystem =
2185+
context.get_subsystem<ccf::NetworkIdentitySubsystemInterface>();
2186+
if (network_identity_subsystem == nullptr)
2187+
{
2188+
ctx.rpc_ctx->set_error(
2189+
HTTP_STATUS_INTERNAL_SERVER_ERROR,
2190+
ccf::errors::InternalError,
2191+
"Network identity subsystem not available");
2192+
return;
2193+
}
2194+
2195+
try
2196+
{
2197+
ccf::historical::verify_cose_receipt(
2198+
receipt, network_identity_subsystem);
2199+
}
2200+
catch (const std::exception& e)
2201+
{
2202+
ctx.rpc_ctx->set_error(
2203+
HTTP_STATUS_BAD_REQUEST,
2204+
ccf::errors::InvalidInput,
2205+
fmt::format("COSE receipt verification failed: {}", e.what()));
2206+
return;
2207+
}
2208+
2209+
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
2210+
};
2211+
2212+
make_read_only_endpoint(
2213+
"/log/public/verify_cose_receipt",
2214+
HTTP_GET,
2215+
verify_cose_receipt,
2216+
auth_policies)
2217+
.set_auto_schema<void, void>()
2218+
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
2219+
.install();
2220+
21652221
auto get_cose_signatures_config =
21662222
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
21672223
auto subsystem =

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)