azure-sdk-for-cpp/sdk/attestation/azure-security-attestation/test/ut/attestation_test.cpp
Rick Winter b54d509c72
Use standard syntax for MIT license (#4786)
* Use standard syntax for MIT license

* Stop appending "All rights reserved"
2023-07-12 22:37:36 -07:00

373 lines
13 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "../src/private/crypto/inc/crypto.hpp"
#include "attestation_collateral.hpp"
#include "azure/attestation/attestation_client.hpp"
#include <azure/core/internal/json/json.hpp>
#include <azure/core/test/test_base.hpp>
#include <azure/identity/client_secret_credential.hpp>
#include <tuple>
#include <gtest/gtest.h>
using namespace Azure::Security::Attestation;
using namespace Azure::Security::Attestation::Models;
using namespace Azure::Core;
namespace Azure { namespace Security { namespace Attestation { namespace Test {
enum class InstanceType
{
Shared,
AAD,
Isolated
};
class AttestationTests
: public Azure::Core::Test::TestBase,
public testing::WithParamInterface<std::tuple<InstanceType, AttestationType>> {
private:
protected:
std::shared_ptr<Azure::Core::Credentials::TokenCredential> m_credential;
std::string m_endpoint;
// Create
virtual void SetUp() override
{
Azure::Core::Test::TestBase::SetUpTestBase(AZURE_TEST_RECORDING_DIR);
InstanceType instanceType(std::get<0>(GetParam()));
if (instanceType == InstanceType::Shared)
{
std::string shortLocation(GetEnv("LOCATION_SHORT_NAME"));
m_endpoint = "https://shared" + shortLocation + "." + shortLocation + ".attest.azure.net";
}
else if (instanceType == InstanceType::AAD)
{
m_endpoint = GetEnv("ATTESTATION_AAD_URL");
}
else if (instanceType == InstanceType::Isolated)
{
m_endpoint = GetEnv("ATTESTATION_ISOLATED_URL");
}
}
AttestationTokenValidationOptions GetTokenValidationOptions()
{
AttestationTokenValidationOptions returnValue;
if (m_testContext.IsPlaybackMode())
{
// Skip validating time stamps if using recordings.
returnValue.ValidateNotBeforeTime = false;
returnValue.ValidateExpirationTime = false;
}
else
{
returnValue.TimeValidationSlack = 10s;
}
return returnValue;
}
AttestationClient CreateClient()
{
// `InitTestClient` takes care of setting up Record&Playback.
auto options = InitClientOptions<Azure::Security::Attestation::AttestationClientOptions>();
options.TokenValidationOptions = GetTokenValidationOptions();
return AttestationClient::Create(m_endpoint, options);
}
AttestationClient CreateAuthenticatedClient()
{
// `InitClientOptions` takes care of setting up Record&Playback.
AttestationClientOptions options = InitClientOptions<AttestationClientOptions>();
options.TokenValidationOptions = GetTokenValidationOptions();
std::shared_ptr<Azure::Core::Credentials::TokenCredential> credential
= CreateClientSecretCredential(
GetEnv("AZURE_TENANT_ID"), GetEnv("AZURE_CLIENT_ID"), GetEnv("AZURE_CLIENT_SECRET"));
return AttestationClient::Create(m_endpoint, credential, options);
}
void ValidateAttestResponse(
Azure::Response<AttestationToken<AttestationResult>> const& response,
Azure::Nullable<AttestationData> data = {},
std::string draftPolicy = {})
{
EXPECT_TRUE(response.Value.Issuer);
if (!m_testContext.IsPlaybackMode())
{
EXPECT_EQ(m_endpoint, *response.Value.Issuer);
}
EXPECT_TRUE(response.Value.Body.SgxMrEnclave);
EXPECT_TRUE(response.Value.Body.SgxMrSigner);
EXPECT_TRUE(response.Value.Body.SgxSvn);
EXPECT_TRUE(response.Value.Body.SgxProductId);
if (data)
{
if (data->DataType == AttestationDataType::Json)
{
EXPECT_TRUE(response.Value.Body.RunTimeClaims);
EXPECT_FALSE(response.Value.Body.EnclaveHeldData);
// canonicalize the JSON sent to the service before checking with the service output.
auto sentJson(Azure::Core::Json::_internal::json::parse(data->Data));
EXPECT_EQ(sentJson.dump(), *response.Value.Body.RunTimeClaims);
}
else
{
EXPECT_FALSE(response.Value.Body.RunTimeClaims);
EXPECT_TRUE(response.Value.Body.EnclaveHeldData);
// If we expected binary, the EnclaveHeldData in the response should be the value sent.
EXPECT_EQ(data->Data, *response.Value.Body.EnclaveHeldData);
}
}
if (!draftPolicy.empty())
{
EXPECT_TRUE(response.Value.Body.PolicyClaims);
auto policyClaims(
Azure::Core::Json::_internal::json::parse(*response.Value.Body.PolicyClaims));
EXPECT_TRUE(policyClaims.contains("custom-name"));
}
}
};
TEST_P(AttestationTests, SimpleAttest)
{
auto client(CreateClient());
AttestationType type = std::get<1>(GetParam());
if (type == AttestationType::OpenEnclave)
{
auto report = AttestationCollateral::OpenEnclaveReport();
auto attestResponse = client.AttestOpenEnclave(report);
ValidateAttestResponse(attestResponse);
}
else if (type == AttestationType::SgxEnclave)
{
auto quote = AttestationCollateral::SgxQuote();
auto attestResponse = client.AttestSgxEnclave(quote);
ValidateAttestResponse(attestResponse);
}
}
TEST_P(AttestationTests, AttestWithRuntimeData)
{
// Attestation clients don't need to be authenticated, but they can be.
auto client(CreateAuthenticatedClient());
auto runtimeData = AttestationCollateral::RunTimeData();
AttestationType type = std::get<1>(GetParam());
AttestationData data{runtimeData, AttestationDataType::Binary};
if (type == AttestationType::OpenEnclave)
{
AttestOpenEnclaveOptions options;
options.RunTimeData = data;
auto report = AttestationCollateral::OpenEnclaveReport();
auto attestResponse = client.AttestOpenEnclave(report, options);
ValidateAttestResponse(attestResponse, data);
}
else if (type == AttestationType::SgxEnclave)
{
AttestSgxEnclaveOptions options;
options.RunTimeData = data;
auto quote = AttestationCollateral::SgxQuote();
auto attestResponse = client.AttestSgxEnclave(quote, options);
ValidateAttestResponse(attestResponse, data);
}
}
TEST_P(AttestationTests, AttestWithDraftPolicy)
{
// Attestation clients don't need to be authenticated, but they can be.
auto client(CreateAuthenticatedClient());
auto runtimeData = AttestationCollateral::RunTimeData();
AttestationType type = std::get<1>(GetParam());
if (type == AttestationType::OpenEnclave)
{
AttestOpenEnclaveOptions options;
options.DraftPolicyForAttestation = R"(version= 1.0;
authorizationrules
{
[ type=="x-ms-sgx-is-debuggable", value==true] &&
[ type=="x-ms-sgx-product-id", value!=0 ] &&
[ type=="x-ms-sgx-svn", value>= 0 ] &&
[ type=="x-ms-sgx-mrsigner", value == "4aea5f9a0ed04b11f889aadfe6a1d376213a29a95a85ce7337ae6f7fece6610c"]
=> permit();
};
issuancerules {
c:[type=="x-ms-sgx-mrsigner"] => issue(type="custom-name", value=c.value);
};)";
auto report = AttestationCollateral::OpenEnclaveReport();
auto attestResponse = client.AttestOpenEnclave(report, options);
// Because a draft policy was set, the resulting token is unsigned.
ValidateAttestResponse(
attestResponse, Azure::Nullable<AttestationData>(), *options.DraftPolicyForAttestation);
options.DraftPolicyForAttestation = R"(version= 1.0;
authorizationrules
{
[ type=="x-ms-sgx-is-debuggable", value==false ] &&
[ type=="x-ms-sgx-product-id", value==0 ] &&
[ type=="x-ms-sgx-svn", value>= 0 ]
=> permit();
};
issuancerules {
c:[type=="x-ms-sgx-mrsigner"] => issue(type="custom-name", value=c.value);
};)";
EXPECT_THROW(client.AttestOpenEnclave(report, options), Azure::Core::RequestFailedException);
}
else if (type == AttestationType::SgxEnclave)
{
AttestSgxEnclaveOptions options;
options.DraftPolicyForAttestation = R"(version= 1.0;
authorizationrules
{
[ type=="x-ms-sgx-is-debuggable", value==true] &&
[ type=="x-ms-sgx-product-id", value!=0 ] &&
[ type=="x-ms-sgx-svn", value>= 0 ] &&
[ type=="x-ms-sgx-mrsigner", value == "4aea5f9a0ed04b11f889aadfe6a1d376213a29a95a85ce7337ae6f7fece6610c"]
=> permit();
};
issuancerules {
c:[type=="x-ms-sgx-mrsigner"] => issue(type="custom-name", value=c.value);
};)";
auto quote = AttestationCollateral::SgxQuote();
auto attestResponse = client.AttestSgxEnclave(quote, options);
ValidateAttestResponse(
attestResponse, Azure::Nullable<AttestationData>(), *options.DraftPolicyForAttestation);
options.DraftPolicyForAttestation = R"(version= 1.0;
authorizationrules
{
[ type=="x-ms-sgx-is-debuggable", value==false ] &&
[ type=="x-ms-sgx-product-id", value==0 ] &&
[ type=="x-ms-sgx-svn", value>= 0 ]
=> permit();
};
issuancerules {
c:[type=="x-ms-sgx-mrsigner"] => issue(type="custom-name", value=c.value);
};)";
EXPECT_THROW(client.AttestSgxEnclave(quote, options), Azure::Core::RequestFailedException);
}
}
TEST_P(AttestationTests, AttestWithRuntimeDataJson)
{
auto client(CreateClient());
auto runtimeData = AttestationCollateral::RunTimeData();
AttestationType type = std::get<1>(GetParam());
AttestationData data{runtimeData, AttestationDataType::Json};
if (type == AttestationType::OpenEnclave)
{
auto report = AttestationCollateral::OpenEnclaveReport();
AttestOpenEnclaveOptions options;
options.RunTimeData = data;
options.TokenValidationOptionsOverride = GetTokenValidationOptions();
(*options.TokenValidationOptionsOverride).ValidationCallback
= [&](AttestationToken<void> const& token, AttestationSigner const& signer) {
EXPECT_TRUE(token.Issuer);
// When running against a live server, the m_endpoint value is mocked, so we cannot
// compare against it.
// The signers KeyId MUST match the key ID in the token.
EXPECT_TRUE(signer.KeyId);
EXPECT_TRUE(token.Header.KeyId);
EXPECT_EQ(*signer.KeyId, *token.Header.KeyId);
EXPECT_TRUE(signer.CertificateChain);
auto cert(Azure::Security::Attestation::_detail::Cryptography::ImportX509Certificate(
(*signer.CertificateChain)[0]));
if (!m_testContext.IsPlaybackMode())
{
// The issuer of an attestation token MUST be the endpoint which we're talking to.
EXPECT_EQ(*token.Issuer, m_endpoint);
// And the endpoint should be in the subject of hte issuer.
EXPECT_NE(cert->GetSubjectName().find(m_endpoint), std::string::npos);
}
};
auto attestResponse = client.AttestOpenEnclave(report, options);
ValidateAttestResponse(attestResponse, data);
}
else if (type == AttestationType::SgxEnclave)
{
auto quote = AttestationCollateral::SgxQuote();
auto attestResponse = client.AttestSgxEnclave(quote, {data});
ValidateAttestResponse(attestResponse, data);
}
}
TEST_P(AttestationTests, CreateAttestationClients)
{
// `InitTestClient` takes care of setting up Record&Playback.
auto options = InitClientOptions<Azure::Security::Attestation::AttestationClientOptions>();
{
AttestationClient client = AttestationClient::Create(this->m_endpoint, options);
EXPECT_EQ(m_endpoint, client.Endpoint());
}
{
AttestationClient const client = AttestationClient::Create(this->m_endpoint, options);
EXPECT_EQ(m_endpoint, client.Endpoint());
}
{
AttestationClient client = AttestationClient::Create(this->m_endpoint, options);
EXPECT_EQ(m_endpoint, client.Endpoint());
}
{
auto client = AttestationClient::Create(this->m_endpoint, options);
EXPECT_EQ(m_endpoint, client.Endpoint());
}
{
auto const client = AttestationClient::Create(this->m_endpoint, options);
EXPECT_EQ(m_endpoint, client.Endpoint());
}
{
std::unique_ptr<AttestationClient> client = std::make_unique<AttestationClient>(
AttestationClient::Create(this->m_endpoint, options));
EXPECT_EQ(m_endpoint, client->Endpoint());
}
{
std::unique_ptr<AttestationClient const> client = std::make_unique<AttestationClient>(
AttestationClient::Create(this->m_endpoint, options));
EXPECT_EQ(m_endpoint, client->Endpoint());
}
}
namespace {
static std::string GetSuffix(const testing::TestParamInfo<AttestationTests::ParamType>& info)
{
std::string rv = std::get<1>(info.param).ToString();
rv += "_";
switch (std::get<0>(info.param))
{
case InstanceType::Shared:
rv += "Shared";
break;
case InstanceType::AAD:
rv += "Aad";
break;
case InstanceType::Isolated:
rv += "Isolated";
break;
default:
throw std::runtime_error("Unknown instance type");
}
return rv;
}
} // namespace
INSTANTIATE_TEST_SUITE_P(
Attestation,
AttestationTests,
::testing::Combine(
::testing::Values(InstanceType::Shared, InstanceType::AAD, InstanceType::Isolated),
::testing::Values(AttestationType::OpenEnclave, AttestationType::SgxEnclave)),
GetSuffix);
}}}} // namespace Azure::Security::Attestation::Test