зеркало из https://github.com/microsoft/CCF.git
Test-cover COSE-based signatures schemas with CDDL (#6569)
Co-authored-by: Amaury Chamayou <amchamay@microsoft.com>
This commit is contained in:
Родитель
d3ba218586
Коммит
843a483598
|
@ -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())
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче