Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- Experimental self-healing-open protocol for automatically transitioning-to-open during a disaster recovery without operator intervention. (#7189)

### Changed

- Improved `ccf::historical::verify_self_issued_receipt` - now can verify receipts signed by the past service identities if they were back-endorsed (#7546).

## [7.0.0-dev6]

[7.0.0-dev6]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.0-dev6
Expand Down
8 changes: 0 additions & 8 deletions doc/schemas/app_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1269,14 +1269,6 @@
"$ref": "#/components/responses/default"
}
},
"security": [
{
"jwt": []
},
{
"user_cose_sign1": []
}
],
"x-ccf-forwarding": {
"$ref": "#/components/x-ccf-forwarding/never"
}
Expand Down
3 changes: 3 additions & 0 deletions include/ccf/crypto/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include <cstdint>
#include <span>
#include <string>
#include <vector>

Expand All @@ -14,6 +15,8 @@ namespace ccf::crypto

std::string b64_from_raw(const uint8_t* data, size_t size);

std::string b64_from_raw(std::span<const uint8_t> data);

std::string b64_from_raw(const std::vector<uint8_t>& data);

std::string b64url_from_raw(
Expand Down
2 changes: 2 additions & 0 deletions include/ccf/crypto/cose_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace ccf::crypto
COSEVerifierUniquePtr make_cose_verifier_from_cert(
const std::vector<uint8_t>& cert);
COSEVerifierUniquePtr make_cose_verifier_from_key(const Pem& public_key);
COSEVerifierUniquePtr make_cose_verifier_from_key(
std::span<const uint8_t> public_key);

struct COSEEndorsementValidity
{
Expand Down
8 changes: 8 additions & 0 deletions include/ccf/crypto/ec_public_key.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ namespace ccf::crypto
*/
ECPublicKeyPtr make_ec_public_key(const std::vector<uint8_t>& der);

/**
* Construct ECPublicKey from a raw public key in DER format
*
* @param der Sequence of bytes containing the key in DER format
* @return Public key
*/
ECPublicKeyPtr make_ec_public_key(std::span<const uint8_t> der);

/**
* Construct ECPublicKey from a JsonWebKeyECPublic object
*
Expand Down
5 changes: 3 additions & 2 deletions include/ccf/historical_queries_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ namespace ccf::historical
std::shared_ptr<NetworkIdentitySubsystemInterface>
network_identity_subsystem);

// Verifies CCF COSE receipt using the *current network* identity's
// certificate.
// Verifies CCF COSE receipt issued by either current service identity or the
// one from the past that both corresponds to the receipt Tx ID and can be
// trusted via back-endorsement chain.
void verify_self_issued_receipt(
const std::vector<uint8_t>& cose_receipt,
std::shared_ptr<NetworkIdentitySubsystemInterface>
Expand Down
4 changes: 4 additions & 0 deletions include/ccf/network_identity_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/crypto/ec_public_key.h"
#include "ccf/node_subsystem_interface.h"

#include <optional>
Expand Down Expand Up @@ -38,5 +39,8 @@ namespace ccf

[[nodiscard]] virtual std::optional<CoseEndorsementsChain>
get_cose_endorsements_chain(ccf::SeqNo seqno) const = 0;

[[nodiscard]] virtual ccf::crypto::ECPublicKeyPtr get_trusted_identity_for(
ccf::SeqNo seqno) const = 0;
};
}
2 changes: 1 addition & 1 deletion samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2124,7 +2124,7 @@ namespace loggingapp
HTTP_GET,
ccf::historical::read_only_adapter_v4(
get_cose_receipt, context, is_tx_committed),
auth_policies)
ccf::no_auth_required)
.set_auto_schema<void, void>()
.set_forwarding_required(ccf::endpoints::ForwardingRequired::Never)
.install();
Expand Down
5 changes: 5 additions & 0 deletions src/crypto/base64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ namespace ccf::crypto
return b64_from_raw(data.data(), data.size());
}

std::string b64_from_raw(std::span<const uint8_t> data)
{
return b64_from_raw(data.data(), data.size());
}

std::string b64url_from_raw(
const uint8_t* data, size_t size, bool with_padding)
{
Expand Down
12 changes: 12 additions & 0 deletions src/crypto/openssl/cose_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ namespace ccf::crypto
public_key = std::make_shared<PublicKey_OpenSSL>(public_key_);
}

COSEKeyVerifier_OpenSSL::COSEKeyVerifier_OpenSSL(
std::span<const uint8_t> public_key_)
{
public_key = std::make_shared<PublicKey_OpenSSL>(public_key_);
}

COSEVerifier_OpenSSL::~COSEVerifier_OpenSSL() = default;

bool COSEVerifier_OpenSSL::verify(
Expand Down Expand Up @@ -235,6 +241,12 @@ namespace ccf::crypto
return std::make_unique<COSEKeyVerifier_OpenSSL>(public_key);
}

COSEVerifierUniquePtr make_cose_verifier_from_key(
std::span<const uint8_t> public_key)
{
return std::make_unique<COSEKeyVerifier_OpenSSL>(public_key);
}

COSEEndorsementValidity extract_cose_endorsement_validity(
std::span<const uint8_t> cose_msg)
{
Expand Down
1 change: 1 addition & 0 deletions src/crypto/openssl/cose_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ namespace ccf::crypto
{
public:
COSEKeyVerifier_OpenSSL(const Pem& public_key);
COSEKeyVerifier_OpenSSL(std::span<const uint8_t> public_key);
};
}
5 changes: 5 additions & 0 deletions src/crypto/openssl/ec_public_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ namespace ccf::crypto
return std::make_shared<ECPublicKey_OpenSSL>(der);
}

ECPublicKeyPtr make_ec_public_key(std::span<const uint8_t> der)
{
return std::make_shared<ECPublicKey_OpenSSL>(der);
}

ECPublicKeyPtr make_ec_public_key(const JsonWebKeyECPublic& jwk)
{
return std::make_shared<ECPublicKey_OpenSSL>(jwk);
Expand Down
10 changes: 10 additions & 0 deletions src/crypto/openssl/public_key.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ namespace ccf::crypto
}
}

PublicKey_OpenSSL(std::span<const uint8_t> der)
{
OpenSSL::Unique_BIO buf(der);
key = d2i_PUBKEY_bio(buf, &key);
if (key == nullptr)
{
throw std::runtime_error("Could not read DER");
}
}

void check_is_cose_compatible(int cose_alg)
{
if (key == nullptr)
Expand Down
14 changes: 12 additions & 2 deletions src/node/historical_queries_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,18 @@ namespace ccf
auto receipt =
cose::decode_ccf_receipt(cose_receipt, /* recompute_root */ true);

const auto& raw_cert = network_identity_subsystem->get()->cert.raw();
const auto verifier = ccf::crypto::make_cose_verifier_from_cert(raw_cert);
const auto tx_id = ccf::TxID::from_str(receipt.phdr.ccf.txid);
if (!tx_id.has_value())
{
throw std::logic_error(fmt::format(
"Failed to convert txid {} to ccf::TxID", receipt.phdr.ccf.txid));
}

const auto trusted_key =
network_identity_subsystem->get_trusted_identity_for(tx_id->seqno);

const auto verifier =
ccf::crypto::make_cose_verifier_from_key(trusted_key->public_key_pem());
if (!verifier->verify_detached(cose_receipt, receipt.merkle_root))
{
throw ccf::cose::COSESignatureValidationError(
Expand Down
120 changes: 120 additions & 0 deletions src/node/rpc/network_identity_subsystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ namespace ccf
const std::unique_ptr<NetworkIdentity>& network_identity;
std::shared_ptr<historical::StateCacheImpl> historical_cache;
std::map<SeqNo, CoseEndorsement> endorsements;
std::map<SeqNo, ccf::crypto::ECPublicKeyPtr> trusted_keys;
std::optional<TxID> current_service_from;
SeqNo earliest_endorsed_seq{0};
std::atomic<FetchStatus> fetch_status{FetchStatus::Retry};
Expand Down Expand Up @@ -179,6 +180,38 @@ namespace ccf
return result;
}

[[nodiscard]] ccf::crypto::ECPublicKeyPtr get_trusted_identity_for(
ccf::SeqNo seqno) const override
{
if (fetch_status.load() != FetchStatus::Done)
{
throw std::logic_error(fmt::format(
"Trusted key requested for seqno {} but the fetching has "
"not been completed yet",
seqno));
}
if (trusted_keys.empty())
{
throw std::logic_error(fmt::format(
"No trusted keys fetched when requested one for seqno {}", seqno));
}
auto it = trusted_keys.upper_bound(seqno);
if (it == trusted_keys.begin())
{
// The earliest known trusted seqno is greater than the requested one.
return nullptr;
}
const auto& [key_seqno, key_ptr] = *(--it);
if (key_seqno > seqno)
{
throw std::logic_error(fmt::format(
"Resolved trusted key for {} with wrong starting seqno {}",
seqno,
key_seqno));
}
return key_ptr;
}

private:
void retry_first_fetch()
{
Expand Down Expand Up @@ -254,6 +287,15 @@ namespace ccf
}
}

try
{
build_trusted_key_chain();
}
catch (const std::exception& e)
{
fail_fetching(e.what());
}

fetch_status.store(FetchStatus::Done);
}

Expand Down Expand Up @@ -423,6 +465,84 @@ namespace ccf
complete_fetching();
}

void build_trusted_key_chain()
{
if (!current_service_from.has_value())
{
throw std::logic_error(
"Attempting to build trusted key chain but no current service "
"created seqno fetched");
}

std::span<const uint8_t> previous_key{};
for (const auto& [seqno, endorsement] : endorsements)
{
auto verifier =
ccf::crypto::make_cose_verifier_from_key(endorsement.endorsing_key);
std::span<uint8_t> endorsed_key;
if (!verifier->verify(endorsement.endorsement, endorsed_key))
{
throw std::logic_error(fmt::format(
"COSE endorsement chain integrity is violated, endorsement from {} "
"to {} failed signature verification",
endorsement.endorsement_epoch_begin.to_str(),
format_epoch(endorsement.endorsement_epoch_end)));
}

LOG_INFO_FMT(
"Adding trusted seq {} key {}",
endorsement.endorsement_epoch_begin.seqno,
ccf::crypto::b64_from_raw(endorsed_key));
trusted_keys.insert(
{endorsement.endorsement_epoch_begin.seqno,
ccf::crypto::make_ec_public_key(endorsed_key)});

if (
!previous_key.empty() &&
!std::equal(
previous_key.begin(),
previous_key.end(),
endorsed_key.begin(),
endorsed_key.end()))
{
throw std::logic_error(fmt::format(
"Endorsement from {} to {} over public key {} doesn't chain with "
"the previous endorsement with key {}",
endorsement.endorsement_epoch_begin.seqno,
format_epoch(endorsement.endorsement_epoch_end),
ccf::ds::to_hex(endorsed_key),
ccf::ds::to_hex(previous_key)));
}

previous_key = endorsement.endorsing_key;
}

const auto& current_pkey =
network_identity->get_key_pair()->public_key_der();
if (
!previous_key.empty() &&
!std::equal(
previous_key.begin(),
previous_key.end(),
current_pkey.begin(),
current_pkey.end()))
{
throw std::logic_error(fmt::format(
"Current service identity public key {} does not match the last "
"endorsing key {}",
ccf::ds::to_hex(previous_key),
ccf::ds::to_hex(current_pkey)));
}

LOG_INFO_FMT(
"Adding trusted seq {} key {}",
current_service_from->seqno,
ccf::crypto::b64_from_raw(current_pkey));
trusted_keys.insert(
{current_service_from->seqno,
ccf::crypto::make_ec_public_key(current_pkey)});
}

void fetch_next_at(ccf::SeqNo seq)
{
auto state = historical_cache->get_state_at(
Expand Down
Loading