Skip to content

Commit

Permalink
SEV-SNP ACI: Retrieve attestation report endorsements from environment (
Browse files Browse the repository at this point in the history
  • Loading branch information
jumaffre authored Feb 7, 2023
1 parent 2d7382a commit 7801b5d
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added `ccf.enableUntrustedDateTime` to JS API. After calling `ccf.enableUntrustedDateTime(true)`, the `Date` global object will use the untrusted host time to retrieve the current time.
- Add new `ccf.crypto.jwkToPem`, `ccf.crypto.pubJwkToPem`, `ccf.crypto.rsaJwkToPem`, `ccf.crypto.pubRsaJwkToPem`, `ccf.crypto.eddsaJwkToPem`, `ccf.crypto.pubEddsaJwkToPem` to JavaScript/TypesScript API to convert EC/RSA/EdDSA keys from PEM to Json Web Key (#4876).
- Add new constructors to cryptography C++ API to generate EC/RSA/EdDSA keys from Json Web Key (#4876).
- Endorsement certificates for SEV-SNP attestation report can now be retrieved via an environment variable, as specified by `attestation.environment.report_endorsements` configuration entry (#4940).

## [4.0.0-dev3]

Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ if(COMPILE_TARGET STREQUAL "sgx")
ccf.enclave ${CCF_IMPL_SOURCE} ${CCF_GENERATED_DIR}/ccf_t.cpp
)

target_compile_definitions(ccf.enclave PUBLIC PLATFORM_SGX)

add_warning_checks(ccf.enclave)

target_include_directories(
Expand Down
36 changes: 20 additions & 16 deletions doc/host_config_schema/cchost_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,25 @@
"attestation": {
"type": "object",
"properties": {
"environment": {
"type": "object",
"properties": {
"report_endorsements": {
"type": ["string", "null"],
"description": "Name of environment variable (e.g. ``UVM_HOST_AMD_CERTIFICATE``) containing base64-encoded attestation report endorsements as JSON object (Azure Container Instance SEV-SNP only). If this is set, specified ``snp_endorsements_servers`` are ignored"
},
"security_policy": {
"type": ["string", "null"],
"description": "Name of environment variable (e.g. ``UVM_SECURITY_POLICY``) containing base64-encoded security policy (Azure Container Instance SEV-SNP only)"
},
"uvm_endorsements": {
"type": ["string", "null"],
"description": "Name of environment variable (e.g. ``UVM_REFERENCE_INFO``) containing base64-encoded UVM endorsements as COSE Sign1 document (Azure Container Instance SEV-SNP only)"
}
},
"description": "Environment variables required to provide best auditability and serviceability for Azure Container Instance deployments (SEV-SNP only)",
"additionalProperties": false
},
"snp_endorsements_servers": {
"type": "array",
"items": {
Expand All @@ -459,22 +478,7 @@
"required": ["url"],
"additionalProperties": false
},
"description": "List of servers used to retrieve attestation report endorsement certificates (SEV-SNP only). The first server in the list is always used and other servers are only specified as fallback"
},
"environment": {
"type": "object",
"properties": {
"security_policy": {
"type": ["string", "null"],
"description": "Name of environment variable (e.g. ``UVM_SECURITY_POLICY``) containing base64-encoded security policy (Azure Container Instance SEV-SNP only)"
},
"uvm_endorsements": {
"type": ["string", "null"],
"description": "Name of environment variable (e.g. ``UVM_REFERENCE_INFO``) containing base64-encoded UVM endorsements (Azure Container Instance SEV-SNP only)"
}
},
"description": "Environment variables required to provide best auditability and serviceability for Azure Container Instance deployments (SEV-SNP only)",
"additionalProperties": false
"description": "List of servers used to retrieve attestation report endorsement certificates (SEV-SNP only). The first server in the list is always used and other servers are only specified as fallback. Ignored if ``environment.report_endorsements`` is set"
}
},
"description": "This section includes configuration for the attestation for AMD SEV-SNP platform (ignored for SGX)",
Expand Down
1 change: 1 addition & 0 deletions include/ccf/node/startup_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct CCFConfig
{
std::optional<std::string> security_policy = std::nullopt;
std::optional<std::string> uvm_endorsements = std::nullopt;
std::optional<std::string> report_endorsements = std::nullopt;

bool operator==(const Environment&) const = default;
};
Expand Down
5 changes: 5 additions & 0 deletions include/ccf/pal/attestation.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ namespace ccf::pal
auto certificates = crypto::split_x509_cert_bundle(std::string_view(
reinterpret_cast<const char*>(quote_info.endorsements.data()),
quote_info.endorsements.size()));
if (certificates.size() != 3)
{
throw std::logic_error(fmt::format(
"Expected 3 endorsement certificates but got {}", certificates.size()));
}
auto chip_certificate = certificates[0];
auto sev_version_certificate = certificates[1];
auto root_certificate = certificates[2];
Expand Down
19 changes: 19 additions & 0 deletions include/ccf/pal/attestation_sev_snp_endorsements.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ namespace ccf::pal::snp
{
constexpr auto product_name = "Milan";

struct ACIReportEndorsements
{
std::string cache_control;
std::string vcek_cert;
std::string certificate_chain;
std::string tcbm;
};
DECLARE_JSON_TYPE(ACIReportEndorsements);
DECLARE_JSON_REQUIRED_FIELDS_WITH_RENAMES(
ACIReportEndorsements,
cache_control,
"cacheControl",
vcek_cert,
"vcekCert",
certificate_chain,
"certificateChain",
tcbm,
"tcbm");

struct EndorsementEndpointsConfiguration
{
struct EndpointInfo
Expand Down
34 changes: 34 additions & 0 deletions include/ccf/pal/platform.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/ds/json.h"

namespace ccf::pal
{
enum class Platform
{
SGX = 0,
SNP = 1,
Virtual = 2,
Unknown = 3,
};
DECLARE_JSON_ENUM(
Platform,
{{Platform::SGX, "SGX"},
{Platform::SNP, "SNP"},
{Platform::Virtual, "Virtual"},
{Platform::Unknown, "Unknown"}});

constexpr static auto platform =
#if defined(PLATFORM_SGX)
Platform::SGX
#elif defined(PLATFORM_SNP)
Platform::SNP
#elif defined(PLATFORM_VIRTUAL)
Platform::Virtual
#else
Platform::Unknown
#endif
;
}
1 change: 1 addition & 0 deletions scripts/azure_deployment/arm_aci.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def append_envvar_to_well_known_file(envvar):
return [
append_envvar_to_well_known_file("UVM_SECURITY_POLICY"),
append_envvar_to_well_known_file("UVM_REFERENCE_INFO"),
append_envvar_to_well_known_file("UVM_HOST_AMD_CERTIFICATE"),
]


Expand Down
5 changes: 4 additions & 1 deletion src/common/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ DECLARE_JSON_OPTIONAL_FIELDS(CCFConfig::JWT, key_refresh_interval);
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CCFConfig::Attestation::Environment);
DECLARE_JSON_REQUIRED_FIELDS(CCFConfig::Attestation::Environment);
DECLARE_JSON_OPTIONAL_FIELDS(
CCFConfig::Attestation::Environment, security_policy, uvm_endorsements);
CCFConfig::Attestation::Environment,
security_policy,
uvm_endorsements,
report_endorsements);

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CCFConfig::Attestation);
DECLARE_JSON_REQUIRED_FIELDS(CCFConfig::Attestation);
Expand Down
26 changes: 18 additions & 8 deletions src/host/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "ccf/ds/logger.h"
#include "ccf/pal/attestation.h"
#include "ccf/pal/platform.h"
#include "ccf/version.h"
#include "config_schema.h"
#include "configuration.h"
Expand Down Expand Up @@ -52,14 +53,7 @@ void print_version(size_t)
{
std::cout << "CCF host: " << ccf::ccf_version << std::endl;
std::cout << "Platform: "
<<
#if defined(PLATFORM_SGX)
"SGX"
#elif defined(PLATFORM_SNP)
"SNP"
#elif defined(PLATFORM_VIRTUAL)
"Virtual"
#endif
<< nlohmann::json(ccf::pal::platform).get<std::string>()
<< std::endl;
exit(0);
}
Expand Down Expand Up @@ -448,6 +442,22 @@ int main(int argc, char** argv)
"UVM endorsements");
}

if (config.attestation.environment.report_endorsements.has_value())
{
startup_config.attestation.environment.report_endorsements =
read_required_environment_variable(
config.attestation.environment.report_endorsements.value(),
"attestation report endorsements");
}
else if (
ccf::pal::platform == ccf::pal::Platform::SNP &&
config.attestation.snp_endorsements_servers.empty())
{
LOG_FATAL_FMT(
"On SEV-SNP, either one of report endorsements environment variable or "
"endorsements server should be set");
}

if (config.node_data_json_file.has_value())
{
startup_config.node_data =
Expand Down
94 changes: 64 additions & 30 deletions src/node/node_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "ccf/ds/logger.h"
#include "ccf/pal/attestation.h"
#include "ccf/pal/locking.h"
#include "ccf/pal/platform.h"
#include "ccf/serdes.h"
#include "ccf/service/node_info_network.h"
#include "ccf/service/tables/acme_certificates.h"
Expand Down Expand Up @@ -374,42 +375,74 @@ namespace ccf
{
auto fetch_endorsements =
[this](
const QuoteInfo& quote_info_,
const QuoteInfo& qi,
const pal::snp::EndorsementEndpointsConfiguration& endpoint_config) {
if (quote_info_.format != QuoteFormat::amd_sev_snp_v1)
// Note: Node lock is already taken here as this is called back
// synchronously with the call to pal::generate_quote

if (
qi.format == QuoteFormat::amd_sev_snp_v1 &&
!config.attestation.environment.report_endorsements.has_value())
{
// Note: Node lock is already taken here as this is called back
// synchronously with the call to pal::generate_quote
CCF_ASSERT_FMT(
quote_info_.format == QuoteFormat::insecure_virtual ||
!quote_info_.endorsements.empty(),
"SGX quote generation should have already fetched endorsements");
quote_info = quote_info_;
launch_node();
// On SEV-SNP, if no attestation report endorsements are set via
// environment, those need to be fetched
quote_endorsements_client =
std::make_shared<QuoteEndorsementsClient>(
rpcsessions,
endpoint_config,
[this, qi](std::vector<uint8_t>&& endorsements) {
std::lock_guard<pal::Mutex> guard(lock);
quote_info = qi;
quote_info.endorsements = std::move(endorsements);
try
{
launch_node();
}
catch (const std::exception& e)
{
LOG_FAIL_FMT("{}", e.what());
throw;
}
quote_endorsements_client.reset();
});

quote_endorsements_client->fetch_endorsements();
return;
}

quote_endorsements_client = std::make_shared<QuoteEndorsementsClient>(
rpcsessions,
endpoint_config,
[this, quote_info_](std::vector<uint8_t>&& endorsements) {
// Note: Only called for SEV-SNP
std::lock_guard<pal::Mutex> guard(lock);
quote_info = quote_info_;
quote_info.endorsements = std::move(endorsements);
try
{
launch_node();
}
catch (const std::exception& e)
{
LOG_FAIL_FMT("{}", e.what());
throw;
}
quote_endorsements_client.reset();
});
CCF_ASSERT_FMT(
(qi.format == QuoteFormat::oe_sgx_v1 && !qi.endorsements.empty()) ||
(qi.format != QuoteFormat::oe_sgx_v1 && qi.endorsements.empty()),
"SGX quote generation should have already fetched endorsements");

quote_endorsements_client->fetch_endorsements();
quote_info = qi;

if (
quote_info.format == QuoteFormat::amd_sev_snp_v1 &&
config.attestation.environment.report_endorsements.has_value())
{
// On SEV-SNP, if reports endorsements are passed via
// environment, read those rather than fetching them from
// endorsement server
pal::snp::ACIReportEndorsements endorsements =
nlohmann::json::parse(crypto::raw_from_b64(
config.attestation.environment.report_endorsements.value()));

CCF_ASSERT_FMT(
quote_info.endorsements.empty(),
"No endorsements should be set by quote generation");

quote_info.endorsements.insert(
quote_info.endorsements.end(),
endorsements.vcek_cert.begin(),
endorsements.vcek_cert.end());
quote_info.endorsements.insert(
quote_info.endorsements.end(),
endorsements.certificate_chain.begin(),
endorsements.certificate_chain.end());
}

launch_node();
};

pal::attestation_report_data report_data = {};
Expand All @@ -418,6 +451,7 @@ namespace ccf
node_pub_key_hash.h.begin(),
node_pub_key_hash.h.end(),
report_data.begin());

pal::generate_quote(
report_data,
fetch_endorsements,
Expand Down
2 changes: 1 addition & 1 deletion src/node/quote_endorsements_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace ccf

// Maximum number of retries per remote server before giving up and moving
// on to the next server.
static constexpr size_t max_server_retries_count = 2;
static constexpr size_t max_server_retries_count = 3;

std::shared_ptr<RPCSessions> rpcsessions;

Expand Down
4 changes: 2 additions & 2 deletions tests/code_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def test_add_node_without_security_policy(network, args):
args.package,
args,
timeout=3,
security_policy_envvar=None,
set_snp_security_policy_envvar=True,
)
network.trust_node(new_node, args)
return network
Expand Down Expand Up @@ -162,7 +162,7 @@ def test_start_node_with_mismatched_host_data(network, args):
timeout=3,
snp_security_policy=b64encode(b"invalid_security_policy").decode(),
)
except TimeoutError:
except (TimeoutError, RuntimeError):
LOG.info("As expected, node with invalid security policy failed to startup")
else:
raise AssertionError("Node startup unexpectedly succeeded")
Expand Down
3 changes: 2 additions & 1 deletion tests/config.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"environment":
{
"security_policy": {{ snp_security_policy_envvar|tojson }},
"uvm_endorsements": {{ snp_uvm_endorsements_envvar|tojson }}
"uvm_endorsements": {{ snp_uvm_endorsements_envvar|tojson }},
"report_endorsements": {{ snp_report_endorsements_envvar|tojson }}
}
},
"service_data_json_file": {{ service_data_json_file|tojson }},
Expand Down
Loading

0 comments on commit 7801b5d

Please sign in to comment.