Test-cover COSE-based signatures schemas with CDDL (#6569)

Co-authored-by: Amaury Chamayou <amchamay@microsoft.com>
This commit is contained in:
Max 2024-10-21 18:13:13 +01:00 коммит произвёл GitHub
Родитель d3ba218586
Коммит 843a483598
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
14 изменённых файлов: 434 добавлений и 83 удалений

Просмотреть файл

@ -0,0 +1,28 @@
ccf-cose-endorsement-tagged = #6.18(ccf-cose-endorsement)
ccf-cose-endorsement = [
phdr : bstr .cbor protected-headers, ; bstr-wrapped protected headers
uhdr : unprotected-headers, ; unwrappeed (plain map) unprotected headers
payload : bstr, ; previous servide identity public key
signature : bstr ; COSE-signature
]
unprotected-headers = {
; empty map instead of 'nil'. QCBOR library verifier doesn't support 'nil'.
}
protected-headers = {
&(alg: 1) => int, ; signing algoritm ID, as per RFC8152
&(cwt: 15) => cwt-map, ; CWT claims, as per RFC8392
&(ccf: "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 = {
&(endorsed-from: "epoch.start.txid") => tstr, ; first committed TxID in the endorsed epoch
? &(endorsed-to: "epoch.end.txid") => tstr, ; last committed TxID in the endorsed epoch
? &(last-endorsed-root: "epoch.end.merkle.root") => bstr .size 32 ; Merkle tree root of the last TxID in the endorsed epoch (string of HASH_SIZE(32) bytes)
}

Просмотреть файл

@ -0,0 +1,28 @@
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 = {
; empty map instead of 'nil'. QCBOR library verifier doesn't support 'nil'.
}
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
}

Просмотреть файл

@ -109,6 +109,17 @@
],
"type": "object"
},
"LoggingGetCoseSignature__Out": {
"properties": {
"cose_signature": {
"$ref": "#/components/schemas/base64string"
}
},
"required": [
"cose_signature"
],
"type": "object"
},
"LoggingGetHistoricalRange__Entry": {
"properties": {
"id": {
@ -1262,6 +1273,37 @@
}
}
},
"/app/log/public/cose_signature": {
"get": {
"operationId": "GetAppLogPublicCoseSignature",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoggingGetCoseSignature__Out"
}
}
},
"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/count": {
"get": {
"operationId": "GetAppLogPublicCount",

Просмотреть файл

@ -148,9 +148,12 @@ namespace ccf
const TxReceiptImpl& in);
using SerialisedCoseEndorsement = std::vector<uint8_t>;
using SerialisedCoseSignature = std::vector<uint8_t>;
using SerialisedCoseEndorsements = std::vector<SerialisedCoseEndorsement>;
std::optional<SerialisedCoseEndorsements> describe_cose_endorsements_v1(
const TxReceiptImpl& in);
std::optional<SerialisedCoseSignature> describe_cose_signature_v1(
const TxReceiptImpl& receipt);
// Manual JSON serializers are specified for these types as they are not
// trivial POD structs

Просмотреть файл

@ -2002,6 +2002,42 @@ namespace loggingapp
.set_auto_schema<void, LoggingGetCoseEndorsements::Out>()
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();
auto get_cose_signature = [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;
}
LoggingGetCoseSignature::Out response{
.cose_signature = signature.value()};
nlohmann::json j_response = response;
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::JSON);
ctx.rpc_ctx->set_response_body(j_response.dump());
};
make_read_only_endpoint(
"/log/public/cose_signature",
HTTP_GET,
ccf::historical::read_only_adapter_v4(
get_cose_signature, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetCoseSignature::Out>()
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();
}
};
}

Просмотреть файл

@ -108,6 +108,16 @@ namespace loggingapp
DECLARE_JSON_TYPE(LoggingGetCoseEndorsements::Out);
DECLARE_JSON_REQUIRED_FIELDS(LoggingGetCoseEndorsements::Out, endorsements);
struct LoggingGetCoseSignature
{
struct Out
{
ccf::SerialisedCoseSignature cose_signature;
};
};
DECLARE_JSON_TYPE(LoggingGetCoseSignature::Out);
DECLARE_JSON_REQUIRED_FIELDS(LoggingGetCoseSignature::Out, cose_signature);
// Public record/get
// Manual schemas, verified then parsed in handler
static const std::string j_record_public_in = R"!!!(

Просмотреть файл

@ -13,7 +13,7 @@ namespace
static constexpr size_t extra_size_for_seq_tag = 1 + 8; // type + size
size_t estimate_buffer_size(
const std::vector<ccf::crypto::COSEParametersFactory>& protected_headers,
const ccf::crypto::COSEHeadersArray& protected_headers,
std::span<const uint8_t> payload)
{
size_t result =
@ -28,7 +28,7 @@ namespace
protected_headers.end(),
result,
[](auto result, const auto& factory) {
return result + factory.estimated_size();
return result + factory->estimated_size();
});
return result + payload.size();
@ -37,7 +37,7 @@ namespace
void encode_protected_headers(
t_cose_sign1_sign_ctx* ctx,
QCBOREncodeContext* encode_ctx,
const std::vector<ccf::crypto::COSEParametersFactory>& protected_headers)
const ccf::crypto::COSEHeadersArray& protected_headers)
{
QCBOREncode_BstrWrap(encode_ctx);
QCBOREncode_OpenMap(encode_ctx);
@ -50,7 +50,7 @@ namespace
// Caller-provided headers follow
for (const auto& factory : protected_headers)
{
factory.apply(encode_ctx);
factory->apply(encode_ctx);
}
QCBOREncode_CloseMap(encode_ctx);
@ -67,7 +67,7 @@ namespace
void encode_parameters_custom(
struct t_cose_sign1_sign_ctx* me,
QCBOREncodeContext* cbor_encode,
const std::vector<ccf::crypto::COSEParametersFactory>& protected_headers)
const ccf::crypto::COSEHeadersArray& protected_headers)
{
encode_protected_headers(me, cbor_encode, protected_headers);
@ -94,57 +94,91 @@ namespace ccf::crypto
}
}
COSEParametersFactory cose_params_cwt_map_int_int(const CWTMap& m)
{
size_t args_size = extra_size_for_seq_tag;
for (const auto& [key, value] : m)
{
args_size += sizeof(key) + sizeof(value) + extra_size_for_int_tag +
extra_size_for_int_tag;
}
COSEMapIntKey::COSEMapIntKey(int64_t key_) : key(key_) {}
return COSEParametersFactory(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_OpenMapInMapN(ctx, COSE_PHEADER_KEY_CWT);
for (const auto& [key, value] : m)
{
QCBOREncode_AddInt64(ctx, key);
QCBOREncode_AddInt64(ctx, value);
}
QCBOREncode_CloseMap(ctx);
},
args_size);
void COSEMapIntKey::apply(QCBOREncodeContext* ctx) const
{
QCBOREncode_AddInt64(ctx, key);
}
COSEParametersFactory cose_params_int_int(int64_t key, int64_t value)
size_t COSEMapIntKey::estimated_size() const
{
return sizeof(key) + extra_size_for_int_tag;
}
COSEMapStringKey::COSEMapStringKey(const std::string& key_) : key(key_) {}
void COSEMapStringKey::apply(QCBOREncodeContext* ctx) const
{
QCBOREncode_AddSZString(ctx, key.c_str());
}
size_t COSEMapStringKey::estimated_size() const
{
return key.size() + extra_size_for_seq_tag;
}
COSEParametersMap::COSEParametersMap(
std::shared_ptr<COSEMapKey> key_,
const std::vector<std::shared_ptr<COSEParametersFactory>>& factories_) :
key(std::move(key_)),
factories(factories_)
{}
void COSEParametersMap::apply(QCBOREncodeContext* ctx) const
{
key->apply(ctx);
QCBOREncode_OpenMap(ctx);
for (const auto& f : factories)
{
f->apply(ctx);
}
QCBOREncode_CloseMap(ctx);
}
size_t COSEParametersMap::estimated_size() const
{
size_t value = key->estimated_size() + extra_size_for_seq_tag;
std::accumulate(
factories.begin(),
factories.end(),
value,
[](auto value, const auto& factory) {
return value + factory->estimated_size();
});
return value;
}
std::shared_ptr<COSEParametersFactory> cose_params_int_int(
int64_t key, int64_t value)
{
const size_t args_size = sizeof(key) + sizeof(value) +
extra_size_for_int_tag + extra_size_for_int_tag;
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddInt64ToMapN(ctx, key, value);
},
args_size);
}
COSEParametersFactory cose_params_int_string(
std::shared_ptr<COSEParametersFactory> cose_params_int_string(
int64_t key, const std::string& value)
{
const size_t args_size = sizeof(key) + value.size() +
extra_size_for_int_tag + extra_size_for_seq_tag;
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddSZStringToMapN(ctx, key, value.data());
},
args_size);
}
COSEParametersFactory cose_params_string_int(
std::shared_ptr<COSEParametersFactory> cose_params_string_int(
const std::string& key, int64_t value)
{
const size_t args_size = key.size() + sizeof(value) +
extra_size_for_seq_tag + extra_size_for_int_tag;
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddSZString(ctx, key.data());
QCBOREncode_AddInt64(ctx, value);
@ -152,12 +186,12 @@ namespace ccf::crypto
args_size);
}
COSEParametersFactory cose_params_string_string(
std::shared_ptr<COSEParametersFactory> cose_params_string_string(
const std::string& key, const std::string& value)
{
const size_t args_size = key.size() + value.size() +
extra_size_for_seq_tag + extra_size_for_seq_tag;
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddSZString(ctx, key.data());
QCBOREncode_AddSZString(ctx, value.data());
@ -165,26 +199,26 @@ namespace ccf::crypto
args_size);
}
COSEParametersFactory cose_params_int_bytes(
std::shared_ptr<COSEParametersFactory> cose_params_int_bytes(
int64_t key, const std::vector<uint8_t>& value)
{
const size_t args_size = sizeof(key) + value.size() +
+extra_size_for_int_tag + extra_size_for_seq_tag;
q_useful_buf_c buf{value.data(), value.size()};
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddBytesToMapN(ctx, key, buf);
},
args_size);
}
COSEParametersFactory cose_params_string_bytes(
std::shared_ptr<COSEParametersFactory> cose_params_string_bytes(
const std::string& key, const std::vector<uint8_t>& value)
{
const size_t args_size = key.size() + value.size() +
extra_size_for_seq_tag + extra_size_for_seq_tag;
q_useful_buf_c buf{value.data(), value.size()};
return COSEParametersFactory(
return std::make_shared<COSEParametersPair>(
[=](QCBOREncodeContext* ctx) {
QCBOREncode_AddSZString(ctx, key.data());
QCBOREncode_AddBytes(ctx, buf);
@ -194,7 +228,8 @@ namespace ccf::crypto
std::vector<uint8_t> cose_sign1(
const KeyPair_OpenSSL& key,
const std::vector<COSEParametersFactory>& protected_headers,
const std::vector<std::shared_ptr<COSEParametersFactory>>&
protected_headers,
std::span<const uint8_t> payload,
bool detached_payload)
{

Просмотреть файл

@ -30,32 +30,114 @@ namespace ccf::crypto
* omitted.
*/
static constexpr int64_t COSE_PHEADER_KEY_IAT = 6;
// CCF headers nested map key.
static const std::string COSE_PHEADER_KEY_CCF = "ccf.v1";
// CCF-specific: last signed TxID.
static const std::string COSE_PHEADER_KEY_TXID = "ccf.txid";
static const std::string COSE_PHEADER_KEY_TXID = "txid";
// CCF-specific: first TX in the range.
static const std::string COSE_PHEADER_KEY_RANGE_BEGIN = "ccf.epoch.begin";
static const std::string COSE_PHEADER_KEY_RANGE_BEGIN = "epoch.start.txid";
// CCF-specific: last TX included in the range.
static const std::string COSE_PHEADER_KEY_RANGE_END = "ccf.epoch.end";
// CCF-specific: Merkle root hash.
static const std::string COSE_PHEADER_KEY_MERKLE_ROOT = "ccf.merkle.root";
static const std::string COSE_PHEADER_KEY_RANGE_END = "epoch.end.txid";
// CCF-specific: last signed Merkle root hash in the range.
static const std::string COSE_PHEADER_KEY_EPOCH_LAST_MERKLE_ROOT =
"epoch.end.merkle.root";
using CWTMap = std::unordered_map<int64_t, int64_t>;
class COSEMapKey
{
public:
virtual void apply(QCBOREncodeContext* ctx) const = 0;
virtual size_t estimated_size() const = 0;
virtual ~COSEMapKey() = default;
};
class COSEMapIntKey : public COSEMapKey
{
public:
COSEMapIntKey(int64_t key_);
~COSEMapIntKey() override = default;
void apply(QCBOREncodeContext* ctx) const override;
size_t estimated_size() const override;
private:
int64_t key;
};
class COSEMapStringKey : public COSEMapKey
{
public:
COSEMapStringKey(const std::string& key_);
~COSEMapStringKey() override = default;
void apply(QCBOREncodeContext* ctx) const override;
size_t estimated_size() const override;
private:
std::string key;
};
class COSEParametersFactory
{
public:
virtual void apply(QCBOREncodeContext* ctx) const = 0;
virtual size_t estimated_size() const = 0;
virtual ~COSEParametersFactory() = default;
};
class COSEParametersMap : public COSEParametersFactory
{
public:
COSEParametersMap(
std::shared_ptr<COSEMapKey> key_,
const std::vector<std::shared_ptr<COSEParametersFactory>>& factories_);
void apply(QCBOREncodeContext* ctx) const override;
size_t estimated_size() const override;
virtual ~COSEParametersMap() = default;
private:
std::shared_ptr<COSEMapKey> key;
std::vector<std::shared_ptr<COSEParametersFactory>> factories{};
};
std::shared_ptr<COSEParametersFactory> cose_params_int_int(
int64_t key, int64_t value);
std::shared_ptr<COSEParametersFactory> cose_params_int_string(
int64_t key, const std::string& value);
std::shared_ptr<COSEParametersFactory> cose_params_string_int(
const std::string& key, int64_t value);
std::shared_ptr<COSEParametersFactory> cose_params_string_string(
const std::string& key, const std::string& value);
std::shared_ptr<COSEParametersFactory> cose_params_int_bytes(
int64_t key, const std::vector<uint8_t>& value);
std::shared_ptr<COSEParametersFactory> cose_params_string_bytes(
const std::string& key, const std::vector<uint8_t>& value);
class COSEParametersPair : public COSEParametersFactory
{
public:
template <typename Callable>
COSEParametersFactory(Callable&& impl, size_t args_size) :
COSEParametersPair(Callable&& impl, size_t args_size) :
impl(std::forward<Callable>(impl)),
args_size{args_size}
{}
void apply(QCBOREncodeContext* ctx) const
virtual ~COSEParametersPair() = default;
void apply(QCBOREncodeContext* ctx) const override
{
impl(ctx);
}
size_t estimated_size() const
size_t estimated_size() const override
{
return args_size;
}
@ -65,24 +147,8 @@ namespace ccf::crypto
size_t args_size{};
};
COSEParametersFactory cose_params_cwt_map_int_int(const CWTMap& m);
COSEParametersFactory cose_params_int_int(int64_t key, int64_t value);
COSEParametersFactory cose_params_int_string(
int64_t key, const std::string& value);
COSEParametersFactory cose_params_string_int(
const std::string& key, int64_t value);
COSEParametersFactory cose_params_string_string(
const std::string& key, const std::string& value);
COSEParametersFactory cose_params_int_bytes(
int64_t key, const std::vector<uint8_t>& value);
COSEParametersFactory cose_params_string_bytes(
const std::string& key, const std::vector<uint8_t>& value);
using COSEHeadersArray =
std::vector<std::shared_ptr<ccf::crypto::COSEParametersFactory>>;
struct COSESignError : public std::runtime_error
{
@ -101,7 +167,8 @@ namespace ccf::crypto
*/
std::vector<uint8_t> cose_sign1(
const KeyPair_OpenSSL& key,
const std::vector<COSEParametersFactory>& protected_headers,
const std::vector<std::shared_ptr<COSEParametersFactory>>&
protected_headers,
std::span<const uint8_t> payload,
bool detached_payload = true);
}

Просмотреть файл

@ -262,7 +262,8 @@ namespace ccf::crypto
throw std::logic_error("Failed to parse COSE_Sign1 as bstr");
}
QCBORDecode_EnterMap(&ctx, NULL);
QCBORDecode_EnterMap(&ctx, NULL); // phdr
QCBORDecode_EnterMapFromMapSZ(&ctx, "ccf.v1"); // phdr["ccf.v1"]
qcbor_result = QCBORDecode_GetError(&ctx);
if (qcbor_result != QCBOR_SUCCESS)
@ -318,7 +319,8 @@ namespace ccf::crypto
// Complete decode to ensure well-formed CBOR.
QCBORDecode_ExitMap(&ctx);
QCBORDecode_ExitMap(&ctx); // phdr["ccf.v1"]
QCBORDecode_ExitMap(&ctx); // phdr
QCBORDecode_ExitBstrWrapped(&ctx);
qcbor_result = QCBORDecode_GetError(&ctx);

Просмотреть файл

@ -254,6 +254,12 @@ namespace ccf
{
return receipt.cose_endorsements;
}
std::optional<SerialisedCoseSignature> describe_cose_signature_v1(
const TxReceiptImpl& receipt)
{
return receipt.cose_signature;
}
}
namespace ccf::historical

Просмотреть файл

@ -378,6 +378,23 @@ namespace ccf
ccf::get_enclave_time())
.count();
auto ccf_headers =
std::static_pointer_cast<ccf::crypto::COSEParametersFactory>(
std::make_shared<ccf::crypto::COSEParametersMap>(
std::make_shared<ccf::crypto::COSEMapStringKey>(
ccf::crypto::COSE_PHEADER_KEY_CCF),
ccf::crypto::COSEHeadersArray{
ccf::crypto::cose_params_string_string(
ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str())}));
auto cwt_headers =
std::static_pointer_cast<ccf::crypto::COSEParametersFactory>(
std::make_shared<ccf::crypto::COSEParametersMap>(
std::make_shared<ccf::crypto::COSEMapIntKey>(
ccf::crypto::COSE_PHEADER_KEY_CWT),
ccf::crypto::COSEHeadersArray{ccf::crypto::cose_params_int_int(
ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch)}));
const auto pheaders = {
// Key digest
ccf::crypto::cose_params_int_bytes(
@ -385,12 +402,10 @@ namespace ccf
// VDS
ccf::crypto::cose_params_int_int(
ccf::crypto::COSE_PHEADER_KEY_VDS, vds_merkle_tree),
// TxID
ccf::crypto::cose_params_string_string(
ccf::crypto::COSE_PHEADER_KEY_TXID, txid.str()),
// iat
ccf::crypto::cose_params_cwt_map_int_int(ccf::crypto::CWTMap{
{ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}})};
// CWT claims
cwt_headers,
// CCF headers
ccf_headers};
auto cose_sign = crypto::cose_sign1(service_kp, pheaders, root_hash);

Просмотреть файл

@ -394,7 +394,6 @@ namespace ccf
ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT);
ccf::CoseEndorsement endorsement{};
std::vector<ccf::crypto::COSEParametersFactory> pheaders{};
std::vector<uint8_t> key_to_endorse{};
std::vector<uint8_t> previous_root{};
@ -441,28 +440,44 @@ namespace ccf
key_to_endorse = endorsement.endorsing_key;
}
pheaders.push_back(ccf::crypto::cose_params_string_string(
std::vector<std::shared_ptr<ccf::crypto::COSEParametersFactory>>
ccf_headers_arr{};
ccf_headers_arr.push_back(ccf::crypto::cose_params_string_string(
ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN,
endorsement.endorsement_epoch_begin.to_str()));
if (endorsement.endorsement_epoch_end)
{
pheaders.push_back(ccf::crypto::cose_params_string_string(
ccf_headers_arr.push_back(ccf::crypto::cose_params_string_string(
ccf::crypto::COSE_PHEADER_KEY_RANGE_END,
endorsement.endorsement_epoch_end->to_str()));
}
if (!previous_root.empty())
{
pheaders.push_back(ccf::crypto::cose_params_string_bytes(
ccf::crypto::COSE_PHEADER_KEY_MERKLE_ROOT, previous_root));
ccf_headers_arr.push_back(ccf::crypto::cose_params_string_bytes(
ccf::crypto::COSE_PHEADER_KEY_EPOCH_LAST_MERKLE_ROOT, previous_root));
}
const auto time_since_epoch =
std::chrono::duration_cast<std::chrono::seconds>(
ccf::get_enclave_time())
.count();
pheaders.push_back(
ccf::crypto::cose_params_cwt_map_int_int(ccf::crypto::CWTMap{
{ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch}}));
auto cwt_headers =
std::static_pointer_cast<ccf::crypto::COSEParametersFactory>(
std::make_shared<ccf::crypto::COSEParametersMap>(
std::make_shared<ccf::crypto::COSEMapIntKey>(
ccf::crypto::COSE_PHEADER_KEY_CWT),
ccf::crypto::COSEHeadersArray{ccf::crypto::cose_params_int_int(
ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch)}));
auto ccf_headers =
std::static_pointer_cast<ccf::crypto::COSEParametersFactory>(
std::make_shared<ccf::crypto::COSEParametersMap>(
std::make_shared<ccf::crypto::COSEMapStringKey>(
ccf::crypto::COSE_PHEADER_KEY_CCF),
ccf_headers_arr));
ccf::crypto::COSEHeadersArray pheaders{cwt_headers, ccf_headers};
try
{

Просмотреть файл

@ -38,6 +38,7 @@ import copy
import programmability
import e2e_common_endpoints
import subprocess
import base64
from loguru import logger as LOG
@ -962,6 +963,54 @@ def test_cbor_merkle_proof(network, args):
return network
@reqs.description("Check COSE signature CDDL model")
def test_cose_signature_schema(network, args):
primary, _ = network.find_nodes()
with primary.client("user0") as client:
r = client.get("/commit")
assert r.status_code == http.HTTPStatus.OK
txid = TxID.from_str(r.body.json()["transaction_id"])
max_retries = 10
for _ in range(max_retries):
response = client.get(
"/log/public/cose_signature",
headers={infra.clients.CCF_TX_ID_HEADER: f"{txid.view}.{txid.seqno}"},
)
if response.status_code == http.HTTPStatus.OK:
signature = response.body.json()["cose_signature"]
signature = base64.b64decode(signature)
signature_filename = os.path.join(
network.common_dir, f"cose_signature_{txid}.cose"
)
with open(signature_filename, "wb") as f:
f.write(signature)
subprocess.run(
[
"cddl",
"../cddl/ccf-merkle-tree-cose-signature.cddl",
"v",
signature_filename,
],
check=True,
)
LOG.debug(f"Checked COSE signature schema for txid {txid}")
break
elif response.status_code == http.HTTPStatus.ACCEPTED:
LOG.debug(f"Transaction {txid} accepted, retrying")
time.sleep(0.1)
else:
LOG.error(f"Failed to get COSE signature for txid {txid}")
break
else:
assert (
False
), f"Failed to get receipt for txid {txid} after {max_retries} retries"
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):
@ -2144,6 +2193,7 @@ def run_main_tests(network, args):
test_record_count(network, args)
if args.package == "samples/apps/logging/liblogging":
test_cbor_merkle_proof(network, args)
test_cose_signature_schema(network, args)
# HTTP2 doesn't support forwarding
if not args.http2:

Просмотреть файл

@ -9,6 +9,7 @@ import infra.crypto
import suite.test_requirements as reqs
import ccf.ledger
import os
import subprocess
import json
from infra.runner import ConcurrentRunner
from distutils.dir_util import copy_tree
@ -71,10 +72,10 @@ def verify_endorsements_chain(primary, endorsements, pubkey):
validate_cose_sign1(cose_sign1=endorsement, pubkey=pubkey)
cose_msg = Sign1Message.decode(endorsement)
last_tx = ccf.tx_id.TxID.from_str(cose_msg.phdr["ccf.epoch.end"])
last_tx = ccf.tx_id.TxID.from_str(cose_msg.phdr["ccf.v1"]["epoch.end.txid"])
receipt = primary.get_receipt(last_tx.view, last_tx.seqno)
root_from_receipt = bytes.fromhex(receipt.json()["leaf"])
root_from_headers = cose_msg.phdr["ccf.merkle.root"]
root_from_headers = cose_msg.phdr["ccf.v1"]["epoch.end.merkle.root"]
assert root_from_receipt == root_from_headers
CWT_KEY = 15
@ -88,6 +89,19 @@ def verify_endorsements_chain(primary, endorsements, pubkey):
time.time() - cose_msg.phdr[CWT_KEY][IAT_CWT_LABEL] < last_five_minutes
), cose_msg.phdr
endorsement_filename = "prev_service_identoty_endorsement.cose"
with open(endorsement_filename, "wb") as f:
f.write(endorsement)
subprocess.run(
[
"cddl",
"../cddl/ccf-cose-endorsement-service-identity.cddl",
"v",
endorsement_filename,
],
check=True,
)
next_key_bytes = cose_msg.payload
pubkey = serialization.load_der_public_key(next_key_bytes, default_backend())