Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add issuer and subject to COSE signatures #6637

Merged
merged 18 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
2 changes: 2 additions & 0 deletions cddl/ccf-merkle-tree-cose-signature.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions cddl/ccf-receipt.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
13 changes: 13 additions & 0 deletions doc/host_config_schema/cchost_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
2 changes: 2 additions & 0 deletions include/ccf/node/startup_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions python/src/ccf/cose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion samples/config/start_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions src/common/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions src/crypto/openssl/cose_sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion src/host/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/host/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
6 changes: 4 additions & 2 deletions src/kv/kv_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -424,8 +425,9 @@ namespace ccf::kv
virtual std::vector<uint8_t> 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<ccf::crypto::KeyPair_OpenSSL>) = 0;
virtual void set_service_signing_identity(
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> keypair,
const COSESignaturesConfig& cose_signatures) = 0;
};

class Consensus : public ConfigurableConsensus
Expand Down
44 changes: 35 additions & 9 deletions src/node/history.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ namespace ccf

void start_signature_emit_timer() override {}

void set_service_kp(
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> service_kp_) override
void set_service_signing_identity(
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> service_kp_,
const COSESignaturesConfig& cose_signatures) override
{
std::ignore = std::move(service_kp_);
}
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -392,8 +396,16 @@ namespace ccf
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)}));
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
Expand Down Expand Up @@ -568,6 +580,7 @@ namespace ccf
ccf::kv::Term term_of_next_version;

std::optional<ccf::crypto::Pem> endorsed_cert = std::nullopt;
COSESignaturesConfig cose_signatures_config;

public:
HashedTxHistory(
Expand All @@ -589,10 +602,16 @@ namespace ccf
}
}

void set_service_kp(
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> service_kp_) override
void set_service_signing_identity(
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> 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
Expand Down Expand Up @@ -860,7 +879,14 @@ namespace ccf
store.commit(
txid,
std::make_unique<MerkleTreeHistoryPendingTx<T>>(
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);
}

Expand Down
19 changes: 13 additions & 6 deletions src/node/identity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -24,6 +25,7 @@ namespace ccf
ccf::crypto::Pem cert;
std::optional<IdentityType> type = IdentityType::REPLICATED;
std::string subject_name = "CN=CCF Service";
COSESignaturesConfig cose_signatures_config;
std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> kp{};

std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> get_key_pair()
Expand All @@ -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;

Expand All @@ -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<ccf::crypto::KeyPair_OpenSSL>(curve_id);
Expand All @@ -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)
{
Expand Down
23 changes: 18 additions & 5 deletions src/node/node_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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<ReplicatedNetworkIdentity>(
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())
Expand Down
Loading