diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dccafcebc37..57a1c30590c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added a `ccf::any_cert_auth_policy` (C++), or `any_cert` (JS/TS), implementing TLS client certificate authentication, but without checking for the presence of the certificate in the governance user or member tables. This enables applications wanting to do so to perform user management in application space, using application tables (#6608). -- Added OpenAPI support for `std::unordered_set`. +- Added OpenAPI support for `std::unordered_set` (#6634). +- Added ["cose_signatures"](https://microsoft.github.io/CCF/main/operations/configuration.html#command-start-cose-signatures) entry in the configuration, which allows setting "issuer" and "subject" at network start or recovery time (#6637). ## [6.0.0-dev5] diff --git a/cddl/ccf-merkle-tree-cose-signature.cddl b/cddl/ccf-merkle-tree-cose-signature.cddl index f73d4dd15563..7ad74096b343 100644 --- a/cddl/ccf-merkle-tree-cose-signature.cddl +++ b/cddl/ccf-merkle-tree-cose-signature.cddl @@ -20,6 +20,8 @@ protected-headers = { } cwt-map = { + &(iss: 1) => tstr, ; "issuer", string + &(sub: 2) => tstr, ; "subject", string &(iat: 6) => int ; "issued at", number of seconds since the epoch } diff --git a/cddl/ccf-receipt.cddl b/cddl/ccf-receipt.cddl index c943a09dfec8..15fb924696e2 100644 --- a/cddl/ccf-receipt.cddl +++ b/cddl/ccf-receipt.cddl @@ -26,6 +26,8 @@ protected-headers = { } cwt-map = { + &(iss: 1) => tstr, ; "issuer", string + &(sub: 2) => tstr, ; "subject", string &(iat: 6) => int ; "issued at", number of seconds since the epoch } diff --git a/doc/host_config_schema/cchost_config.json b/doc/host_config_schema/cchost_config.json index 4662da4aad36..f457ccbac250 100644 --- a/doc/host_config_schema/cchost_config.json +++ b/doc/host_config_schema/cchost_config.json @@ -296,6 +296,19 @@ "default": "CN=CCF Service", "description": "Subject name to include in service certificate. Can only be set once on service start." }, + "cose_signatures": { + "type": "object", + "properties": { + "issuer": { + "type": "string", + "description": "Issuer, set in CWT_Claims of COSE ledger signatures. Can only be set once on service start." + }, + "subject": { + "type": "string", + "description": "Subject, set in CWT_Claims of COSE ledger signatures. Can only be set once on service start." + } + } + }, "members": { "type": "array", "items": { diff --git a/src/node/rpc/cose_signatures_config.h b/include/ccf/node/cose_signatures_config.h similarity index 87% rename from src/node/rpc/cose_signatures_config.h rename to include/ccf/node/cose_signatures_config.h index c7d29b010567..9b8eae01f840 100644 --- a/src/node/rpc/cose_signatures_config.h +++ b/include/ccf/node/cose_signatures_config.h @@ -8,8 +8,8 @@ struct COSESignaturesConfig { - std::string issuer; - std::string subject; + std::string issuer = ""; + std::string subject = ""; bool operator==(const COSESignaturesConfig& other) const = default; }; diff --git a/include/ccf/node/startup_config.h b/include/ccf/node/startup_config.h index c2091b8e54ec..69e28a0772c5 100644 --- a/include/ccf/node/startup_config.h +++ b/include/ccf/node/startup_config.h @@ -4,6 +4,7 @@ #include "ccf/crypto/curve.h" #include "ccf/ds/unit_strings.h" +#include "ccf/node/cose_signatures_config.h" #include "ccf/pal/attestation_sev_snp_endorsements.h" #include "ccf/service/consensus_config.h" #include "ccf/service/node_info_network.h" @@ -86,6 +87,7 @@ struct StartupConfig : CCFConfig // Only if starting or recovering size_t initial_service_certificate_validity_days = 1; std::string service_subject_name = "CN=CCF Service"; + COSESignaturesConfig cose_signatures; nlohmann::json service_data = nullptr; diff --git a/python/src/ccf/cose.py b/python/src/ccf/cose.py index fb9c2713b6b0..573e2c13f449 100644 --- a/python/src/ccf/cose.py +++ b/python/src/ccf/cose.py @@ -243,6 +243,7 @@ def verify_receipt( raise ValueError("Signature verification failed") if claim_digest != leaf[2]: raise ValueError(f"Claim digest mismatch: {leaf[2]!r} != {claim_digest!r}") + return receipt.phdr _SIGN_DESCRIPTION = """Create and sign a COSE Sign1 message for CCF governance diff --git a/samples/config/start_config.json b/samples/config/start_config.json index bf2a53064270..251655ea4a1a 100644 --- a/samples/config/start_config.json +++ b/samples/config/start_config.json @@ -51,7 +51,11 @@ "maximum_node_certificate_validity_days": 365 }, "initial_service_certificate_validity_days": 1, - "service_subject_name": "CN=A Sample CCF Service" + "service_subject_name": "CN=A Sample CCF Service", + "cose_signatures": { + "issuer": "service.example.com", + "subject": "ledger.signature" + } } }, "ledger": { diff --git a/src/common/configuration.h b/src/common/configuration.h index c12771265e2c..7ea0eb9e5109 100644 --- a/src/common/configuration.h +++ b/src/common/configuration.h @@ -117,6 +117,7 @@ DECLARE_JSON_REQUIRED_FIELDS( snapshot_tx_interval, initial_service_certificate_validity_days, service_subject_name, + cose_signatures, service_data, node_data, start, diff --git a/src/crypto/openssl/cose_sign.h b/src/crypto/openssl/cose_sign.h index af7567961f8f..a8513cca91b6 100644 --- a/src/crypto/openssl/cose_sign.h +++ b/src/crypto/openssl/cose_sign.h @@ -30,6 +30,19 @@ namespace ccf::crypto * omitted. */ static constexpr int64_t COSE_PHEADER_KEY_IAT = 6; + // Standardised: issuer CWT claim. + // https://www.iana.org/assignments/cose/cose.xhtml#header-parameters + /* The "iss" (issuer) claim identifies the principal that issued the CWT. + * The "iss" value is a case-sensitive string containing a StringOrURI value. + */ + static constexpr int64_t COSE_PHEADER_KEY_ISS = 1; + // Standardised: subject CWT claim. + // https://www.iana.org/assignments/cose/cose.xhtml#header-parameters + /* The "sub" (subject) claim identifies the principal that is the subject of + * the CWT. The claims in a CWT are normally statements about the subject. + * The "sub" value is a case-sensitive string containing a StringOrURI value. + */ + static constexpr int64_t COSE_PHEADER_KEY_SUB = 2; // CCF headers nested map key. static const std::string COSE_PHEADER_KEY_CCF = "ccf.v1"; // CCF-specific: last signed TxID. diff --git a/src/host/configuration.h b/src/host/configuration.h index cecc2cd412ac..7f49aeed48a9 100644 --- a/src/host/configuration.h +++ b/src/host/configuration.h @@ -144,6 +144,7 @@ namespace host ccf::ServiceConfiguration service_configuration; size_t initial_service_certificate_validity_days = 1; std::string service_subject_name = "CN=CCF Service"; + COSESignaturesConfig cose_signatures; bool operator==(const Start&) const = default; }; @@ -209,7 +210,8 @@ namespace host CCHostConfig::Command::Start, service_configuration, initial_service_certificate_validity_days, - service_subject_name); + service_subject_name, + cose_signatures); DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CCHostConfig::Command::Join); DECLARE_JSON_REQUIRED_FIELDS(CCHostConfig::Command::Join, target_rpc_address); diff --git a/src/host/main.cpp b/src/host/main.cpp index 847a5bd81a5d..469b233a5af4 100644 --- a/src/host/main.cpp +++ b/src/host/main.cpp @@ -640,6 +640,7 @@ int main(int argc, char** argv) config.command.start.initial_service_certificate_validity_days; startup_config.service_subject_name = config.command.start.service_subject_name; + startup_config.cose_signatures = config.command.start.cose_signatures; LOG_INFO_FMT( "Creating new node: new network (with {} initial member(s) and {} " "member(s) required for recovery)", diff --git a/src/kv/kv_types.h b/src/kv/kv_types.h index 5c55afb4f243..d527a46de17d 100644 --- a/src/kv/kv_types.h +++ b/src/kv/kv_types.h @@ -10,6 +10,7 @@ #include "ccf/kv/get_name.h" #include "ccf/kv/hooks.h" #include "ccf/kv/version.h" +#include "ccf/node/cose_signatures_config.h" #include "ccf/tx_id.h" #include "crypto/openssl/key_pair.h" #include "enclave/consensus_type.h" @@ -424,8 +425,9 @@ namespace ccf::kv virtual std::vector serialise_tree(size_t to) = 0; virtual void set_endorsed_certificate(const ccf::crypto::Pem& cert) = 0; virtual void start_signature_emit_timer() = 0; - virtual void set_service_kp( - std::shared_ptr) = 0; + virtual void set_service_signing_identity( + std::shared_ptr keypair, + const COSESignaturesConfig& cose_signatures) = 0; }; class Consensus : public ConfigurableConsensus diff --git a/src/node/history.h b/src/node/history.h index d24e9dd3040c..d55a6c841ce0 100644 --- a/src/node/history.h +++ b/src/node/history.h @@ -196,8 +196,9 @@ namespace ccf void start_signature_emit_timer() override {} - void set_service_kp( - std::shared_ptr service_kp_) override + void set_service_signing_identity( + std::shared_ptr service_kp_, + const COSESignaturesConfig& cose_signatures) override { std::ignore = std::move(service_kp_); } @@ -322,6 +323,7 @@ namespace ccf ccf::crypto::KeyPair& node_kp; ccf::crypto::KeyPair_OpenSSL& service_kp; ccf::crypto::Pem& endorsed_cert; + const COSESignaturesConfig& cose_signatures_config; public: MerkleTreeHistoryPendingTx( @@ -331,14 +333,16 @@ namespace ccf const NodeId& id_, ccf::crypto::KeyPair& node_kp_, ccf::crypto::KeyPair_OpenSSL& service_kp_, - ccf::crypto::Pem& endorsed_cert_) : + ccf::crypto::Pem& endorsed_cert_, + const COSESignaturesConfig& cose_signatures_config_) : txid(txid_), store(store_), history(history_), id(id_), node_kp(node_kp_), service_kp(service_kp_), - endorsed_cert(endorsed_cert_) + endorsed_cert(endorsed_cert_), + cose_signatures_config(cose_signatures_config_) {} ccf::kv::PendingTxInfo call() override @@ -392,8 +396,16 @@ namespace ccf std::make_shared( std::make_shared( ccf::crypto::COSE_PHEADER_KEY_CWT), - ccf::crypto::COSEHeadersArray{ccf::crypto::cose_params_int_int( - ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch)})); + ccf::crypto::COSEHeadersArray{ + ccf::crypto::cose_params_int_int( + ccf::crypto::COSE_PHEADER_KEY_IAT, time_since_epoch), + ccf::crypto::cose_params_int_string( + ccf::crypto::COSE_PHEADER_KEY_ISS, + cose_signatures_config.issuer), + ccf::crypto::cose_params_int_string( + ccf::crypto::COSE_PHEADER_KEY_SUB, + cose_signatures_config.subject), + })); const auto pheaders = { // Key digest @@ -568,6 +580,7 @@ namespace ccf ccf::kv::Term term_of_next_version; std::optional endorsed_cert = std::nullopt; + COSESignaturesConfig cose_signatures_config; public: HashedTxHistory( @@ -589,10 +602,16 @@ namespace ccf } } - void set_service_kp( - std::shared_ptr service_kp_) override + void set_service_signing_identity( + std::shared_ptr service_kp_, + const COSESignaturesConfig& cose_signatures_config_) override { service_kp = std::move(service_kp_); + cose_signatures_config = cose_signatures_config_; + LOG_INFO_FMT( + "Setting service signing identity to iss: {} sub: {}", + cose_signatures_config.issuer, + cose_signatures_config.subject); } void start_signature_emit_timer() override @@ -860,7 +879,14 @@ namespace ccf store.commit( txid, std::make_unique>( - txid, store, *this, id, node_kp, *service_kp, endorsed_cert.value()), + txid, + store, + *this, + id, + node_kp, + *service_kp, + endorsed_cert.value(), + cose_signatures_config), true); } diff --git a/src/node/identity.h b/src/node/identity.h index 1c231b51924c..6aad7ca03b9e 100644 --- a/src/node/identity.h +++ b/src/node/identity.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/crypto/curve.h" +#include "ccf/node/cose_signatures_config.h" #include "crypto/certs.h" #include "crypto/openssl/key_pair.h" @@ -24,6 +25,7 @@ namespace ccf ccf::crypto::Pem cert; std::optional type = IdentityType::REPLICATED; std::string subject_name = "CN=CCF Service"; + COSESignaturesConfig cose_signatures_config; std::shared_ptr kp{}; std::shared_ptr get_key_pair() @@ -39,12 +41,16 @@ namespace ccf bool operator==(const NetworkIdentity& other) const { return cert == other.cert && priv_key == other.priv_key && - type == other.type && subject_name == other.subject_name; + type == other.type && subject_name == other.subject_name && + cose_signatures_config == other.cose_signatures_config; } - NetworkIdentity(const std::string& subject_name_) : + NetworkIdentity( + const std::string& subject_name_, + const COSESignaturesConfig& cose_signatures_config_) : type(IdentityType::REPLICATED), - subject_name(subject_name_) + subject_name(subject_name_), + cose_signatures_config(cose_signatures_config_) {} NetworkIdentity() = default; @@ -68,8 +74,9 @@ namespace ccf const std::string& subject_name_, ccf::crypto::CurveID curve_id, const std::string& valid_from, - size_t validity_period_days) : - NetworkIdentity(subject_name_) + size_t validity_period_days, + const COSESignaturesConfig& cose_signatures_config_) : + NetworkIdentity(subject_name_, cose_signatures_config_) { auto identity_key_pair = std::make_shared(curve_id); @@ -84,7 +91,7 @@ namespace ccf } ReplicatedNetworkIdentity(const NetworkIdentity& other) : - NetworkIdentity(other.subject_name) + NetworkIdentity(other.subject_name, other.cose_signatures_config) { if (type != other.type) { diff --git a/src/node/node_state.h b/src/node/node_state.h index cb7a3d9ed5ff..129c2b7d6941 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -8,6 +8,7 @@ #include "ccf/crypto/verifier.h" #include "ccf/ds/logger.h" #include "ccf/js/core/context.h" +#include "ccf/node/cose_signatures_config.h" #include "ccf/pal/attestation.h" #include "ccf/pal/locking.h" #include "ccf/pal/platform.h" @@ -501,11 +502,13 @@ namespace ccf config.service_subject_name, curve_id, config.startup_host_time, - config.initial_service_certificate_validity_days); + config.initial_service_certificate_validity_days, + config.cose_signatures); network.ledger_secrets->init(); - history->set_service_kp(network.identity->get_key_pair()); + history->set_service_signing_identity( + network.identity->get_key_pair(), config.cose_signatures); setup_consensus( ServiceStatus::OPENING, @@ -540,9 +543,11 @@ namespace ccf ccf::crypto::get_subject_name(previous_service_identity_cert), curve_id, config.startup_host_time, - config.initial_service_certificate_validity_days); + config.initial_service_certificate_validity_days, + config.cose_signatures); - history->set_service_kp(network.identity->get_key_pair()); + history->set_service_signing_identity( + network.identity->get_key_pair(), config.cose_signatures); LOG_INFO_FMT("Created recovery node {}", self); return {self_signed_node_cert, network.identity->cert}; @@ -654,12 +659,20 @@ namespace ccf // Set network secrets, node id and become part of network. if (resp.node_status == NodeStatus::TRUSTED) { + if (!resp.network_info.has_value()) + { + throw std::logic_error("Expected network info in join response"); + } + network.identity = std::make_unique( resp.network_info->identity); network.ledger_secrets->init_from_map( std::move(resp.network_info->ledger_secrets)); - history->set_service_kp(network.identity->get_key_pair()); + history->set_service_signing_identity( + network.identity->get_key_pair(), + resp.network_info->cose_signatures_config.value_or( + COSESignaturesConfig{})); ccf::crypto::Pem n2n_channels_cert; if (!resp.network_info->endorsed_certificate.has_value()) diff --git a/src/node/rpc/node_call_types.h b/src/node/rpc/node_call_types.h index d48ff6c016e1..d36180fa0430 100644 --- a/src/node/rpc/node_call_types.h +++ b/src/node/rpc/node_call_types.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/ds/json_schema.h" +#include "ccf/node/cose_signatures_config.h" #include "ccf/node_startup_state.h" #include "ccf/pal/mem.h" #include "ccf/service/node_info_network.h" @@ -14,7 +15,6 @@ #include "enclave/interface.h" #include "node/identity.h" #include "node/ledger_secrets.h" -#include "node/rpc/cose_signatures_config.h" #include "node/uvm_endorsements.h" #include diff --git a/src/node/rpc/test/frontend_test_infra.h b/src/node/rpc/test/frontend_test_infra.h index f03accdcfcec..1ba5aa351d9b 100644 --- a/src/node/rpc/test/frontend_test_infra.h +++ b/src/node/rpc/test/frontend_test_infra.h @@ -123,7 +123,8 @@ std::unique_ptr make_test_network_ident() "CN=CCF test network", ccf::crypto::service_identity_curve_choice, valid_from, - 2); + 2, + COSESignaturesConfig{}); } void init_network(NetworkState& network) diff --git a/src/node/test/historical_queries.cpp b/src/node/test/historical_queries.cpp index ffe1d825cc9f..5959b38489d4 100644 --- a/src/node/test/historical_queries.cpp +++ b/src/node/test/historical_queries.cpp @@ -62,7 +62,7 @@ TestState create_and_init_state(bool initialise_ledger_rekey = true) auto h = std::make_shared(*ts.kv_store, node_id, *ts.node_kp); h->set_endorsed_certificate({}); - h->set_service_kp(ts.service_kp); + h->set_service_signing_identity(ts.service_kp, COSESignaturesConfig{}); ts.kv_store->set_history(h); ts.kv_store->initialise_term(2); diff --git a/src/node/test/history.cpp b/src/node/test/history.cpp index e9d03376ef0a..1ad1cb2e9415 100644 --- a/src/node/test/history.cpp +++ b/src/node/test/history.cpp @@ -87,7 +87,8 @@ TEST_CASE("Check signature verification") std::make_shared( primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); - primary_history->set_service_kp(service_kp); + primary_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -97,7 +98,8 @@ TEST_CASE("Check signature verification") std::make_shared( backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); - backup_history->set_service_kp(service_kp); + backup_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); backup_store.set_history(backup_history); backup_store.initialise_term(store_term); @@ -163,7 +165,8 @@ TEST_CASE("Check signing works across rollback") std::make_shared( primary_store, ccf::kv::test::PrimaryNodeId, *node_kp); primary_history->set_endorsed_certificate(self_signed); - primary_history->set_service_kp(service_kp); + primary_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); primary_store.set_history(primary_history); primary_store.initialise_term(store_term); @@ -172,7 +175,8 @@ TEST_CASE("Check signing works across rollback") std::make_shared( backup_store, ccf::kv::test::FirstBackupNodeId, *node_kp); backup_history->set_endorsed_certificate(self_signed); - backup_history->set_service_kp(service_kp); + backup_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); backup_store.set_history(backup_history); backup_store.set_encryptor(encryptor); backup_store.initialise_term(store_term); diff --git a/src/node/test/snapshot.cpp b/src/node/test/snapshot.cpp index 225b4bc65fe2..0c9f72982bad 100644 --- a/src/node/test/snapshot.cpp +++ b/src/node/test/snapshot.cpp @@ -34,7 +34,8 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) auto source_history = std::make_shared( source_store, source_node_id, *source_node_kp); source_history->set_endorsed_certificate({}); - source_history->set_service_kp(service_kp); + source_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); source_store.set_history(source_history); source_store.initialise_term(2); @@ -102,7 +103,8 @@ TEST_CASE("Snapshot with merkle tree" * doctest::test_suite("snapshot")) auto target_history = std::make_shared( target_store, ccf::kv::test::PrimaryNodeId, *target_node_kp); target_history->set_endorsed_certificate({}); - target_history->set_service_kp(service_kp); + target_history->set_service_signing_identity( + service_kp, COSESignaturesConfig{}); target_store.set_history(target_history); } diff --git a/tests/config.jinja b/tests/config.jinja index d94ca8ded5a7..65407e0befb6 100644 --- a/tests/config.jinja +++ b/tests/config.jinja @@ -39,7 +39,12 @@ "maximum_service_certificate_validity_days": {{ maximum_service_certificate_validity_days }} }, "initial_service_certificate_validity_days": {{ initial_service_cert_validity_days }}, - "service_subject_name": {{ service_subject_name|tojson }} + "service_subject_name": {{ service_subject_name|tojson }}, + "cose_signatures": + { + "issuer": {{ cose_signatures_issuer|tojson }}, + "subject": {{ cose_signatures_subject|tojson }} + } }, "join": { diff --git a/tests/e2e_logging.py b/tests/e2e_logging.py index 6519a9e72080..b6b23cc57379 100644 --- a/tests/e2e_logging.py +++ b/tests/e2e_logging.py @@ -1053,7 +1053,11 @@ def test_cose_receipt_schema(network, args): if r.status_code == http.HTTPStatus.OK: cbor_proof = r.body.data() - ccf.cose.verify_receipt(cbor_proof, service_key, b"\0" * 32) + receipt_phdr = ccf.cose.verify_receipt( + cbor_proof, service_key, b"\0" * 32 + ) + assert receipt_phdr[15][1] == "service.example.com" + assert receipt_phdr[15][2] == "ledger.signature" cbor_proof_filename = os.path.join( network.common_dir, f"receipt_{txid}.cose" ) diff --git a/tests/e2e_operations.py b/tests/e2e_operations.py index 08e9968d5de6..7655432e00fb 100644 --- a/tests/e2e_operations.py +++ b/tests/e2e_operations.py @@ -8,6 +8,8 @@ import infra.e2e_args import infra.network import ccf.ledger +from ccf.tx_id import TxID +import base64 import suite.test_requirements as reqs import infra.crypto import ipaddress @@ -19,9 +21,11 @@ import subprocess import time import http +import copy import infra.snp as snp from cryptography import x509 from cryptography.hazmat.backends import default_backend +from pycose.messages import Sign1Message from loguru import logger as LOG @@ -589,6 +593,64 @@ def run_service_subject_name_check(args): assert cert.subject.rfc4514_string() == "CN=This test service", cert +def run_cose_signatures_config_check(args): + nargs = copy.deepcopy(args) + nargs.nodes = infra.e2e_args.max_nodes(nargs, f=0) + + with infra.network.network( + nargs.nodes, + nargs.binary_dir, + nargs.debug_nodes, + nargs.perf_nodes, + pdb=nargs.pdb, + ) as network: + network.start_and_open( + nargs, + cose_signatures_issuer="test.issuer.example.com", + cose_signatures_subject="test.subject", + ) + + for node in network.get_joined_nodes(): + with node.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) + sig = Sign1Message.decode(signature) + assert sig.phdr[15][1] == "test.issuer.example.com" + assert sig.phdr[15][2] == "test.subject" + LOG.debug( + "Checked COSE signature schema for issuer and subject" + ) + 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" + + def run(args): run_max_uncommitted_tx_count(args) run_file_operations(args) @@ -599,3 +661,4 @@ def run(args): run_preopen_readiness_check(args) run_sighup_check(args) run_service_subject_name_check(args) + run_cose_signatures_config_check(args) diff --git a/tests/infra/remote.py b/tests/infra/remote.py index 9999b4d716a6..c36c020c0af1 100644 --- a/tests/infra/remote.py +++ b/tests/infra/remote.py @@ -341,6 +341,8 @@ def __init__( snp_uvm_endorsements_file=None, service_subject_name="CN=CCF Test Service", historical_cache_soft_limit=None, + cose_signatures_issuer="service.example.com", + cose_signatures_subject="ledger.signature", **kwargs, ): """ @@ -536,6 +538,8 @@ def __init__( snp_uvm_endorsements_file=snp_uvm_endorsements_file, service_subject_name=service_subject_name, historical_cache_soft_limit=historical_cache_soft_limit, + cose_signatures_issuer=cose_signatures_issuer, + cose_signatures_subject=cose_signatures_subject, **kwargs, )