зеркало из https://github.com/microsoft/CCF.git
Move attestation report generation/verification to `Pal` (#4083)
This commit is contained in:
Родитель
dd6da6fdea
Коммит
4bb77b4ce0
|
@ -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;
|
||||
|
@ -156,5 +85,4 @@ namespace ccf
|
|||
return verify_quoted_node_public_key(
|
||||
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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче