Move attestation report generation/verification to `Pal` (#4083)

This commit is contained in:
Julien Maffre 2022-08-09 21:19:08 +01:00 коммит произвёл GitHub
Родитель dd6da6fdea
Коммит 4bb77b4ce0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 363 добавлений и 233 удалений

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

@ -111,8 +111,6 @@ if("sgx" IN_LIST COMPILE_TARGETS)
target_link_libraries(ccf.enclave PUBLIC nghttp2.enclave)
enable_quote_code(ccf.enclave)
add_lvi_mitigations(ccf.enclave)
install(
@ -414,9 +412,11 @@ if(BUILD_TESTS)
target_link_libraries(http_test PRIVATE http_parser.host)
add_unit_test(
frontend_test ${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
frontend_test
${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/frontend_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/enclave/enclave_time.cpp
${CCF_DIR}/src/node/quote.cpp
)
target_link_libraries(
frontend_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} http_parser.host sss.host
@ -441,6 +441,7 @@ if(BUILD_TESTS)
add_unit_test(
node_frontend_test ${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/node_frontend_test.cpp
${CCF_DIR}/src/node/quote.cpp
)
target_link_libraries(
node_frontend_test

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

@ -138,13 +138,6 @@ function(sign_app_library name app_oe_conf_path enclave_sign_key_path)
endif()
endfunction()
# Util functions used by add_ccf_app and others
function(enable_quote_code name)
if(QUOTES_ENABLED)
target_compile_definitions(${name} PUBLIC -DGET_QUOTE)
endif()
endfunction()
# Enclave library wrapper
function(add_ccf_app name)

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

@ -243,7 +243,6 @@ add_executable(cchost ${CCHOST_SOURCES})
add_warning_checks(cchost)
add_san(cchost)
enable_quote_code(cchost)
target_compile_options(cchost PRIVATE ${COMPILE_LIBCXX})
target_include_directories(cchost PRIVATE ${CCF_GENERATED_DIR})

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

@ -612,7 +612,9 @@
},
"QuoteFormat": {
"enum": [
"OE_SGX_v1"
"OE_SGX_v1",
"Insecure_Virtual",
"AMD_SEV_SNP_v1"
],
"type": "string"
},
@ -840,7 +842,7 @@
"info": {
"description": "This API provides public, uncredentialed access to service and node state.",
"title": "CCF Public Node API",
"version": "2.28.0"
"version": "2.29.0"
},
"openapi": "3.0.0",
"paths": {

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

@ -3,10 +3,10 @@
#pragma once
#include "ccf/crypto/pem.h"
#include "ccf/ds/quote_info.h"
#include "ccf/endpoint_metrics.h"
#include "ccf/endpoint_registry.h"
#include "ccf/node_context.h"
#include "ccf/quote_info.h"
#include "ccf/tx_status.h"
namespace ccf

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

@ -2,14 +2,26 @@
// Licensed under the Apache 2.0 License.
#pragma once
#ifdef GET_QUOTE
#if defined(INSIDE_ENCLAVE) && !defined(VIRTUAL_ENCLAVE)
# include <openenclave/attestation/attester.h>
# include <openenclave/attestation/custom_claims.h>
# include <openenclave/attestation/sgx/evidence.h>
# include <openenclave/attestation/verifier.h>
#endif
#include <array>
namespace ccf
{
static constexpr size_t attestation_report_data_size = 32;
static constexpr size_t attestation_measurement_size = 32;
using attestation_report_data =
std::array<uint8_t, attestation_report_data_size>;
using attestation_measurement =
std::array<uint8_t, attestation_measurement_size>;
#if defined(INSIDE_ENCLAVE) && !defined(VIRTUAL_ENCLAVE)
// Set of wrappers for safe memory management
struct Claims
{
@ -68,5 +80,5 @@ namespace ccf
static constexpr oe_uuid_t oe_quote_format = {OE_FORMAT_UUID_SGX_ECDSA};
static constexpr auto sgx_report_data_claim_name = OE_CLAIM_SGX_REPORT_DATA;
}
#endif
}

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

@ -2,6 +2,9 @@
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/ds/attestation_types.h"
#include "ccf/ds/quote_info.h"
#include <cstdint>
#include <cstdlib>
@ -81,6 +84,28 @@ namespace ccf
info.peak_allocated_heap_size = 0;
return true;
}
static QuoteInfo generate_quote(attestation_report_data&&)
{
QuoteInfo node_quote_info = {};
node_quote_info.format = QuoteFormat::insecure_virtual;
return node_quote_info;
}
static void verify_quote(
const QuoteInfo& quote_info,
attestation_measurement& unique_id,
attestation_report_data& report_data)
{
if (quote_info.format != QuoteFormat::insecure_virtual)
{
// Virtual enclave cannot verify true (i.e. sgx) enclave quotes
throw std::logic_error(
"Cannot verify real attestation report on virtual build");
}
unique_id = {};
report_data = {};
}
};
using Pal = HostPal;
@ -176,6 +201,151 @@ namespace ccf
return true;
}
static QuoteInfo generate_quote(std::array<uint8_t, 32>&& report_data)
{
QuoteInfo node_quote_info = {};
node_quote_info.format = QuoteFormat::oe_sgx_v1;
Evidence evidence;
Endorsements endorsements;
SerialisedClaims serialised_custom_claims;
// Serialise hash of node's public key as a custom claim
const size_t custom_claim_length = 1;
oe_claim_t custom_claim;
custom_claim.name = const_cast<char*>(sgx_report_data_claim_name);
custom_claim.value = report_data.data();
custom_claim.value_size = report_data.size();
auto rc = oe_serialize_custom_claims(
&custom_claim,
custom_claim_length,
&serialised_custom_claims.buffer,
&serialised_custom_claims.size);
if (rc != OE_OK)
{
throw std::logic_error(fmt::format(
"Could not serialise node's public key as quote custom claim: {}",
oe_result_str(rc)));
}
rc = oe_get_evidence(
&oe_quote_format,
0,
serialised_custom_claims.buffer,
serialised_custom_claims.size,
nullptr,
0,
&evidence.buffer,
&evidence.size,
&endorsements.buffer,
&endorsements.size);
if (rc != OE_OK)
{
throw std::logic_error(
fmt::format("Failed to get evidence: {}", oe_result_str(rc)));
}
node_quote_info.quote.assign(
evidence.buffer, evidence.buffer + evidence.size);
node_quote_info.endorsements.assign(
endorsements.buffer, endorsements.buffer + endorsements.size);
return node_quote_info;
}
static void verify_quote(
const QuoteInfo& quote_info,
attestation_measurement& unique_id,
attestation_report_data& report_data)
{
if (quote_info.format != QuoteFormat::oe_sgx_v1)
{
throw std::logic_error(fmt::format(
"Cannot verify non OE SGX report: {}", quote_info.format));
}
Claims claims;
auto rc = oe_verify_evidence(
&oe_quote_format,
quote_info.quote.data(),
quote_info.quote.size(),
quote_info.endorsements.data(),
quote_info.endorsements.size(),
nullptr,
0,
&claims.data,
&claims.length);
if (rc != OE_OK)
{
throw std::logic_error(
fmt::format("Failed to verify evidence: {}", oe_result_str(rc)));
}
bool unique_id_found = false;
bool sgx_report_data_found = false;
for (size_t i = 0; i < claims.length; i++)
{
auto& claim = claims.data[i];
auto claim_name = std::string(claim.name);
if (claim_name == OE_CLAIM_UNIQUE_ID)
{
std::copy(
claim.value, claim.value + claim.value_size, unique_id.begin());
unique_id_found = true;
}
else if (claim_name == OE_CLAIM_CUSTOM_CLAIMS_BUFFER)
{
// Find sgx report data in custom claims
CustomClaims custom_claims;
rc = oe_deserialize_custom_claims(
claim.value,
claim.value_size,
&custom_claims.data,
&custom_claims.length);
if (rc != OE_OK)
{
throw std::logic_error(fmt::format(
"Failed to deserialise custom claims", oe_result_str(rc)));
}
for (size_t j = 0; j < custom_claims.length; j++)
{
auto& custom_claim = custom_claims.data[j];
if (std::string(custom_claim.name) == sgx_report_data_claim_name)
{
if (custom_claim.value_size != report_data.size())
{
throw std::logic_error(fmt::format(
"Expected {} of size {}, had size {}",
sgx_report_data_claim_name,
report_data.size(),
custom_claim.value_size));
}
std::copy(
custom_claim.value,
custom_claim.value + custom_claim.value_size,
report_data.begin());
sgx_report_data_found = true;
break;
}
}
}
}
if (!unique_id_found)
{
throw std::logic_error("Could not find measurement");
}
if (!sgx_report_data_found)
{
throw std::logic_error("Could not find report data");
}
}
private:
static void open_enclave_logging_callback(
void* context,

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

@ -10,10 +10,16 @@ namespace ccf
{
enum class QuoteFormat
{
oe_sgx_v1 = 0
oe_sgx_v1 = 0,
insecure_virtual = 1,
amd_sev_snp_v1 = 2
};
DECLARE_JSON_ENUM(QuoteFormat, {{QuoteFormat::oe_sgx_v1, "OE_SGX_v1"}})
DECLARE_JSON_ENUM(
QuoteFormat,
{{QuoteFormat::oe_sgx_v1, "OE_SGX_v1"},
{QuoteFormat::insecure_virtual, "Insecure_Virtual"},
{QuoteFormat::amd_sev_snp_v1, "AMD_SEV_SNP_v1"}})
struct QuoteInfo
{

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

@ -2,7 +2,7 @@
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/quote_info.h"
#include "ccf/ds/quote_info.h"
#include "ccf/service/code_digest.h"
#include "ccf/tx.h"

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

@ -4,8 +4,8 @@
#include "ccf/crypto/pem.h"
#include "ccf/ds/json.h"
#include "ccf/ds/quote_info.h"
#include "ccf/kv/version.h"
#include "ccf/quote_info.h"
#include "ccf/service/node_info_network.h"
#define FMT_HEADER_ONLY

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

@ -6,6 +6,7 @@
#include "ccf/crypto/pem.h"
#include "ccf/crypto/symmetric_key.h"
#include "ccf/crypto/verifier.h"
#include "ccf/ds/attestation_types.h"
#include "ccf/ds/logger.h"
#include "ccf/serdes.h"
#include "ccf/service/node_info_network.h"
@ -23,7 +24,6 @@
#include "indexing/indexer.h"
#include "js/wrap.h"
#include "network_state.h"
#include "node/attestation_types.h"
#include "node/hooks.h"
#include "node/http_node_client.h"
#include "node/jwt_key_auto_refresh.h"
@ -67,64 +67,6 @@ namespace ccf
data.shrink_to_fit();
}
#ifdef GET_QUOTE
static QuoteInfo generate_quote(
const std::vector<uint8_t>& node_public_key_der)
{
QuoteInfo node_quote_info;
node_quote_info.format = QuoteFormat::oe_sgx_v1;
crypto::Sha256Hash h{node_public_key_der};
Evidence evidence;
Endorsements endorsements;
SerialisedClaims serialised_custom_claims;
// Serialise hash of node's public key as a custom claim
const size_t custom_claim_length = 1;
oe_claim_t custom_claim;
custom_claim.name = const_cast<char*>(sgx_report_data_claim_name);
custom_claim.value = h.h.data();
custom_claim.value_size = h.SIZE;
auto rc = oe_serialize_custom_claims(
&custom_claim,
custom_claim_length,
&serialised_custom_claims.buffer,
&serialised_custom_claims.size);
if (rc != OE_OK)
{
throw std::logic_error(fmt::format(
"Could not serialise node's public key as quote custom claim: {}",
oe_result_str(rc)));
}
rc = oe_get_evidence(
&oe_quote_format,
0,
serialised_custom_claims.buffer,
serialised_custom_claims.size,
nullptr,
0,
&evidence.buffer,
&evidence.size,
&endorsements.buffer,
&endorsements.size);
if (rc != OE_OK)
{
throw std::logic_error(
fmt::format("Failed to get evidence: {}", oe_result_str(rc)));
}
node_quote_info.quote.assign(
evidence.buffer, evidence.buffer + evidence.size);
node_quote_info.endorsements.assign(
endorsements.buffer, endorsements.buffer + endorsements.size);
return node_quote_info;
}
#endif
class NodeState : public AbstractNodeState
{
private:
@ -132,7 +74,7 @@ namespace ccf
// this node's core state
//
ds::StateMachine<NodeStartupState> sm;
ccf::Pal::Mutex lock;
Pal::Mutex lock;
crypto::CurveID curve_id;
std::vector<crypto::SubjectAltName> subject_alt_names = {};
@ -290,16 +232,8 @@ namespace ccf
const std::vector<uint8_t>& expected_node_public_key_der,
CodeDigest& code_digest) override
{
#ifdef GET_QUOTE
return EnclaveAttestationProvider::verify_quote_against_store(
tx, quote_info, expected_node_public_key_der, code_digest);
#else
(void)tx;
(void)quote_info;
(void)expected_node_public_key_der;
(void)code_digest;
return QuoteVerificationResult::Verified;
#endif
}
//
@ -313,7 +247,7 @@ namespace ccf
size_t sig_tx_interval_,
size_t sig_ms_interval_)
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
sm.expect(NodeStartupState::uninitialized);
consensus_config = consensus_config_;
@ -341,7 +275,7 @@ namespace ccf
//
NodeCreateInfo create(StartType start_type, StartupConfig&& config_)
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
sm.expect(NodeStartupState::initialized);
config = std::move(config_);
@ -358,8 +292,8 @@ namespace ccf
accept_node_tls_connections();
open_frontend(ActorsType::nodes);
#ifdef GET_QUOTE
quote_info = generate_quote(node_sign_kp->public_key_der());
quote_info = Pal::generate_quote(
crypto::Sha256Hash((node_sign_kp->public_key_der())).h);
auto code_id = EnclaveAttestationProvider::get_code_id(quote_info);
if (code_id.has_value())
{
@ -369,7 +303,6 @@ namespace ccf
{
throw std::logic_error("Failed to extract code id from quote");
}
#endif
// Signatures are only emitted on a timer once the public ledger has been
// recovered
@ -503,7 +436,7 @@ namespace ccf
http_status status,
http::HeaderMap&& headers,
std::vector<uint8_t>&& data) {
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
if (!sm.check(NodeStartupState::pending))
{
return;
@ -686,7 +619,7 @@ namespace ccf
}
},
[this](const std::string& error_msg) {
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
auto long_error_msg = fmt::format(
"Early error when joining existing network at {}: {}. Shutting "
"down node gracefully...",
@ -728,7 +661,7 @@ namespace ccf
// (https://github.com/microsoft/CCF/issues/2981)
void initiate_join()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
initiate_join_unsafe();
}
@ -756,7 +689,7 @@ namespace ccf
void join()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
start_join_timer();
}
@ -797,7 +730,7 @@ namespace ccf
//
void start_ledger_recovery()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
if (
!sm.check(NodeStartupState::readingPublicLedger) &&
!sm.check(NodeStartupState::verifyingSnapshot))
@ -816,7 +749,7 @@ namespace ccf
void recover_public_ledger_entries(const std::vector<uint8_t>& entries)
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
std::shared_ptr<kv::Store> store;
if (sm.check(NodeStartupState::readingPublicLedger))
@ -1004,13 +937,13 @@ namespace ccf
void verify_snapshot_end()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
verify_snapshot_end_unsafe();
}
void advance_part_of_public_network()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
sm.expect(NodeStartupState::readingPublicLedger);
history->start_signature_emit_timer();
sm.advance(NodeStartupState::partOfPublicNetwork);
@ -1125,7 +1058,7 @@ namespace ccf
//
void recover_private_ledger_entries(const std::vector<uint8_t>& entries)
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
if (!sm.check(NodeStartupState::readingPrivateLedger))
{
LOG_FAIL_FMT(
@ -1297,7 +1230,7 @@ namespace ccf
//
void recover_ledger_end()
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
if (is_reading_public_ledger())
{
@ -1434,7 +1367,7 @@ namespace ccf
acme_clients.emplace(
cfg_name,
std::make_shared<ccf::ACMEClient>(
std::make_shared<ACMEClient>(
cfg_name,
cfg,
rpc_map,
@ -1468,7 +1401,7 @@ namespace ccf
kv::Tx& tx,
AbstractGovernanceEffects::ServiceIdentities identities) override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
auto service = tx.rw<Service>(Tables::SERVICE);
auto service_info = service->get();
@ -1492,9 +1425,9 @@ namespace ccf
if (service_info->status == ServiceStatus::RECOVERING)
{
const auto prev_ident = tx.ro<ccf::PreviousServiceIdentity>(
ccf::Tables::PREVIOUS_SERVICE_IDENTITY)
->get();
const auto prev_ident =
tx.ro<PreviousServiceIdentity>(Tables::PREVIOUS_SERVICE_IDENTITY)
->get();
if (!prev_ident.has_value() || !identities.previous.has_value())
{
throw std::logic_error(
@ -1567,7 +1500,7 @@ namespace ccf
void initiate_private_recovery(kv::Tx& tx) override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
sm.expect(NodeStartupState::partOfPublicNetwork);
recovered_ledger_secrets = share_manager.restore_recovery_shares_info(
@ -1717,7 +1650,7 @@ namespace ccf
ExtendedState state() override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
auto s = sm.value();
if (s == NodeStartupState::readingPrivateLedger)
{
@ -1731,7 +1664,7 @@ namespace ccf
bool rekey_ledger(kv::Tx& tx) override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
sm.expect(NodeStartupState::partOfNetwork);
// The ledger should not be re-keyed when the service is not open
@ -1766,7 +1699,7 @@ namespace ccf
kv::Version get_startup_snapshot_seqno() override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
return startup_seqno;
}
@ -1777,7 +1710,7 @@ namespace ccf
crypto::Pem get_self_signed_certificate() override
{
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
return self_signed_node_cert;
}
@ -2262,7 +2195,7 @@ namespace ccf
"Could not find endorsed node certificate for {}", self));
}
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
endorsed_node_cert = endorsed_certificate.value();
history->set_endorsed_certificate(endorsed_node_cert.value());
@ -2356,8 +2289,7 @@ namespace ccf
network.tables->set_global_hook(
network.acme_certificates.get_name(),
network.acme_certificates.wrap_commit_hook(
[this](
kv::Version hook_version, const ccf::ACMECertificates::Write& w) {
[this](kv::Version hook_version, const ACMECertificates::Write& w) {
for (auto const& [interface_id, interface] :
config.network.rpc_interfaces)
{
@ -2389,7 +2321,7 @@ namespace ccf
// from. If the primary changes while the network is public-only, the
// new primary should also know at which version the new ledger secret
// is applicable from.
std::lock_guard<ccf::Pal::Mutex> guard(lock);
std::lock_guard<Pal::Mutex> guard(lock);
return last_recovered_signed_idx;
}

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

@ -3,95 +3,12 @@
#include "ccf/node/quote.h"
#ifdef GET_QUOTE
# include "ccf/service/tables/code_id.h"
# include "node/attestation_types.h"
#include "ccf/ds/attestation_types.h"
#include "ccf/ds/pal.h"
#include "ccf/service/tables/code_id.h"
namespace ccf
{
QuoteVerificationResult verify_quote(
const QuoteInfo& quote_info,
CodeDigest& unique_id,
crypto::Sha256Hash& hash_node_public_key)
{
Claims claims;
auto rc = oe_verify_evidence(
&oe_quote_format,
quote_info.quote.data(),
quote_info.quote.size(),
quote_info.endorsements.data(),
quote_info.endorsements.size(),
nullptr,
0,
&claims.data,
&claims.length);
if (rc != OE_OK)
{
LOG_FAIL_FMT("Failed to verify evidence: {}", oe_result_str(rc));
return QuoteVerificationResult::Failed;
}
bool unique_id_found = false;
bool sgx_report_data_found = false;
for (size_t i = 0; i < claims.length; i++)
{
auto& claim = claims.data[i];
auto claim_name = std::string(claim.name);
if (claim_name == OE_CLAIM_UNIQUE_ID)
{
std::copy(
claim.value, claim.value + claim.value_size, unique_id.data.begin());
unique_id_found = true;
}
else if (claim_name == OE_CLAIM_CUSTOM_CLAIMS_BUFFER)
{
// Find sgx report data in custom claims
CustomClaims custom_claims;
rc = oe_deserialize_custom_claims(
claim.value,
claim.value_size,
&custom_claims.data,
&custom_claims.length);
if (rc != OE_OK)
{
throw std::logic_error(fmt::format(
"Failed to deserialise custom claims", oe_result_str(rc)));
}
for (size_t j = 0; j < custom_claims.length; j++)
{
auto& custom_claim = custom_claims.data[j];
if (std::string(custom_claim.name) == sgx_report_data_claim_name)
{
if (custom_claim.value_size != hash_node_public_key.SIZE)
{
throw std::logic_error(fmt::format(
"Expected {} of size {}, had size {}",
sgx_report_data_claim_name,
hash_node_public_key.SIZE,
custom_claim.value_size));
}
std::copy(
custom_claim.value,
custom_claim.value + custom_claim.value_size,
hash_node_public_key.h.begin());
sgx_report_data_found = true;
break;
}
}
}
}
if (!unique_id_found || !sgx_report_data_found)
{
return QuoteVerificationResult::Failed;
}
return QuoteVerificationResult::Verified;
}
QuoteVerificationResult verify_enclave_measurement_against_store(
kv::ReadOnlyTx& tx, const CodeDigest& unique_id)
{
@ -121,11 +38,14 @@ namespace ccf
const QuoteInfo& quote_info)
{
CodeDigest unique_id = {};
crypto::Sha256Hash h;
auto rc = verify_quote(quote_info, unique_id, h);
if (rc != QuoteVerificationResult::Verified)
crypto::Sha256Hash h = {};
try
{
LOG_FAIL_FMT("Failed to verify quote: {}", rc);
Pal::verify_quote(quote_info, unique_id.data, h.h);
}
catch (const std::exception& e)
{
LOG_FAIL_FMT("Failed to verify attestation report: {}", e.what());
return std::nullopt;
}
@ -140,14 +60,23 @@ namespace ccf
CodeDigest& code_digest)
{
crypto::Sha256Hash quoted_hash;
auto rc = verify_quote(quote_info, code_digest, quoted_hash);
if (rc != QuoteVerificationResult::Verified)
try
{
return rc;
Pal::verify_quote(quote_info, code_digest.data, quoted_hash.h);
}
catch (const std::exception& e)
{
LOG_FAIL_FMT("Failed to verify attestation report: {}", e.what());
return QuoteVerificationResult::Failed;
}
rc = verify_enclave_measurement_against_store(tx, code_digest);
if (quote_info.format == QuoteFormat::insecure_virtual)
{
LOG_FAIL_FMT("Skipped attestation report verification");
return QuoteVerificationResult::Verified;
}
auto rc = verify_enclave_measurement_against_store(tx, code_digest);
if (rc != QuoteVerificationResult::Verified)
{
return rc;
@ -157,4 +86,3 @@ namespace ccf
expected_node_public_key_der, quoted_hash);
}
}
#endif

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

@ -140,14 +140,14 @@ namespace ccf
{
case QuoteVerificationResult::Failed:
return std::make_pair(
HTTP_STATUS_INTERNAL_SERVER_ERROR, "Quote could not be verified");
HTTP_STATUS_UNAUTHORIZED, "Quote could not be verified");
case QuoteVerificationResult::FailedCodeIdNotFound:
return std::make_pair(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
HTTP_STATUS_UNAUTHORIZED,
"Quote does not contain known enclave measurement");
case QuoteVerificationResult::FailedInvalidQuotedPublicKey:
return std::make_pair(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
HTTP_STATUS_UNAUTHORIZED,
"Quote report data does not contain node's public key hash");
default:
return std::make_pair(
@ -259,7 +259,6 @@ namespace ccf
CodeDigest code_digest;
#ifdef GET_QUOTE
QuoteVerificationResult verify_result = this->node_operation.verify_quote(
tx, in.quote_info, pubk_der, code_digest);
if (verify_result != QuoteVerificationResult::Verified)
@ -267,9 +266,6 @@ namespace ccf
const auto [code, message] = quote_verification_error(verify_result);
return make_error(code, ccf::errors::InvalidQuote, message);
}
#else
LOG_INFO_FMT("Skipped joining node quote verification");
#endif
std::optional<kv::Version> ledger_secret_seqno = std::nullopt;
if (
@ -371,7 +367,7 @@ namespace ccf
openapi_info.description =
"This API provides public, uncredentialed access to service and node "
"state.";
openapi_info.document_version = "2.28.0";
openapi_info.document_version = "2.29.0";
}
void init_handlers() override
@ -698,7 +694,6 @@ namespace ccf
q.endorsements = node_quote_info.endorsements;
q.format = node_quote_info.format;
#ifdef GET_QUOTE
// get_code_id attempts to re-validate the quote to extract mrenclave
// and the Open Enclave is insufficiently flexible to allow quotes
// with expired collateral to be parsed at all. Recent nodes therefore
@ -727,7 +722,6 @@ namespace ccf
"Failed to extract code id from node quote.");
}
}
#endif
return make_success(q);
}
@ -771,7 +765,6 @@ namespace ccf
q.endorsements = node_info.quote_info.endorsements;
q.format = node_info.quote_info.format;
#ifdef GET_QUOTE
// get_code_id attempts to re-validate the quote to extract
// mrenclave and the Open Enclave is insufficiently flexible to
// allow quotes with expired collateral to be parsed at all. Recent
@ -791,7 +784,6 @@ namespace ccf
q.mrenclave = ds::to_hex(code_id.value().data);
}
}
#endif
quotes.emplace_back(q);
}
return true;
@ -1520,10 +1512,7 @@ namespace ccf
in.certificate_signing_request,
in.public_key};
g.add_node(in.node_id, node_info);
#ifdef GET_QUOTE
g.trust_node_code_id(in.code_digest);
#endif
LOG_INFO_FMT("Created service");
return make_success(true);

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

@ -3,8 +3,8 @@
#pragma once
#include "ccf/crypto/pem.h"
#include "ccf/ds/quote_info.h"
#include "ccf/node_startup_state.h"
#include "ccf/quote_info.h"
#include "ccf/service/tables/code_id.h"
#include "common/configuration.h"
#include "node/rpc/gov_effects_interface.h"

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

@ -3,10 +3,10 @@
#pragma once
#include "ccf/crypto/pem.h"
#include "ccf/ds/quote_info.h"
#include "ccf/node/quote.h"
#include "ccf/node_startup_state.h"
#include "ccf/node_subsystem_interface.h"
#include "ccf/quote_info.h"
#include "ccf/service/tables/code_id.h"
#include "ccf/tx.h"
#include "node/session_metrics.h"

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

@ -12,6 +12,9 @@ from infra.checker import check_can_progress
from loguru import logger as LOG
# Dummy code id used by virtual nodes
VIRTUAL_CODE_ID = "0" * 64
@reqs.description("Verify node evidence")
def test_verify_quotes(network, args):
@ -108,6 +111,8 @@ def test_update_all_nodes(network, args):
],
key=lambda x: x["digest"],
)
if args.enclave_type == "virtual":
expected.insert(0, {"digest": VIRTUAL_CODE_ID, "status": "AllowedToJoin"})
assert versions == expected, versions
LOG.info("Remove old code id")
@ -121,6 +126,8 @@ def test_update_all_nodes(network, args):
],
key=lambda x: x["digest"],
)
if args.enclave_type == "virtual":
expected.insert(0, {"digest": VIRTUAL_CODE_ID, "status": "AllowedToJoin"})
assert versions == expected, versions
old_nodes = network.nodes.copy()

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

@ -20,7 +20,11 @@ def test_nobuiltins_endpoints(network, args):
body_j = r.body.json()
assert body_j["committed_view"] >= tx_id.view
assert body_j["committed_seqno"] >= tx_id.seqno
assert body_j["quote_format"] == "OE_SGX_v1"
assert (
body_j["quote_format"] == "Insecure_Virtual"
if args.enclave_type == "virtual"
else "OE_SGX_v1"
)
assert body_j["node_id"] == primary.node_id
r = c.get("/app/api")
@ -56,4 +60,8 @@ def test_nobuiltins_endpoints(network, args):
assert (
node_id in known_node_ids
), f"Response contains '{node_id}', which is not in known IDs: {known_node_ids}"
assert node_info["quote_format"] == "OE_SGX_v1"
assert (
node_info["quote_format"] == "Insecure_Virtual"
if args.enclave_type == "virtual"
else "OE_SGX_v1"
)

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

@ -16,6 +16,7 @@ import infra.crypto
from datetime import datetime
from infra.checker import check_can_progress
from infra.runner import ConcurrentRunner
import http
from loguru import logger as LOG
@ -375,6 +376,87 @@ def test_version(network, args):
)
@reqs.description("Issue fake join requests as untrusted client")
def test_issue_fake_join(network, args):
primary, _ = network.find_primary()
# Assemble dummy join request body
net = {"bind_address": "0:0"}
req = {}
req["node_info_network"] = {
"node_to_node_interface": net,
"rpc_interfaces": {"name": net},
}
req["consensus_type"] = "CFT"
req["startup_seqno"] = 0
with open(
os.path.join(network.common_dir, "member0_enc_pubk.pem"), "r", encoding="utf-8"
) as f:
req["public_encryption_key"] = f.read()
with primary.client(identity="user0") as c:
LOG.info("Join with SGX dummy quote")
req["quote_info"] = {"format": "OE_SGX_v1", "quote": "", "endorsements": ""}
r = c.post("/node/join", body=req)
assert r.status_code == http.HTTPStatus.UNAUTHORIZED
assert (
r.body.json()["error"]["code"] == "InvalidQuote"
), "Quote verification should fail when OE_SGX_v1 is specified"
LOG.info("Join with SGX real quote, but different TLS key")
# First, retrieve real quote from primary node
r = c.get("/node/quotes/self").body.json()
req["quote_info"] = {
"format": "OE_SGX_v1",
"quote": r["raw"],
"endorsements": r["endorsements"],
}
r = c.post("/node/join", body=req)
assert r.status_code == http.HTTPStatus.UNAUTHORIZED
assert r.body.json()["error"]["code"] == "InvalidQuote"
if args.enclave_type == "virtual":
assert r.body.json()["error"]["message"] == "Quote could not be verified"
else:
assert (
r.body.json()["error"]["message"]
== "Quote report data does not contain node's public key hash"
)
LOG.info("Join with virtual quote")
req["quote_info"] = {
"format": "Insecure_Virtual",
"quote": "",
"endorsements": "",
}
r = c.post("/node/join", body=req)
if args.enclave_type == "virtual":
assert r.status_code == http.HTTPStatus.OK
assert r.body.json()["node_status"] == ccf.ledger.NodeStatus.PENDING.value
else:
assert r.status_code == http.HTTPStatus.UNAUTHORIZED
assert (
r.body.json()["error"]["code"] == "InvalidQuote"
), "Virtual node must never join SGX network"
LOG.info("Join with AMD SEV-SNP quote")
req["quote_info"] = {
"format": "AMD_SEV_SNP_v1",
"quote": "",
"endorsements": "",
}
r = c.post("/node/join", body=req)
if args.enclave_type == "virtual":
assert r.status_code == http.HTTPStatus.OK
assert r.body.json()["node_status"] == ccf.ledger.NodeStatus.PENDING.value
else:
assert r.status_code == http.HTTPStatus.UNAUTHORIZED
# https://github.com/microsoft/CCF/issues/4072
assert (
r.body.json()["error"]["code"] == "InvalidQuote"
), "SEV-SNP node cannot currently join SGX network"
return network
@reqs.description("Replace a node on the same addresses")
@reqs.can_kill_n_nodes(1)
def test_node_replacement(network, args):
@ -590,6 +672,7 @@ def run(args):
network.start_and_open(args)
test_version(network, args)
test_issue_fake_join(network, args)
if args.consensus != "BFT":
test_add_node_invalid_service_cert(network, args)