зеркало из https://github.com/microsoft/CCF.git
Add API to allow setting unprotected headers (#6586)
Co-authored-by: Max <maxtropets@gmail.com>
This commit is contained in:
Родитель
da1b2adbd5
Коммит
1bf76ddd7e
|
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
### Changed
|
||||
|
||||
- Set VMPL value when creating SNP attestations, and check VMPL value is in guest range when verifiying attestation, since recent [updates allow host-initiated attestations](https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/56860.pdf) (#6583).
|
||||
- Added ccf::cose::edit::set_unprotected_header() API, to allow easy injection of proofs in signatures, and of receipts in signed statements (#6586).
|
||||
|
||||
## [6.0.0-dev2]
|
||||
|
||||
|
|
|
@ -864,6 +864,13 @@ if(BUILD_TESTS)
|
|||
)
|
||||
target_link_libraries(base64_test PRIVATE ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
add_unit_test(
|
||||
cose_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/cose.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
cose_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto.host qcbor.host
|
||||
)
|
||||
|
||||
add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp)
|
||||
target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
ccf-cose-root-signature-tagged = #6.18(ccf-cose-root-signature)
|
||||
|
||||
ccf-cose-root-signature = [
|
||||
phdr : bstr .cbor protected-headers, ; bstr-wrapped protected headers
|
||||
uhdr : unprotected-headers, ; unwrappeed (plain map) unprotected headers
|
||||
payload : nil, ; signed Merkle tree root hash, *detached* payload
|
||||
signature : bstr ; COSE-signature
|
||||
]
|
||||
|
||||
unprotected-headers = {
|
||||
&(vdp: 396) => verifiable-proofs
|
||||
}
|
||||
|
||||
inclusion-proofs = [ + bstr .cbor ccf-inclusion-proof ]
|
||||
|
||||
verifiable-proofs = {
|
||||
&(inclusion-proof: -1) => inclusion-proofs
|
||||
}
|
||||
|
||||
protected-headers = {
|
||||
&(alg: 1) => int, ; signing algoritm ID, as per RFC8152
|
||||
&(kid: 4) => bstr, ; signing key hash
|
||||
&(cwt: 15) => cwt-map, ; CWT claims, as per RFC8392
|
||||
&(vds: 395) => int, ; verifiable data structure, as per COSE Receipts (draft) RFC (https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/)
|
||||
"ccf.v1" => ccf-map ; a set of CCF-specific parameters
|
||||
}
|
||||
|
||||
cwt-map = {
|
||||
&(iat: 6) => int ; "issued at", number of seconds since the epoch
|
||||
}
|
||||
|
||||
ccf-map = {
|
||||
&(last-signed-txid: "txid") => tstr ; last committed transaction ID this COSE-signature signs
|
||||
}
|
||||
|
||||
ccf-inclusion-proof = {
|
||||
&(leaf: 1) => ccf-leaf
|
||||
&(path: 2) => [+ ccf-proof-element]
|
||||
}
|
||||
|
||||
ccf-leaf = [
|
||||
internal-transaction-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes
|
||||
internal-evidence: tstr .size (1..1024) ; a string of at most 1024 bytes
|
||||
data-hash: bstr .size 32 ; a string of HASH_SIZE(32) bytes
|
||||
]
|
||||
|
||||
ccf-proof-element = [
|
||||
left: bool ; position of the element
|
||||
hash: bstr .size 32 ; hash of the proof element (string of HASH_SIZE(32) bytes)
|
||||
]
|
|
@ -15,6 +15,7 @@ set(CCFCRYPTO_SRC
|
|||
${CCF_DIR}/src/crypto/hmac.cpp
|
||||
${CCF_DIR}/src/crypto/pem.cpp
|
||||
${CCF_DIR}/src/crypto/ecdsa.cpp
|
||||
${CCF_DIR}/src/crypto/cose.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/symmetric_key.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/public_key.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/key_pair.cpp
|
||||
|
|
|
@ -158,3 +158,9 @@ HTTP Entity Tags Matching
|
|||
.. doxygenclass:: ccf::http::Matcher
|
||||
:project: CCF
|
||||
:members:
|
||||
|
||||
COSE
|
||||
----
|
||||
|
||||
.. doxygenfunction:: ccf::cose::edit::set_unprotected_header
|
||||
:project: CCF
|
|
@ -295,7 +295,7 @@
|
|||
"info": {
|
||||
"description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.",
|
||||
"title": "CCF Sample Logging App",
|
||||
"version": "2.4.3"
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
@ -1273,6 +1273,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/app/log/public/cose_receipt": {
|
||||
"get": {
|
||||
"operationId": "GetAppLogPublicCoseReceipt",
|
||||
"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_signature": {
|
||||
"get": {
|
||||
"operationId": "GetAppLogPublicCoseSignature",
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace ccf::cose::edit
|
||||
{
|
||||
namespace pos
|
||||
{
|
||||
struct InArray
|
||||
{};
|
||||
|
||||
struct AtKey
|
||||
{
|
||||
int64_t key;
|
||||
};
|
||||
|
||||
using Type = std::variant<InArray, AtKey>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unprotected header of a COSE_Sign1 message, to a map containing
|
||||
* @p key and depending on the value of @p position, either an array
|
||||
* containing
|
||||
* @p value, or a map with key @p subkey and value @p value.
|
||||
*
|
||||
* Useful to add a proof to a signature to turn it into a receipt, or to
|
||||
* add a receipt to a signed statement to turn it into a transparent
|
||||
* statement.
|
||||
*
|
||||
* @param cose_input The COSE_Sign1 message to edit.
|
||||
* @param key The key at which to insert either an array or a map.
|
||||
* @param position Either InArray or AtKey, to determine whether to insert an
|
||||
* array or a map.
|
||||
*
|
||||
* @return The COSE_Sign1 message with the new unprotected header.
|
||||
*/
|
||||
std::vector<uint8_t> set_unprotected_header(
|
||||
const std::span<const uint8_t>& cose_input,
|
||||
int64_t key,
|
||||
pos::Type position,
|
||||
const std::vector<uint8_t> value);
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
// CCF
|
||||
#include "ccf/app_interface.h"
|
||||
#include "ccf/common_auth_policies.h"
|
||||
#include "ccf/crypto/cose.h"
|
||||
#include "ccf/crypto/verifier.h"
|
||||
#include "ccf/ds/hash.h"
|
||||
#include "ccf/endpoints/authentication/all_of_auth.h"
|
||||
|
@ -458,7 +459,7 @@ namespace loggingapp
|
|||
"recording messages at client-specified IDs. It demonstrates most of "
|
||||
"the features available to CCF apps.";
|
||||
|
||||
openapi_info.document_version = "2.4.3";
|
||||
openapi_info.document_version = "2.5.0";
|
||||
|
||||
index_per_public_key = std::make_shared<RecordsIndexingStrategy>(
|
||||
PUBLIC_RECORDS, context, 10000, 20);
|
||||
|
@ -2038,6 +2039,53 @@ namespace loggingapp
|
|||
.set_auto_schema<void, LoggingGetCoseSignature::Out>()
|
||||
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
|
||||
.install();
|
||||
|
||||
auto get_cose_receipt = [this](
|
||||
ccf::endpoints::ReadOnlyEndpointContext& ctx,
|
||||
ccf::historical::StatePtr historical_state) {
|
||||
auto historical_tx = historical_state->store->create_read_only_tx();
|
||||
|
||||
assert(historical_state->receipt);
|
||||
auto signature = describe_cose_signature_v1(*historical_state->receipt);
|
||||
if (!signature.has_value())
|
||||
{
|
||||
ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_NOT_FOUND,
|
||||
ccf::errors::ResourceNotFound,
|
||||
"No COSE signature available for this transaction");
|
||||
return;
|
||||
}
|
||||
auto proof = describe_merkle_proof_v1(*historical_state->receipt);
|
||||
if (!proof.has_value())
|
||||
{
|
||||
ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_NOT_FOUND,
|
||||
ccf::errors::ResourceNotFound,
|
||||
"No merkle proof available for this transaction");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t vdp = 396;
|
||||
auto inclusion_proof = ccf::cose::edit::pos::AtKey{-1};
|
||||
|
||||
auto cose_receipt = ccf::cose::edit::set_unprotected_header(
|
||||
*signature, vdp, inclusion_proof, *proof);
|
||||
|
||||
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(
|
||||
"/log/public/cose_receipt",
|
||||
HTTP_GET,
|
||||
ccf::historical::read_only_adapter_v4(
|
||||
get_cose_receipt, context, is_tx_committed),
|
||||
auth_policies)
|
||||
.set_auto_schema<void, void>()
|
||||
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
|
||||
.install();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/crypto/cose.h"
|
||||
|
||||
#include <optional>
|
||||
#include <qcbor/qcbor_decode.h>
|
||||
#include <qcbor/qcbor_encode.h>
|
||||
#include <qcbor/qcbor_spiffy_decode.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ccf::cose::edit
|
||||
{
|
||||
std::vector<uint8_t> set_unprotected_header(
|
||||
const std::span<const uint8_t>& cose_input,
|
||||
int64_t key,
|
||||
pos::Type pos,
|
||||
const std::vector<uint8_t> value)
|
||||
{
|
||||
UsefulBufC buf{cose_input.data(), cose_input.size()};
|
||||
|
||||
QCBORError err;
|
||||
QCBORDecodeContext ctx;
|
||||
QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL);
|
||||
|
||||
size_t pos_start = 0;
|
||||
size_t pos_end = 0;
|
||||
|
||||
QCBORDecode_EnterArray(&ctx, nullptr);
|
||||
err = QCBORDecode_GetError(&ctx);
|
||||
if (err != QCBOR_SUCCESS)
|
||||
{
|
||||
throw std::logic_error("Failed to parse COSE_Sign1 outer array");
|
||||
}
|
||||
|
||||
auto tag = QCBORDecode_GetNthTagOfLast(&ctx, 0);
|
||||
if (tag != CBOR_TAG_COSE_SIGN1)
|
||||
{
|
||||
throw std::logic_error("Failed to parse COSE_Sign1 tag");
|
||||
}
|
||||
|
||||
QCBORItem item;
|
||||
err = QCBORDecode_GetNext(&ctx, &item);
|
||||
if (err != QCBOR_SUCCESS || item.uDataType != QCBOR_TYPE_BYTE_STRING)
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Failed to parse COSE_Sign1 protected header as bstr");
|
||||
}
|
||||
UsefulBufC phdr = {item.val.string.ptr, item.val.string.len};
|
||||
|
||||
// Skip unprotected header
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
|
||||
err = QCBORDecode_PartialFinish(&ctx, &pos_start);
|
||||
if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED)
|
||||
{
|
||||
throw std::logic_error("Failed to find start of payload");
|
||||
}
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
err = QCBORDecode_PartialFinish(&ctx, &pos_end);
|
||||
if (err != QCBOR_ERR_ARRAY_OR_MAP_UNCONSUMED)
|
||||
{
|
||||
throw std::logic_error("Failed to find end of payload");
|
||||
}
|
||||
UsefulBufC payload = {cose_input.data() + pos_start, pos_end - pos_start};
|
||||
|
||||
// QCBORDecode_PartialFinish() before and after should allow constructing a
|
||||
// span of the encoded payload, which can perhaps then be passed to
|
||||
// QCBOREncode_AddEncoded and would allow blindly copying the payload
|
||||
// without parsing it.
|
||||
|
||||
err = QCBORDecode_GetNext(&ctx, &item);
|
||||
if (err != QCBOR_SUCCESS && item.uDataType != QCBOR_TYPE_BYTE_STRING)
|
||||
{
|
||||
throw std::logic_error("Failed to parse COSE_Sign1 signature");
|
||||
}
|
||||
UsefulBufC signature = {item.val.string.ptr, item.val.string.len};
|
||||
|
||||
QCBORDecode_ExitArray(&ctx);
|
||||
err = QCBORDecode_Finish(&ctx);
|
||||
if (err != QCBOR_SUCCESS)
|
||||
{
|
||||
throw std::logic_error("Failed to parse COSE_Sign1");
|
||||
}
|
||||
|
||||
// Maximum expected size of the additional map, sub-map is the
|
||||
// worst-case scenario
|
||||
const size_t additional_map_size = QCBOR_HEAD_BUFFER_SIZE + // map
|
||||
QCBOR_HEAD_BUFFER_SIZE + // key
|
||||
sizeof(key) + // key
|
||||
QCBOR_HEAD_BUFFER_SIZE + // submap
|
||||
QCBOR_HEAD_BUFFER_SIZE + // subkey
|
||||
sizeof(pos::AtKey::key) + // subkey
|
||||
QCBOR_HEAD_BUFFER_SIZE + // value
|
||||
value.size(); // value
|
||||
|
||||
// We add one extra QCBOR_HEAD_BUFFER_SIZE, because we parse and re-encode
|
||||
// the protected header bstr, which involves variable integer encoding, just
|
||||
// in case the library does not pick the most compact encoding.
|
||||
std::vector<uint8_t> output(
|
||||
cose_input.size() + additional_map_size + QCBOR_HEAD_BUFFER_SIZE);
|
||||
UsefulBuf output_buf{output.data(), output.size()};
|
||||
|
||||
QCBOREncodeContext ectx;
|
||||
QCBOREncode_Init(&ectx, output_buf);
|
||||
QCBOREncode_AddTag(&ectx, CBOR_TAG_COSE_SIGN1);
|
||||
QCBOREncode_OpenArray(&ectx);
|
||||
QCBOREncode_AddBytes(&ectx, phdr);
|
||||
QCBOREncode_OpenMap(&ectx);
|
||||
|
||||
if (std::holds_alternative<pos::InArray>(pos))
|
||||
{
|
||||
QCBOREncode_OpenArrayInMapN(&ectx, key);
|
||||
QCBOREncode_AddBytes(&ectx, {value.data(), value.size()});
|
||||
QCBOREncode_CloseArray(&ectx);
|
||||
}
|
||||
else if (std::holds_alternative<pos::AtKey>(pos))
|
||||
{
|
||||
QCBOREncode_OpenMapInMapN(&ectx, key);
|
||||
auto subkey = std::get<pos::AtKey>(pos).key;
|
||||
QCBOREncode_OpenArrayInMapN(&ectx, subkey);
|
||||
QCBOREncode_AddBytes(&ectx, {value.data(), value.size()});
|
||||
QCBOREncode_CloseArray(&ectx);
|
||||
QCBOREncode_CloseMap(&ectx);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Invalid COSE_Sign1 edit operation");
|
||||
}
|
||||
|
||||
QCBOREncode_CloseMap(&ectx);
|
||||
QCBOREncode_AddEncoded(&ectx, payload);
|
||||
QCBOREncode_AddBytes(&ectx, signature);
|
||||
QCBOREncode_CloseArray(&ectx);
|
||||
|
||||
UsefulBufC cose_output;
|
||||
err = QCBOREncode_Finish(&ectx, &cose_output);
|
||||
if (err != QCBOR_SUCCESS)
|
||||
{
|
||||
throw std::logic_error("Failed to encode COSE_Sign1");
|
||||
}
|
||||
output.resize(cose_output.len);
|
||||
output.shrink_to_fit();
|
||||
return output;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include "ccf/crypto/cose.h"
|
||||
|
||||
#include "crypto/openssl/cose_sign.h"
|
||||
#include "crypto/openssl/cose_verifier.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <doctest/doctest.h>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <qcbor/qcbor_decode.h>
|
||||
#include <qcbor/qcbor_spiffy_decode.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static const std::vector<int64_t> keys = {
|
||||
42, std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max()};
|
||||
|
||||
static const std::vector<ccf::cose::edit::pos::Type> positions = {
|
||||
ccf::cose::edit::pos::AtKey{42},
|
||||
ccf::cose::edit::pos::AtKey{std::numeric_limits<int64_t>::min()},
|
||||
ccf::cose::edit::pos::AtKey{std::numeric_limits<int64_t>::max()},
|
||||
ccf::cose::edit::pos::InArray{}};
|
||||
|
||||
const std::vector<uint8_t> value = {1, 2, 3, 4};
|
||||
|
||||
enum class PayloadType
|
||||
{
|
||||
Detached,
|
||||
Flat,
|
||||
NestedCBOR // Useful to test the payload transfer
|
||||
};
|
||||
|
||||
struct Signer
|
||||
{
|
||||
ccf::crypto::KeyPair_OpenSSL kp;
|
||||
std::vector<uint8_t> payload;
|
||||
bool detached_payload = false;
|
||||
|
||||
Signer(PayloadType type) : kp(ccf::crypto::CurveID::SECP384R1)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PayloadType::Detached:
|
||||
detached_payload = true;
|
||||
payload = {'p', 'a', 'y', 'l', 'o', 'a', 'd'};
|
||||
break;
|
||||
case PayloadType::Flat:
|
||||
payload = {'p', 'a', 'y', 'l', 'o', 'a', 'd'};
|
||||
break;
|
||||
case PayloadType::NestedCBOR:
|
||||
{
|
||||
payload.resize(1024);
|
||||
QCBOREncodeContext ctx;
|
||||
QCBOREncode_Init(&ctx, {payload.data(), payload.size()});
|
||||
QCBOREncode_OpenArray(&ctx);
|
||||
QCBOREncode_AddInt64(&ctx, 1);
|
||||
QCBOREncode_OpenArray(&ctx);
|
||||
QCBOREncode_AddInt64(&ctx, 2);
|
||||
QCBOREncode_AddInt64(&ctx, 3);
|
||||
QCBOREncode_CloseArray(&ctx);
|
||||
QCBOREncode_CloseArray(&ctx);
|
||||
UsefulBufC result;
|
||||
QCBOREncode_Finish(&ctx, &result);
|
||||
payload.resize(result.len);
|
||||
payload.shrink_to_fit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> make_cose_sign1()
|
||||
{
|
||||
const auto pheaders = {
|
||||
ccf::crypto::cose_params_int_bytes(300, value),
|
||||
ccf::crypto::cose_params_int_int(301, 34)};
|
||||
|
||||
return ccf::crypto::cose_sign1(kp, pheaders, payload, detached_payload);
|
||||
};
|
||||
|
||||
void verify(const std::vector<uint8_t>& cose_sign1)
|
||||
{
|
||||
auto verifier =
|
||||
ccf::crypto::make_cose_verifier_from_key(kp.public_key_pem());
|
||||
if (detached_payload)
|
||||
{
|
||||
verifier->verify_detached(cose_sign1, payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::span<uint8_t> payload_;
|
||||
REQUIRE(verifier->verify(cose_sign1, payload_));
|
||||
std::vector<uint8_t> payload_copy(payload_.begin(), payload_.end());
|
||||
REQUIRE(payload == payload_copy);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE("Verification and payload invariant")
|
||||
{
|
||||
for (auto type :
|
||||
{PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR})
|
||||
{
|
||||
Signer signer(type);
|
||||
auto csp = signer.make_cose_sign1();
|
||||
signer.verify(csp);
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
for (const auto& position : positions)
|
||||
{
|
||||
auto csp_set =
|
||||
ccf::cose::edit::set_unprotected_header(csp, key, position, value);
|
||||
|
||||
signer.verify(csp_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Idempotence")
|
||||
{
|
||||
for (auto type :
|
||||
{PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR})
|
||||
{
|
||||
Signer signer(type);
|
||||
auto csp = signer.make_cose_sign1();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
for (const auto& position : positions)
|
||||
{
|
||||
auto csp_set_once =
|
||||
ccf::cose::edit::set_unprotected_header(csp, key, position, value);
|
||||
|
||||
auto csp_set_twice = ccf::cose::edit::set_unprotected_header(
|
||||
csp_set_once, key, position, value);
|
||||
REQUIRE(csp_set_once == csp_set_twice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Check unprotected header")
|
||||
{
|
||||
for (auto type :
|
||||
{PayloadType::Detached, PayloadType::Flat, PayloadType::NestedCBOR})
|
||||
{
|
||||
Signer signer(type);
|
||||
auto csp = signer.make_cose_sign1();
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
for (const auto& position : positions)
|
||||
{
|
||||
auto csp_set =
|
||||
ccf::cose::edit::set_unprotected_header(csp, key, position, value);
|
||||
|
||||
std::vector<uint8_t> ref(1024);
|
||||
{
|
||||
// Create expected reference value for the unprotected header
|
||||
UsefulBuf ref_buf{ref.data(), ref.size()};
|
||||
QCBOREncodeContext ctx;
|
||||
QCBOREncode_Init(&ctx, ref_buf);
|
||||
QCBOREncode_OpenMap(&ctx);
|
||||
|
||||
if (std::holds_alternative<ccf::cose::edit::pos::InArray>(position))
|
||||
{
|
||||
QCBOREncode_OpenArrayInMapN(&ctx, key);
|
||||
QCBOREncode_AddBytes(&ctx, {value.data(), value.size()});
|
||||
QCBOREncode_CloseArray(&ctx);
|
||||
}
|
||||
else if (std::holds_alternative<ccf::cose::edit::pos::AtKey>(
|
||||
position))
|
||||
{
|
||||
QCBOREncode_OpenMapInMapN(&ctx, key);
|
||||
auto subkey = std::get<ccf::cose::edit::pos::AtKey>(position).key;
|
||||
QCBOREncode_OpenArrayInMapN(&ctx, subkey);
|
||||
QCBOREncode_AddBytes(&ctx, {value.data(), value.size()});
|
||||
QCBOREncode_CloseArray(&ctx);
|
||||
QCBOREncode_CloseMap(&ctx);
|
||||
}
|
||||
QCBOREncode_CloseMap(&ctx);
|
||||
UsefulBufC ref_buf_c;
|
||||
QCBOREncode_Finish(&ctx, &ref_buf_c);
|
||||
ref.resize(ref_buf_c.len);
|
||||
ref.shrink_to_fit();
|
||||
}
|
||||
|
||||
size_t uhdr_start, uhdr_end;
|
||||
QCBORError err;
|
||||
QCBORItem item;
|
||||
QCBORDecodeContext ctx;
|
||||
UsefulBufC buf{csp_set.data(), csp_set.size()};
|
||||
QCBORDecode_Init(&ctx, buf, QCBOR_DECODE_MODE_NORMAL);
|
||||
QCBORDecode_EnterArray(&ctx, nullptr);
|
||||
QCBORDecode_GetNthTagOfLast(&ctx, 0);
|
||||
// Protected header
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
// Unprotected header
|
||||
QCBORDecode_PartialFinish(&ctx, &uhdr_start);
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
QCBORDecode_PartialFinish(&ctx, &uhdr_end);
|
||||
std::vector<uint8_t> uhdr{
|
||||
csp_set.data() + uhdr_start, csp_set.data() + uhdr_end};
|
||||
REQUIRE(uhdr == ref);
|
||||
// Payload
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
// Signature
|
||||
QCBORDecode_VGetNextConsume(&ctx, &item);
|
||||
QCBORDecode_ExitArray(&ctx);
|
||||
err = QCBORDecode_Finish(&ctx);
|
||||
REQUIRE(err == QCBOR_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -202,9 +202,10 @@ namespace ccf
|
|||
std::optional<std::vector<uint8_t>> describe_merkle_proof_v1(
|
||||
const TxReceiptImpl& receipt)
|
||||
{
|
||||
constexpr size_t buf_size = 2048;
|
||||
constexpr size_t buf_size = 2048; // TBD: calculate why this is enough
|
||||
std::vector<uint8_t> underlying_buffer(buf_size);
|
||||
q_useful_buf buffer{underlying_buffer.data(), buf_size};
|
||||
UsefulBuf buffer{underlying_buffer.data(), underlying_buffer.size()};
|
||||
assert(buffer.len == buf_size);
|
||||
|
||||
QCBOREncodeContext ctx;
|
||||
QCBOREncode_Init(&ctx, buffer);
|
||||
|
@ -232,7 +233,7 @@ namespace ccf
|
|||
|
||||
QCBOREncode_CloseMap(&ctx);
|
||||
|
||||
struct q_useful_buf_c result;
|
||||
UsefulBufC result;
|
||||
auto qerr = QCBOREncode_Finish(&ctx, &result);
|
||||
if (qerr)
|
||||
{
|
||||
|
|
|
@ -1978,7 +1978,7 @@ TEST_CASE("Valid merkle proof from receipts")
|
|||
REQUIRE_EQ(
|
||||
ccf::ds::to_hex(decoded.claims_digest),
|
||||
historical_state->receipt->claims_digest.value()
|
||||
.hex_str()); // HEX as workaround emmpy claims (set flag).
|
||||
.hex_str()); // HEX as workaround empty claims (set flag).
|
||||
|
||||
auto it = decoded.path.begin();
|
||||
for (const auto& node : *historical_state->receipt->path)
|
||||
|
|
|
@ -1011,6 +1011,58 @@ def test_cose_signature_schema(network, args):
|
|||
return network
|
||||
|
||||
|
||||
@reqs.description("Check COSE receipt CDDL schema")
|
||||
def test_cose_receipt_schema(network, args):
|
||||
primary, _ = network.find_nodes()
|
||||
|
||||
with primary.client("user0") as client:
|
||||
r = client.get("/commit")
|
||||
assert r.status_code == http.HTTPStatus.OK
|
||||
last_txid = TxID.from_str(r.body.json()["transaction_id"])
|
||||
|
||||
for seqno in range(last_txid.seqno, last_txid.seqno - 10, -1):
|
||||
txid = f"{last_txid.view}.{seqno}"
|
||||
LOG.debug(f"Trying to get COSE receipt for txid {txid}")
|
||||
max_retries = 10
|
||||
found_proof = False
|
||||
for _ in range(max_retries):
|
||||
r = client.get(
|
||||
"/log/public/cose_receipt",
|
||||
headers={infra.clients.CCF_TX_ID_HEADER: txid},
|
||||
log_capture=[], # Do not emit raw binary to stdout
|
||||
)
|
||||
if r.status_code == http.HTTPStatus.OK:
|
||||
cbor_proof = r.body.data()
|
||||
cbor_proof_filename = os.path.join(
|
||||
network.common_dir, f"receipt_{txid}.cose"
|
||||
)
|
||||
with open(cbor_proof_filename, "wb") as f:
|
||||
f.write(cbor_proof)
|
||||
subprocess.run(
|
||||
["cddl", "../cddl/ccf-receipt.cddl", "v", cbor_proof_filename],
|
||||
check=True,
|
||||
)
|
||||
found_proof = True
|
||||
LOG.debug(f"Checked COSE receipt for txid {txid}")
|
||||
break
|
||||
elif r.status_code == http.HTTPStatus.ACCEPTED:
|
||||
LOG.debug(f"Transaction {txid} accepted, retrying")
|
||||
time.sleep(0.1)
|
||||
elif r.status_code == http.HTTPStatus.NOT_FOUND:
|
||||
LOG.debug(f"Transaction {txid} is a signature")
|
||||
break
|
||||
else:
|
||||
assert (
|
||||
False
|
||||
), f"Failed to get receipt for txid {txid} after {max_retries} retries"
|
||||
if found_proof:
|
||||
break
|
||||
else:
|
||||
assert False, "Failed to find a non-signature in the last 10 transactions"
|
||||
|
||||
return network
|
||||
|
||||
|
||||
@reqs.description("Read range of historical state")
|
||||
@reqs.supports_methods("/app/log/public", "/app/log/public/historical/range")
|
||||
def test_historical_query_range(network, args):
|
||||
|
@ -2194,6 +2246,7 @@ def run_main_tests(network, args):
|
|||
if args.package == "samples/apps/logging/liblogging":
|
||||
test_cbor_merkle_proof(network, args)
|
||||
test_cose_signature_schema(network, args)
|
||||
test_cose_receipt_schema(network, args)
|
||||
|
||||
# HTTP2 doesn't support forwarding
|
||||
if not args.http2:
|
||||
|
|
|
@ -19,6 +19,7 @@ set(CCFCRYPTO_SRC
|
|||
${CCF_DIR}/src/crypto/key_wrap.cpp
|
||||
${CCF_DIR}/src/crypto/hmac.cpp
|
||||
${CCF_DIR}/src/crypto/pem.cpp
|
||||
${CCF_DIR}/src/crypto/cose.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/symmetric_key.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/public_key.cpp
|
||||
${CCF_DIR}/src/crypto/openssl/key_pair.cpp
|
||||
|
@ -32,6 +33,7 @@ set(CCFCRYPTO_SRC
|
|||
add_library(stdcxxccfcrypto.host STATIC "${CCFCRYPTO_SRC}")
|
||||
target_link_libraries(stdcxxccfcrypto.host PUBLIC crypto)
|
||||
target_link_libraries(stdcxxccfcrypto.host PUBLIC ssl)
|
||||
target_link_libraries(stdcxxccfcrypto.host PUBLIC qcbor.host)
|
||||
|
||||
target_link_libraries(
|
||||
submit PRIVATE stdcxxhttp_parser.host stdcxxccfcrypto.host arrow parquet
|
||||
|
|
Загрузка…
Ссылка в новой задаче